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 328273744b docs: MCP 2026-07-28 sampling + elicitation (TODO-328)
328273744b is described below
commit 328273744b52c4317c9f8387093464333298a5ae
Author: James Bognar <[email protected]>
AuthorDate: Mon Aug 3 20:07:23 2026 -0700
docs: MCP 2026-07-28 sampling + elicitation (TODO-328)
Document the SEP-2322 elicitation surface (ElicitRequest/ElicitResult/
ElicitAction wire beans, the ElicitSchema restricted-schema builder, and
the server/client helpers ElicitationRequests/ElicitationResponses/
ElicitationAccess) riding the existing MRTR input_required pause/resume
loop with zero mechanism changes.
Document full-fidelity SEP-2577 sampling support (CreateMessageRequest/
CreateMessageResult/SamplingMessage/ModelPreferences/ModelHint/
SamplingCapability, the new AudioContent content type, and the hand-driven
duplex exchange pattern), plus a note that Juneau does not mark the
Sampling/Roots/Logging Java types @Deprecated even though SEP-2577
deprecates them at the protocol level.
Co-authored-by: Cursor <[email protected]>
---
pages/topics/05.07.JuneauBeanMcp.md | 53 +++++++++++++++++++++++++++++--
pages/topics/11.01.JuneauRestClientMcp.md | 25 +++++++++++++++
pages/topics/11.JuneauRestServerMcp.md | 29 +++++++++++++++++
3 files changed, 105 insertions(+), 2 deletions(-)
diff --git a/pages/topics/05.07.JuneauBeanMcp.md
b/pages/topics/05.07.JuneauBeanMcp.md
index 059d6e11a5..eb1cddd219 100644
--- a/pages/topics/05.07.JuneauBeanMcp.md
+++ b/pages/topics/05.07.JuneauBeanMcp.md
@@ -332,7 +332,7 @@ Trace-context propagation (the
`traceparent`/`tracestate`/`baggage` keys above)
|---|---|
| `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 [...]
+`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 are owned by their
eventual consumers — elicitation's shape (`ElicitRequest`) is covered below in
[MCP `2026-07-28`: Elicitation](#mcp-2026-07-28-elicitation); sampling's is a
separate, fully-supported mechanism (see [MCP Sampling](#mcp-sampling) further
down). There is deliberatel [...]
### Resume fields
@@ -349,10 +349,59 @@ Both are omitted from the wire entirely when unset, so an
ordinary one-shot call
| 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. |
+| `ElicitationCapability` | An empty marker bean (serializes as `{}`), exposed
as the new nullable `elicitation` field on `ClientCapabilities`
(`getElicitation()`/`setElicitation(...)`), matching `SamplingCapability`'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.
+## MCP `2026-07-28`: Elicitation
+
+`juneau-bean-mcp-v20260728` adds the wire beans for MCP `2026-07-28` SEP-2322
elicitation: a handler can ask the end user a schema-shaped question mid-call,
riding the same Multi-Round-Trip Requests pause/resume loop described above
with zero changes to that mechanism. These beans model only the wire shape and
the schema-building helper — the server-side placement/parsing helpers are a
`juneau-rest-server-mcp-v20260728` concern, and the client-side typed accessors
are a `juneau-rest-clie [...]
+
+| Bean | Purpose |
+|---|---|
+| `ElicitRequest` | One question: `message` (the prompt shown to the end user)
and `requestedSchema` (a restricted JSON Schema `JsonMap` built via
`ElicitSchema`, below). Placed into an `input_required` pause's `inputRequests`
map under a server-assigned id. |
+| `ElicitResult` | The end user's answer: `action` (`ElicitAction`) and
`content` (a dynamic `Map<String,Object>`, present only when `action` is
`ACCEPT` — its shape is whatever `requestedSchema` described, so it is
deliberately left untyped). Echoed back keyed by the same id in the resume
call's `inputResponses`. |
+| `ElicitAction` | A closed three-value wire enum: `accept` / `decline` /
`cancel`. |
+
+### `ElicitSchema`: the restricted-schema builder
+
+Elicitation's requested-answer schema is deliberately restricted to
primitive-typed, non-nested top-level properties — no objects, arrays, or
`$ref`. `ElicitSchema` enforces this by construction: there is no
`objectField`/`arrayField` method at all, so a nested schema is a compile-time
impossibility, not a runtime-rejected one.
+
+```java
+JsonMap schema = ElicitSchema.create()
+
.stringField("email").title("Email").format("email").minLength(3).maxLength(120)
+ .integerField("age").min(0).max(150)
+ .enumField("plan", "free", "pro").enumNames("Free", "Pro")
+ .required("email", "plan")
+ .build();
+// {"type":"object","properties":{...},"required":["email","plan"]}
+```
+
+Each `xxxField(name)` call
(`stringField`/`numberField`/`integerField`/`booleanField`/`enumField`) starts
a new property and becomes the target of every subsequent per-field modifier
(`title`, `description`, `format`, `minLength`/`maxLength` — string fields only
— `min`/`max`, `defaultValue`, `enumNames`) until the next `xxxField(...)`
call. `required(String...)` is different: it's a builder-level, not per-field,
call — like every other modifier it returns `this`, so chaining continues n
[...]
+
+## MCP Sampling
+
+MCP SEP-2577 deprecates the Sampling capability (along with Roots and Logging)
at the protocol level in favor of newer interaction patterns — elicitation
(above) and the Multi-Round-Trip Requests loop it rides are this codebase's
newer, actively-recommended mechanism for interactive mid-call input. Juneau
continues to fully support Sampling, though: `juneau-bean-mcp-v20260728` ships
full-fidelity typed beans for `sampling/createMessage`, since the beans are
useful today wherever a caller [...]
+
+| Bean | Purpose |
+|---|---|
+| `CreateMessageRequest` | `sampling/createMessage`'s parameters: `messages`
(`List<SamplingMessage>`), `modelPreferences`, `systemPrompt`,
`includeContext`, `temperature`, `maxTokens`, `stopSequences`, `metadata`. |
+| `CreateMessageResult` | The result: `role` (`Role`), `content` (`Content`),
`model`, `stopReason`. |
+| `SamplingMessage` | One conversation turn: `role` (`Role`, reusing the
existing enum) and `content` (`Content` — a single block, not a list). |
+| `ModelPreferences` | Model-selection hints: `hints` (`List<ModelHint>`),
`costPriority`/`speedPriority`/`intelligencePriority` (each `Double`,
`0.0`-`1.0`). |
+| `ModelHint` | A single fuzzy model-family/name hint (`name`). |
+| `SamplingCapability` | An empty marker bean (serializes as `{}`), the typed
value of `ClientCapabilities.getSampling()`/`setSampling(...)` (replacing the
prior opaque `Map<String,Object>`). |
+
+A hand-driven sampling exchange needs no framework changes — build a
`JsonRpcRequest` with `method = McpMethods.SAMPLING_CREATE_MESSAGE` and
`params` set to a serialized `CreateMessageRequest`, dispatch it via the
existing `McpDuplexDispatcher`/`McpServerRequestHandler` seam, and decode the
handler's typed `CreateMessageResult` return value. See
[juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp#sse-and-duplex-seam)
for the duplex mechanism itself, which is entirely unchanged by t [...]
+
+### `Content` gains `AudioContent`
+
+`Content`'s polymorphic dictionary
(`TextContent`/`ImageContent`/`EmbeddedResourceContent`) gains a fourth member,
`AudioContent` (`type: "audio"`, `data`/`mimeType`, mirroring `ImageContent`'s
exact shape) — completing genuine `text`/`image`/`audio`/`resource` fidelity
for `SamplingMessage.content`/`CreateMessageResult.content` against the real
MCP sampling schema. Like the other three members of the `Content` dictionary,
`AudioContent` is general-purpose `Content` vocabulary usable any [...]
+
+### SEP-2577 and the Roots/Logging capabilities
+
+SEP-2577 groups three capabilities together — Sampling, Roots, and Logging —
but Sampling is the new surface introduced above; only the other two were
already shipping. `RootsCapability` (and
`ClientCapabilities.getRoots()`/`setRoots(...)`) and `LoggingCapability` (and
`ServerCapabilities.getLogging()`/`setLogging(...)`) remain ordinary,
fully-supported beans — SEP-2577 deprecates these capabilities at the protocol
level, but Juneau does not mark the corresponding Java types/members `@De [...]
+
## 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.01.JuneauRestClientMcp.md
b/pages/topics/11.01.JuneauRestClientMcp.md
index 7d313a77c4..cbf206afd7 100644
--- a/pages/topics/11.01.JuneauRestClientMcp.md
+++ b/pages/topics/11.01.JuneauRestClientMcp.md
@@ -25,6 +25,31 @@ The duplex loop uses the general-purpose SSE capability on
`juneau-rest-client`:
Inbound server->client requests are dispatched through
`McpServerRequestHandler` as raw `JsonRpcRequest` payloads; params remain
generic `JsonMap`/`Object` and are not typed or rewritten in the seam.
+The illustrative server-initiated method name used elsewhere in this
codebase's Javadoc/tests for this seam is `sampling/createMessage` (see
[juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-sampling)) —
elicitation, by contrast, resumes over the ordinary Multi-Round-Trip Requests
HTTP re-call path below, never over this duplex channel.
+
+## Elicitation (MCP `2026-07-28`, SEP-2322)
+
+**v2-only.** `org.apache.juneau.rest.client.mcp.v20260728.ElicitationAccess`
is a static helper for driving 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.
+
+```java
+import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.rest.client.mcp.v20260728.*;
+
+var paused = client.callRaw(McpMethods.TOOLS_CALL, new
CallToolRequest().setName("confirm").setArguments(Map.of()));
+if (ElicitationAccess.isInputRequired(paused)) {
+ var questions = ElicitationAccess.requests(paused); // Map<String,
ElicitRequest>
+ // ... show questions.get("confirm").getMessage() to the end user, collect
an answer ...
+ var answer = new
ElicitResult().setAction(ElicitAction.ACCEPT).putContent("confirm", true);
+ var completed = client.callRaw(McpMethods.TOOLS_CALL, new
CallToolRequest().setName("confirm")
+ .setRequestState(ElicitationAccess.requestState(paused))
+ .setInputResponses(ElicitationAccess.toInputResponse("confirm",
answer)));
+}
+```
+
+`ElicitationAccess` works 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.
`ElicitationAccess.toInputResponses(Map<String,ElicitResult>)` encodes se [...]
+
+See
[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#elicitation-mcp-2026-07-28-sep-2322)
for the server-side helper half of this loop.
+
## Auth seam
`McpAuthInterceptor` is the client-side auth seam. It injects `Authorization:
Bearer ...` from a supplier callback at request-init time. OAuth/OIDC flows
plug in through the supplier implementation.
diff --git a/pages/topics/11.JuneauRestServerMcp.md
b/pages/topics/11.JuneauRestServerMcp.md
index eb8105e1b9..af8a7dac2e 100644
--- a/pages/topics/11.JuneauRestServerMcp.md
+++ b/pages/topics/11.JuneauRestServerMcp.md
@@ -689,6 +689,35 @@ A `requestState` that fails to unseal — tampered,
truncated, sealed for a diff
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`).
+## Elicitation (MCP `2026-07-28`, SEP-2322)
+
+**v2-only.** Elicitation rides the Multi-Round-Trip Requests loop above with
**zero mechanism changes** — no new dispatcher hook, no new capability gate
(the existing `elicitationSupported()` gate was already elicitation-named,
since C6 was its intended concrete consumer from the start), no new `BeanStore`
bean type. Two static helper classes in
`org.apache.juneau.rest.server.mcp.v20260728` add typed convenience on top of
the plain `Map<String,Object>` contract `McpInputRequiredSignal`/` [...]
+
+| Helper | Purpose |
+|---|---|
+| `ElicitationRequests` | Builds an `McpInputRequiredSignal` from one or more
typed
[`ElicitRequest`](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-elicitation)s,
serializing each to the `Map`-shaped value the signal's constructor requires. |
+| `ElicitationResponses` | Parses typed
[`ElicitResult`](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-elicitation)s back
out of an `McpMrtrResumeContext`'s `inputResponses()`. |
+
+```java
+import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.rest.server.mcp.v20260728.*;
+
+// inside McpToolHandler.call(...)
+var resume = ctx.getBean(McpMrtrResumeContext.class);
+if (resume.isEmpty()) {
+ var question = new ElicitRequest()
+ .setMessage("Proceed with deletion?")
+
.setRequestedSchema(ElicitSchema.create().booleanField("confirm").title("Confirm").build());
+ throw ElicitationRequests.of("confirm", question, "my-continuation");
+}
+var answer = ElicitationResponses.get(resume.get(), "confirm");
+// answer.getAction() is ACCEPT / DECLINE / CANCEL; answer.getContent() is
populated only on ACCEPT
+```
+
+Both helpers support the multi-question case identically:
`ElicitationRequests.of(Map<String,ElicitRequest>, continuation)` places
several questions in one round, and `ElicitationResponses.all(resume.get())`
returns every answer keyed the same way — no extra round trip is needed to ask
(or answer) more than one question at once.
+
+See
[juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-elicitation)
for `ElicitRequest`/`ElicitResult`/`ElicitAction`/`ElicitSchema`'s wire shapes
and the `ElicitSchema` builder API in full.
+
## 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.