[Doc] Align the Docs Page Names With the MCP Specification
Three pages carried names the 2026-07-28 specification does not use: "Multi-Round-Trip Results" for the pattern the specification titles Multi Round-Trip Requests (MRTR) and serves at basic/patterns/mrtr - the results are the `InputRequiredResult` wire type, not the feature - plus "Completions" for the utility the specification titles Completion, and "Notification Subscriptions" for the pattern it titles Subscriptions. Retitle the three pages to the specification's names, move them to /server/mrtr/ and /client/mrtr/, /server/completion/, and /server/subscriptions/ following the specification's slugs, update every internal reference, and keep the URLs published with v1.3.0 working via jekyll-redirect-from. The Discovery page keeps its title but moves from /server/discovery/ to /server/discover/, matching the specification's slug and the `server/discover` RPC, with its published URL redirected the same way.
This commit is contained in:
parent
fcb1ac935d
commit
c9d36b04bf
21 changed files with 55 additions and 45 deletions
|
|
@ -9,7 +9,7 @@ Detailed guides are available at https://ruby.sdk.modelcontextprotocol.io.
|
||||||
- Build [MCP servers](https://ruby.sdk.modelcontextprotocol.io/server/) that expose tools, prompts, and resources to any MCP host
|
- Build [MCP servers](https://ruby.sdk.modelcontextprotocol.io/server/) that expose tools, prompts, and resources to any MCP host
|
||||||
- Build [MCP clients](https://ruby.sdk.modelcontextprotocol.io/client/) that connect to any MCP server, with automatic lifecycle negotiation and OAuth 2.1 authorization
|
- Build [MCP clients](https://ruby.sdk.modelcontextprotocol.io/client/) that connect to any MCP server, with automatic lifecycle negotiation and OAuth 2.1 authorization
|
||||||
- Speak every standard transport: stdio and Streamable HTTP (including SSE), with a Rails integration
|
- Speak every standard transport: stdio and Streamable HTTP (including SSE), with a Rails integration
|
||||||
- Cover the full protocol surface: server-to-client requests, multi round-trip results, notifications, progress, logging, cancellation, completions, and pagination
|
- Cover the full protocol surface: server-to-client requests, multi round-trip requests, notifications, progress, logging, cancellation, completions, and pagination
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ This class supports:
|
||||||
- Resource reading via the `resources/read` method (`MCP::Client#read_resource`)
|
- Resource reading via the `resources/read` method (`MCP::Client#read_resource`)
|
||||||
- Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
|
- Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
|
||||||
- Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
|
- Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
|
||||||
- Completion requests via the `completion/complete` method (`MCP::Client#complete`); see [Completions](/server/completions/)
|
- Completion requests via the `completion/complete` method (`MCP::Client#complete`); see [Completion](/server/completion/)
|
||||||
- Automatic driving of multi round-trip `input_required` results once `on_elicitation`, `on_sampling`,
|
- Automatic driving of multi round-trip `input_required` results once `on_elicitation`, `on_sampling`,
|
||||||
or `on_roots` handlers are registered; see [Multi-Round-Trip Results](/client/multi-round-trip-results/)
|
or `on_roots` handlers are registered; see [Multi Round-Trip Requests](/client/mrtr/)
|
||||||
- Cancellation of in-flight requests via the `cancellation:` keyword; see [Cancellation](/client/cancellation/)
|
- Cancellation of in-flight requests via the `cancellation:` keyword; see [Cancellation](/client/cancellation/)
|
||||||
- Cursor-based page iteration on the `list_*` methods and whole-collection fetching with
|
- Cursor-based page iteration on the `list_*` methods and whole-collection fetching with
|
||||||
the `max_pages` guard; see [Pagination](/client/pagination/)
|
the `max_pages` guard; see [Pagination](/client/pagination/)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ only when the server does not serve the modern lifecycle, or when `mode: :legacy
|
||||||
## Lifecycle Negotiation
|
## Lifecycle Negotiation
|
||||||
|
|
||||||
`MCP::Client#connect` selects the protocol lifecycle automatically by default: on the bundled
|
`MCP::Client#connect` selects the protocol lifecycle automatically by default: on the bundled
|
||||||
`MCP::Client::HTTP` and `MCP::Client::Stdio` transports it probes [`server/discover`](/server/discovery/) first and adopts
|
`MCP::Client::HTTP` and `MCP::Client::Stdio` transports it probes [`server/discover`](/server/discover/) first and adopts
|
||||||
the stateless modern lifecycle (MCP 2026-07-28, SEP-2575) when the server serves it, falling back to
|
the stateless modern lifecycle (MCP 2026-07-28, SEP-2575) when the server serves it, falling back to
|
||||||
the classic `initialize` handshake otherwise. Custom transports whose `connect` does not declare
|
the classic `initialize` handshake otherwise. Custom transports whose `connect` does not declare
|
||||||
a `mode:` keyword always receive the classic call shape, unchanged.
|
a `mode:` keyword always receive the classic call shape, unchanged.
|
||||||
|
|
@ -69,7 +69,7 @@ Pass `mode: :legacy` for an immediate return to the previous behavior, or switch
|
||||||
`MCP::Client#discover` sends `server/discover` directly: sessionless capability discovery
|
`MCP::Client#discover` sends `server/discover` directly: sessionless capability discovery
|
||||||
that works before (or instead of) `connect`. It returns an `MCP::Client::DiscoverResult` struct
|
that works before (or instead of) `connect`. It returns an `MCP::Client::DiscoverResult` struct
|
||||||
exposing `supported_versions`, `capabilities`, `server_info`, `instructions`, and
|
exposing `supported_versions`, `capabilities`, `server_info`, `instructions`, and
|
||||||
the `ttl_ms` / `cache_scope` cache hints; see the server [Discovery](/server/discovery/) page
|
the `ttl_ms` / `cache_scope` cache hints; see the server [Discovery](/server/discover/) page
|
||||||
for the wire shapes.
|
for the wire shapes.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
|
|
|
||||||
62
docs/_client/mrtr.md
Normal file
62
docs/_client/mrtr.md
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Multi Round-Trip Requests
|
||||||
|
nav_order: 4
|
||||||
|
redirect_from:
|
||||||
|
- /client/multi-round-trip-results/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Multi Round-Trip Requests
|
||||||
|
|
||||||
|
MCP 2026-07-28 replaces in-flight server-to-client requests with Multi Round-Trip Requests (SEP-2322): instead of issuing `sampling/createMessage`, `roots/list`,
|
||||||
|
or `elicitation/create` while a request is being processed, a server may answer with a result whose `resultType` is `"input_required"`, carrying an `inputRequests` map
|
||||||
|
and an opaque `requestState`; the client fulfills the requests and re-issues the original request with `inputResponses` and the echoed `requestState`.
|
||||||
|
|
||||||
|
## Automatic Driving
|
||||||
|
|
||||||
|
The Ruby client drives such results automatically: once a handler is registered through `on_elicitation`, `on_sampling`, or `on_roots`, the `call_tool`, `get_prompt`,
|
||||||
|
and `read_resource` methods fulfill the embedded requests and re-issue the original request with `inputResponses` plus the echoed `requestState`, capped at `input_required_max_rounds`
|
||||||
|
(10 by default, matching the TypeScript and Python SDKs).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
client = MCP::Client.new(transport: transport)
|
||||||
|
client.connect(capabilities: { elicitation: { form: {} } })
|
||||||
|
|
||||||
|
client.on_elicitation do |params|
|
||||||
|
{ action: "accept", content: { name: "Alice" } }
|
||||||
|
end
|
||||||
|
|
||||||
|
# The input_required round trips are driven automatically; this returns the final result.
|
||||||
|
response = client.call_tool(name: "collect_name", arguments: {})
|
||||||
|
```
|
||||||
|
|
||||||
|
Declare the capabilities matching the registered handlers on `connect`: a server embeds only the request kinds
|
||||||
|
the client declared.
|
||||||
|
|
||||||
|
## Manual Driving
|
||||||
|
|
||||||
|
Without a matching handler, `MCP::Client::InputRequiredError` is raised instead of returning the result as if it were final;
|
||||||
|
the error exposes `input_requests`, `request_state`, and the raw `result` for manual driving via the `input_responses:` and `request_state:` keywords:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
begin
|
||||||
|
client.call_tool(name: "collect_name", arguments: {})
|
||||||
|
rescue MCP::Client::InputRequiredError => error
|
||||||
|
answers = error.input_requests.transform_values { |request| answer_for(request) }
|
||||||
|
|
||||||
|
client.call_tool(
|
||||||
|
name: "collect_name",
|
||||||
|
arguments: {},
|
||||||
|
input_responses: answers,
|
||||||
|
request_state: error.request_state,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
`MCP::ResultType::COMPLETE` and `MCP::ResultType::INPUT_REQUIRED` are provided for forward compatibility.
|
||||||
|
Servers on legacy protocol versions never send `resultType`, so existing behavior is unchanged.
|
||||||
|
|
||||||
|
## Server Side
|
||||||
|
|
||||||
|
Authoring `input_required` results with `InputRequiredResult`, securing `requestState`, `resultType` stamping, and the legacy fulfillment
|
||||||
|
shim that serves pre-2026 clients are documented on the server [Multi Round-Trip Requests](/server/mrtr/) page.
|
||||||
|
|
@ -166,7 +166,7 @@ a JSON-RPC `-32601` (method not found) error. To handle methods other than `elic
|
||||||
|
|
||||||
On a [modern](/client/lifecycle/) connection servers cannot send requests at all; the same
|
On a [modern](/client/lifecycle/) connection servers cannot send requests at all; the same
|
||||||
registered handlers instead drive the requests embedded in `input_required` results,
|
registered handlers instead drive the requests embedded in `input_required` results,
|
||||||
as documented on [Multi-Round-Trip Results](/client/multi-round-trip-results/).
|
as documented on [Multi Round-Trip Requests](/client/mrtr/).
|
||||||
|
|
||||||
### Server-to-Client Requests (Sampling)
|
### Server-to-Client Requests (Sampling)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ server = MCP::Server.new(name: "my_server", capabilities: capabilities)
|
||||||
The declared extensions appear in the `initialize` result's `capabilities.extensions`. Extensions the client declared during `initialize` are
|
The declared extensions appear in the `initialize` result's `capabilities.extensions`. Extensions the client declared during `initialize` are
|
||||||
readable via `server.client_capabilities[:extensions]` (or `session.client_capabilities[:extensions]` for per-session transports).
|
readable via `server.client_capabilities[:extensions]` (or `session.client_capabilities[:extensions]` for per-session transports).
|
||||||
|
|
||||||
On the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle), the same declarations appear in the `server/discover` result,
|
On the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle), the same declarations appear in the `server/discover` result,
|
||||||
and the client's extensions ride each request's `_meta` envelope instead of an `initialize` handshake.
|
and the client's extensions ride each request's `_meta` envelope instead of an `initialize` handshake.
|
||||||
Inside a handler, `server_context.client_capabilities[:extensions]` reads the current request's declarations
|
Inside a handler, `server_context.client_capabilities[:extensions]` reads the current request's declarations
|
||||||
with envelope-first resolution, on either lifecycle.
|
with envelope-first resolution, on either lifecycle.
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ is never cancellable per the spec.
|
||||||
{: .note }
|
{: .note }
|
||||||
> Cancellation by notification belongs to the handshake lifecycle, where the session correlates
|
> Cancellation by notification belongs to the handshake lifecycle, where the session correlates
|
||||||
> `notifications/cancelled` with the in-flight request it targets. Requests of the
|
> `notifications/cancelled` with the in-flight request it targets. Requests of the
|
||||||
> [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) are sessionless single POST exchanges, so a separately
|
> [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) are sessionless single POST exchanges, so a separately
|
||||||
> POSTed cancel notification cannot reach them; a modern client abandons a request by closing
|
> POSTed cancel notification cannot reach them; a modern client abandons a request by closing
|
||||||
> the connection instead.
|
> the connection instead.
|
||||||
|
|
||||||
|
|
|
||||||
54
docs/_server/completion.md
Normal file
54
docs/_server/completion.md
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Completion
|
||||||
|
nav_order: 16
|
||||||
|
redirect_from:
|
||||||
|
- /server/completions/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Completion
|
||||||
|
|
||||||
|
MCP spec includes [Completions](https://modelcontextprotocol.io/specification/latest/server/utilities/completion),
|
||||||
|
which enable servers to provide autocompletion suggestions for prompt arguments and resource URIs.
|
||||||
|
|
||||||
|
To enable completions, declare the `completions` capability and register a handler:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
server = MCP::Server.new(
|
||||||
|
name: "my_server",
|
||||||
|
prompts: [CodeReviewPrompt],
|
||||||
|
resource_templates: [FileTemplate],
|
||||||
|
capabilities: { completions: {} },
|
||||||
|
)
|
||||||
|
|
||||||
|
server.completion_handler do |params|
|
||||||
|
ref = params[:ref]
|
||||||
|
argument = params[:argument]
|
||||||
|
value = argument[:value]
|
||||||
|
|
||||||
|
case ref[:type]
|
||||||
|
when "ref/prompt"
|
||||||
|
values = case argument[:name]
|
||||||
|
when "language"
|
||||||
|
["python", "pytorch", "pyside"].select { |v| v.start_with?(value) }
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
{ completion: { values: values, hasMore: false } }
|
||||||
|
when "ref/resource"
|
||||||
|
{ completion: { values: [], hasMore: false } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The handler receives a `params` hash with:
|
||||||
|
|
||||||
|
- `ref` - The reference (`{ type: "ref/prompt", name: "..." }` or `{ type: "ref/resource", uri: "..." }`)
|
||||||
|
- `argument` - The argument being completed (`{ name: "...", value: "..." }`)
|
||||||
|
- `context` (optional) - Previously resolved arguments (`{ arguments: { ... } }`)
|
||||||
|
|
||||||
|
The handler must return a hash with a `completion` key containing `values` (array of strings), and optionally `total` and `hasMore`.
|
||||||
|
The SDK automatically enforces the 100-item limit per the MCP specification.
|
||||||
|
|
||||||
|
The server validates that the referenced prompt, resource, or resource template is registered before calling the handler.
|
||||||
|
Requests for unknown references return an error.
|
||||||
|
|
@ -201,6 +201,6 @@ If an invalid `protocol_version` value is set, an `ArgumentError` is raised.
|
||||||
The pin scopes the `initialize` handshake, so it accepts handshake versions (`2025-11-25` and earlier) only. Per the SEP-2575 era model,
|
The pin scopes the `initialize` handshake, so it accepts handshake versions (`2025-11-25` and earlier) only. Per the SEP-2575 era model,
|
||||||
`2026-07-28` carries its version on every request and has no handshake at all, so there is nothing for a pin to configure there and setting it raises `ArgumentError`;
|
`2026-07-28` carries its version on every request and has no handshake at all, so there is nothing for a pin to configure there and setting it raises `ArgumentError`;
|
||||||
a client asking `initialize` for a modern version is counter-offered the pinned version (or the latest handshake version), matching the TypeScript and Python SDKs.
|
a client asking `initialize` for a modern version is counter-offered the pinned version (or the latest handshake version), matching the TypeScript and Python SDKs.
|
||||||
Clients reach `2026-07-28` through [`server/discover`](/server/discovery/) and the per-request `_meta` envelope, which the bundled transports serve alongside the handshake with no configuration needed.
|
Clients reach `2026-07-28` through [`server/discover`](/server/discover/) and the per-request `_meta` envelope, which the bundled transports serve alongside the handshake with no configuration needed.
|
||||||
|
|
||||||
Be sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.
|
Be sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.
|
||||||
|
|
|
||||||
63
docs/_server/discover.md
Normal file
63
docs/_server/discover.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Discovery
|
||||||
|
nav_order: 3
|
||||||
|
redirect_from:
|
||||||
|
- /server/discovery/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Discovery
|
||||||
|
|
||||||
|
`server/discover` is the sessionless capability discovery method of MCP 2026-07-28 (SEP-2575).
|
||||||
|
It responds before `initialize` and without an `Mcp-Session-Id`, so a client learns what a server
|
||||||
|
offers in a single exchange, without creating a session.
|
||||||
|
|
||||||
|
## The Discovery Result
|
||||||
|
|
||||||
|
The result carries the modern `supportedVersions`, `capabilities`, and `instructions`, together with
|
||||||
|
the required `ttlMs`/`cacheScope` cache hints, and the server identity as the optional
|
||||||
|
`io.modelcontextprotocol/serverInfo` stamp in the result `_meta`.
|
||||||
|
|
||||||
|
The request takes no parameters:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "jsonrpc": "2.0", "id": 1, "method": "server/discover" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The server answers with the full discovery result:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {
|
||||||
|
"supportedVersions": ["2026-07-28"],
|
||||||
|
"capabilities": { "tools": { "listChanged": true } },
|
||||||
|
"instructions": "Use the tools of this server as a last resort",
|
||||||
|
"_meta": { "io.modelcontextprotocol/serverInfo": { "name": "my_server", "version": "1.0.0" } },
|
||||||
|
"ttlMs": 0,
|
||||||
|
"cacheScope": "private",
|
||||||
|
"resultType": "complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Stateless Modern Lifecycle
|
||||||
|
|
||||||
|
Beyond answering `server/discover`, the server serves the full stateless modern lifecycle: requests carrying the SEP-2575 `_meta` envelope
|
||||||
|
(`io.modelcontextprotocol/protocolVersion`, `clientInfo`, and `clientCapabilities`) are validated per request,
|
||||||
|
and the Streamable HTTP transport serves them on a sessionless single-exchange path.
|
||||||
|
Calling `server/discover` first is not required: the envelope alone selects the modern lifecycle for a request,
|
||||||
|
while a client performing the classic `initialize` handshake is served on the handshake lifecycle -
|
||||||
|
the lifecycle is a per-request property, not a server-wide mode.
|
||||||
|
|
||||||
|
The bundled transports serve `server/discover` and the per-request `_meta` envelope alongside the `initialize` handshake
|
||||||
|
with no configuration needed. The `protocol_version` pin scopes the handshake only
|
||||||
|
and does not affect the modern lifecycle; see [Configuration](/server/configuration/) for details.
|
||||||
|
|
||||||
|
## Client Side
|
||||||
|
|
||||||
|
On the client, `MCP::Client#connect` negotiates the lifecycle automatically by default
|
||||||
|
(probe `server/discover`, fall back to the `initialize` handshake), `connect(mode: :modern)` skips
|
||||||
|
the handshake entirely, `connect(mode: :legacy)` forces the classic handshake, and `MCP::Client#discover`
|
||||||
|
exposes the raw discovery result. See the client-side [Lifecycle](/client/lifecycle/) page for details.
|
||||||
|
|
@ -13,9 +13,9 @@ Elicitation is a **server-to-client request**. The server sends a request and bl
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
> Unlike [roots](/server/roots/) and [sampling](/server/sampling/), elicitation carries no SEP-2577 deprecation
|
> Unlike [roots](/server/roots/) and [sampling](/server/sampling/), elicitation carries no SEP-2577 deprecation
|
||||||
> and remains fully available. On the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) (MCP 2026-07-28), which forbids server-initiated requests,
|
> and remains fully available. On the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) (MCP 2026-07-28), which forbids server-initiated requests,
|
||||||
> an `elicitation/create` request is embedded in an `input_required` result instead;
|
> an `elicitation/create` request is embedded in an `input_required` result instead;
|
||||||
> see [Multi-Round-Trip Results](/server/multi-round-trip-results/).
|
> see [Multi Round-Trip Requests](/server/mrtr/).
|
||||||
|
|
||||||
{: .important }
|
{: .important }
|
||||||
> Per SEP-2260, server-to-client requests (`roots/list`, `sampling/createMessage`, `elicitation/create`) must be associated with
|
> Per SEP-2260, server-to-client requests (`roots/list`, `sampling/createMessage`, `elicitation/create`) must be associated with
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ It implements the Model Context Protocol specification, handling model context r
|
||||||
|
|
||||||
- `initialize` - Initializes the protocol and returns server capabilities
|
- `initialize` - Initializes the protocol and returns server capabilities
|
||||||
- `server/discover` - Sessionless capability discovery (MCP 2026-07-28, SEP-2575): returns the server's capabilities
|
- `server/discover` - Sessionless capability discovery (MCP 2026-07-28, SEP-2575): returns the server's capabilities
|
||||||
before `initialize` and without an `Mcp-Session-Id`, and anchors the stateless modern lifecycle; see [Discovery](/server/discovery/)
|
before `initialize` and without an `Mcp-Session-Id`, and anchors the stateless modern lifecycle; see [Discovery](/server/discover/)
|
||||||
- `subscriptions/listen` - Long-lived notification subscription stream (MCP 2026-07-28, SEP-2575), replacing the legacy HTTP GET
|
- `subscriptions/listen` - Long-lived notification subscription stream (MCP 2026-07-28, SEP-2575), replacing the legacy HTTP GET
|
||||||
listening stream; see [Notification Subscriptions](/server/notification-subscriptions/)
|
listening stream; see [Subscriptions](/server/subscriptions/)
|
||||||
- Multi round-trip `input_required` results (MCP 2026-07-28, SEP-2322): handlers return `MCP::Server::InputRequiredResult` to ask
|
- Multi round-trip `input_required` results (MCP 2026-07-28, SEP-2322): handlers return `MCP::Server::InputRequiredResult` to ask
|
||||||
the client for additional input instead of performing a server-initiated request; see [Multi-Round-Trip Results](/server/multi-round-trip-results/)
|
the client for additional input instead of performing a server-initiated request; see [Multi Round-Trip Requests](/server/mrtr/)
|
||||||
- `ping` - Simple health check
|
- `ping` - Simple health check
|
||||||
- `logging/setLevel` - Configures the minimum log level for the server
|
- `logging/setLevel` - Configures the minimum log level for the server
|
||||||
- `tools/list` - Lists all registered tools and their schemas
|
- `tools/list` - Lists all registered tools and their schemas
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ The `notifications/message` notification is used for structured logging between
|
||||||
> while remaining fully supported under `2025-11-25`. Use stderr or OpenTelemetry for new servers.
|
> while remaining fully supported under `2025-11-25`. Use stderr or OpenTelemetry for new servers.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
> On the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle), where `logging/setLevel` does not exist, the level
|
> On the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle), where `logging/setLevel` does not exist, the level
|
||||||
> comes per request from the `io.modelcontextprotocol/logLevel` `_meta` member: it authorizes
|
> comes per request from the `io.modelcontextprotocol/logLevel` `_meta` member: it authorizes
|
||||||
> `notifications/message` for that request only, delivered on the request's own response stream.
|
> `notifications/message` for that request only, delivered on the request's own response stream.
|
||||||
> A request without the member (or with an unrecognized level) receives no log messages.
|
> A request without the member (or with an unrecognized level) receives no log messages.
|
||||||
|
|
|
||||||
87
docs/_server/mrtr.md
Normal file
87
docs/_server/mrtr.md
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Multi Round-Trip Requests
|
||||||
|
nav_order: 10
|
||||||
|
redirect_from:
|
||||||
|
- /server/multi-round-trip-results/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Multi Round-Trip Requests
|
||||||
|
|
||||||
|
The [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) (MCP 2026-07-28) forbids server-initiated requests. Instead, per SEP-2322, a `tools/call`, `prompts/get`, or `resources/read` handler that
|
||||||
|
opts in to [`server_context:`](/server/server-context/) may return `MCP::Server::InputRequiredResult.new(input_requests:, request_state:)` to ask the client for
|
||||||
|
additional input (`elicitation/create`, `sampling/createMessage`, or `roots/list` shapes):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class GreetingTool < MCP::Tool
|
||||||
|
description "Greets the user by name"
|
||||||
|
|
||||||
|
def self.call(server_context:, **_args)
|
||||||
|
response = server_context.input_response("user_name")
|
||||||
|
|
||||||
|
unless response
|
||||||
|
return MCP::Server::InputRequiredResult.new(
|
||||||
|
input_requests: {
|
||||||
|
user_name: {
|
||||||
|
method: "elicitation/create",
|
||||||
|
params: {
|
||||||
|
message: "What is your name?",
|
||||||
|
requestedSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: { name: { type: "string" } },
|
||||||
|
required: ["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
MCP::Tool::Response.new([{ type: "text", text: "Hello, #{response.dig(:content, :name)}!" }])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deterministic Replay
|
||||||
|
|
||||||
|
On the retried request the handler re-runs from the start and reads the answers via
|
||||||
|
`server_context.input_responses` / `server_context.input_response(key)` and the echoed opaque `server_context.request_state`
|
||||||
|
(deterministic replay; the server holds no memory between rounds). The server returns `-32021`
|
||||||
|
when an embedded request needs a client capability the request did not declare.
|
||||||
|
|
||||||
|
## Securing `requestState`
|
||||||
|
|
||||||
|
The echoed `requestState` arrives as client-controlled input: pass `MCP::Server::RequestStateSecurity.new(key:)` (a 32-byte key) via
|
||||||
|
`Server.new(request_state_security:)` to have it sealed with AES-256-GCM and bound to a TTL plus the originating method, target, and arguments,
|
||||||
|
all transparently to handlers. Multi-process deployments must share the key across workers; without `request_state_security:` the state crosses
|
||||||
|
the wire exactly as the handler wrote it and protecting it is the handler author's responsibility.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
server = MCP::Server.new(
|
||||||
|
name: "my_server",
|
||||||
|
tools: [GreetingTool],
|
||||||
|
request_state_security: MCP::Server::RequestStateSecurity.new(key: ENV.fetch("MCP_REQUEST_STATE_KEY")),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `resultType` Stamping
|
||||||
|
|
||||||
|
SEP-2322 also makes `resultType` a required member of every result a 2026-07-28 server returns. The server stamps `resultType: "complete"`
|
||||||
|
on all results of requests carrying the modern `_meta` envelope (and on `server/discover` results), while results that already carry
|
||||||
|
a discriminator (`"input_required"`, the tasks extension's `"task"`) keep it. Legacy results stay unstamped, and clients treat an absent
|
||||||
|
`resultType` as `"complete"` per the spec.
|
||||||
|
|
||||||
|
## Legacy Clients
|
||||||
|
|
||||||
|
Handlers written in the 2026 style serve pre-2026 clients too: when a `tools/call`, `prompts/get`, or `resources/read` handler returns
|
||||||
|
an `InputRequiredResult` on the legacy wire, the server fulfills it in place of the client's driver. Each `inputRequests` entry is sent
|
||||||
|
as the equivalent real server-to-client request (`elicitation/create`, `sampling/createMessage`, `roots/list`), associated with
|
||||||
|
the originating request per SEP-2260; the answers are collected under the same keys, and the handler re-runs with
|
||||||
|
`server_context.input_responses` populated and the raw `requestState` echoed, the same deterministic replay contract
|
||||||
|
the modern client driver follows. The shim is on by default (matching the TypeScript SDK) and capped at 8 rounds;
|
||||||
|
`MCP::Server.new(input_required_legacy_shim: false)` restores the strict rejection of `input_required` results on legacy requests.
|
||||||
|
|
||||||
|
## Client Side
|
||||||
|
|
||||||
|
`call_tool`, `get_prompt`, and `read_resource` drive `input_required` results automatically once the matching handlers are registered;
|
||||||
|
see the client [Multi Round-Trip Requests](/client/mrtr/) page.
|
||||||
|
|
@ -26,8 +26,8 @@ No extra configuration is needed.
|
||||||
- **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
|
- **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
|
||||||
as they represent server-wide state changes. These should be called on the `server` instance directly.
|
as they represent server-wide state changes. These should be called on the `server` instance directly.
|
||||||
|
|
||||||
On the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) (MCP 2026-07-28), clients receive these broadcasts through the `subscriptions/listen` stream;
|
On the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) (MCP 2026-07-28), clients receive these broadcasts through the `subscriptions/listen` stream;
|
||||||
see [Notification Subscriptions](/server/notification-subscriptions/).
|
see [Subscriptions](/server/subscriptions/).
|
||||||
|
|
||||||
## Notification Format
|
## Notification Format
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ A `ping` request has no parameters, and the receiver MUST respond promptly with
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
> `ping` belongs to the handshake lifecycle: MCP 2026-07-28 removes the method altogether (SEP-2575),
|
> `ping` belongs to the handshake lifecycle: MCP 2026-07-28 removes the method altogether (SEP-2575),
|
||||||
> since requests of the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) are single POST exchanges whose connection
|
> since requests of the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) are single POST exchanges whose connection
|
||||||
> itself signals liveness, leaving nothing to probe between requests. The server answers `ping` on
|
> itself signals liveness, leaving nothing to probe between requests. The server answers `ping` on
|
||||||
> the handshake lifecycle only - a modern request naming it is rejected with `-32601` Method not found -
|
> the handshake lifecycle only - a modern request naming it is rejected with `-32601` Method not found -
|
||||||
> and calling `ping` on a `server_context` while serving a modern request raises an error. The long-lived
|
> and calling `ping` on a `server_context` while serving a modern request raises an error. The long-lived
|
||||||
> [`subscriptions/listen`](/server/notification-subscriptions/) stream is kept alive by SSE keepalive
|
> [`subscriptions/listen`](/server/subscriptions/) stream is kept alive by SSE keepalive
|
||||||
> frames instead.
|
> frames instead.
|
||||||
|
|
||||||
Servers respond to incoming `ping` requests automatically - no setup is required.
|
Servers respond to incoming `ping` requests automatically - no setup is required.
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ The `server_context.report_progress` method accepts:
|
||||||
string tokens are supported.
|
string tokens are supported.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
> On the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle), progress notifications emitted during a request ride
|
> On the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle), progress notifications emitted during a request ride
|
||||||
> the request's own SSE response stream. The bundled transport buffers them and flushes after
|
> the request's own SSE response stream. The bundled transport buffers them and flushes after
|
||||||
> the handler returns, so they preserve order but arrive together with the final response rather
|
> the handler returns, so they preserve order but arrive together with the final response rather
|
||||||
> than in real time.
|
> than in real time.
|
||||||
|
|
|
||||||
73
docs/_server/subscriptions.md
Normal file
73
docs/_server/subscriptions.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Subscriptions
|
||||||
|
nav_order: 12
|
||||||
|
redirect_from:
|
||||||
|
- /server/notification-subscriptions/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Subscriptions
|
||||||
|
|
||||||
|
`subscriptions/listen` is the long-lived notification subscription stream of MCP 2026-07-28 (SEP-2575),
|
||||||
|
replacing the legacy HTTP GET listening stream.
|
||||||
|
|
||||||
|
## Subscribing
|
||||||
|
|
||||||
|
The client opts in via the `notifications` filter (`toolsListChanged` / `promptsListChanged` / `resourcesListChanged` / `resourceSubscriptions`),
|
||||||
|
the server acknowledges the honored subset with `notifications/subscriptions/acknowledged` as the first stream message,
|
||||||
|
and every delivered notification carries the correlating `io.modelcontextprotocol/subscriptionId` in `_meta`.
|
||||||
|
The honored subset follows the capabilities the server declares (`listChanged` and `subscribe` flags).
|
||||||
|
|
||||||
|
The client opens the stream with a `subscriptions/listen` request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "listen-1",
|
||||||
|
"method": "subscriptions/listen",
|
||||||
|
"params": {
|
||||||
|
"notifications": { "toolsListChanged": true, "resourceSubscriptions": ["file:///a.txt"] },
|
||||||
|
"_meta": {
|
||||||
|
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
||||||
|
"io.modelcontextprotocol/clientInfo": { "name": "my_client", "version": "1.0.0" },
|
||||||
|
"io.modelcontextprotocol/clientCapabilities": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first SSE event on the stream is the acknowledgement:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "notifications/subscriptions/acknowledged",
|
||||||
|
"params": {
|
||||||
|
"notifications": { "toolsListChanged": true, "resourceSubscriptions": ["file:///a.txt"] },
|
||||||
|
"_meta": { "io.modelcontextprotocol/subscriptionId": "listen-1" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transport Support
|
||||||
|
|
||||||
|
The stream is served on the Streamable HTTP modern path; stdio answers `-32601`.
|
||||||
|
|
||||||
|
## Limits and Keepalives
|
||||||
|
|
||||||
|
Concurrent streams are capped by `max_listen_subscriptions:` (default 1000; pass `nil` to remove the cap), and each stream receives an SSE keepalive
|
||||||
|
comment frame every `listen_keepalive_interval:` seconds (default 15) so a dropped connection frees its slot; pass `listen_keepalive_interval: nil`
|
||||||
|
when an upstream proxy already keeps the stream alive.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
transport = MCP::Server::Transports::StreamableHTTPTransport.new(
|
||||||
|
server,
|
||||||
|
max_listen_subscriptions: 500,
|
||||||
|
listen_keepalive_interval: 30,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
A stream stays open until the client closes the connection; on graceful shutdown via `transport.close`,
|
||||||
|
each open stream receives its `subscriptions/listen` response before closing.
|
||||||
|
|
||||||
|
See [Notifications](/server/notifications/) for the notification types themselves and their session scoping.
|
||||||
|
|
@ -76,10 +76,10 @@ The following examples show two common integration styles in Rails.
|
||||||
> routed to the same instance.
|
> routed to the same instance.
|
||||||
>
|
>
|
||||||
> Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
|
> Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
|
||||||
> Requests of the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) (MCP 2026-07-28) are likewise sessionless single exchanges and work
|
> Requests of the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) (MCP 2026-07-28) are likewise sessionless single exchanges and work
|
||||||
> with any configuration, with two caveats: a [`subscriptions/listen`](/server/notification-subscriptions/) stream
|
> with any configuration, with two caveats: a [`subscriptions/listen`](/server/subscriptions/) stream
|
||||||
> is held in memory by the process that accepted it, so only notifications emitted in that process reach it,
|
> is held in memory by the process that accepted it, so only notifications emitted in that process reach it,
|
||||||
> and the [multi round-trip](/server/multi-round-trip-results/) `requestState` crosses workers only when they
|
> and the [multi round-trip](/server/mrtr/) `requestState` crosses workers only when they
|
||||||
> share a `RequestStateSecurity` key.
|
> share a `RequestStateSecurity` key.
|
||||||
|
|
||||||
{: .important }
|
{: .important }
|
||||||
|
|
@ -103,7 +103,7 @@ The following examples show two common integration styles in Rails.
|
||||||
> so both `"mcp.example.com"` and `"mcp.example.com:8443"` work. Pass `dns_rebinding_protection: false`
|
> so both `"mcp.example.com"` and `"mcp.example.com:8443"` work. Pass `dns_rebinding_protection: false`
|
||||||
> to disable the check entirely (e.g., when an upstream proxy or middleware already validates `Host`/`Origin`).
|
> to disable the check entirely (e.g., when an upstream proxy or middleware already validates `Host`/`Origin`).
|
||||||
> The check runs before any lifecycle dispatch, so it protects requests of
|
> The check runs before any lifecycle dispatch, so it protects requests of
|
||||||
> the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) as well.
|
> the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) as well.
|
||||||
|
|
||||||
### Rails (mount)
|
### Rails (mount)
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ and repeated `initialize` requests are permitted. Request-scoped notifications s
|
||||||
(there is no stream to deliver them), while server-to-client requests (`sampling/createMessage`, `roots/list`, `elicitation/create`) raise an error.
|
(there is no stream to deliver them), while server-to-client requests (`sampling/createMessage`, `roots/list`, `elicitation/create`) raise an error.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
> This transport option is distinct from the sessionless [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) of MCP 2026-07-28.
|
> This transport option is distinct from the sessionless [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) of MCP 2026-07-28.
|
||||||
> The option governs how handshake-lifecycle clients are served; modern requests carry their own `_meta` envelope
|
> The option governs how handshake-lifecycle clients are served; modern requests carry their own `_meta` envelope
|
||||||
> and are served as single exchanges whether or not `stateless: true` is set.
|
> and are served as single exchanges whether or not `stateless: true` is set.
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ This mode is suitable for simple tool servers that do not need server-initiated
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
> Like [stateless mode](#stateless-mode), this option applies to handshake-lifecycle clients only.
|
> Like [stateless mode](#stateless-mode), this option applies to handshake-lifecycle clients only.
|
||||||
> Requests of the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) of MCP 2026-07-28 ignore `enable_json_response:`
|
> Requests of the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) of MCP 2026-07-28 ignore `enable_json_response:`
|
||||||
> and are served as SSE-framed single exchanges, so request-scoped notifications still reach the client.
|
> and are served as SSE-framed single exchanges, so request-scoped notifications still reach the client.
|
||||||
|
|
||||||
### Session Limits
|
### Session Limits
|
||||||
|
|
@ -224,8 +224,8 @@ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session
|
||||||
```
|
```
|
||||||
|
|
||||||
Stateless mode (`stateless: true`) retains no sessions, so neither limit applies to it. The same holds
|
Stateless mode (`stateless: true`) retains no sessions, so neither limit applies to it. The same holds
|
||||||
for requests of the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle), which never create a session;
|
for requests of the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle), which never create a session;
|
||||||
their long-lived [`subscriptions/listen`](/server/notification-subscriptions/) streams are bounded separately
|
their long-lived [`subscriptions/listen`](/server/subscriptions/) streams are bounded separately
|
||||||
by `max_listen_subscriptions:`.
|
by `max_listen_subscriptions:`.
|
||||||
|
|
||||||
### Session Ownership
|
### Session Ownership
|
||||||
|
|
@ -253,7 +253,7 @@ it also records the `Origin` header at `initialize` and rejects a later request
|
||||||
when both are present - a non-browser client that omits `Origin` (e.g. `curl` or a script) is not stopped by this check.
|
when both are present - a non-browser client that omits `Origin` (e.g. `curl` or a script) is not stopped by this check.
|
||||||
Enforcing ownership against a determined attacker requires supplying the validator with an authenticated principal.
|
Enforcing ownership against a determined attacker requires supplying the validator with an authenticated principal.
|
||||||
|
|
||||||
Requests of the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) carry no `Mcp-Session-Id` and touch no stored session,
|
Requests of the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) carry no `Mcp-Session-Id` and touch no stored session,
|
||||||
so there is no session to steal, and neither the validator nor the recorded-`Origin` comparison runs for them
|
so there is no session to steal, and neither the validator nor the recorded-`Origin` comparison runs for them
|
||||||
(the per-request `Origin` validation of the DNS rebinding protection above still applies);
|
(the per-request `Origin` validation of the DNS rebinding protection above still applies);
|
||||||
on that path, authorization is enforced per request by the deploying application.
|
on that path, authorization is enforced per request by the deploying application.
|
||||||
|
|
@ -271,4 +271,4 @@ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, max_req
|
||||||
```
|
```
|
||||||
|
|
||||||
Unlike the deployment options above, these bounds apply to both lifecycles:
|
Unlike the deployment options above, these bounds apply to both lifecycles:
|
||||||
requests of the [modern lifecycle](/server/discovery/#the-stateless-modern-lifecycle) are read through the same byte and nesting limits.
|
requests of the [modern lifecycle](/server/discover/#the-stateless-modern-lifecycle) are read through the same byte and nesting limits.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ The official Ruby SDK for the [Model Context Protocol](https://modelcontextproto
|
||||||
- Build [MCP servers](/server/) that expose tools, prompts, and resources to any MCP host
|
- Build [MCP servers](/server/) that expose tools, prompts, and resources to any MCP host
|
||||||
- Build [MCP clients](/client/) that connect to any MCP server, with automatic lifecycle negotiation and OAuth 2.1 authorization
|
- Build [MCP clients](/client/) that connect to any MCP server, with automatic lifecycle negotiation and OAuth 2.1 authorization
|
||||||
- Speak every standard transport: stdio and Streamable HTTP (including SSE), with a Rails integration
|
- Speak every standard transport: stdio and Streamable HTTP (including SSE), with a Rails integration
|
||||||
- Cover the full protocol surface: server-to-client requests, multi round-trip results, notifications, progress, logging, cancellation, completions, and pagination
|
- Cover the full protocol surface: server-to-client requests, multi round-trip requests, notifications, progress, logging, cancellation, completions, and pagination
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
|
@ -110,8 +110,8 @@ For comprehensive documentation, see:
|
||||||
- [Installation](/installation/) - installing the gem and optional feature dependencies
|
- [Installation](/installation/) - installing the gem and optional feature dependencies
|
||||||
- [Examples](/examples/) - runnable example scripts and a complete Rails application
|
- [Examples](/examples/) - runnable example scripts and a complete Rails application
|
||||||
- [Protocol Versions](/protocol-versions/) - supported versions, the era model, and client negotiation
|
- [Protocol Versions](/protocol-versions/) - supported versions, the era model, and client negotiation
|
||||||
- [Building Servers](server/) - transports, discovery, tools, prompts, resources, server-to-client requests, multi round-trip results, notifications, protocol utilities, and configuration
|
- [Building Servers](server/) - transports, discovery, tools, prompts, resources, server-to-client requests, multi round-trip requests, notifications, protocol utilities, and configuration
|
||||||
- [Building Clients](client/) - transports, lifecycle negotiation, multi round-trip results, and OAuth 2.1 authorization
|
- [Building Clients](client/) - transports, lifecycle negotiation, multi round-trip requests, and OAuth 2.1 authorization
|
||||||
- [Extensions](/extensions/) - capability extensions and MCP Apps
|
- [Extensions](/extensions/) - capability extensions and MCP Apps
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue