From 3da3950fce0b68ce77c655e9c675354faabba940 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 13:34:42 +0000 Subject: [PATCH] fix(mcp): session ID mismatch, rename_rundown data loss, DELETE status - Session ID: deterministic UUID passed to sessionIdGenerator so the map key matches the mcp-session-id header sent to clients; previously two independent randomUUID() calls produced different IDs, breaking all post-initialize requests - rename_rundown: use spread { ...rundown, title } instead of narrow structuredClone cast that silently stripped order/entries/revision - DELETE /mcp: return 204 No Content instead of 200 with body https://claude.ai/code/session_01U24MeuUacYXeQhbX3tatEe --- apps/server/src/api-mcp/mcp.router.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/server/src/api-mcp/mcp.router.ts b/apps/server/src/api-mcp/mcp.router.ts index 12ca213ad..0d86aa998 100644 --- a/apps/server/src/api-mcp/mcp.router.ts +++ b/apps/server/src/api-mcp/mcp.router.ts @@ -355,12 +355,11 @@ async function handleToolCall(name: string, args: Record): Prom try { const { id, title } = args as { id: string; title: string }; const dataProvider = getDataProvider(); - const rundown = structuredClone(dataProvider.getRundown(id)) as { id: string; title: string }; - rundown.title = title; - await dataProvider.setRundown(id, rundown as never); + const rundown = dataProvider.getRundown(id); + if (!rundown) throw new Error(`Rundown ${id} not found`); + await dataProvider.setRundown(id, { ...rundown, title }); if (id === getCurrentRundown().id) { - const customFields = dataProvider.getCustomFields(); - await initRundown(rundown as never, customFields); + await initRundown(dataProvider.getRundown(id), dataProvider.getCustomFields()); } return ok(rundownListResponse()); } catch (e) { @@ -536,17 +535,15 @@ mcpRouter.post('/', async (req, res) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (isInitializeRequest(body)) { - // New session: create server + transport + // New session: pin the session ID so the map key matches what the transport + // sends back to the client in the mcp-session-id response header. + const sessionId = randomUUID(); const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), + sessionIdGenerator: () => sessionId, }); - const id = randomUUID(); - sessions.set(id, transport); - - transport.onclose = () => { - sessions.delete(id); - }; + sessions.set(sessionId, transport); + transport.onclose = () => sessions.delete(sessionId); const mcpServer = createMcpServer(); await mcpServer.connect(transport); @@ -587,5 +584,5 @@ mcpRouter.delete('/', async (req, res) => { const transport = sessions.get(sessionId)!; sessions.delete(sessionId); await transport.close(); - res.status(200).json({ ok: true }); + res.status(204).send(); });