Skip to content

Handle an initialize request sent without an ID#456

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:handle_initialize_request_without_id
Open

Handle an initialize request sent without an ID#456
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:handle_initialize_request_without_id

Conversation

@koic

@koic koic commented Jul 13, 2026

Copy link
Copy Markdown
Member

Motivation and Context

StreamableHTTPTransport#handle_post routes to handle_initialization via initialize_request?, which matches on the method name alone. An initialize message sent without an id (framed as a notification) therefore reaches handle_initialization. Server#init runs and marks the session initialized, but JSON-RPC emits no response for an id-less request, so handle_json returns nil.

Two things then went wrong:

  • handle_initialization returned [200, headers, [nil]]. A nil body element is not a valid Rack response, so the web server raises while serializing it (NoMethodError on nil.bytesize) and the client gets a malformed response or a 500.
  • The if session_id && !server_session.initialized? cleanup guard was skipped, because the session is marked initialized. The session was retained with a Mcp-Session-Id header, so a stream of id-less initialize messages accumulates sessions toward the max_sessions cap until the reaper reclaims them.

handle_initialization now treats a nil response as an id-less initialize: it discards the orphaned session and acks with 202, mirroring how handle_regular_request already handles a nil response. A well-formed initialize request (with an id) is unaffected.

How Has This Been Tested?

A new test in test/mcp/server/transports/streamable_http_transport_test.rb posts an initialize with no id and asserts a 202 with an empty body, no Mcp-Session-Id header, and an empty session map. It fails against the previous code (a 200 with a nil body element and a retained session) and passes now.

Breaking Changes

None. A conforming client sends initialize as a request with an id and is unaffected; only the malformed id-less form changes, from a broken 200 to a 202 with no retained session.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

`StreamableHTTPTransport#handle_post` routes to `handle_initialization` via `initialize_request?`,
which matches on the method name alone. An `initialize` message sent without an id (framed as a notification)
therefore reaches `handle_initialization`. `Server#init` runs and marks the session initialized,
but JSON-RPC emits no response for an id-less request, so `handle_json` returns nil.

Two things then went wrong:

- `handle_initialization` returned `[200, headers, [nil]]`. A `nil` body element is not a valid Rack response,
  so the web server raises while serializing it (`NoMethodError` on `nil.bytesize`)
  and the client gets a malformed response or a 500.
- The `if session_id && !server_session.initialized?` cleanup guard was skipped, because the session is marked initialized.
  The session was retained with a `Mcp-Session-Id` header, so a stream of id-less `initialize` messages accumulates sessions
  toward the `max_sessions` cap until the reaper reclaims them.

`handle_initialization` now treats a nil response as an id-less initialize: it discards the orphaned session
and acks with 202, mirroring how `handle_regular_request` already handles a nil response.
A well-formed `initialize` request (with an id) is unaffected.

## How Has This Been Tested?

A new test in `test/mcp/server/transports/streamable_http_transport_test.rb` posts an `initialize`
with no id and asserts a 202 with an empty body, no `Mcp-Session-Id` header, and an empty session map.
It fails against the previous code (a 200 with a nil body element and a retained session) and passes now.

## Breaking Changes

None. A conforming client sends `initialize` as a request with an id and is unaffected;
only the malformed id-less form changes, from a broken 200 to a 202 with no retained session.
@koic koic force-pushed the handle_initialize_request_without_id branch from 62eb2fa to fc0c6b8 Compare July 13, 2026 16:09
@koic koic changed the title Handle an Initialize Request Sent Without an Id Handle an initialize request sent without an ID Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant