yikart/AiToEarn · error
Session not found
Error message
Session not found
What it means
The MCP SSE service routes POST messages to a per-session transport held in an in-memory map. If the sessionId query parameter has no registered transport, the request is rejected with 404 'Session not found' — the client is addressing a session the server no longer knows.
Source
Thrown at project/aitoearn-backend/libs/nest-mcp/src/services/mcp-sse.service.ts:110
this.mcpServers.delete(sessionId)
this.pingService.removeConnection(sessionId)
}
await mcpServer.connect(transport)
}
/**
* Handle message processing for SSE
*/
async handleMessage(rawReq: any, rawRes: any, body: unknown): Promise<any> {
const adapter = HttpAdapterFactory.getAdapter(rawReq, rawRes)
const req = adapter.adaptRequest(rawReq)
const res = adapter.adaptResponse(rawRes)
const sessionId = req.query['sessionId'] as string
const transport = this.transports.get(sessionId)
if (!transport) {
return res.status(404).send('Session not found')
}
const mcpServer = this.mcpServers.get(sessionId)
if (!mcpServer) {
return res.status(404).send('MCP server not found for session')
}
// Resolve the request-scoped tool executor service
const contextId = ContextIdFactory.getByRequest(req)
const executor = await this.moduleRef.resolve(
McpExecutorService,
contextId,
)
// Register request handlers with the user context from this specific request
executor.registerRequestHandlers(mcpServer, req)
// Process the messageView on GitHub (pinned to d3aa8bea5b)
Solutions
- Re-establish the SSE connection (GET /sse) to obtain a fresh sessionId, then use it for POSTs
- Ensure sticky sessions / same-instance routing so POSTs reach the instance holding the transport
- Implement client logic to detect 404 and automatically reconnect the SSE stream
- Enable SSE heartbeat/keepalive and tune timeouts so idle sessions are not dropped
Example fix
// before
await post(endpoint, { ...body, sessionId: storedSessionId }) // stale after restart
// after
try {
await post(endpoint, body, { params: { sessionId } })
} catch (e) {
if (isSessionNotFound(e)) { sessionId = await reconnectSse() }
} Defensive patterns
Strategy: retry
Validate before calling
// before POSTing, confirm the SSE stream is open and the id is current
if (!sseConnectionOpen || Date.now() - sessionIssuedAt > MAX_SESSION_TTL) {
sessionId = await reconnectSse()
} Type guard
function isSessionNotFound(resp: { status: number; body?: unknown }): boolean {
return resp.status === 404 && typeof resp.body === 'string' && resp.body.includes('Session not found')
} Try / catch
try {
await postMessage(sessionId, payload)
} catch (e) {
if (isSessionNotFoundErr(e)) {
sessionId = await reconnectSse() // GET /sse for a new id
await postMessage(sessionId, payload)
} else { throw e }
} Prevention
- Treat sessionId as ephemeral: always refresh after server restarts
- Enable SSE heartbeats to keep sessions alive
- Use sticky routing so POSTs hit the instance owning the session
- Implement automatic reconnect on 404
When it happens
Trigger: POST to the SSE message endpoint with a sessionId that was never opened, or whose SSE connection was closed/server restarted, so transports no longer contains the id.
Common situations: Server restart or multi-instance deployment where the SSE connection lives on another pod; client reconnecting POSTs with a stale sessionId after network drop; load balancer routing POSTs to a different instance than the GET /sse; long idle timeout closing the SSE stream while the client keeps POSTing.
Related errors
- MCP server not found for session
- -32001
- -32000
- ResponseCode.ChannelAuthSessionInvalid
- ResponseCode.ChannelAuthPlatformMismatch
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/dedce6c0a4cd92e4.
Report an issue: GitHub.