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 36b38d776b docs: MCP client-side MRTR auto-resume ergonomics (TODO-326)
36b38d776b is described below

commit 36b38d776b6c5c683db1a4071c178a3a5cb9d2d7
Author: James Bognar <[email protected]>
AuthorDate: Wed Aug 5 18:05:35 2026 -0700

    docs: MCP client-side MRTR auto-resume ergonomics (TODO-326)
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/topics/11.03.JuneauMcpRecipes.md    | 21 +++++++++++++++++++++
 pages/topics/11.05.JuneauRestClientMcp.md | 30 ++++++++++++++++++++++++++++--
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/pages/topics/11.03.JuneauMcpRecipes.md 
b/pages/topics/11.03.JuneauMcpRecipes.md
index 4942e7f7c6..f03d77e76e 100644
--- a/pages/topics/11.03.JuneauMcpRecipes.md
+++ b/pages/topics/11.03.JuneauMcpRecipes.md
@@ -231,6 +231,27 @@ McpToolHandler confirmDeleteTool = McpToolHandler.of(
 
 This requires no special server config beyond a v2 servlet/mixin — MRTR 
support is on by default (see `McpOptions.mrtr(Consumer<McpMrtrConfig>)` in the 
[server reference](/docs/topics/JuneauRestServerMcp) to customize the codec, 
TTL, or round cap).
 
+### Client side: answering the elicitation
+
+On the client, `McpClient.callToolWithElicitation(...)` (and its 
`getPromptWithElicitation`/`readResourceWithElicitation` siblings) drives the 
whole pause/resume loop for you — supply an `McpElicitationHandler` and get 
back the terminal typed result. No hand-managed `requestState`/`inputResponses`:
+
+```java
+import java.util.*;
+
+import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.rest.client.mcp.v20260728.*;
+
+var result = client.callToolWithElicitation("confirm-delete", Map.of(), 
requests -> {
+    var answers = new LinkedHashMap<String,ElicitResult>();
+    requests.forEach((id, req) ->        // req.getMessage() → "Proceed with 
deletion?"
+        answers.put(id, new 
ElicitResult().setAction(ElicitAction.ACCEPT).putContent("confirm", true)));
+    return answers;
+});
+// result is a CallToolResult; the input_required round(s) were answered and 
resumed transparently.
+```
+
+The handler is invoked once per `input_required` pause and may answer several 
requests in one round. Return an `ElicitResult` with 
`ElicitAction.DECLINE`/`CANCEL` to refuse (the server decides the outcome). The 
loop is bounded by `McpClient.DEFAULT_MAX_ELICITATION_ROUNDS` (override via a 
`maxRounds` argument); exceeding it throws `McpElicitationLimitException`. For 
full manual control, `callRaw(...)` + 
`ElicitationRequests`/`ElicitationResponses` remain available — see the [client 
refere [...]
+
 ## MRTR key management (v2 only)
 
 By default, MRTR `requestState` tokens are sealed under a random key generated 
once per process (`EphemeralKeyProvider`, the zero-config default beneath 
`AeadRequestStateCodec`) — deliberately **not shareable across instances and 
not durable across a restart** (see the [server 
reference](/docs/topics/JuneauRestServerMcp#key-management-keyprovider)). A 
load-balanced deployment, where RESUME can land on a different node than the 
PAUSE, needs a shared sealing key instead.
diff --git a/pages/topics/11.05.JuneauRestClientMcp.md 
b/pages/topics/11.05.JuneauRestClientMcp.md
index a6ef98a67c..dd73b00d8f 100644
--- a/pages/topics/11.05.JuneauRestClientMcp.md
+++ b/pages/topics/11.05.JuneauRestClientMcp.md
@@ -31,7 +31,33 @@ The illustrative server-initiated method name used elsewhere 
in this codebase's
 
 ## Elicitation (MCP `2026-07-28`, SEP-2322)
 
-**v2-only.** Two static helper classes in 
`org.apache.juneau.rest.client.mcp.v20260728` drive elicitation over 
`McpClient.callRaw(...)`'s raw `Map<String,Object>` result — the typed 
`callTool`/`getPrompt`/`readResource` methods cannot represent a paused 
`input_required` result or set `requestState`/`inputResponses` on a resume 
call, so a caller must go through `callRaw(...)` for the resumable leg of the 
loop:
+**v2-only.** A `tools/call`, `prompts/get`, or `resources/read` handler can 
pause with an `input_required` result to elicit more input (Multi-Round-Trip 
Requests). The client offers two ways to consume that loop: an ergonomic 
**auto-resume** helper for the common case, and a raw **manual** path as the 
escape hatch.
+
+### Auto-resume (recommended)
+
+`McpClient.callToolWithElicitation(...)`, `getPromptWithElicitation(...)`, and 
`readResourceWithElicitation(...)` take an `McpElicitationHandler` and drive 
the whole loop for you: they detect each `input_required` pause, decode that 
round's requests, invoke your handler for the answers, echo them back with the 
carried `requestState`, and repeat until a terminal result — which they return 
already decoded into the same typed result bean 
(`CallToolResult`/`GetPromptResult`/`ReadResourceResu [...]
+
+```java
+import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.rest.client.mcp.v20260728.*;
+
+// One call. The handler is invoked once per input_required pause and answers 
that round's
+// requests (there may be more than one), keyed by the same server-assigned 
ids.
+var result = client.callToolWithElicitation("confirm", Map.of(), requests -> {
+    var answers = new LinkedHashMap<String,ElicitResult>();
+    requests.forEach((id, req) ->        // req.getMessage() is the prompt to 
show the end user
+        answers.put(id, new 
ElicitResult().setAction(ElicitAction.ACCEPT).putContent("confirm", true)));
+    return answers;                      // Map<String,ElicitResult>
+});
+```
+
+- **Multiple requests per round** — a round's `requests` map may hold more 
than one entry; the handler answers all of them and returns one keyed map.
+- **Decline / cancel** — return an `ElicitResult` with `ElicitAction.DECLINE` 
or `ElicitAction.CANCEL` for a request. Refusals are echoed back like any other 
answer (the *server* decides the terminal outcome of a refused elicitation); 
the client does not short-circuit locally.
+- **Bounded** — the loop is capped at 
`McpClient.DEFAULT_MAX_ELICITATION_ROUNDS` (8) rounds; a `maxRounds` overload 
sets your own bound. A server that keeps pausing past the cap throws 
`McpElicitationLimitException` (with `getMaxRounds()`) rather than looping 
forever.
+
+### Manual resume (escape hatch)
+
+For full control — or to resume a call you issued through `callRaw(...)` 
yourself — two static helper classes in 
`org.apache.juneau.rest.client.mcp.v20260728` work directly on `callRaw`'s raw 
`Map<String,Object>` result:
 
 | Helper | Purpose |
 |---|---|
@@ -55,7 +81,7 @@ if (ElicitationRequests.isInputRequired(paused)) {
 }
 ```
 
-Both helpers work at the raw map/JSON level rather than exposing typed 
overloads per concrete request bean, because 
`CallToolRequest`/`GetPromptRequest`/`ReadResourceRequest` share no common 
"has-`inputResponses`-and-`requestState`" interface — a caller resuming a 
specific one of the three makes its own final 
`.setInputResponses(...).setRequestState(...)` call on the concrete bean it 
already knows it holds. 
`ElicitationResponses.toInputResponses(Map<String,ElicitResult>)` encodes 
several [...]
+Both helpers work at the raw map/JSON level rather than exposing typed 
overloads per concrete request bean, because 
`CallToolRequest`/`GetPromptRequest`/`ReadResourceRequest` share no common 
"has-`inputResponses`-and-`requestState`" interface — a caller resuming a 
specific one of the three makes its own final 
`.setInputResponses(...).setRequestState(...)` call on the concrete bean it 
already knows it holds. 
`ElicitationResponses.toInputResponses(Map<String,ElicitResult>)` encodes 
several [...]
 
 See 
[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#elicitation-mcp-2026-07-28-sep-2322)
 for the server-side helper half of this loop.
 

Reply via email to