You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/08/06 11:49:30 UTC
(juneau) branch docs updated: docs: MCP example module (juneau-examples-mcp) + v2 adoption conveniences
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 ef3c6e9d03 docs: MCP example module (juneau-examples-mcp) + v2 adoption conveniences
ef3c6e9d03 is described below
commit ef3c6e9d035d58e101f0f36a6f525dc961189786
Author: James Bognar <ja...@apache.org>
AuthorDate: Thu Aug 6 04:49:23 2026 -0700
docs: MCP example module (juneau-examples-mcp) + v2 adoption conveniences
Co-authored-by: Cursor <cu...@cursor.com>
---
artifact-paths.json | 2 ++
pages/release-notes/10.0.0.md | 27 +++++++++++++++++++++++++++
pages/topics/11.03.JuneauMcpRecipes.md | 20 ++++++++++++++++----
pages/topics/11.04.JuneauRestServerMcp.md | 8 +++++++-
pages/topics/11.05.JuneauRestClientMcp.md | 4 ++++
5 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/artifact-paths.json b/artifact-paths.json
index 30f2a7b50b..8a0787658e 100644
--- a/artifact-paths.json
+++ b/artifact-paths.json
@@ -22,6 +22,7 @@
"juneau-distrib": "juneau-distrib",
"juneau-examples": "juneau-examples",
"juneau-examples-core": "juneau-examples/juneau-examples-core/build-overlay",
+ "juneau-examples-mcp": "juneau-examples/juneau-examples-mcp/build-overlay",
"juneau-integration-tests": "juneau-integration-tests",
"juneau-marshall": "juneau-core/juneau-marshall",
"juneau-marshall-rdf": "juneau-core/juneau-marshall-rdf",
@@ -45,6 +46,7 @@
"juneau-rest-client-classic": "juneau-rest/juneau-rest-client-classic",
"juneau-rest-client-jetty": "juneau-rest/juneau-rest-client-jetty",
"juneau-rest-client-mcp": "juneau-rest/juneau-rest-client-mcp",
+ "juneau-rest-client-mcp-auth": "juneau-rest/juneau-rest-client-mcp-auth",
"juneau-rest-client-mcp-v20250618": "juneau-rest/juneau-rest-client-mcp-v20250618",
"juneau-rest-client-mcp-v20260728": "juneau-rest/juneau-rest-client-mcp-v20260728",
"juneau-rest-client-okhttp": "juneau-rest/juneau-rest-client-okhttp",
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 40f427abfa..32942e6cfd 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -987,6 +987,33 @@ See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#replay-protection-
See the [OAuth 2.1 recipe](/docs/topics/JuneauMcpRecipes#securing-an-mcp-client-and-server-with-oauth-21-v2-only) (end-to-end client + server), the [DCR + step-up recipes](/docs/topics/JuneauMcpRecipes#dynamic-client-registration-and-step-up-v2-only), the [juneau-rest-client-mcp auth reference](/docs/topics/JuneauRestClientMcp#oauth-21-authorization-juneau-rest-client-mcp-auth), and the [juneau-rest-server-mcp resource-server reference](/docs/topics/JuneauRestServerMcp#oauth-21-resource- [...]
+### MCP `2026-07-28` — first-party runnable example module (`juneau-examples-mcp`) (TODO-312)
+
+A new example module, `juneau-examples-mcp` (under `juneau-examples/`, alongside `juneau-examples-core`), ships the single highest-value adoption artifact for the now-complete MCP framework above: a **runnable, copy-me reference implementation** of both the MCP server and client sides, built against the `2026-07-28` revision. One cohesive in-memory "notes" service (`NoteStore`, a `title -> body` map) is exercised end to end by every headline surface:
+
+- **tool** — `publishNote(title, body)`, which stores a note and pushes a `resources/updated` change to subscribers.
+- **tool + elicitation/MRTR** — `deleteNote(title)`, which pauses with `input_required` to confirm before resuming to delete.
+- **prompt + completion** — `summarize(title)`, whose `title` argument offers `completion/complete` suggestions from existing note titles.
+- **resource** — the fixed `note:///index` resource, listing all note titles.
+- **resource template + completion** — `note:///{title}`, reading one note by title, with the same title-completion behavior on its `{title}` variable.
+- **subscription (SEP-2575)** — `subscriptions/listen` held-open SSE, delivering the `resources/updated` change `publishNote` fires.
+
+`ExampleMcpServer` (a v2 `McpRestServlet`) registers every surface above; `ExampleServer` is an embedded-Jetty launcher (`Microservice` + `JettyConfiguration`, `main(...)` plus a `start(port)` handle); `ExampleClient` is a guided, numbered `McpClient` walkthrough printing each step's request/response top-to-bottom like a tutorial; and a Spring Boot variant (`SpringExampleMcpServer`/`SpringExampleApplication`) uses `SpringMcpRestServlet` to resolve a Spring-managed `GreetingService` throu [...]
+
+See the [MCP Recipes](/docs/topics/JuneauMcpRecipes#full-example-juneau-examples-mcp), [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp), and [juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp) topics for pointers to the module, and the module's own `README.md` for run instructions.
+
+### MCP `2026-07-28` — client/server adoption convenience methods
+
+Four small, additive convenience methods land alongside the example module above, each replacing a small piece of repetitive code the example (and real-world adopters) would otherwise repeat at every call site. None deprecates or replaces the lower-level API it sits on top of.
+
+- **`CallToolResult.firstText()`** (`juneau-bean-mcp-v20260728`) — returns the text of the first `TextContent` block found while scanning the result's `content` list in order, or `null` if the list is empty/unset or contains no `TextContent` block. Kills the `((TextContent) result.getContent().get(i)).getText()` cast-and-scan a caller who only wants the tool's text result would otherwise repeat everywhere.
+- **`McpClient.callToolText(String name, Map<String,Object> arguments)`** (`juneau-rest-client-mcp-v20260728`) — convenience for `callTool(...)` followed by `CallToolResult.firstText()`; null-safe if `callTool(...)` itself returns a `null` result.
+- **`ServerDiscoverResult.getServerInfo()`** (`juneau-bean-mcp-v20260728`) — null-safe shortcut for `getMeta() == null ? null : getMeta().getServerInfo()`, so a caller doesn't have to null-check the intermediate `_meta` envelope just to reach the server's `Implementation` identity. `@BeanIgnore`d so it does not add a second, top-level `serverInfo` member to the `server/discover` wire format.
+- **`McpMrtrResumeContext.continuationAsString()`** (`juneau-rest-server-mcp-v20260728`) — shorthand for `continuationAs(String.class)`, for the common case of a plain `String` continuation.
+- **`ElicitationResponses.getBoolean(ctx, id, field)` / `getString(ctx, id, field)`** (`juneau-rest-server-mcp-v20260728`) — decline/omission-safe typed reads of a single field from an elicitation answer: a missing id, a missing field, or a non-`ACCEPT` action (declined/cancelled) all resolve to a safe default (`false` for `getBoolean`, `null` for `getString`) instead of requiring the caller to null-check `get(...)` and its content map by hand.
+
+See the [confirm-delete recipe](/docs/topics/JuneauMcpRecipes#elicitation--multi-round-trip-requests-v2-only) and the [client recipe](/docs/topics/JuneauMcpRecipes#client-call-read-and-handle-errors) in MCP Recipes, the [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#elicitation-mcp-2026-07-28-sep-2322) elicitation reference, and the [juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp) client reference for these methods in context.
+
### MCP `2025-06-18` — resource-not-found error code corrected to `-32002` (TODO-332)
`2025-06-18`-only bug fix. A `resources/read` for a URI the server does not serve now returns the JSON-RPC error code **`-32002`** ("Resource not found") — the `2025-06-18` spec's dedicated missing-resource code — instead of the previous generic **`-32601`** ("method not found"). This was one of the four "not found" kinds that the re-layered `2025-06-18` `McpRevision` initially collapsed onto `-32601` (see the `juneau-rest-server-mcp-v20250618` module notes above); resource-not-found is [...]
diff --git a/pages/topics/11.03.JuneauMcpRecipes.md b/pages/topics/11.03.JuneauMcpRecipes.md
index f03d77e76e..5613f58f42 100644
--- a/pages/topics/11.03.JuneauMcpRecipes.md
+++ b/pages/topics/11.03.JuneauMcpRecipes.md
@@ -5,6 +5,7 @@ slug: JuneauMcpRecipes
Copy-pasteable snippets for common MCP tasks, targeting revision `2026-07-28` unless noted. Each recipe is self-contained: assume `config` is an `McpServerConfig` you're building up (as in the [Quickstart](/docs/topics/JuneauMcpQuickstart)), and paste imports as shown.
+- [Full example: `juneau-examples-mcp`](#full-example-juneau-examples-mcp)
- [Writing a tool](#writing-a-tool)
- [Writing a prompt](#writing-a-prompt)
- [Serving a resource](#serving-a-resource)
@@ -17,6 +18,10 @@ Copy-pasteable snippets for common MCP tasks, targeting revision `2026-07-28` un
- [Securing an MCP client and server with OAuth 2.1 (v2 only)](#securing-an-mcp-client-and-server-with-oauth-21-v2-only)
- [Dynamic client registration and step-up (v2 only)](#dynamic-client-registration-and-step-up-v2-only)
+## Full example: `juneau-examples-mcp`
+
+Before diving into individual recipes, consider starting from [`juneau-examples-mcp`](https://github.com/apache/juneau/tree/master/juneau-examples/juneau-examples-mcp) — a complete, runnable "notes" service exercising every recipe on this page (and more) in one small, copy-me Maven module targeting revision `2026-07-28`: a `publishNote` tool, a `deleteNote` tool that walks a full elicitation/MRTR confirm-then-resume round-trip, a `summarize` prompt with argument completion, a `note:///in [...]
+
## Writing a tool
**Raw, via `McpToolHandler.of(...)`** — full control over the input schema:
@@ -216,8 +221,7 @@ McpToolHandler confirmDeleteTool = McpToolHandler.of(
var question = new ElicitRequest().setMessage("Proceed with deletion?").setRequestedSchema(schema);
throw ElicitationRequests.of("confirm", question, "confirm-delete-continuation");
}
- var answer = ElicitationResponses.get(resume.get(), "confirm");
- if (answer == null || answer.getAction() != ElicitAction.ACCEPT || ! Boolean.TRUE.equals(answer.getContent().get("confirm")))
+ if (! ElicitationResponses.getBoolean(resume.get(), "confirm", "confirm"))
return McpToolOutcome.text("Deletion cancelled.");
// ... perform the deletion here ...
return McpToolOutcome.text("Deleted.");
@@ -226,8 +230,8 @@ McpToolHandler confirmDeleteTool = McpToolHandler.of(
```
- On the first round, `ctx.getBean(McpMrtrResumeContext.class)` is empty, so the handler throws `ElicitationRequests.of(id, request, continuation)` — an `McpInputRequiredSignal` (a `RuntimeException`) that the dispatcher turns into an `input_required` pause.
-- The client answers, then re-sends the call; on that round, `McpMrtrResumeContext` is present, and `ElicitationResponses.get(ctx, id)` decodes the typed `ElicitResult` keyed by the same `id` you posed the question under.
-- `ElicitResult.getContent()` is populated only when `getAction() == ElicitAction.ACCEPT`; a `DECLINE`/`CANCEL` answer carries no content.
+- The client answers, then re-sends the call; on that round, `McpMrtrResumeContext` is present. `ElicitationResponses.getBoolean(ctx, id, field)` reads a single boolean field decline/omission-safely in one call — a missing id, a missing field, or a non-`ACCEPT` action (declined/cancelled) all resolve to `false`, so there's no need to null-check `get(...)` and its content map by hand for a plain confirm/deny question. `ElicitationResponses.getString(ctx, id, field)` is the equivalent for [...]
+- If a handler needs to carry state across rounds (e.g. which record to delete), stash it in the `continuation` argument to `ElicitationRequests.of(...)` and recover it on resume via `resume.get().continuationAsString()` (or `continuationAs(Class)` for a non-`String` shape) — see [MRTR resume contexts](/docs/topics/JuneauRestServerMcp#resuming-the-two-beanstore-injected-handler-contexts) in the server reference.
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).
@@ -415,12 +419,18 @@ import org.apache.juneau.bean.jsonrpc.McpException;
import org.apache.juneau.rest.client.mcp.v20260728.McpClient;
try (var client = McpClient.connect("http://localhost:8080/mcp")) {
+ System.out.println("Connected to: " + client.discoveredServer().getServerInfo());
+
var tools = client.listTools();
System.out.println(tools);
var callResult = client.callTool("weather", Map.of("city", "Seattle"));
System.out.println(callResult.getContent());
+ // Just want the tool's text? callToolText(...) is callTool(...) + CallToolResult.firstText() in one
+ // call -- no manual ((TextContent) callResult.getContent().get(0)).getText() cast-and-scan needed.
+ System.out.println(client.callToolText("weather", Map.of("city", "Seattle")));
+
var resource = client.readResource("file:///readme.txt");
System.out.println(resource.getContents());
} catch (McpException e) {
@@ -432,6 +442,8 @@ try (var client = McpClient.connect("http://localhost:8080/mcp")) {
`McpClient` is `Closeable`, so try-with-resources closes the underlying transport when the block exits, on either the success or error path. Every typed call (`callTool`, `getPrompt`, `readResource`, `listTools`, `listPrompts`, `listResources`, `listResourceTemplates`, `complete`) throws `McpException` for a JSON-RPC error response and `IOException` for a transport/(de)serialization failure — there's no single unified exception type, so a caller that needs both should catch both, as shown.
+`ServerDiscoverResult.getServerInfo()` and `McpClient.callToolText(...)` are additive convenience methods: the former is a null-safe shortcut to the server's `Implementation` identity off the `server/discover` result that `connect(...)` (and `serverDiscover()`) already cache, and the latter folds `callTool(...)` + `CallToolResult.firstText()` into one call for the common case of wanting just a tool's rendered text. Neither replaces the lower-level `getContent()`/`getMeta()` access shown [...]
+
## Securing an MCP client and server with OAuth 2.1 (v2 only)
This recipe walks both sides of an OAuth 2.1 / MCP `2026-07-28` deployment as one story: turn a v2 MCP endpoint into a protected resource server, then acquire a token on the client and call it. Authorization is **v2-only** and **off by default** — an endpoint that doesn't opt in behaves exactly as before.
diff --git a/pages/topics/11.04.JuneauRestServerMcp.md b/pages/topics/11.04.JuneauRestServerMcp.md
index 987735473d..de3c122c73 100644
--- a/pages/topics/11.04.JuneauRestServerMcp.md
+++ b/pages/topics/11.04.JuneauRestServerMcp.md
@@ -6,6 +6,10 @@ slug: JuneauRestServerMcp
New to MCP in Juneau? Start with the [MCP Quickstart](/docs/topics/JuneauMcpQuickstart) and [MCP Recipes](/docs/topics/JuneauMcpRecipes) — this page is the deep reference for lookup once you're past the basics.
+:::tip Want a runnable, end-to-end example instead?
+[`juneau-examples-mcp`](https://github.com/apache/juneau/tree/master/juneau-examples/juneau-examples-mcp) is a complete, copy-me Maven module exercising every major surface on this page — tools, elicitation/MRTR, prompts + completion, resources + resource templates, and subscriptions — against a tiny in-memory "notes" service, targeting revision `2026-07-28`. It ships as a downloadable project zip in the Apache release. See [MCP Recipes](/docs/topics/JuneauMcpRecipes#full-example-juneau- [...]
+:::
+
`juneau-rest-server-mcp` is a **revision-neutral core** for exposing a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) JSON-RPC endpoint on `juneau-rest-server`. On its own it has zero compile-time knowledge of any MCP protocol revision; a protocol revision is supplied by a separate adapter module. Today that's **`juneau-rest-server-mcp-v20250618`**, which binds the core to the `2025-06-18` wire beans in [`juneau-bean-mcp-v20250618`](/docs/topics/JuneauBeanMcp). This pag [...]
## Overview
@@ -763,7 +767,7 @@ The dispatcher catches the signal, seals the continuation (plus the method, a 1-
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.
+- **`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. For the common case of a `String` continuation, `continuationAsString()` is a shorthand for `continuationAs(String.class)`.
- **`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.
@@ -877,6 +881,8 @@ var answer = ElicitationResponses.get(resume.get(), "confirm");
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.
+For the common case of reading a single typed field — a boolean confirm/deny or a string answer — `ElicitationResponses.getBoolean(ctx, id, field)` and `getString(ctx, id, field)` skip the `get(...)` + null-check + content-map dance: a missing id, a missing field, or a non-`ACCEPT` action (declined/cancelled) all resolve to a safe default (`false` for `getBoolean`, `null` for `getString`) rather than requiring the caller to null-check by hand. See the [confirm-delete recipe](/docs/topics [...]
+
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
diff --git a/pages/topics/11.05.JuneauRestClientMcp.md b/pages/topics/11.05.JuneauRestClientMcp.md
index dd73b00d8f..dc2a061dde 100644
--- a/pages/topics/11.05.JuneauRestClientMcp.md
+++ b/pages/topics/11.05.JuneauRestClientMcp.md
@@ -6,6 +6,10 @@ slug: JuneauRestClientMcp
New to MCP in Juneau? Start with the [MCP Quickstart](/docs/topics/JuneauMcpQuickstart) and [MCP Recipes](/docs/topics/JuneauMcpRecipes) — this page is the deep reference for lookup once you're past the basics.
+:::tip Want a runnable, end-to-end example instead?
+[`juneau-examples-mcp`](https://github.com/apache/juneau/tree/master/juneau-examples/juneau-examples-mcp) is a complete, copy-me Maven module pairing a `2026-07-28` MCP server with a guided `McpClient` walkthrough (`ExampleClient`) that exercises every client call covered by this reference — discovery, tool calls, resource/template reads, completions, prompts, subscriptions, and an elicitation round-trip. It ships as a downloadable project zip in the Apache release. See [MCP Recipes](/do [...]
+:::
+
`juneau-rest-client-mcp` is the revision-neutral MCP client core built on `juneau-rest-client`, with two dated adapters: `juneau-rest-client-mcp-v20250618` and `juneau-rest-client-mcp-v20260728`.
## Overview