{"record":{"id":"9d48cd64fa3b2480","repo":"abhigyanpatwari/GitNexus","slug":"32000","errorCode":"-32000","errorMessage":"First request must be initialize. No session ID provided.","messagePattern":"First request must be initialize\\. No session ID provided\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"gitnexus/src/mcp/http-transport.ts","lineNumber":276,"sourceCode":"      const session = sessions.get(sessionId)!;\n      session.lastActivity = Date.now();\n      await session.transport.handleRequest(req, res, req.body);\n    } else if (sessionId) {\n      // Unknown / expired session ID — tell the client to re-initialize (per MCP spec).\n      res.status(404).json({\n        jsonrpc: '2.0',\n        error: { code: -32001, message: 'Session not found. Re-initialize.' },\n        id: null,\n      });\n    } else if (req.method === 'POST') {\n      // No session ID — new client. Only accept initialize requests to avoid\n      // orphaned Server instances that can never be reclaimed by the TTL sweep.\n      // Use the SDK's isInitializeRequest so a single-element JSON-RPC batch is\n      // recognised too, rather than a brittle `body.method === 'initialize'` check.\n      const body = req.body as unknown;\n      const messages = Array.isArray(body) ? body : [body];\n      if (!messages.some(isInitializeRequest)) {\n        res.status(400).json({\n          jsonrpc: '2.0',\n          error: {\n            code: -32000,\n            message: 'First request must be initialize. No session ID provided.',\n          },\n          id: null,\n        });\n        return;\n      }\n\n      // Reject when the session cap is reached — prevents memory exhaustion via\n      // an initialize flood (each session holds a live Server + Transport).\n      if (sessions.size >= MAX_SESSIONS) {\n        res.status(503).json({\n          jsonrpc: '2.0',\n          error: { code: -32000, message: 'Server at session capacity. Try again later.' },\n          id: null,\n        });","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/mcp/http-transport.ts#L258-L294","documentation":"Thrown by the GitNexus MCP streamable-HTTP endpoint (POST /mcp) when a request arrives with no mcp-session-id header and its JSON-RPC body (single message or batch) contains no initialize request. The MCP protocol requires the very first request on a fresh connection to be initialize, because that is the only path that allocates a Server + Transport pair and assigns a session ID. Rejecting anything else up front prevents orphaned Server instances that the TTL sweep could never reclaim.","triggerScenarios":"POST to /mcp whose body is e.g. {jsonrpc:'2.0',method:'tools/list',id:1} with no mcp-session-id header; a JSON-RPC batch whose elements include no initialize message; a hand-rolled fetch/axios script that skips the handshake; a proxy or gateway that strips the mcp-session-id header, making every request look like a new client.","commonSituations":"Porting a stdio-based MCP client to streamable HTTP without adding the initialize handshake; forgetting to persist the mcp-session-id response header between requests; sending the notifications/initialized notification before the initialize response arrives; testing the endpoint with curl using a random JSON-RPC method.","solutions":["Send a proper JSON-RPC initialize request first: POST /mcp with Content-Type: application/json and Accept: application/json, text/event-stream, body {jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion, capabilities:{}, clientInfo:{...}}}","Read the mcp-session-id response header and include it as a request header on every subsequent POST/GET/DELETE to /mcp","If using the MCP TypeScript SDK, let StreamableHTTPClientTransport manage the handshake and session header instead of hand-rolling fetch calls","If batching the first request, make sure the array contains the initialize message (the server checks Array.isArray(body) ? body : [body] with the SDK's isInitializeRequest)"],"exampleFix":"// before\nawait fetch(`${base}/mcp`, {\n  method: 'POST',\n  headers: { 'content-type': 'application/json' },\n  body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/list', id: 1 }),\n}); // 400 -32000\n\n// after\nconst initRes = await fetch(`${base}/mcp`, {\n  method: 'POST',\n  headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },\n  body: JSON.stringify({\n    jsonrpc: '2.0', id: 1, method: 'initialize',\n    params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'ci', version: '1.0.0' } },\n  }),\n});\nconst sessionId = initRes.headers.get('mcp-session-id');\nawait fetch(`${base}/mcp`, {\n  method: 'POST',\n  headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', 'mcp-session-id': sessionId! },\n  body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),\n});","handlingStrategy":"validation","validationCode":"// Before the first POST to /mcp, verify the message is an initialize request.\nfunction buildFirstMessage(): { jsonrpc: '2.0'; id: number; method: string; params?: unknown } {\n  return { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'my-client', version: '1.0.0' } } };\n}\nconst first = buildFirstMessage();\nif (first.method !== 'initialize') throw new Error('first message must be initialize');\nconst res = await fetch(`${base}/mcp`, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' }, body: JSON.stringify(first) });\nconst sessionId = res.headers.get('mcp-session-id');\nif (!sessionId) throw new Error(`handshake rejected: HTTP ${res.status} ${await res.text()}`);","typeGuard":"function isInitializeRequest(m: unknown): m is { jsonrpc: '2.0'; method: 'initialize' } {\n  return (\n    typeof m === 'object' && m !== null &&\n    (m as Record<string, unknown>).jsonrpc === '2.0' &&\n    (m as Record<string, unknown>).method === 'initialize'\n  );\n}","tryCatchPattern":"const res = await postToMcp(msg);\nif (res.status === 400) {\n  const body = await res.json();\n  if (body.error?.code === -32000 && /First request must be initialize/.test(body.error.message)) {\n    sessionId = await initialize(); // re-run handshake, capture new mcp-session-id\n    return postToMcp(msg, sessionId); // retry the original call exactly once\n  }\n}","preventionTips":["Always open a streamable MCP session with an initialize request before anything else","Persist the mcp-session-id response header and attach it to every subsequent request","Use the official MCP SDK's StreamableHTTPClientTransport instead of hand-rolled HTTP calls","Never batch non-initialize messages into the very first request"],"tags":["mcp","json-rpc","http-transport","session","handshake"],"backgroundTag":"mcp-session-initialization","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}