Skip to content

Conversation

@keisku
Copy link
Contributor

@keisku keisku commented Jan 9, 2026

Motivation and Context

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

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
  • Session management (MCP-Session-Id header)
  • Handle 404 as session expiration
  • DELETE for session termination
  • Protocol version header (MCP-Protocol-Version)

How Has This Been Tested?

Terminal 1 - Server

bundle exec ruby ./examples/streamable_http_server.rb

# After client requests...

[MCP] Request: initialize (id: 7bb764c8-cd31-4ca8-8073-a7fc977a53dd)
[SSE] INFO 23:03:05.053 - New client initializing session
[MCP] Response: success (id: 7bb764c8-cd31-4ca8-8073-a7fc977a53dd)
[SSE] INFO 23:03:05.053 - Session created: 6e58438d-ca95-47d3-b2cd-4df7c6915dcf
::1 - - [09/Jan/2026:23:03:05 +0900] "POST / HTTP/1.1" 200 - 0.0003
[MCP] Request: tools/list (id: d2cc3984-cbf4-4e31-9513-074b36c7dab5)
[MCP] Response: success (id: d2cc3984-cbf4-4e31-9513-074b36c7dab5)
::1 - - [09/Jan/2026:23:03:05 +0900] "POST / HTTP/1.1" 200 - 0.0001
[SSE] INFO 23:03:05.055 - SSE connection request for session: 6e58438d-ca95-47d3-b2cd-4df7c6915dcf
[SSE] INFO 23:03:05.055 - SSE stream established
::1 - - [09/Jan/2026:23:03:05 +0900] "GET / HTTP/1.1" 200 - 0.0001
[MCP] Request: tools/call (id: 822967ca-da23-484f-86b3-caaef42e4209)
[SSE] INFO 23:03:17.068 - Returning notification message: HELLO
[SSE] INFO 23:03:17.068 - Response sent via SSE stream
::1 - - [09/Jan/2026:23:03:17 +0900] "POST / HTTP/1.1" 200 - 3.0057
[MCP] Request: tools/call (id: fa2ad1e5-1fef-4d02-b20e-3f1ae9425bd0)
[SSE] INFO 23:03:24.339 - Response sent via SSE stream
::1 - - [09/Jan/2026:23:03:24 +0900] "POST / HTTP/1.1" 200 - 0.0005
[MCP] Request: tools/list (id: 6d532649-b594-4f7f-bfce-ebb091015e6b)
[SSE] INFO 23:03:26.380 - Response sent via SSE stream
::1 - - [09/Jan/2026:23:03:26 +0900] "POST / HTTP/1.1" 200 - 0.0002
::1 - - [09/Jan/2026:23:03:27 +0900] "DELETE / HTTP/1.1" 200 - 0.0001

Terminal 2 - Client

bundle exec ruby examples/streamable_http_client.rb
MCP Streamable HTTP Client (SDK + SSE)
Make sure the server is running (ruby examples/streamable_http_server.rb)
============================================================
=== Initializing session ===
Session ID: 6e58438d-ca95-47d3-b2cd-4df7c6915dcf
Protocol Version: 2025-11-25
Server Info: {"icons" => [], "name" => "sse_test_server", "version" => "0.1.0"}
=== Listing tools ===
  - notification_tool: Returns a notification message that will be sent via SSE if stream is active
  - echo: Simple echo tool
[CLIENT] INFO 23:03:05.054 - Connecting to SSE stream...
[CLIENT] INFO 23:03:05.055 - SSE stream connected successfully

=== Available Actions ===
1. Send notification (triggers SSE event)
2. Echo message
3. List tools
0. Exit

Choose an action:
1
Enter notification message: HELLO
Enter delay in seconds (0 for immediate): 3
=== Calling tool: notification_tool ===
[CLIENT] INFO 23:03:17.068 - SSE event: {"jsonrpc":"2.0","id":"822967ca-da23-484f-86b3-caaef42e4209","result":{"content":[{"type":"text","text":"Notification: HELLO (timestamp: 2026-01-09T23:03:17+09:00)"}],"isError":false}}
Response: {
  "accepted": true
}

=== Available Actions ===
1. Send notification (triggers SSE event)
2. Echo message
3. List tools
0. Exit

Choose an action:
2
Enter message to echo: HELOOOOOOO
=== Calling tool: echo ===
[CLIENT] INFO 23:03:24.339 - SSE event: {"jsonrpc":"2.0","id":"fa2ad1e5-1fef-4d02-b20e-3f1ae9425bd0","result":{"content":[{"type":"text","text":"Echo: HELOOOOOOO"}],"isError":false}}
Response: {
  "accepted": true
}

=== Available Actions ===
1. Send notification (triggers SSE event)
2. Echo message
3. List tools
0. Exit

Choose an action:
3
=== Listing tools ===
(Note: Response will appear in SSE stream when active)
[CLIENT] INFO 23:03:26.380 - SSE event: {"jsonrpc":"2.0","id":"6d532649-b594-4f7f-bfce-ebb091015e6b","result":{"tools":[{"name":"notification_tool","description":"Returns a notification message that will be sent via SSE if stream is active","inputSchema":{"properties":{"message":{"type":"string","description":"Message to send via SSE"},"delay":{"type":"number","description":"Delay in seconds before returning (optional)"}},"required":["message"],"type":"object"}},{"name":"echo","description":"Simple echo tool","icons":[],"inputSchema":{"properties":{"message":{"type":"string"}},"required":["message"],"type":"object"}}]}}

=== Available Actions ===
1. Send notification (triggers SSE event)
2. Echo message
3. List tools
0. Exit

Choose an action:
0
[CLIENT] INFO 23:03:27.995 - Exiting...
=== Closing session ===
Session closed

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

Additional context

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

if response.code == "200"
logger.info("SSE stream connected successfully")

response.read_body do |chunk|
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use https://rubygems.org/gems/event_stream_parser for parsing. SSE appears easy to parse but it has weird edge cases.

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