yikart/AiToEarn · error
-32000
-32000
Error message
Bad Request: Mcp-Session-Id header is required
What it means
In stateful mode, a POST arrived with no Mcp-Session-Id header and the body was not an initialize request. Since only initialize may create a session, the server cannot route the call and returns -32000 with HTTP 400. Every non-initialize stateful request must reference an existing session.
Source
Thrown at project/aitoearn-backend/libs/nest-mcp/src/services/mcp-streamable-http.service.ts:313
id: null,
})
return
}
// Use existing transport
const transport = this.transports[sessionId]
this.logger.debug(
`[${sessionId}] Handling request with existing session`,
)
// Handle the request with existing transport and handlers
await transport.handleRequest(req.raw, res.raw, body)
return
}
// Case 3: No session ID and not initialization
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: Mcp-Session-Id header is required',
},
id: null,
})
}
/**
* Handle GET requests for SSE streams
*/
async handleGetRequest(req: any, res: any): Promise<void> {
const adapter = HttpAdapterFactory.getAdapter(req, res)
const adaptedReq = adapter.adaptRequest(req)
const adaptedRes = adapter.adaptResponse(res)
if (this.isStatelessMode) {View on GitHub (pinned to d3aa8bea5b)
Solutions
- Send an initialize request first, store the Mcp-Session-Id response header, and attach it to all subsequent requests.
- If your usage is one-shot calls without sessions, use the library's stateless mode (isStatelessMode) instead.
- Audit your HTTP client so the mcp-session-id header persists across every request in the conversation.
- Check that middleware or the proxy isn't stripping the Mcp-Session-Id header.
Example fix
// before
await fetch(url, { method: 'POST', body: JSON.stringify(callToolReq) })
// after
const initRes = await fetch(url, { method: 'POST', body: JSON.stringify(initReq) })
const sid = initRes.headers.get('mcp-session-id')
await fetch(url, { method: 'POST', headers: { 'mcp-session-id': sid }, body: JSON.stringify(callToolReq) }) Defensive patterns
Strategy: validation
Validate before calling
if (!sessionId) {
throw new Error('Call initialize first and pass the returned Mcp-Session-Id header')
} Type guard
function isStatefulReady(ctx: { sessionId?: string; initialized: boolean }): boolean {
return ctx.initialized && typeof ctx.sessionId === 'string' && ctx.sessionId.length > 0
} Try / catch
try {
return await post(url, body, { 'mcp-session-id': sessionId })
} catch (e) {
if (e.status === 400 && e.body?.error?.code === -32000) {
sessionId = await initialize(url)
return post(url, body, { 'mcp-session-id': sessionId })
}
throw e
} Prevention
- Store the session id in a client context object and inject the header on every request.
- Use an official MCP client transport instead of hand-rolled fetch calls.
- Check middleware/proxies for stripping of custom mcp-* headers.
- If sessions are unwanted, switch the endpoint to stateless mode rather than skipping initialize.
When it happens
Trigger: POSTing tools/list, tools/call, resources/*, or notifications without initializing first (no session id header), or losing the header between requests because the HTTP client drops custom headers on subsequent calls.
Common situations: Calling a tool directly with curl without doing the initialize dance first, fetch/axios wrapper that rebuilds headers per request and forgets mcp-session-id, or misconfiguring the client to talk to a stateful endpoint when it assumes stateless behavior.
Related errors
- Session not found
- -32001
- ResponseCode.ChannelAuthSessionInvalid
- ResponseCode.ChannelAuthPlatformMismatch
- ResponseCode.ChannelAuthSessionCompleted
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/9c221b5dbdd76148.
Report an issue: GitHub.