Skip to content

Commit 51db52a

Browse files
authored
Merge pull request #650 from emirn/fix-non-object-meta
Handle a `_meta` that is not an object without raising
2 parents 35a8df2 + 96b50f2 commit 51db52a

4 files changed

Lines changed: 28 additions & 3 deletions

File tree

‎lib/mcp/server.rb‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ def call_tool(request, session: nil, related_request_id: nil, cancellation: nil,
14031403
end
14041404
end
14051405

1406-
progress_token = request.dig(:_meta, :progressToken)
1406+
progress_token = request.dig(:_meta, :progressToken) if request[:_meta].is_a?(Hash)
14071407

14081408
response = call_tool_with_args(
14091409
tool,
@@ -1915,7 +1915,7 @@ def handler_declares_server_context?(handler)
19151915
# (progress, cancellation, nested server-to-client requests).
19161916
def build_server_context(request:, session:, related_request_id:, cancellation:, envelope: nil, auth_info: nil)
19171917
meta_source = request.is_a?(Hash) ? request : {}
1918-
progress_token = meta_source.dig(:_meta, :progressToken)
1918+
progress_token = meta_source.dig(:_meta, :progressToken) if meta_source[:_meta].is_a?(Hash)
19191919
progress = Progress.new(notification_target: session, progress_token: progress_token, related_request_id: related_request_id)
19201920
retry_fields = mrtr_retry_fields(meta_source)
19211921
ServerContext.new(

‎lib/mcp/server/transports/streamable_http_transport.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ def handle_modern(request, header_version, body_string: nil)
980980
# converge on enforcement.
981981
def validate_modern_headers(request, body, header_version)
982982
params = body[:params]
983-
meta_version = params.is_a?(Hash) ? params.dig(:_meta, :"io.modelcontextprotocol/protocolVersion") : nil
983+
meta_version = params.is_a?(Hash) && params[:_meta].is_a?(Hash) ? params.dig(:_meta, :"io.modelcontextprotocol/protocolVersion") : nil
984984
if meta_version && meta_version != header_version
985985
return header_mismatch_response(
986986
"MCP-Protocol-Version header value '#{header_version}' does not match body value '#{meta_version}'",

‎test/mcp/server/transports/streamable_http_transport_test.rb‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6505,6 +6505,17 @@ def string
65056505
assert_equal(-32602, JSON.parse(response[2][0]).dig("error", "code"))
65066506
end
65076507

6508+
test "modern POST rejects a _meta that is not an object with 400 and -32602" do
6509+
["not-an-object", []].each do |meta|
6510+
response = @transport.handle_request(modern_rack_request(
6511+
{ jsonrpc: "2.0", method: "tools/list", id: 1, params: { _meta: meta } }.to_json,
6512+
))
6513+
6514+
assert_equal 400, response[0]
6515+
assert_equal(-32602, JSON.parse(response[2][0]).dig("error", "code"))
6516+
end
6517+
end
6518+
65086519
test "modern POST rejects an envelope missing clientCapabilities with 400 and -32602" do
65096520
body = {
65106521
jsonrpc: "2.0",

‎test/mcp/server_test.rb‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4350,6 +4350,20 @@ def server_context
43504350
end
43514351
end
43524352

4353+
test "#handle tools/call and prompts/get ignore a _meta that is not an object" do
4354+
@server.define_tool(name: "echo_tool") { Tool::Response.new([]) }
4355+
4356+
["not-an-object", []].each do |meta|
4357+
tool = @server.handle({ jsonrpc: "2.0", method: "tools/call", id: 1, params: { name: "echo_tool", _meta: meta } })
4358+
prompt = @server.handle({
4359+
jsonrpc: "2.0", method: "prompts/get", id: 1, params: { name: "test_prompt", arguments: { test_argument: "x" }, _meta: meta },
4360+
})
4361+
4362+
assert_nil tool[:error]
4363+
assert_nil prompt[:error]
4364+
end
4365+
end
4366+
43534367
test "#handle tools/call mirrors non-object structuredContent when content is omitted or nil" do
43544368
# Per SEP-2106, `structuredContent` may be any JSON value. Older clients may only read `content`,
43554369
# so the server adds a serialized fallback when the tool provided no content blocks.

0 commit comments

Comments
 (0)