mastra-ai/mastra · error

Bad Request: ${req.method} request requires a valid session

Error message

Bad Request: ${req.method} request requires a valid session ID

What it means

The MCPServer's streamable-HTTP handler rejects GET or DELETE requests that arrive without a valid `mcp-session-id` header. The streamable HTTP transport is sessionful: only the initial `initialize` POST can run without a session; every subsequent request (including SSE GET streams and session-terminating DELETEs) must carry the session ID returned during initialize. The server responds with HTTP 400 and JSON-RPC code -32000.

Source

Thrown at packages/mcp/src/server/server.ts:2225

          } else {
            // POST request but not initialize, and no session ID
            this.logger.warn('Received non-initialize POST request without session ID');
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(
              JSON.stringify({
                jsonrpc: '2.0',
                error: {
                  code: -32000,
                  message: 'Bad Request: No valid session ID provided for non-initialize request',
                },
                id: (body as any)?.id ?? null, // Include original request ID if available
              }),
            );
          }
        } else {
          // Non-POST request (GET/DELETE) without a session ID
          this.logger.warn('Received request without session ID', { method: req.method });
          res.writeHead(400, { 'Content-Type': 'application/json' });
          res.end(
            JSON.stringify({
              jsonrpc: '2.0',
              error: {
                code: -32000,
                message: `Bad Request: ${req.method} request requires a valid session ID`,
              },
              id: null,
            }),
          );
        }
      }
    } catch (error) {
      const mastraError = new MastraError(
        {
          id: 'MCP_SERVER_HTTP_CONNECTION_FAILED',
          domain: ErrorDomain.MCP,
          category: ErrorCategory.USER,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Perform the `initialize` JSON-RPC POST first and capture the session ID from the `mcp-session-id` response header.
  2. Send that session ID back as the `mcp-session-id` header on every subsequent GET/DELETE/POST request.
  3. If your deployment is serverless or multi-instance, enable the serverless/stateless request path (sessionIdGenerator: undefined / handleServerlessRequest) so no session state is required.
  4. Verify no proxy, API gateway, or middleware strips the `mcp-session-id` header.
  5. If the server restarted, re-initialize the session instead of reusing a stale session ID.

Example fix

// before
curl -X GET http://localhost:4111/mcp
// after
SESSION=$(curl -s -D - -X POST http://localhost:4111/mcp -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}' \
  | grep -i mcp-session-id | awk '{print $2}' | tr -d '\r')
curl -X GET http://localhost:4111/mcp -H "mcp-session-id: $SESSION"
Defensive patterns

Strategy: validation

Validate before calling

const sid = headers['mcp-session-id'];
if (!sid && !isInitializeRequest(body)) {
  throw new Error('mcp-session-id header required for non-initialize MCP requests');
}

Type guard

function hasSessionId(h: Record<string, string | string[] | undefined>): h is Record<string, string> & { 'mcp-session-id': string } {
  return typeof h['mcp-session-id'] === 'string' && h['mcp-session-id'].length > 0;
}

Try / catch

try {
  const res = await fetch(mcpUrl, { method: 'GET', headers: { 'mcp-session-id': sessionId } });
  if (res.status === 400) {
    const err = await res.json();
    if (err.error?.code === -32000) sessionId = await reinitialize();
  }
} catch (e) { /* retry with fresh initialize */ }

Prevention

When it happens

Trigger: Calling the MCP HTTP endpoint with GET (to open an SSE stream) or DELETE (to close a session) while omitting the `mcp-session-id` header; a client that never performed the `initialize` handshake; a proxy/gateway stripping custom headers; a client reusing a base URL without persisting the session ID; server restart that lost in-memory `streamableHTTPTransports` while the client still sends its old session ID (that variant surfaces as the sibling 'No valid session ID provided' error for POST).

Common situations: Hand-rolled HTTP clients or curl probes that hit the GET endpoint directly without initializing; third-party MCP clients behind header-stripping corporate proxies; serverless/edge deployments where in-memory session maps don't survive between invocations (stateless mode should be used instead); load balancers routing follow-up requests to a different replica than the one holding the session.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/0e47ecae6adbd34d. Report an issue: GitHub.