This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new 953dc29d7b MCP: document the 2026-07-28 MRTR / input_required surface
(TODO-318)
953dc29d7b is described below
commit 953dc29d7b4f857673f4c4ebf787ba527b422390
Author: James Bognar <[email protected]>
AuthorDate: Mon Aug 3 09:45:38 2026 -0700
MCP: document the 2026-07-28 MRTR / input_required surface (TODO-318)
Documents InputRequiredResult, the free-form inputRequests map,
requestState,
inputResponses, and the elicitation client capability (no dedicated MRTR
flag),
plus the server dispatch surface: throw-to-pause McpInputRequiredSignal, the
BeanStore-injected resume/capability contexts, RequestStateCodec /
AeadRequestStateCodec, McpMrtrConfig, the three error codes, and the note
that
MRTR is synchronous request/response, not SSE.
Co-authored-by: Cursor <[email protected]>
---
pages/topics/05.07.JuneauBeanMcp.md | 31 ++++++++++++++++
pages/topics/11.JuneauRestServerMcp.md | 64 ++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)
diff --git a/pages/topics/05.07.JuneauBeanMcp.md
b/pages/topics/05.07.JuneauBeanMcp.md
index f41750a501..059d6e11a5 100644
--- a/pages/topics/05.07.JuneauBeanMcp.md
+++ b/pages/topics/05.07.JuneauBeanMcp.md
@@ -322,6 +322,37 @@ A representative `tools/call` round trip showing the full
`_meta` shape on both
Trace-context propagation (the `traceparent`/`tracestate`/`baggage` keys
above) is a REST-server-adapter behavior, not a bean-level one — see
[Trace-Context
Propagation](/docs/topics/JuneauRestServerMcp#trace-context-propagation-mcp-2026-07-28)
on the `juneau-rest-server-mcp` topic and
[Observability](/docs/topics/RestServerObservability) for the full mechanism.
+## MCP `2026-07-28`: Multi-Round-Trip Requests
+
+`juneau-bean-mcp-v20260728` adds the wire beans for MCP `2026-07-28` SEP-2322
Multi-Round-Trip Requests: a `tools/call`, `prompts/get`, or `resources/read`
handler can *pause* to ask the caller for more input, and the caller *resumes*
by re-sending the same request with its answers. These beans model only the
wire shape — the server-side pause/resume dispatch, the sealing of
`requestState`, and capability gating are `juneau-rest-server-mcp-v20260728`
concerns, covered in [juneau-rest-ser [...]
+
+### Pause result
+
+| Bean | Purpose |
+|---|---|
+| `InputRequiredResult` | A `Result<InputRequiredResult>` subtype whose
constructor pins `resultType` to `"input_required"`. Carries a
server-assigned-id-keyed `inputRequests` (`Map<String,JsonMap>`) and/or an
opaque `requestState` (`String`). Its `validate()` method enforces the
pinned-schema invariant that **at least one of the two** is present. |
+
+`inputRequests` values are **free-form and lossless**: each entry is a raw
`JsonMap` sub-request carried to the wire byte-for-byte, with no synthetic
`{type, payload}` envelope — the concrete sub-request shapes (sampling,
elicitation) are owned by future consumers and out of scope here. There is
deliberately **no** dedicated per-entry bean; the map value is a bare
`JsonMap`, mirroring the equally free-form request-side `inputResponses`.
`requestState` is opaque ciphertext to a client — m [...]
+
+### Resume fields
+
+`CallToolRequest`, `GetPromptRequest`, and `ReadResourceRequest` each gain two
optional, nullable fields for the resume leg:
+
+| Field | Type | Purpose |
+|---|---|---|
+| `requestState` | `String` | The opaque token echoed back from a prior
`InputRequiredResult`, identifying the paused operation to the server. |
+| `inputResponses` | `Map<String,Object>` | The caller's collected answers,
keyed by the same ids the pause's `inputRequests` used. Free-form, mirroring
`inputRequests`. |
+
+Both are omitted from the wire entirely when unset, so an ordinary one-shot
call is byte-identical to before this addition.
+
+### Capability
+
+| Bean | Purpose |
+|---|---|
+| `ElicitationCapability` | An empty marker bean (serializes as `{}`), exposed
as the new nullable `elicitation` field on `ClientCapabilities`
(`getElicitation()`/`setElicitation(...)`), matching `RootsCapability`'s
presence-checkable shape. |
+
+**There is no dedicated `input_required` capability flag.** Gating reuses the
existing `elicitation` client capability: a client that advertises
`clientCapabilities.elicitation` is signalling it can handle an
`input_required` pause. This is a deliberate divergence from a separate
per-feature flag — the pinned `2026-07-28` schema defines no such flag, so none
is modelled here.
+
## Related Modules
- **[juneau-bean-jsonrpc](/docs/topics/JuneauBeanJsonRpc)** — The
revision-neutral JSON-RPC 2.0 envelope beans that carry MCP traffic on the wire.
diff --git a/pages/topics/11.JuneauRestServerMcp.md
b/pages/topics/11.JuneauRestServerMcp.md
index 7c9eb9ca72..eb8105e1b9 100644
--- a/pages/topics/11.JuneauRestServerMcp.md
+++ b/pages/topics/11.JuneauRestServerMcp.md
@@ -625,6 +625,70 @@ Because a dated MCP adapter always answers over HTTP `200`
— even for a JSON-R
All of the above attribute names and span-naming choices are pinned to a
specific, **experimental** OpenTelemetry GenAI semantic-conventions snapshot —
see [Observability — Micrometer +
OpenTelemetry](/docs/topics/RestServerObservability#mcp-trace-context-propagation-2026-07-28)
for the exact pin, the full attribute reference, and the neutral seam these
classes are built on.
+## Multi-Round-Trip Requests (MCP `2026-07-28`, SEP-2322)
+
+**v2-only.** The `2026-07-28` adapter
(`org.apache.juneau.rest.server.mcp.v20260728`) adds server-side support for
SEP-2322 Multi-Round-Trip Requests: a handler for `tools/call`, `prompts/get`,
or the **exact-resource** `resources/read` path can *pause* to ask the caller
for more input, returning an
[`InputRequiredResult`](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-multi-round-trip-requests);
the caller *resumes* by re-sending the same request with its answers plus the
echoed `requestStat [...]
+
+The resource-*template* read path, `completion/complete`, and the
list/discover/ping paths are **not** MRTR-wired — a pause signal thrown from
one of them is not caught and surfaces as the `-32603` internal-error
fail-safe, by design.
+
+### Pausing from a handler
+
+A handler signals a pause by throwing `McpInputRequiredSignal` from its
ordinary `call`/`get`/`read` body — a **v2-only unchecked exception**, not an
`McpException` (it becomes a *successful* `input_required` result, not a
JSON-RPC error). This throw-to-pause mechanism is a v2-adapter addition; the
Phase 0 design deliberately kept it off the neutral core, so no
revision-neutral type changed:
+
+```java
+import org.apache.juneau.rest.server.mcp.v20260728.*;
+
+// inside McpToolHandler.call(...)
+throw new McpInputRequiredSignal(
+ Map.of("q1", Map.of("type", "elicitation")), // inputRequests: at least
one, free-form sub-requests
+ "my-continuation"); // opaque, handler-owned
continuation (may be null)
+```
+
+The dispatcher catches the signal, seals the continuation (plus the method, a
1-based round counter, and an expiry) into an opaque `requestState`, and
returns an `InputRequiredResult` carrying the handler's `inputRequests` map and
that `requestState`.
+
+### Resuming: the two BeanStore-injected handler contexts
+
+On a resume call the same handler runs again; two immutable beans in the
per-request `BeanStore` let it detect and react to the round:
+
+- **`McpMrtrResumeContext`** — stashed **only** on a resume call. Carries
`continuation()` (the decoded value, as generic JSON — use
`continuationAs(Class)` to recover a typed view) and `inputResponses()` (the
caller's answers). A handler detects resume via
`ctx.getBean(McpMrtrResumeContext.class)` returning a present `Optional`;
absent means a first-round call.
+- **`McpMrtrCapabilityContext`** — stashed on **every** in-scope call
(first-round and resume), exposing `elicitationSupported()` so a handler can
pre-check and degrade gracefully (return a normal result or a domain error)
instead of throwing a pause into a client that can't handle it.
+
+Only the sealed `continuation` is tamper-evident across rounds; the per-round
`arguments` are re-sent by the client and fully client-controlled, so anything
a handler must trust across rounds belongs *inside* the continuation, never
re-read from the arguments.
+
+### Sealing `requestState`: the codec SPI
+
+`RequestStateCodec` is the pluggable seal/unseal SPI (mirroring the
client-side `McpResponseCache` "SPI + built-in default" shape). The built-in
default, `AeadRequestStateCodec`, is **AES-256-GCM with a per-process ephemeral
key**: it is deliberately **not restart-durable and not shareable across
process instances** — a token sealed by one instance can never be unsealed by
another, or by the same process after a restart. Operators who need
cross-restart or multi-instance resumption suppl [...]
+
+### Configuring MRTR: `McpMrtrConfig`
+
+`McpMrtrConfig` is the adapter-owned configuration bean, placed exactly like
`McpCacheConfig` (v2-adapter-only, never on the neutral core). It carries the
`RequestStateCodec`, a `requestState` TTL (default 5 minutes), and a max-rounds
cap (default 10). Supply it via the servlet's `createMrtrConfig()` hook (or the
mixin's `mrtrConfig()`):
+
+```java
+@Override
+protected McpMrtrConfig createMrtrConfig() {
+ return new McpMrtrConfig()
+ .setCodec(new AeadRequestStateCodec()) // or a shared/rotating-key
implementation
+ .setTtlMs(5 * 60 * 1000L)
+ .setMaxRounds(10);
+}
+```
+
+The config is memoized at binding time and treated as read-only afterward
(each `AeadRequestStateCodec` holds a distinct random key, so exactly one
instance must be published per binding).
+
+### Error codes
+
+Alongside the standard JSON-RPC codes, MRTR dispatch adds three v2-only codes
(constants on `org.apache.juneau.rest.server.mcp.v20260728.McpRevision`):
+
+| Code | Constant | Condition |
+|---|---|---|
+| `-32021` | `CODE_MISSING_REQUIRED_CLIENT_CAPABILITY` | A handler tried to
pause, but the request's `_meta.clientCapabilities` did not advertise
`elicitation`. Gating reuses the existing `elicitation` client capability —
there is no dedicated `input_required` capability flag. |
+| `-32022` | `CODE_REQUEST_STATE_EXPIRED` | The echoed `requestState` unsealed
successfully but its sealed expiry has passed. |
+| `-32023` | `CODE_MAX_ROUNDS_EXCEEDED` | The resume chain exceeded the
configured max-rounds cap. |
+
+A `requestState` that fails to unseal — tampered, truncated, sealed for a
different method, or otherwise invalid — is the standard `-32602`
invalid-params code (the codec returns empty rather than throwing; the
dispatcher maps that to `-32602`).
+
+See
[juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-multi-round-trip-requests)
for the wire-bean side (`InputRequiredResult`, the
`inputResponses`/`requestState` request fields, and `ElicitationCapability`).
+
## Related Modules
- **[juneau-bean-mcp-v20250618](/docs/topics/JuneauBeanMcp)** — The
`2025-06-18` wire beans consumed by the adapter module, plus the
revision-neutral JSON-RPC envelope beans (`juneau-bean-jsonrpc`) both modules
build on.