ruby-sdk/docs/_server/mrtr.md
Koichi ITO c9d36b04bf [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.
2026-08-22 18:07:38 +09:00

87 lines
4 KiB
Markdown

---
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.