Commit graph ruby-sdk/examples/streamable_http_server.rb
Author SHA1 Message Date
Koichi ITO
bfc6e551a3 Update examples for the 2026-07-28 era
## Motivation and Context

The runnable examples drifted from the SDK they ship with:

- POST requests whose `Accept` header does not cover both `application/json` and `text/event-stream` are answered 406,
  so the cURL walkthroughs  no longer worked as printed.
- `MCP::Client#connect` now defaults to `mode: :auto`, which adopts the sessionless modern lifecycle (2026-07-28)
  against a dual-era server; the Streamable HTTP client example requires a session and broke against its own server.
- POST responses on an established session arrive as an SSE stream, which the hand-rolled HTTP client did not parse.
- `notification_tool` claimed to send SSE notifications but only returned a `Tool::Response`.
- `http_client.rb` read a resource URI the server never registered and skipped `notifications/initialized`.

Handshake examples now offer the latest handshake protocol version (`2025-11-25`) instead of `2024-11-05`,
the Streamable HTTP client pins `mode: :legacy` for its session-based demo, `notification_tool` reports
real `notifications/progress` events requested through `_meta.progressToken`, and the interactive client streams
those events from the POST response so they are visible on screen.

The modern lifecycle itself had no runnable example, so a new server and client pair (`modern_http_server.rb`,
`modern_http_client.rb`) shows the sessionless flow end to end: `server/discover` capability discovery,
the per-request `_meta` envelope with the `Mcp-Method` and `Mcp-Name` headers (stamped by the SDK client automatically),
`resultType` stamping, SEP-2549 cache hints, a SEP-2322 multi round-trip `deploy` tool resumed automatically by
the client's elicitation handler, and the removal of legacy-only methods such as `ping`. The same server keeps serving
the legacy `initialize` flow, routed per request by the `MCP-Protocol-Version` header. `examples/README.md` documents
the pair and adds a cURL walkthrough covering the modern headers, the envelope, and the multi round-trip exchange.

## How Has This Been Tested?

- `ruby examples/stdio_client.rb` against `stdio_server.rb`
- `ruby examples/http_client.rb` against `http_server.rb`
- `ruby examples/streamable_http_client.rb` against `streamable_http_server.rb`, including the progress stream
- `ruby examples/modern_http_client.rb` against `modern_http_server.rb` (discover, modern adoption, cache hints,
  `resultType`, the multi round-trip resume, `ping` rejection)
- The cURL walkthroughs in the banners and `examples/README.md`, including the `requestState` echo and a legacy `initialize`
  against the modern example server

## Breaking Changes

None.
2026-08-28 12:03:52 +09:00
Koichi ITO
991926a10d Lowercase response header names in StreamableHTTPTransport
## Motivation and Context

The Rack 3 SPEC requires response header names to be lowercase.
https://github.com/rack/rack/blob/main/SPEC.rdoc says in its "The Headers" section:

> Header keys must not contain uppercase ASCII characters (A-Z).

https://github.com/rack/rack/blob/main/SPEC.rdoc#the-headers

However, `StreamableHTTPTransport` emitted capitalized names such as `Content-Type` and `Mcp-Session-Id`.
Any `Rack::Lint`-wrapped stack, including the default development mode of rackup, rejects every response
from the transport with a `LintError` 500. Lowercasing the names restores Rack 3 compliance.
HTTP header names are case-insensitive on the wire, so HTTP clients are unaffected;
only in-process consumers that read the raw Rack headers hash see the new keys.

Lowercase is also the only representation valid across HTTP versions: RFC 9113 makes uppercase field names
a protocol error on HTTP/2, so proxies and h2-capable servers already require it.
The TypeScript SDK sets `headers['mcp-session-id']` and the Python SDK defines `MCP_SESSION_ID_HEADER = "mcp-session-id"`,
so lowercase also matches what the reference SDKs emit; the MCP spec's `Mcp-Session-Id` casing is prose convention,
not a wire requirement.

Examples and documentation deliberately keep the spec's `Mcp-Session-Id` casing where it does not name
a Rack response hash key: curl invocations and `examples/streamable_http_client.rb` set request headers,
which the Rack response rule does not govern; the rack-cors `expose:` entry carries a header name as
an `Access-Control-Expose-Headers` value, which browsers match case-insensitively;
and README/docs prose follows the MCP specification's documentation convention.
Only code reading the raw Rack response headers hash, a case-sensitive Hash lookup, had to switch to lowercase keys.

AGENTS.md now records the lowercase rule under the code style guidelines, since the MCP spec's `Mcp-Session-Id` prose casing
invites reintroducing capitalized names in future transport work.

## How Has This Been Tested?

Added a regression test asserting that header names are lowercase in initialize, GET SSE, DELETE, and parse error responses.
Existing tests that read the raw headers hash were updated to the new keys. Verified with rake test, rake rubocop,
and rake conformance.

## Breaking Changes

Code that reads the raw Rack response headers hash returned by `handle_request`, or by middleware around the mounted transport,
with exact-case keys such as `"Mcp-Session-Id"` must switch to the lowercase keys. Clients speaking HTTP are
unaffected because header names are case-insensitive on the wire.
2026-07-16 11:45:48 +09:00
Ates Goral
cfc42dbfe7 Handle 202 Accepted response in HTTP client
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>
2026-04-19 14:06:07 -04:00
Koichi ITO
af99a303e1 Make StreamableHTTPTransport a Rack application
## Motivation and Context

Add `call(env)` to `StreamableHTTPTransport`, making it a Rack application
that works with `mount`, `run`, and Rack middleware.

Refactor examples to use Rack middleware classes for MCP logging instead of proc wrappers,
demonstrating idiomatic Rack composition with the new `run(transport)` pattern.

Update README.md with mount and controller integration patterns.

Closes #59, #60

## How Has This Been Tested?

Added tests for `call(env)` as a Rack app. All tests pass.

## Breaking Change

No breaking changes. All existing APIs are preserved:

- `StreamableHTTPTransport.new(server)` continues to work as before.
- `handle_request(request)` is unchanged. The new `call(env)` is a purely additive public method.
2026-04-10 04:55:25 +09:00
Koichi ITO
de38804ed7 Auto-set server.transport in Transport#initialize
## Motivation and Context

Transport subclasses (StreamableHTTPTransport, StdioTransport) already
receive the server in their constructor, but users must manually call
`server.transport = transport` after creating a transport instance.
This is boilerplate that can be eliminated by setting the connection
automatically in `Transport#initialize`.

## How Has This Been Tested?

All existing tests pass. Redundant `server.transport = transport` lines
were removed from tests, examples, README.md, and conformance server.

## Breaking Changes

None. Existing code that manually sets `server.transport = transport`
continues to work (idempotent assignment).
2026-04-09 12:08:55 +09:00
Koichi ITO
e5d3b2c135 Support CORS and Accept wildcard for browser-based MCP clients
## Summary

When using browser-based MCP clients such as MCP Inspector, connections to
the example HTTP server failed due to two issues:

1. CORS preflight (OPTIONS) requests returned 405 Method Not Allowed,
causing browsers to block all cross-origin requests.
2. `Accept: */*` (commonly sent by browsers and fetch API) returned 406 Not Acceptable
because the transport required the Accept header to explicitly list `application/json`
and `text/event-stream`.

Add `rack-cors` middleware to the example HTTP servers so that preflight requests are handled correctly
and `Mcp-Session-Id` is exposed to browser clients. Also fix `validate_accept_header` in `StreamableHTTPTransport`
to treat `*/*` as satisfying all required Accept types.

Fixes #141.

## Repro Steps

### 1. Start the example server (Terminal 1)

```console
$ bundle exec ruby examples/streamable_http_server.rb
```

### 2. Start MCP Inspector (Terminal 2)

```console
$ npx @modelcontextprotocol/inspector
```

### 3. Open the Inspector Web UI (http://localhost:6274) in a browser

- Set Transport Type to "Streamable HTTP"
- Set URL to http://localhost:9393
- Disable the Authorization header toggle (the example server does not require authentication)
- Click "Connect"

Before this change, the connection fails due to CORS or 406 errors.
After this change, the connection succeeds and tools are listed.

These steps have been added to examples/README.md.
2026-03-09 10:45:53 +09:00
dak2
afa2fb4077 Add logging support
A server can send structured logging messages to the client.
https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#logging

Logging was specified in the 2024-11-05 specification, but since it was not supported in ruby-sdk, I implemented it.
https://modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging

I also made it possible to output a simple notification message in the examples.
2026-01-26 22:23:46 +09:00
Koichi ITO
4851a16d39 Remove redundant rack gem
This PR removes the existing `rack` dependency.
`rack` is a dependency of `rackup`.
2025-10-20 19:24:21 +09:00
Koichi ITO
bee6eace22 Remove unnecessary transport requires
The transports are already required by `require 'mcp'`:
https://github.com/modelcontextprotocol/ruby-sdk/blob/v0.3.0/lib/mcp.rb#L16-L17
2025-10-07 14:10:48 +09:00
Koichi ITO
03e35c73c3 Reduce stdout hedge in examples
This PR reduces redundant stdout in examples by replacing
multiple `puts` with a heredoc and by removing unnecessary `puts`.
2025-09-02 11:30:31 +09:00
Ates Goral
c8fdf25cdb Use the new --json arg of curl 2025-07-14 20:43:43 -04:00
Sam Bostock
9a85975d12
Log headers in all curl examples
The examples showcase to the user how the server works, so it makes
sense to include headers to show the full picture.
2025-06-24 11:23:33 -04:00
Sam Bostock
cccb7494fa
Log headers in curl initialize requests
The user needs the `mcp-session-id` header, so we should log headers.
2025-06-24 11:22:46 -04:00
Koichi ITO
100d1a1ab3 Remove shebang from example files
Since the example files are not executable binaries, this PR removes the shebangs.

This ensures that all examples are consistently executed via `ruby` command.
It also removes the executable permission from `examples/stdio_server.rb` using `chmod 444`,
bringing it in line with the other example files.
2025-06-19 16:00:38 +09:00
dak2
10cb6c0eb9 Rename the example file names related to SSE.
This PR renamed the files related to the SSE example and fixed the README.md.
Because missing files were specified in README.md.

And also, I removed the word 'test' from the file names related to the SSE example.
Other examples file names do not contain the word `test`.
2025-06-18 18:30:40 +09:00
Renamed from examples/streaming_http_test_server.rb (Previous history)