ruby-sdk/test
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Koichi ITO 18761cf367 Preserve the Request ID in Invalid Request Error Responses
## Motivation and Context

Resolves #398.

`JsonRpcHandler` returned `"id": null` for JSON-RPC envelope errors
(wrong or missing `jsonrpc` version, non-string `method`) even when
the incoming payload carried a concrete, valid request id.
From the client's perspective the original request stayed pending forever,
since no response with the matching id ever arrived:

```
{"jsonrpc":"1.0","id":3,"method":"ping","params":{}}
=> {"jsonrpc":"2.0","id":null,"error":{"code":-32600,...}}
```

Per JSON-RPC 2.0 (Response object, `id`), the response id MUST be
the same as the request's id; null is reserved for requests whose id
could not be detected (e.g. Parse error). The envelope validation in
`process_request` runs after JSON parsing with the request hash in
hand, so the id is detectable - it was simply discarded by passing
the `:unknown_id` sentinel unconditionally.

The fix passes the request's id to `error_response` whenever
the request carries one. The existing guards keep every other case intact:
`error_response` already nils out ids that fail validation
(so a type-invalid or pattern-violating id still yields `"id": null`,
preserving the XSS-prevention policy on echoed ids), and the `:unknown_id`
sentinel is kept for requests without an id so an Invalid Request error
still produces a null-id response, matching the spec's own example.
Parse errors and non-object requests are unchanged (null is correct there),
and batch entries pick up the fix automatically since each entry goes through
`process_request`.

For reference, the TypeScript and Python SDKs currently respond with
`"id": null` in these cases as well, because both validate the whole
message against a schema (zod / pydantic) before extracting the id.
This change is driven by the JSON-RPC 2.0 requirement rather than
parity; the Ruby transport layer already echoes the request id where
recoverable (`invalid_request_response(request_id:)` in
`StreamableHTTPTransport`), and this closes the remaining gap in
`JsonRpcHandler`.

## How Has This Been Tested?

- Reproduced the three payloads from #398 before and after the fix;
  they now return `"id":3`, `"id":4`, and `"id":8` respectively
- New and updated unit tests covering: id preserved for wrong version,
  missing `jsonrpc`, numeric `method`, and `rpc.`-prefixed `method`;
  id preserved for an invalid entry inside a batch; null id kept for
  requests without an id, with an explicit null id, and with an id
  that fails validation

## Breaking Changes

None in the protocol sense - this aligns the error responses with
JSON-RPC 2.0. Clients that matched `"id": null` on these envelope
errors will now see the request's own id, which is what allows them
to correlate the error with the pending request.
2026-06-13 02:16:25 +09:00
..
mcp Add size field to MCP::Resource per MCP specification 2026-06-07 21:10:41 +09:00
instrumentation_test_helper.rb Internal Release 0.5.1 2025-05-08 17:38:05 -04:00
json_rpc_handler_test.rb Preserve the Request ID in Invalid Request Error Responses 2026-06-13 02:16:25 +09:00
test_helper.rb Support Ruby 2.7 - Ruby 3.1 2026-01-06 10:38:56 +09:00