Skip to content

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

Closed
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions