Per MCP spec, clients that no longer need a session SHOULD send an HTTP
DELETE to the MCP endpoint with the Mcp-Session-Id header to explicitly
terminate it. Servers MAY respond with 405 Method Not Allowed when they
don't support client-initiated termination.
`MCP::Client::HTTP#close` sends the DELETE with session headers, clears
local session state regardless of outcome, and is a no-op when no
session has been established.
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management
Per the Streamable HTTP spec, if the server returns an Mcp-Session-Id
header during `initialize`, the client MUST include it on subsequent
requests. Capture the session ID and protocol version from the
`initialize` response and attach them automatically on subsequent
POSTs.
Per the Protocol Version Header section of the spec, the client MUST
also include `MCP-Protocol-Version` on all subsequent requests so the
server can respond based on the negotiated protocol version.
Map 404 responses to a new `SessionExpiredError` (a subclass of
`RequestHandlerError` for backward compatibility) and clear local
session state so callers can start a fresh session with a new
`initialize` request, as required by the spec.
- https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management
- https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#protocol-version-header
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per the Streamable HTTP spec, a server returns 202 Accepted with no
body as the ACK for a JSON-RPC notification or response. The client
previously errored on 202 because it fell through Content-Type
dispatch. Short-circuit to return nil before Content-Type handling so
fire-and-forget messages (e.g. notifications/initialized) no longer
raise.
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Streamable HTTP spec allows servers to respond with either
`application/json` or `text/event-stream`. The client previously
rejected the latter. Add SSE parsing via the optional
`event_stream_parser` gem (users add it to their Gemfile when their
server uses SSE), dispatch on the response Content-Type, and scan the
event stream for the first JSON-RPC response message.
Document the new optional dependency in both the README's HTTP
Transport Layer section and docs/building-clients.md.
No other Streamable HTTP features (202 handling, session IDs,
`connect`, DELETE termination) are included here; those will land in
follow-up PRs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
## Motivation and Context
`MCP::Client::HTTP` builds its Faraday connection internally in a private `client` method
with no way to customize the middleware stack or adapter.
Users who need observability (request/response recording, failure logging) or
a different HTTP adapter must override a private method, coupling to an internal API.
This accepts an optional block in `MCP::Client::HTTP.new` that yields the Faraday builder
after default middleware is configured, allowing users to add custom middleware or swap
the HTTP adapter.
## How Has This Been Tested?
Added a test that verifies custom headers set via the block are included in requests.
All existing tests continue to pass.
## Breaking Changes
None. The block argument is optional, so existing code is unaffected.
Resolves#303
Background threads simulating server responses could race with the `ensure` block
that closes IO pipes, intermittently producing this warning depending on thread scheduling:
```console
#<Thread:0x00007fb69290cbd0 test/mcp/client/stdio_test.rb:28 run>
terminated with exception (report_on_exception is true):
test/mcp/client/stdio_test.rb:55:in `write': stream closed in another thread (IOError)
```
Store each background thread in a `server_thread` variable and call `server_thread&.join(1)`
at the top of each `ensure` block to wait for the thread to finish before closing pipes.
## Motivation and Context
The `MCP::Client` currently only supports HTTP transport (`MCP::Client::HTTP`).
To achieve parity with other MCP SDKs (Python, TypeScript, C#) which provide
stdio client transports for spawning and communicating with server subprocesses,
the Ruby SDK needs a stdio client transport.
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio
This enables building MCP clients that launch server processes and communicate
via stdin/stdout using newline-delimited JSON-RPC 2.0 messages.
## How Has This Been Tested?
Added tests for process spawning, protocol initialization handshake,
notification handling, error handling, and transport close lifecycle.
## Additional context
Enabled the `Lint/IncompatibleIoSelectWithFiberScheduler` cop in `.rubocop.yml`
to detect accidental use of `IO.select` instead of `IO#wait_readable`.
- Set Accept header to request JSON or SSE responses
- Validate response Content-Type and raise error for non-JSON responses
- Add Accept header to test fixtures
- Use explicit hash syntax for Ruby 2.7 compatibility
This commit implements optional JSON Schema validation for tool outputs according to the MCP specification:
- Add MCP::Tool::OutputSchema class mirroring InputSchema functionality
- Support output_schema in class definitions, Tool.define, and OutputSchema objects
- Include validate_result method for runtime validation
- Update MCP::Client::Tool to track output schemas
- Add comprehensive test coverage with 249 passing tests
- Update README with dedicated Tool Output Schemas section
- Follow MCP spec requirements for server/client validation