Skip to content

sdk/server/streamableHttp.js cannot support stateless mode #340

@didiercolens

Description

@didiercolens

Describe the bug
https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/streamableHttp.ts#L373-L389

  private validateSession(req: IncomingMessage, res: ServerResponse): boolean {
    if (!this._initialized) {
      // If the server has not been initialized yet, reject all requests
      res.writeHead(400).end(JSON.stringify({
        jsonrpc: "2.0",
        error: {
          code: -32000,
          message: "Bad Request: Server not initialized"
        },
        id: null
      }));
      return false;
    }
    if (this.sessionId === undefined) {
      // If the session ID is not set, the session management is disabled
      // and we don't need to validate the session ID
      return true;
    }

can never succeed in stateless mode because the server cannot re-use an existing transport when there is no session-id. Therefore this._initialized is always false and validateSession always fails.
Note that this.sessionId is also not set in such a case.

In a scenario where the MCP server is a Kubernetes deployment, it is also possible a request reaches another pod which does not have the transport in its cache.

To Reproduce
Create a streamableHttp server with sessionId disabled for example:

app.post('/mcp', async (req: Request, res: Response) => {
  console.log('Received MCP request:', req.body);
  try {
    let transport: StreamableHTTPServerTransport;

    if (isInitializeRequest(req.body)) {
      // New initialization request - use JSON response mode
      transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => undefined,
        enableJsonResponse: true,
      });

    } else {
        transport = new StreamableHTTPServerTransport({
            sessionIdGenerator: () => undefined
        });        
    }
    // Handle the request with the transport
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error('Error handling MCP request:', error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: '2.0',
        error: {
          code: -32603,
          message: 'Internal server error',
        },
        id: null,
      });
    }
  }
});

and try to connect with a client

Expected behavior
client should be able to send the intialized message after initialisation even if there is no session-id to track sessions. i.E. stateless mode.

Logs

    Error: Error POSTing to endpoint (HTTP 400): {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Server not initialized"},"id":null}
        at StreamableHTTPClientTransport.send (file:///xxx/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:172:23)

One solution is likely to move the if (this.sessionId === undefined) { block to the top of the function

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions