Commit graph ruby-sdk/examples/README.md
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
16a6df340c Add Rails example app
## Motivation and Context

The README.md documents the "Rails (mount)" integration pattern, but the repository
had no runnable Rails example. This adds a minimal Rails application under examples/rails
that mounts StreamableHTTPTransport at /mcp and demonstrates class-based tools and
a resource with a read handler.

The MCP server is built in config/routes.rb rather than in an initializer because
Zeitwerk sets up autoloading after initializers run, so the tool classes in app/tools
cannot be referenced there.
Code reloading is disabled because the transport holds the server built at boot.
The app is booted with puma instead of rackup because rackup inserts `Rack::Lint`
in its development default, which rejects the capitalized response header names
that the transport currently emits.

Closes #44.

## How Has This Been Tested?

The curl walkthrough in examples/rails/README.md has been verified end to end against
the running app: initialize and the initialized notification, tools/list, tools/call for
both tools, resources/list, resources/read, the standalone SSE stream,
and session termination via DELETE.

## Breaking Changes

None. This only adds an example application and documentation links.
2026-07-09 01:37:58 +09:00
Yuki Minamiya
ac392cba55 [Doc] Use registered callable names in examples/README.md
The `tools/call` cURL example in `examples/README.md` uses `"name": "ExampleTool"`, but a tool registered by class without an explicit `tool_name` gets a snake_case name derived from its class name (via `StringUtils.handle_from_class_name`), so the example fails with `Tool not found: ExampleTool`. The Tools/Prompts lists in the same file had the same mismatch (`ExampleTool`, `ExamplePrompt`, `NotificationTool`). All entries now use the registered callable names.

How Tested: Ran `ruby examples/http_server.rb` and exercised the cURL flow. `tools/list` returned `example_tool` and `echo`; `tools/call` with `"name": "ExampleTool"` returned `Tool not found: ExampleTool`; with `"name": "example_tool"` returned `The sum of 5 and 3 is 8`. `prompts/list` returned `example_prompt`. The full unit suite and RuboCop pass locally.

Breaking Changes: None (documentation only).
2026-07-03 01:08:45 +09:00
Koichi ITO
46b738e945 Add client-level connect handshake to stdio transport
## Motivation and Context

This commit implements Phase 1 of 3 for #334.

The MCP specification requires an `initialize` request followed by
a `notifications/initialized` notification before any other interaction.
PR #327 added `MCP::Client#connect` and `MCP::Client::HTTP#connect`
to expose this handshake explicitly for the HTTP transport,
but stdio remained on its private `initialize_session` lazy-init path,
inconsistent with the Python SDK (`ClientSession.initialize()`) and
TypeScript SDK (`Client.connect(transport)`),
both of which require an explicit handshake call regardless of transport.

The implicit init path on the first `send_request` is
preserved as a non-breaking compatibility shim. Public API added on `Stdio`:

- `connect(client_info:, protocol_version:, capabilities:)` performs the
  handshake, validates the negotiated `protocolVersion` against
  `MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS`,
  caches the `InitializeResult`, and is idempotent.
- `connected?` reports handshake completion.
- `server_info` exposes the cached `InitializeResult`.

Documentation and examples are updated so new users learn the explicit pattern
from the start.

## How Has This Been Tested?

Fifteen regression tests cover: explicit handshake, `server_info` caching,
idempotence, custom keyword arguments, JSON-RPC error response, missing
or non-Hash `result`, unsupported protocol version with state rollback,
state rollback when the `notifications/initialized` write fails,
retry after a failed handshake, and the `connected?` / `server_info` lifecycle.

The pre-existing stdio tests pass without modification, verifying that
the implicit-init compatibility path still works.

## Breaking Changes

None for the typical "implicit init via `client.tools` / `client.call_tool`" usage.
Existing stdio clients keep working unchanged.

Three observable behavior changes for code that inspects transport state directly:

- `Client#connected?` for a never-initialized stdio client returns `false`
  (previously fell through to a `true` default via the `respond_to?` fallback).
- `Client#server_info` for stdio returns the cached `InitializeResult`
  after the handshake (previously always `nil` via the fallback).
- The implicit handshake path now validates the server's negotiated
  protocol version, raising `RequestHandlerError` for unsupported versions.
  Production servers that return one of the supported versions are unaffected;
  this closes a pre-existing spec-conformance gap on stdio.
2026-05-03 15:57:22 +09:00
Koichi ITO
b3125d4605 [Doc] Align stdio with specification
Follow-up to https://github.com/modelcontextprotocol/ruby-sdk/pull/262#discussion_r2957734483.
2026-03-26 04:05:05 +09:00
Koichi ITO
21eef8df9a Add MCP::Client::Stdio transport
## 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`.
2026-03-19 12:41:27 +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
Ates Goral
c8fdf25cdb Use the new --json arg of curl 2025-07-14 20:43:43 -04:00
Koichi ITO
6335d21349 [Doc] Use console instead of bash for code block highlighting
Follow-up to https://github.com/modelcontextprotocol/ruby-sdk/pull/67#discussion_r2162083685.

This PR updates code blocks for command examples to use `console` as the language identifier.
2025-06-25 19:14:30 +09: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
7a2043b49c
Add space before pipe 2025-06-24 11:21:27 -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
Riley Shott
4e4455772b StreamableHTTP -> Streamable HTTP 2025-06-09 14:12:13 -07:00
Riley Shott
3e03a2af66 Implement StreamableHTTP transport
Co-authored-by: Topher Bullock <topher.bullock@shopify.com>
2025-06-09 14:12:11 -07:00