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
|
|
@ -63,12 +63,29 @@ module JsonRpcHandler
|
|||
message: "Parse error",
|
||||
data: "Invalid JSON",
|
||||
})
|
||||
rescue StandardError
|
||||
# Last-resort guard so an unexpected handling error returns a JSON-RPC error
|
||||
# instead of escaping to the transport loop.
|
||||
response = error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: {
|
||||
code: ErrorCode::INTERNAL_ERROR,
|
||||
message: "Internal error",
|
||||
})
|
||||
end
|
||||
|
||||
response&.to_json
|
||||
end
|
||||
|
||||
def process_request(request, id_validation_pattern:, &method_finder)
|
||||
# A batch element can be any JSON value; a non-object element cannot carry an id or a method,
|
||||
# so reject it before the `request[:id]` access below.
|
||||
unless request.is_a?(Hash)
|
||||
return error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: {
|
||||
code: ErrorCode::INVALID_REQUEST,
|
||||
message: "Invalid Request",
|
||||
data: "Request must be an object",
|
||||
})
|
||||
end
|
||||
|
||||
id = request[:id]
|
||||
|
||||
error = if !valid_version?(request[:jsonrpc])
|
||||
|
|
|
|||
Loading…
Reference in a new issue