Skip to content

Add MCP Streamable HTTP specification support for the client - #210

Closed
keisku wants to merge 3 commits into
modelcontextprotocol:mainfrom
keisku:streamable-http-spec-support-for-client
Closed

Add MCP Streamable HTTP specification support for the client#210
keisku wants to merge 3 commits into
modelcontextprotocol:mainfrom
keisku:streamable-http-spec-support-for-client

Conversation

@keisku

@keiskukeisku commented Jan 9, 2026

Copy link
Copy Markdown
Contributor

Motivation and Context

Implements the MCP Streamable HTTP specification for the Ruby SDK client.

Rebased from #210 (original by @keisku) with merge conflict resolution, bug fixes, and review feedback addressed.

Already supported:

  • POST with JSON body
  • Accept: application/json, text/event-stream header
  • Parse application/json response

This PR adds:

  • Parse text/event-stream (SSE) response via event_stream_parser gem
  • Session management (MCP-Session-Id header)
  • Handle 404 as session expiration
  • Handle 202 Accepted when server delivers response via SSE stream
  • DELETE for session termination
  • Protocol version header (MCP-Protocol-Version)
  • Client conformance test runner (conformance/client.rb)

How Has This Been Tested?

Unit tests — 59 tests pass (40 client + 19 HTTP transport):

ruby -I lib -I test test/mcp/client_test.rb # 40 runs, 125 assertions, 0 failures
ruby -I lib -I test test/mcp/client/http_test.rb # 19 runs, 48 assertions, 0 failures

Conformance tests — core client scenarios pass:

npx @modelcontextprotocol/conformance client --suite core --command "ruby conformance/client.rb"
✓ initialize: 1 passed, 0 failed
✓ tools_call: 1 passed, 0 failed

E2E — manual testing with examples/streamable_http_server.rb and examples/streamable_http_client.rb.

Breaking Changes

None. The public send_request(request:) method signature is preserved for backward compatibility.

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

API Design

  • MCP::Client::HTTP#send_request(request:) — Returns body only (backward compatible)
  • MCP::Client::HTTP#post(body:, headers:) — Returns Response struct with body + headers (for session management)
  • MCP::Client::HTTP#delete(headers:) — For session termination
  • MCP::Client#connect(client_info:, protocol_version:, capabilities:) — Initialization handshake
  • MCP::Client#close — Session termination

Pending TODOs

Comment threadexamples/streamable_http_client.rb
@keisku
keisku requested a review from atesgoralJanuary 13, 2026 00:52
@keisku

Copy link
Copy Markdown
ContributorAuthor

@atesgoral Thanks for reviewing this! I've addressed it.

@keisku

Copy link
Copy Markdown
ContributorAuthor

@atesgoral Any chance you could take a look at this PR?

Comment threadexamples/streamable_http_client.rb Outdated
Comment threadexamples/streamable_http_client.rb Outdated
Comment threadexamples/streamable_http_client.rb Outdated
Comment threadexamples/streamable_http_client.rb Outdated
Comment threadexamples/streamable_http_client.rb Outdated
Comment threadlib/mcp/client.rb Outdated
Comment threadlib/mcp/server/transports/streamable_http_transport.rb Outdated
Comment threadlib/mcp/server/transports/streamable_http_transport.rb
@keisku
keisku requested a review from koicJanuary 27, 2026 05:50
Comment threadlib/mcp/client.rb Outdated
Comment threadlib/mcp/client.rb Outdated
Comment threadGemfile Outdated
Comment threadlib/mcp/client/http.rb Outdated
@koic

koic commented Mar 11, 2026

Copy link
Copy Markdown
Member

@keisku Could you review the feedback and squash the commits into a single commit?

@atesgoral
atesgoralforce-pushed the streamable-http-spec-support-for-client branch from b91a9db to 2ee2be4CompareApril 4, 2026 01:34
keiskuand others added 2 commits April 4, 2026 00:30
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an SSE stream is active, the server responds with 202 Accepted
(no Content-Type, empty body) to POST requests, delivering the actual
response via the SSE stream. Previously this raised an unsupported
Content-Type error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@atesgoral
atesgoralforce-pushed the streamable-http-spec-support-for-client branch from 2ee2be4 to eb801ccCompareApril 4, 2026 04:30
@atesgoral
atesgoral requested a review from koicApril 4, 2026 04:30
@atesgoral

Copy link
Copy Markdown
Contributor

Commits:

  1. Squashed and rebased original PR while also matching the code base's new stylistic preferences
  2. Fixed an issue
  3. Addressed review comments

- Move event_stream_parser from Gemfile to gemspec as runtime dependency
- Add brief descriptions to TODO comments in client/http.rb and
streamable_http_transport.rb
- Add description to spec URL comment in client.rb
- Use SESSION_ID_HEADER constant for session ID header lookup in connect
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment threadmcp.gemspec
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency("event_stream_parser", ">= 1.0")

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.

My previous comment may have overlooked some aspects.

EventStreamParser is only used in lib/mcp/client/http.rb. Considering how Faraday is currently handled, I'm not entirely certain, but it may be more consistent to make this an optional dependency that users include in their Gemfile when needed:
https://github.com/modelcontextprotocol/ruby-sdk/blob/v0.10.0/lib/mcp/client/http.rb#L84-L90

Assuming that many users of the SDK primarily use the server functionality, a setup similar to Faraday, where it is not a direct runtime dependency, may be more appropriate.

@atesgoral

Copy link
Copy Markdown
Contributor

@keisku thank you for kicking this off. This is now superseded by my fork + breaking down into smaller PRs. Marking this as done.

Sign up for freeto 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.

4 participants

@keisku@koic@atesgoral@scutuatua-crypto