feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
@@ -1,118 +1,83 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const node_crypto_1 = require("node:crypto");
const z = __importStar(require("zod/v4"));
const zod_1 = require("zod");
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const router_js_1 = require("../../server/auth/router.js");
const bearerAuth_js_1 = require("../../server/auth/middleware/bearerAuth.js");
const express_js_1 = require("../../server/express.js");
const types_js_1 = require("../../types.js");
const inMemoryEventStore_js_1 = require("../shared/inMemoryEventStore.js");
const in_memory_js_1 = require("../../experimental/tasks/stores/in-memory.js");
const demoInMemoryOAuthProvider_js_1 = require("./demoInMemoryOAuthProvider.js");
const auth_utils_js_1 = require("../../shared/auth-utils.js");
const auth_utils_js_1 = require("src/shared/auth-utils.js");
const cors_1 = __importDefault(require("cors"));
// Check for OAuth flag
const useOAuth = process.argv.includes('--oauth');
const strictOAuth = process.argv.includes('--oauth-strict');
// Create shared task store for demonstration
const taskStore = new in_memory_js_1.InMemoryTaskStore();
// Create an MCP server with implementation details
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'simple-streamable-http-server',
version: '1.0.0',
icons: [{ src: './mcp.svg', sizes: ['512x512'], mimeType: 'image/svg+xml' }],
websiteUrl: 'https://github.com/modelcontextprotocol/typescript-sdk'
}, {
capabilities: { logging: {}, tasks: { requests: { tools: { call: {} } } } },
taskStore, // Enable task support
taskMessageQueue: new in_memory_js_1.InMemoryTaskMessageQueue()
});
version: '1.0.0'
}, { capabilities: { logging: {} } });
// Register a simple tool that returns a greeting
server.registerTool('greet', {
title: 'Greeting Tool', // Display name for UI
description: 'A simple greeting tool',
inputSchema: {
name: z.string().describe('Name to greet')
}
name: zod_1.z.string().describe('Name to greet'),
},
}, async ({ name }) => {
return {
content: [
{
type: 'text',
text: `Hello, ${name}!`
}
]
text: `Hello, ${name}!`,
},
],
};
});
// Register a tool that sends multiple greetings with notifications (with annotations)
server.registerTool('multi-greet', {
description: 'A tool that sends different greetings with delays between them',
inputSchema: {
name: z.string().describe('Name to greet')
},
annotations: {
title: 'Multiple Greeting Tool',
readOnlyHint: true,
openWorldHint: false
}
}, async ({ name }, extra) => {
server.tool('multi-greet', 'A tool that sends different greetings with delays between them', {
name: zod_1.z.string().describe('Name to greet'),
}, {
title: 'Multiple Greeting Tool',
readOnlyHint: true,
openWorldHint: false
}, async ({ name }, { sendNotification }) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
await server.sendLoggingMessage({
level: 'debug',
data: `Starting multi-greet for ${name}`
}, extra.sessionId);
await sendNotification({
method: "notifications/message",
params: { level: "debug", data: `Starting multi-greet for ${name}` }
});
await sleep(1000); // Wait 1 second before first greeting
await server.sendLoggingMessage({
level: 'info',
data: `Sending first greeting to ${name}`
}, extra.sessionId);
await sendNotification({
method: "notifications/message",
params: { level: "info", data: `Sending first greeting to ${name}` }
});
await sleep(1000); // Wait another second before second greeting
await server.sendLoggingMessage({
level: 'info',
data: `Sending second greeting to ${name}`
}, extra.sessionId);
await sendNotification({
method: "notifications/message",
params: { level: "info", data: `Sending second greeting to ${name}` }
});
return {
content: [
{
type: 'text',
text: `Good morning, ${name}!`
text: `Good morning, ${name}!`,
}
]
],
};
});
// Register a tool that demonstrates form elicitation (user input collection with a schema)
// Register a tool that demonstrates elicitation (user input collection)
// This creates a closure that captures the server instance
server.registerTool('collect-user-info', {
description: 'A tool that collects user information through form elicitation',
inputSchema: {
infoType: z.enum(['contact', 'preferences', 'feedback']).describe('Type of information to collect')
}
}, async ({ infoType }, extra) => {
server.tool('collect-user-info', 'A tool that collects user information through elicitation', {
infoType: zod_1.z.enum(['contact', 'preferences', 'feedback']).describe('Type of information to collect'),
}, async ({ infoType }) => {
let message;
let requestedSchema;
switch (infoType) {
@@ -124,21 +89,21 @@ const getServer = () => {
name: {
type: 'string',
title: 'Full Name',
description: 'Your full name'
description: 'Your full name',
},
email: {
type: 'string',
title: 'Email Address',
description: 'Your email address',
format: 'email'
format: 'email',
},
phone: {
type: 'string',
title: 'Phone Number',
description: 'Your phone number (optional)'
}
description: 'Your phone number (optional)',
},
},
required: ['name', 'email']
required: ['name', 'email'],
};
break;
case 'preferences':
@@ -151,23 +116,23 @@ const getServer = () => {
title: 'Theme',
description: 'Choose your preferred theme',
enum: ['light', 'dark', 'auto'],
enumNames: ['Light', 'Dark', 'Auto']
enumNames: ['Light', 'Dark', 'Auto'],
},
notifications: {
type: 'boolean',
title: 'Enable Notifications',
description: 'Would you like to receive notifications?',
default: true
default: true,
},
frequency: {
type: 'string',
title: 'Notification Frequency',
description: 'How often would you like notifications?',
enum: ['daily', 'weekly', 'monthly'],
enumNames: ['Daily', 'Weekly', 'Monthly']
}
enumNames: ['Daily', 'Weekly', 'Monthly'],
},
},
required: ['theme']
required: ['theme'],
};
break;
case 'feedback':
@@ -180,44 +145,40 @@ const getServer = () => {
title: 'Rating',
description: 'Rate your experience (1-5)',
minimum: 1,
maximum: 5
maximum: 5,
},
comments: {
type: 'string',
title: 'Comments',
description: 'Additional comments (optional)',
maxLength: 500
maxLength: 500,
},
recommend: {
type: 'boolean',
title: 'Would you recommend this?',
description: 'Would you recommend this to others?'
}
description: 'Would you recommend this to others?',
},
},
required: ['rating', 'recommend']
required: ['rating', 'recommend'],
};
break;
default:
throw new Error(`Unknown info type: ${infoType}`);
}
try {
// Use sendRequest through the extra parameter to elicit input
const result = await extra.sendRequest({
method: 'elicitation/create',
params: {
mode: 'form',
message,
requestedSchema
}
}, types_js_1.ElicitResultSchema);
// Use the underlying server instance to elicit input from the client
const result = await server.server.elicitInput({
message,
requestedSchema,
});
if (result.action === 'accept') {
return {
content: [
{
type: 'text',
text: `Thank you! Collected ${infoType} information: ${JSON.stringify(result.content, null, 2)}`
}
]
text: `Thank you! Collected ${infoType} information: ${JSON.stringify(result.content, null, 2)}`,
},
],
};
}
else if (result.action === 'decline') {
@@ -225,9 +186,9 @@ const getServer = () => {
content: [
{
type: 'text',
text: `No information was collected. User declined ${infoType} information request.`
}
]
text: `No information was collected. User declined ${infoType} information request.`,
},
],
};
}
else {
@@ -235,9 +196,9 @@ const getServer = () => {
content: [
{
type: 'text',
text: `Information collection was cancelled by the user.`
}
]
text: `Information collection was cancelled by the user.`,
},
],
};
}
}
@@ -246,9 +207,9 @@ const getServer = () => {
content: [
{
type: 'text',
text: `Error collecting ${infoType} information: ${error}`
}
]
text: `Error collecting ${infoType} information: ${error}`,
},
],
};
}
});
@@ -257,8 +218,8 @@ const getServer = () => {
title: 'Greeting Template', // Display name for UI
description: 'A simple greeting prompt template',
argsSchema: {
name: z.string().describe('Name to include in greeting')
}
name: zod_1.z.string().describe('Name to include in greeting'),
},
}, async ({ name }) => {
return {
messages: [
@@ -266,32 +227,32 @@ const getServer = () => {
role: 'user',
content: {
type: 'text',
text: `Please greet ${name} in a friendly manner.`
}
}
]
text: `Please greet ${name} in a friendly manner.`,
},
},
],
};
});
// Register a tool specifically for testing resumability
server.registerTool('start-notification-stream', {
description: 'Starts sending periodic notifications for testing resumability',
inputSchema: {
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
count: z.number().describe('Number of notifications to send (0 for 100)').default(50)
}
}, async ({ interval, count }, extra) => {
server.tool('start-notification-stream', 'Starts sending periodic notifications for testing resumability', {
interval: zod_1.z.number().describe('Interval in milliseconds between notifications').default(100),
count: zod_1.z.number().describe('Number of notifications to send (0 for 100)').default(50),
}, async ({ interval, count }, { sendNotification }) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;
while (count === 0 || counter < count) {
counter++;
try {
await server.sendLoggingMessage({
level: 'info',
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
}, extra.sessionId);
await sendNotification({
method: "notifications/message",
params: {
level: "info",
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
}
});
}
catch (error) {
console.error('Error sending notification:', error);
console.error("Error sending notification:", error);
}
// Wait for the specified interval
await sleep(interval);
@@ -300,9 +261,9 @@ const getServer = () => {
content: [
{
type: 'text',
text: `Started sending periodic notifications every ${interval}ms`
text: `Started sending periodic notifications every ${interval}ms`,
}
]
],
};
});
// Create a simple resource at a fixed URI
@@ -315,9 +276,9 @@ const getServer = () => {
contents: [
{
uri: 'https://example.com/greetings/default',
text: 'Hello, world!'
}
]
text: 'Hello, world!',
},
],
};
});
// Create additional resources for ResourceLink demonstration
@@ -330,9 +291,9 @@ const getServer = () => {
contents: [
{
uri: 'file:///example/file1.txt',
text: 'This is the content of file 1'
}
]
text: 'This is the content of file 1',
},
],
};
});
server.registerResource('example-file-2', 'file:///example/file2.txt', {
@@ -344,9 +305,9 @@ const getServer = () => {
contents: [
{
uri: 'file:///example/file2.txt',
text: 'This is the content of file 2'
}
]
text: 'This is the content of file 2',
},
],
};
});
// Register a tool that returns ResourceLinks
@@ -354,8 +315,8 @@ const getServer = () => {
title: 'List Files with ResourceLinks',
description: 'Returns a list of files as ResourceLinks without embedding their content',
inputSchema: {
includeDescriptions: z.boolean().optional().describe('Whether to include descriptions in the resource links')
}
includeDescriptions: zod_1.z.boolean().optional().describe('Whether to include descriptions in the resource links'),
},
}, async ({ includeDescriptions = true }) => {
const resourceLinks = [
{
@@ -384,60 +345,27 @@ const getServer = () => {
content: [
{
type: 'text',
text: 'Here are the available files as resource links:'
text: 'Here are the available files as resource links:',
},
...resourceLinks,
{
type: 'text',
text: '\nYou can read any of these resources using their URI.'
text: '\nYou can read any of these resources using their URI.',
}
]
],
};
});
// Register a long-running tool that demonstrates task execution
// Using the experimental tasks API - WARNING: may change without notice
server.experimental.tasks.registerToolTask('delay', {
title: 'Delay',
description: 'A simple tool that delays for a specified duration, useful for testing task execution',
inputSchema: {
duration: z.number().describe('Duration in milliseconds').default(5000)
}
}, {
async createTask({ duration }, { taskStore, taskRequestedTtl }) {
// Create the task
const task = await taskStore.createTask({
ttl: taskRequestedTtl
});
// Simulate out-of-band work
(async () => {
await new Promise(resolve => setTimeout(resolve, duration));
await taskStore.storeTaskResult(task.taskId, 'completed', {
content: [
{
type: 'text',
text: `Completed ${duration}ms delay`
}
]
});
})();
// Return CreateTaskResult with the created task
return {
task
};
},
async getTask(_args, { taskId, taskStore }) {
return await taskStore.getTask(taskId);
},
async getTaskResult(_args, { taskId, taskStore }) {
const result = await taskStore.getTaskResult(taskId);
return result;
}
});
return server;
};
const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
const app = (0, express_js_1.createMcpExpressApp)();
const app = (0, express_1.default)();
app.use(express_1.default.json());
// Allow CORS all domains, expose the Mcp-Session-Id header
app.use((0, cors_1.default)({
origin: '*', // Allow all origins
exposedHeaders: ["Mcp-Session-Id"]
}));
// Set up OAuth if enabled
let authMiddleware = null;
if (useOAuth) {
@@ -454,15 +382,14 @@ if (useOAuth) {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token: token
}).toString()
});
if (!response.ok) {
const text = await response.text().catch(() => null);
throw new Error(`Invalid or expired token: ${text}`);
throw new Error(`Invalid or expired token: ${await response.text()}`);
}
const data = await response.json();
if (strictOAuth) {
@@ -478,7 +405,7 @@ if (useOAuth) {
token,
clientId: data.client_id,
scopes: data.scope ? data.scope.split(' ') : [],
expiresAt: data.exp
expiresAt: data.exp,
};
}
};
@@ -487,12 +414,12 @@ if (useOAuth) {
oauthMetadata,
resourceServerUrl: mcpServerUrl,
scopesSupported: ['mcp:tools'],
resourceName: 'MCP Demo Server'
resourceName: 'MCP Demo Server',
}));
authMiddleware = (0, bearerAuth_js_1.requireBearerAuth)({
verifier: tokenVerifier,
requiredScopes: [],
resourceMetadataUrl: (0, router_js_1.getOAuthProtectedResourceMetadataUrl)(mcpServerUrl)
resourceMetadataUrl: (0, router_js_1.getOAuthProtectedResourceMetadataUrl)(mcpServerUrl),
});
}
// Map to store transports by session ID
@@ -521,7 +448,7 @@ const mcpPostHandler = async (req, res) => {
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
onsessioninitialized: (sessionId) => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
@@ -549,9 +476,9 @@ const mcpPostHandler = async (req, res) => {
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
message: 'Bad Request: No valid session ID provided',
},
id: null
id: null,
});
return;
}
@@ -566,9 +493,9 @@ const mcpPostHandler = async (req, res) => {
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
message: 'Internal server error',
},
id: null
id: null,
});
}
}
@@ -634,7 +561,7 @@ if (useOAuth && authMiddleware) {
else {
app.delete('/mcp', mcpDeleteHandler);
}
app.listen(MCP_PORT, error => {
app.listen(MCP_PORT, (error) => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);