Handle non-object JSON-RPC messages without raising
## Motivation and Context
A JSON-RPC message is an object, but two stdio-facing paths assumed that and raised
on a non-object value parsed from an incoming frame:
- `JsonRpcHandler.handle` maps each element of a batch array through `process_request`,
which reads `request[:id]` assuming a Hash. A non-object batch element (for example `[[]]`
or `[5]`) makes `[][:id]` raise `TypeError`. `handle_json` rescued only `JSON::ParserError`,
so the error escaped; over stdio it escaped the `StdioTransport#open` read loop.
The HTTP transport already rejects array bodies before dispatch, so this surfaced only on stdio.
- `MCP::Client::Stdio#read_response` parses each frame from the server and calls `parsed.key?("id")`,
which raises `NoMethodError` when the server emits a non-object frame.
Only `JSON::ParserError` was rescued, so one malformed line from the server escaped.
Both are robustness gaps: a malformed frame should be reported or skipped, not raise
an unhandled exception out of the transport.
- `process_request` now returns an `INVALID_REQUEST` error response for a non-object request
before touching `request[:id]`, so a malformed batch element becomes a normal per-element error.
- `handle_json` gains a last-resort `StandardError` rescue that returns an `INTERNAL_ERROR` response,
so an unexpected handling error still produces a JSON-RPC reply.
- `read_response` skips a non-object frame the same way it skips a frame without an id.
## How Has This Been Tested?
New tests in `test/json_rpc_handler_test.rb` cover non-object batch elements
(`[[]]`, `[5]`, `["x"]`, `[nil]`, `[true]`), a valid request followed by one non-object element,
and `handle_json("[[]]")` returning an error instead of raising. A test in
`test/mcp/server/transports/stdio_transport_test.rb` drives `open` with a `[[]]` line and
asserts it emits an error response without raising. A test in `test/mcp/client/stdio_test.rb`
has the server emit non-object frames before the response and asserts the client skips them
and still returns the response.
## Breaking Changes
None. A well-formed request, batch, or response is handled exactly as before;
only a non-object message now yields a JSON-RPC error on the server or is skipped by the client
instead of raising.
This commit is contained in:
parent
61233ed43b
commit
8875990909
1 changed files with 134 additions and 1 deletions
|
|
@ -682,6 +682,37 @@ describe JsonRpcHandler do
|
|||
assert_equal 3, @response.first[:result]
|
||||
assert_equal(-32600, @response.last.dig(:error, :code))
|
||||
end
|
||||
|
||||
it "returns an invalid request error for a non-object batch element instead of raising" do
|
||||
[[], [1], ["x"], [nil], [true]].each do |batch|
|
||||
handle batch
|
||||
|
||||
assert_equal(-32600, @response.dig(:error, :code))
|
||||
assert_equal "Invalid Request", @response.dig(:error, :message)
|
||||
assert_nil @response[:id]
|
||||
end
|
||||
end
|
||||
|
||||
it "handles a valid request followed by a non-object element without raising" do
|
||||
register("add") { |params| params[:a] + params[:b] }
|
||||
|
||||
handle [
|
||||
{ jsonrpc: "2.0", id: 100, method: "add", params: { a: 1, b: 2 } },
|
||||
[],
|
||||
]
|
||||
|
||||
assert @response.is_a?(Array)
|
||||
assert_equal 3, @response.first[:result]
|
||||
assert_equal(-32600, @response.last.dig(:error, :code))
|
||||
assert_nil @response.last[:id]
|
||||
end
|
||||
|
||||
it "does not raise from handle_json when a batch element is not an object" do
|
||||
result = handle_json("[[]]")
|
||||
|
||||
assert_equal(-32600, @response.dig(:error, :code))
|
||||
refute_nil result
|
||||
end
|
||||
end
|
||||
|
||||
# 7 Examples
|
||||
|
|
|
|||
Loading…
Reference in a new issue