Skip to content

Commit e7fafdd

Browse files
committed
Fix JSON-RPC notification format in Streamable HTTP transport
Update send_notification method to properly construct JSON-RPC notification objects with jsonrpc version and method fields. This ensures notifications conform to the JSON-RPC 2.0 specification when sent over HTTP transport. https://www.jsonrpc.org/specification#notification Another goal is to standardise the `send_notification` interface. Before the revision, it was sufficient to pass the method and parameters in the case of stdio. However, in the case of Streamable, a hash based on the JSON-RPC specification must be passed.
1 parent eb6156c commit e7fafdd

File tree

3 files changed

+17
-14
lines changed

3 files changed

+17
-14
lines changed

lib/mcp/server/transports/streamable_http_transport.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ def close
3434
end
3535
end
3636

37-
def send_notification(notification, session_id: nil)
37+
def send_notification(method, params = nil, session_id: nil)
38+
notification = {
39+
jsonrpc: "2.0",
40+
method:,
41+
}
42+
notification[:params] = params if params
43+
3844
@mutex.synchronize do
3945
if session_id
4046
# Send to specific session

test/mcp/server/transports/streamable_http_notification_integration_test.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ class StreamableHTTPNotificationIntegrationTest < ActiveSupport::TestCase
5858
io.rewind
5959
output = io.read
6060

61-
assert_includes output, "data: #{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}"
62-
assert_includes output, "data: #{Methods::NOTIFICATIONS_PROMPTS_LIST_CHANGED}"
63-
assert_includes output, "data: #{Methods::NOTIFICATIONS_RESOURCES_LIST_CHANGED}"
61+
assert_includes output, "data: {\"jsonrpc\":\"2.0\",\"method\":\"#{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}\"}"
62+
assert_includes output, "data: {\"jsonrpc\":\"2.0\",\"method\":\"#{Methods::NOTIFICATIONS_PROMPTS_LIST_CHANGED}\"}"
63+
assert_includes output, "data: {\"jsonrpc\":\"2.0\",\"method\":\"#{Methods::NOTIFICATIONS_RESOURCES_LIST_CHANGED}\"}"
6464
end
6565

6666
test "notifications are broadcast to all connected sessions" do
@@ -111,11 +111,11 @@ class StreamableHTTPNotificationIntegrationTest < ActiveSupport::TestCase
111111
# Check both sessions received the notification
112112
io1.rewind
113113
output1 = io1.read
114-
assert_includes output1, "data: #{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}"
114+
assert_includes output1, "data: {\"jsonrpc\":\"2.0\",\"method\":\"#{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}\"}"
115115

116116
io2.rewind
117117
output2 = io2.read
118-
assert_includes output2, "data: #{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}"
118+
assert_includes output2, "data: {\"jsonrpc\":\"2.0\",\"method\":\"#{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}\"}"
119119
end
120120

121121
test "server continues to work when SSE connection is closed" do
@@ -191,7 +191,7 @@ class StreamableHTTPNotificationIntegrationTest < ActiveSupport::TestCase
191191
# Check the notification was received
192192
io.rewind
193193
output = io.read
194-
assert_includes output, "data: #{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}"
194+
assert_includes output, "data: {\"jsonrpc\":\"2.0\",\"method\":\"#{Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED}\"}"
195195

196196
# Verify the tool was added to the server
197197
assert @server.tools.key?("dynamic_tool")
@@ -229,7 +229,7 @@ class StreamableHTTPNotificationIntegrationTest < ActiveSupport::TestCase
229229
output = io.read
230230

231231
# SSE format should be "data: <message>\n\n"
232-
assert_match(/data: #{Regexp.escape(Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED)}\n/, output)
232+
assert_match(/data: \{"jsonrpc":"2\.0","method":"#{Regexp.escape(Methods::NOTIFICATIONS_TOOLS_LIST_CHANGED)}"\}\n/, output)
233233
end
234234

235235
private

test/mcp/server/transports/streamable_http_transport_test.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,7 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
391391
@server.define_singleton_method(:handle_json) do |request|
392392
result = original_handle_json.call(request)
393393
# Send notification while still in request context - broadcast to all sessions
394-
notification = { jsonrpc: "2.0", method: "test_notification", params: { session: "current" } }
395-
transport.send_notification(notification)
394+
transport.send_notification("test_notification", { session: "current" })
396395
result
397396
end
398397

@@ -452,8 +451,7 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
452451
sleep(0.1)
453452

454453
# Send notification to specific session
455-
notification = { jsonrpc: "2.0", method: "test_notification", params: { message: "Hello" } }
456-
result = @transport.send_notification(notification, session_id: session_id)
454+
result = @transport.send_notification("test_notification", { message: "Hello" }, session_id: session_id)
457455

458456
assert result
459457

@@ -507,8 +505,7 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
507505
sleep(0.1)
508506

509507
# Broadcast notification to all sessions
510-
notification = { jsonrpc: "2.0", method: "broadcast", params: { message: "Hello everyone" } }
511-
sent_count = @transport.send_notification(notification)
508+
sent_count = @transport.send_notification("broadcast", { message: "Hello everyone" })
512509

513510
assert_equal 2, sent_count
514511

0 commit comments

Comments
 (0)