Skip to content

Track Mcp-Session-Id and protocol version in HTTP client#325

Merged
koic merged 1 commit intomodelcontextprotocol:mainfrom
atesgoral:ag/client-session-tracking
Apr 20, 2026
Merged

Track Mcp-Session-Id and protocol version in HTTP client#325
koic merged 1 commit intomodelcontextprotocol:mainfrom
atesgoral:ag/client-session-tracking

Conversation

@atesgoral
Copy link
Copy Markdown
Contributor

Summary

  • Capture Mcp-Session-Id and protocolVersion from the initialize response and expose them on the transport as session_id / protocol_version
  • Automatically include Mcp-Session-Id on subsequent requests once captured
  • Map 404 responses to a new MCP::Client::SessionExpiredError (subclass of RequestHandlerError) and clear local session state so callers can start a new session

Per the Streamable HTTP spec:

If the server returns an Mcp-Session-Id header during initialization, clients MUST include it on all subsequent HTTP requests.

When a client receives HTTP 404 in response to a request containing the Mcp-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.

https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management

This is part of a series of PRs splitting up #321. Previous: #322 (SSE parsing) and #323 (202 Accepted). Next: session termination via DELETE (close), then client handshake ergonomics and example rewrite.

Test plan

  • New tests for session capture on initialize
  • New tests that session header is included on subsequent requests
  • New test that session ID is not overwritten by later responses
  • New test that stateless servers (no Mcp-Session-Id header) work fine
  • 404 now raises SessionExpiredError and clears session state
  • Full suite passes (bundle exec rake test)

🤖 Generated with Claude Code

@atesgoral atesgoral force-pushed the ag/client-session-tracking branch from 7cb64a4 to cc9b8b4 Compare April 20, 2026 03:55
Comment thread lib/mcp/client/http.rb Outdated
# respond with 404 to requests containing an expired session ID.
# https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management
clear_session
raise SessionExpiredError.new(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation currently raises SessionExpiredError for every 404, including when @session_id is nil (e.g., the very first request before initialize, or a stateless server where the URL is misconfigured).

The spec wording is:

When a client receives HTTP 404 in response to a request containing an Mcp-Session-Id, it MUST start a new session...

So strictly speaking, "session expired" would apply only when a session ID was actually attached.

Not a blocker, and it's fine to keep the current behavior if that's intentional, but one alternative would be:

if @session_id
  clear_session
  raise SessionExpiredError.new(...)
else
  raise RequestHandlerError.new(..., error_type: :not_found, ...)
end

That way a 404 from a wrong URL or a server that's simply not there surfaces as a generic :not_found rather than as a potentially misleading "session expired".

Would this make sense?

Copy link
Copy Markdown
Contributor Author

@atesgoral atesgoral Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, the spec scopes the "start a new session" MUST to 404s carrying an MCP-Session-Id, so the session-expired interpretation shouldn't apply to plain 404s. Updated per your suggestion: branch on @session_id and only raise SessionExpiredError (and clear session state) when a session was attached; otherwise raise a generic RequestHandlerError with :not_found. Split the test into pre-session and post-session cases.

Comment thread lib/mcp/client/http.rb
# https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#resumability-and-redelivery
class HTTP
ACCEPT_HEADER = "application/json, text/event-stream"
SESSION_ID_HEADER = "Mcp-Session-Id"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec has this as "MCP-Session-Id". Will standardize casing for existing server code + this new client code in a separate PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, it looks like this was updated in the spec at some point.
modelcontextprotocol/modelcontextprotocol#1325

@atesgoral atesgoral force-pushed the ag/client-session-tracking branch from cc9b8b4 to 90f91d4 Compare April 20, 2026 04:15
Per the Streamable HTTP spec, if the server returns an Mcp-Session-Id
header during `initialize`, the client MUST include it on subsequent
requests. Capture the session ID and protocol version from the
`initialize` response and attach them automatically on subsequent
POSTs.

Per the Protocol Version Header section of the spec, the client MUST
also include `MCP-Protocol-Version` on all subsequent requests so the
server can respond based on the negotiated protocol version.

Map 404 responses to a new `SessionExpiredError` (a subclass of
`RequestHandlerError` for backward compatibility) and clear local
session state so callers can start a fresh session with a new
`initialize` request, as required by the spec.

- https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management
- https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#protocol-version-header

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@atesgoral atesgoral force-pushed the ag/client-session-tracking branch from 90f91d4 to 07226b7 Compare April 20, 2026 04:23
@atesgoral atesgoral requested a review from koic April 20, 2026 04:36
@koic koic merged commit 0770e27 into modelcontextprotocol:main Apr 20, 2026
11 checks passed
@koic
Copy link
Copy Markdown
Member

koic commented Apr 20, 2026

Thanks!

@atesgoral atesgoral deleted the ag/client-session-tracking branch April 21, 2026 02:10
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.

2 participants