ruby-sdk/lib
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Koichi ITO 9c2e438d4a Validate MCP-Protocol-Version header in StreamableHTTPTransport
## Motivation and Context

MCP 2025-11-25 (basic/transports#protocol-version-header) requires servers
to respond with `400 Bad Request` when receiving a request with an invalid
or unsupported `MCP-Protocol-Version` header:

> If the server receives a request with an invalid or unsupported
> `MCP-Protocol-Version`, it MUST respond with `400 Bad Request`.

The Ruby SDK was not reading the header at all, accepting any value
(including malformed strings like `not-a-version` and unsupported versions
like `1900-01-01`) and dispatching requests normally with HTTP 200.

## Behavior

`StreamableHTTPTransport` now validates the `MCP-Protocol-Version` header
on POST (non-initialize), GET, and DELETE requests. Missing headers are
accepted as before; the spec SHOULD requirement to default to `2025-03-26`
is intentionally left for a follow-up. The `initialize` POST is exempt
because the client does not know the negotiated version until the response
arrives, matching the Python (`src/mcp/server/streamable_http.py`) and
TypeScript (`packages/server/src/server/streamableHttp.ts`) SDKs.

The error response uses a JSON-RPC envelope with `code: -32600`
(`INVALID_REQUEST`) to match the Python SDK shape:

```json
{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": -32600,
    "message": "Bad Request: Unsupported protocol version: <value>. Supported versions: ..."
  }
}
```

Validation order is session then protocol version, matching the Python and
TypeScript SDKs.

`handle_post` was reorganized so that non-Hash request bodies (e.g., JSON
arrays, which MCP 2025-11-25 no longer supports as batches) also pass
through the header check. The pre-existing broken response for Array
bodies sent with a valid header is unchanged and is tracked as a separate
follow-up.

## How Has This Been Tested?

Added regression tests covering:

- POST `initialize` ignores the header (bypass)
- POST non-initialize with unsupported / malformed / valid / missing values
- POST array body with unsupported value returns 400
- GET with unsupported / missing values
- DELETE with unsupported value in both stateful and stateless modes
- DELETE validates session before protocol version

`bundle exec rake test` and `bundle exec rake rubocop` both pass.

## Breaking Changes

Strictly additive for compliant clients. Third-party clients sending stale
or malformed `MCP-Protocol-Version` values will now receive `400` instead
of `200`, which is the intended spec behavior.

Fixes #346.
2026-05-14 01:03:21 +09:00
..
mcp Validate MCP-Protocol-Version header in StreamableHTTPTransport 2026-05-14 01:03:21 +09:00
json_rpc_handler.rb Support notifications/cancelled per MCP specification 2026-05-03 04:02:22 +09:00
mcp.rb Support notifications/cancelled per MCP specification 2026-05-03 04:02:22 +09:00