[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
1 changed files with 55 additions and 45 deletions
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.
|
||||
Loading…
Reference in a new issue