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
This commit is contained in:
Claude
2026-04-23 13:34:42 +00:00
committed by Carlos Valente
parent ac3694ce4d
commit 3da3950fce
+11 -14
View File
@@ -355,12 +355,11 @@ async function handleToolCall(name: string, args: Record<string, unknown>): Prom
try { try {
const { id, title } = args as { id: string; title: string }; const { id, title } = args as { id: string; title: string };
const dataProvider = getDataProvider(); const dataProvider = getDataProvider();
const rundown = structuredClone(dataProvider.getRundown(id)) as { id: string; title: string }; const rundown = dataProvider.getRundown(id);
rundown.title = title; if (!rundown) throw new Error(`Rundown ${id} not found`);
await dataProvider.setRundown(id, rundown as never); await dataProvider.setRundown(id, { ...rundown, title });
if (id === getCurrentRundown().id) { if (id === getCurrentRundown().id) {
const customFields = dataProvider.getCustomFields(); await initRundown(dataProvider.getRundown(id), dataProvider.getCustomFields());
await initRundown(rundown as never, customFields);
} }
return ok(rundownListResponse()); return ok(rundownListResponse());
} catch (e) { } catch (e) {
@@ -536,17 +535,15 @@ mcpRouter.post('/', async (req, res) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined; const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (isInitializeRequest(body)) { 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({ const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(), sessionIdGenerator: () => sessionId,
}); });
const id = randomUUID(); sessions.set(sessionId, transport);
sessions.set(id, transport); transport.onclose = () => sessions.delete(sessionId);
transport.onclose = () => {
sessions.delete(id);
};
const mcpServer = createMcpServer(); const mcpServer = createMcpServer();
await mcpServer.connect(transport); await mcpServer.connect(transport);
@@ -587,5 +584,5 @@ mcpRouter.delete('/', async (req, res) => {
const transport = sessions.get(sessionId)!; const transport = sessions.get(sessionId)!;
sessions.delete(sessionId); sessions.delete(sessionId);
await transport.close(); await transport.close();
res.status(200).json({ ok: true }); res.status(204).send();
}); });