linshenkx/prompt-optimizer · error
Invalid or missing session ID
Error message
Invalid or missing session ID
What it means
The GET /mcp endpoint (server-to-client SSE notifications) requires an mcp-session-id header matching a transport in the in-memory transports map. A missing header or an unknown/expired session ID yields HTTP 400 with this plain-text body.
Source
Thrown at packages/mcp-server/src/index.ts:443
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided',
},
id: null,
});
return;
}
// 处理请求
await httpTransport.handleRequest(req, res, req.body);
});
// 处理 GET 请求(服务器到客户端通知,通过 SSE)
app.get('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const httpTransport = transports[sessionId];
await httpTransport.handleRequest(req, res);
});
// 处理 DELETE 请求(会话终止)
app.delete('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const httpTransport = transports[sessionId];
await httpTransport.handleRequest(req, res);
});View on GitHub (pinned to 3e677b1d9f)
Solutions
- Re-run the initialize handshake to obtain a fresh session ID before opening the SSE stream.
- If running multiple instances, use sticky sessions or a shared/external session store for transports.
- Detect 400 on the SSE GET and automatically re-initialize + resubscribe.
Example fix
// before
const es = new EventSource(MCP_URL + '/mcp')
// after
const es = new EventSource(MCP_URL + '/mcp', { } ) // EventSource cannot set headers; use fetch-based SSE:
// const res = await fetch(MCP_URL + '/mcp', { headers: { 'mcp-session-id': sessionId } }) Defensive patterns
Strategy: retry
Validate before calling
if (!sessionId || !(await sessionExists(sessionId))) {
sessionId = await initializeSession() // handshake returns fresh mcp-session-id
}
const sseRes = await fetch(MCP_URL + '/mcp', { headers: { 'mcp-session-id': sessionId, accept: 'text/event-stream' } })
if (sseRes.status === 400) { sessionId = await initializeSession(); /* retry once */ } Type guard
const isInvalidSessionResponse = (res: Response): boolean => res.status === 400 && /Invalid or missing session ID/.test(res.text as unknown as string ?? '')
Try / catch
try { await openNotificationStream(sessionId) } catch { const fresh = await initializeSession(); await openNotificationStream(fresh) } Prevention
- Open the SSE/GET stream only after a successful initialize.
- Detect 400 on GET /mcp and re-initialize + resubscribe automatically.
- Behind load balancers, use sticky sessions or externalize session state.
When it happens
Trigger: GET /mcp with no mcp-session-id header, with a session ID from before a server restart, or after the session was deleted via DELETE /mcp; long-lived SSE clients that never re-initialize.
Common situations: Server process restarted (transports map is memory-only and empty), horizontal scaling where the SSE request lands on a different instance that doesn't hold the session, or clients that open the notification stream before completing initialize.
Related errors
- -32000
- ${providerErrorMessage}
- [BasicSystemSession] ImageStorageService is unavailable; can
- [BasicSystemSession] Failed to persist test image
- [ImageImage2ImageSession] PreferenceService is unavailable;
AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27).
Data as JSON: /api/errors/26512165e30fb2a8.
Report an issue: GitHub.