Skip to content

Streamable servlet transport does not close SSE response for unknown methods #1050

Description

@AlanBurlison

DISCLAIMER: Codex-generated bug report

Bug description

With HttpServletStreamableServerTransportProvider in Java SDK 2.0.0, a request for an unknown or unsupported JSON-RPC method receives the correct -32601 Method not found
response, but the POST response's SSE stream remains open indefinitely.

Supported methods close their SSE streams normally after returning a response.

The MCP Streamable HTTP specification says that after the JSON-RPC response has been sent, the server should close the SSE stream:

https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#sending-messages-to-the-server

Environment

  • MCP Java SDK: 2.0.0
  • Transport: HttpServletStreamableServerTransportProvider
  • Jetty: 12.1.10
  • Jakarta Servlet API: 6.1
  • Java: 25.0.3
  • Negotiated MCP protocol: 2025-06-18

The server advertises tools but not resources or prompts.

Steps to reproduce

Initialize a session:

curl --max-time 5 -i -X POST http://127.0.0.1:8080/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  --data-binary '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": {
        "name": "reproducer",
        "version": "1.0"
      }
    }
  }'

Copy the returned Mcp-Session-Id, send notifications/initialized, and then call an unsupported method:

curl --max-time 3 -i -X POST http://127.0.0.1:8080/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2025-06-18' \
  -H 'Mcp-Session-Id: SESSION_ID' \
  --data-binary '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "resources/list",
    "params": {}
  }'

The server sends an SSE event promptly:

event: message
data: {"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found: resources/list"}}

However, the HTTP response does not reach end-of-stream. Curl eventually reports:

curl: (28) Operation timed out after 3002 milliseconds

Calling a supported method such as tools/list returns its response and closes the stream immediately.

Expected behavior

After sending the -32601 JSON-RPC response, the server should complete the asynchronous servlet context and close the POST response's SSE stream.

Actual behavior

The JSON-RPC error is delivered, but the SSE stream and HTTP request remain open.

Apparent cause

In McpStreamableServerSession.responseStream, the missing-handler branch returns only:

return transport.sendMessage(
    McpSchema.JSONRPCResponse.error(...));

The registered-handler branch ends with:

.flatMap(transport::sendMessage)
.then(transport.closeGracefully());

The missing-handler branch appears to need the same transport closure:

return transport.sendMessage(
    McpSchema.JSONRPCResponse.error(...))
    .then(transport.closeGracefully());

Impact

Clients that probe an unsupported method may wait indefinitely unless they impose a timeout. The server also retains the asynchronous servlet request until the client disconnects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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