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 2ac3576c0b docs: MCP subscriptions/listen + adoption-first 
landing/quickstart/recipes (TODO-312c8)
2ac3576c0b is described below

commit 2ac3576c0b1eb73dac540ab2d5edd9f14063a117
Author: James Bognar <[email protected]>
AuthorDate: Tue Aug 4 13:17:46 2026 -0700

    docs: MCP subscriptions/listen + adoption-first landing/quickstart/recipes 
(TODO-312c8)
    
    Adds an adoption-focused MCP documentation set and documents the 2026-07-28
    subscriptions/listen server->client push surface.
    
    - New 11.00 landing, 11.01 quickstart, 11.02 choosing-setup guide, 11.03 
recipes.
    - Relocated the server/client reference pages to 11.04/11.05.
    - Examples verified against the real API.
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/topics/11.00.JuneauMcp.md                    |  21 ++
 pages/topics/11.01.JuneauMcpQuickstart.md          | 123 ++++++++
 pages/topics/11.02.JuneauMcpChoosingSetup.md       | 115 +++++++
 pages/topics/11.03.JuneauMcpRecipes.md             | 335 +++++++++++++++++++++
 ...stServerMcp.md => 11.04.JuneauRestServerMcp.md} |  12 +-
 ...stClientMcp.md => 11.05.JuneauRestClientMcp.md} |   4 +-
 sidebars.ts                                        |  42 ++-
 7 files changed, 639 insertions(+), 13 deletions(-)

diff --git a/pages/topics/11.00.JuneauMcp.md b/pages/topics/11.00.JuneauMcp.md
new file mode 100644
index 0000000000..711cefb34f
--- /dev/null
+++ b/pages/topics/11.00.JuneauMcp.md
@@ -0,0 +1,21 @@
+---
+id: 11.juneau-mcp
+title: "MCP (Model Context Protocol)"
+slug: JuneauMcp
+---
+
+Juneau provides both server-side and client-side support for the [Model 
Context Protocol (MCP)](https://modelcontextprotocol.io/), a JSON-RPC 2.0 
protocol that lets AI assistants and agents discover and invoke external 
**tools**, **prompts**, and **resources**.
+
+Two protocol revisions are supported side-by-side as separate, revision-bound 
Maven modules: `2025-06-18` and `2026-07-28` (the current revision). A 
client/server pair picks one revision at compile time — see [Choosing your MCP 
setup](/docs/topics/JuneauMcpChoosingSetup) for the full comparison.
+
+## Where to start
+
+- **[MCP Quickstart](/docs/topics/JuneauMcpQuickstart)** — a working server 
and client talking to each other in about 5 minutes.
+- **[Choosing your MCP setup](/docs/topics/JuneauMcpChoosingSetup)** — 
protocol revision, dedicated servlet vs mixin, plain vs Spring Boot.
+- **[MCP Recipes](/docs/topics/JuneauMcpRecipes)** — copy-pasteable snippets 
for tools, prompts, resources, resource templates, completions, error handling, 
elicitation, and subscriptions.
+
+## Deep reference
+
+- **[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp)** — full 
server-side API reference: capabilities, pagination, notifications, cache 
hints, tracing, Multi-Round-Trip Requests, elicitation, and subscriptions.
+- **[juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp)** — full 
client-side API reference: the duplex server-request channel, auth, and 
response caching.
+- **[juneau-bean-mcp-v20250618](/docs/topics/JuneauBeanMcp)** — the MCP 
wire-bean catalog underlying both the server and client modules.
diff --git a/pages/topics/11.01.JuneauMcpQuickstart.md 
b/pages/topics/11.01.JuneauMcpQuickstart.md
new file mode 100644
index 0000000000..fe95b6f893
--- /dev/null
+++ b/pages/topics/11.01.JuneauMcpQuickstart.md
@@ -0,0 +1,123 @@
+---
+title: "MCP Quickstart"
+slug: JuneauMcpQuickstart
+---
+
+Get a working [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 
server and client talking to each other in about 5 minutes, using MCP revision 
`2026-07-28` (the current revision — see [Choosing your MCP 
setup](/docs/topics/JuneauMcpChoosingSetup) if you need `2025-06-18` instead).
+
+## 1. Add the dependency
+
+```xml
+<dependency>
+    <groupId>org.apache.juneau</groupId>
+    <artifactId>juneau-rest-server-mcp-v20260728</artifactId>
+    <version>${juneau.version}</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.juneau</groupId>
+    <artifactId>juneau-rest-client-mcp-v20260728</artifactId>
+    <version>${juneau.version}</version>
+</dependency>
+```
+
+The server artifact transitively pulls in the revision-neutral 
`juneau-rest-server-mcp` core plus the `2026-07-28` wire beans 
(`juneau-bean-mcp-v20260728`). The client artifact does the same on the client 
side.
+
+## 2. Write a minimal server
+
+Extend the concrete, revision-bound `McpRestServlet` and register one tool. 
`McpToolHandler.of(...)` wires a descriptor and a call body together in one 
expression — `McpToolHandler` has two abstract methods, so it can't be 
satisfied by a bare lambda:
+
+```java
+package com.example;
+
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
+import org.apache.juneau.rest.server.mcp.v20260728.McpRestServlet;
+
+@Rest(path="/mcp")
+public class EchoMcpServlet extends McpRestServlet {
+
+    @Override
+    protected McpServerConfig createMcpConfig() {
+        return new McpServerConfig()
+            .setName("echo-server")
+            .setVersion("1.0.0")
+            .addTool(McpToolHandler.of(
+                new McpToolSpec().setName("echo").setDescription("Echoes the 
supplied text back."),
+                (arguments, ctx) -> 
McpToolOutcome.text(String.valueOf(arguments.getOrDefault("text", "")))
+            ));
+    }
+}
+```
+
+Mount `EchoMcpServlet` like any other `@Rest` resource (Jetty microservice, 
`web.xml`, or Spring Boot — see [Choosing your MCP 
setup](/docs/topics/JuneauMcpChoosingSetup)).
+
+## 3. Write a minimal client
+
+`McpClient.connect(endpoint)` builds a client and performs its one mandatory 
`server/discover` handshake in a single call — `2026-07-28` has no `initialize` 
method, so this is the call that replaces it:
+
+```java
+package com.example;
+
+import java.util.*;
+
+import org.apache.juneau.rest.client.mcp.v20260728.McpClient;
+
+public class EchoMcpClientDemo {
+    public static void main(String[] args) throws Exception {
+        try (var client = McpClient.connect("http://localhost:8080/mcp";)) {
+            System.out.println(client.listTools());
+            System.out.println(client.callTool("echo", Map.of("text", 
"hello")));
+        }
+    }
+}
+```
+
+`McpClient` implements `Closeable`, so try-with-resources closes the 
underlying transport for you.
+
+## 4. Talk to it with raw curl / JSON-RPC
+
+MCP `2026-07-28` (SEP-2243) requires every request to carry `Mcp-Method` and 
`Mcp-Name` HTTP headers alongside the JSON-RPC body — `Mcp-Method` echoes the 
JSON-RPC `method`, and `Mcp-Name` is the routed tool/prompt name (for 
`tools/call`/`prompts/get`) or resource URI (for `resources/read`); it's empty 
for every other method:
+
+```bash
+curl -s http://localhost:8080/mcp \
+  -H "Content-Type: application/json" \
+  -H "Mcp-Method: tools/call" \
+  -H "Mcp-Name: echo" \
+  -d '{
+    "jsonrpc": "2.0",
+    "id": 1,
+    "method": "tools/call",
+    "params": {
+      "name": "echo",
+      "arguments": {"text": "hello"},
+      "_meta": {
+        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+        "io.modelcontextprotocol/clientCapabilities": {}
+      }
+    }
+  }'
+```
+
+Response:
+
+```json
+{
+  "jsonrpc": "2.0",
+  "id": 1,
+  "result": {
+    "resultType": "complete",
+    "_meta": {
+      "io.modelcontextprotocol/serverInfo": {"name": "echo-server", "version": 
"1.0.0"}
+    },
+    "content": [{"type": "text", "text": "hello"}]
+  }
+}
+```
+
+A request missing the required `_meta.protocolVersion`/`clientCapabilities`, 
or missing the `Mcp-Method`/`Mcp-Name` headers, is rejected with a JSON-RPC 
`-32600` error before it ever reaches your tool handler.
+
+## Next steps
+
+- **[Choosing your MCP setup](/docs/topics/JuneauMcpChoosingSetup)** — 
`2025-06-18` vs `2026-07-28`, dedicated servlet vs mixin, plain vs Spring Boot.
+- **[MCP Recipes](/docs/topics/JuneauMcpRecipes)** — copy-pasteable snippets 
for typed tools, prompts, resources, resource templates, completions, error 
handling, elicitation, subscriptions, and the client side.
+- **[juneau-rest-server-mcp reference](/docs/topics/JuneauRestServerMcp)** and 
**[juneau-rest-client-mcp reference](/docs/topics/JuneauRestClientMcp)** — full 
API reference for both sides.
diff --git a/pages/topics/11.02.JuneauMcpChoosingSetup.md 
b/pages/topics/11.02.JuneauMcpChoosingSetup.md
new file mode 100644
index 0000000000..79e82caf27
--- /dev/null
+++ b/pages/topics/11.02.JuneauMcpChoosingSetup.md
@@ -0,0 +1,115 @@
+---
+title: "Choosing your MCP setup"
+slug: JuneauMcpChoosingSetup
+---
+
+Three independent decisions determine which classes you extend/implement and 
which Maven artifacts you depend on. They can be mixed freely — e.g. 
`2026-07-28` + mixin + Spring Boot is a perfectly normal combination.
+
+## 1. Which protocol revision: `2025-06-18` vs `2026-07-28`
+
+A client/server pair is revision-bound at compile time — pick one adapter 
module per side. There is no automatic runtime revision switching.
+
+| | `2025-06-18` (v1) | `2026-07-28` (v2) |
+|---|---|---|
+| Server artifact | `juneau-rest-server-mcp-v20250618` | 
`juneau-rest-server-mcp-v20260728` |
+| Client artifact | `juneau-rest-client-mcp-v20250618` | 
`juneau-rest-client-mcp-v20260728` |
+| Server package | `org.apache.juneau.rest.server.mcp.v20250618` | 
`org.apache.juneau.rest.server.mcp.v20260728` |
+| Client package | `org.apache.juneau.rest.client.mcp.v20250618` | 
`org.apache.juneau.rest.client.mcp.v20260728` |
+| Client handshake | `McpClient.initialize()` | `McpClient.serverDiscover()` 
(also via `McpClient.connect(...)`) |
+| Structured tool output | Object-rooted schemas only; `structuredContent` 
must be a JSON object | Arbitrary JSON Schema Draft 2020-12 shapes; 
`structuredContent` may be any JSON value |
+| Resource templates, template reads, completions | Yes (cross-revision core 
feature) | Yes (cross-revision core feature) |
+| Cache hints (SEP-2549) | No | Yes |
+| Multi-Round-Trip Requests / elicitation (SEP-2322) | No | Yes |
+| Subscriptions (SEP-2575) | No | Yes |
+| Trace-context propagation via `params._meta` | No | Yes |
+| Spring Boot convenience (`SpringMcpRestServlet`) | Yes | Yes |
+
+If you don't have a specific reason to target `2025-06-18` (an existing client 
that only speaks that revision), use `2026-07-28` — it's a strict superset of 
adopter-facing capability. See the [Recipes](/docs/topics/JuneauMcpRecipes) 
page for the v2-only features and the [juneau-rest-server-mcp 
reference](/docs/topics/JuneauRestServerMcp) for the full breakdown.
+
+## 2. Dedicated servlet vs mixin
+
+Both paths bind the same revision and expose the same hooks (`capabilities()`, 
cache/MRTR/subscriptions config); they differ only in how the endpoint attaches 
to your REST resource.
+
+**Dedicated servlet** — use when MCP is the *only* thing this resource does, 
or you're starting fresh:
+
+```java
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
+import org.apache.juneau.rest.server.mcp.v20260728.McpRestServlet;
+
+@Rest(path="/mcp")
+public class MyMcpServlet extends McpRestServlet {
+    @Override
+    protected McpServerConfig createMcpConfig() {
+        return new 
McpServerConfig().setName("my-server").setVersion("1.0.0").addTool(new 
MyEchoTool());
+    }
+}
+```
+
+**Mixin** (`McpEndpoint`) — use when you already have a `@Rest` resource (with 
its own unrelated endpoints) and want to bolt an MCP endpoint onto it at `POST 
/mcp`:
+
+```java
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
+import org.apache.juneau.rest.server.mcp.v20260728.McpEndpoint;
+
+@Rest(path="/api")
+public class MyResource extends BasicRestServlet implements McpEndpoint {
+    @Override
+    public McpServerConfig getMcpConfig() {
+        return new McpServerConfig().addTool(new MyEchoTool());
+    }
+}
+```
+
+Both `org.apache.juneau.rest.server.mcp.v20260728.McpRestServlet` and 
`...v20260728.McpEndpoint` are the concrete, revision-bound classes you 
actually extend/implement. They in turn extend the revision-neutral core types 
`org.apache.juneau.rest.server.mcp.AbstractMcpRestServlet` and 
`org.apache.juneau.rest.server.mcp.McpEndpointMixin` — you don't touch those 
core types directly; they exist so a future protocol revision can be added as a 
second adapter without changing the JSON-RPC envelo [...]
+
+## 3. Plain vs Spring Boot
+
+**Plain** (Jetty microservice, `web.xml`, or any non-Spring container) — use 
the dedicated servlet or mixin exactly as shown above; there is no second 
bean-resolution path to worry about.
+
+**Spring Boot** — extend `SpringMcpRestServlet` instead of composing 
`BasicRestServlet implements McpEndpoint` yourself. It combines the revision's 
`McpEndpoint` mixin with `BasicSpringRestServlet`, giving your 
tool/prompt/resource handlers access to Spring-managed beans via 
`BeanStore.getBean(...)`:
+
+```java
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
+import org.apache.juneau.rest.server.mcp.v20260728.SpringMcpRestServlet;
+
+@Rest(path="/mcp")
+public class MyMcpResource extends SpringMcpRestServlet {
+    @Override
+    public McpServerConfig getMcpConfig() {
+        return new McpServerConfig().addTool(new MySpringAwareTool());
+    }
+}
+```
+
+```xml
+<dependency>
+    <groupId>org.apache.juneau</groupId>
+    <artifactId>juneau-rest-server-springboot</artifactId>
+    <version>${juneau.version}</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.juneau</groupId>
+    <artifactId>juneau-rest-server-mcp-v20260728</artifactId>
+    <version>${juneau.version}</version>
+</dependency>
+```
+
+`SpringMcpRestServlet` ships for both revisions 
(`org.apache.juneau.rest.server.mcp.v20250618.SpringMcpRestServlet` and 
`...v20260728.SpringMcpRestServlet`).
+
+:::caution Don't extend the plain `McpRestServlet` under Spring Boot
+`McpRestServlet extends BasicRestServlet` — a *sibling* of 
`SpringRestServlet`, not a subclass of it. A servlet that extends it still 
answers MCP requests under Spring Boot, but every `BeanStore.getBean(...)` call 
inside a handler silently comes back empty, because that servlet was never 
wired to Spring's `ApplicationContext`. There's no compile-time signal for this 
— always use `SpringMcpRestServlet` (or the mixin on `BasicSpringRestServlet`) 
under Spring Boot instead.
+:::
+
+See [juneau-rest-server-mcp reference](/docs/topics/JuneauRestServerMcp) for 
servlet registration details (self-registration, `web.xml`, Spring Boot 
auto-registration) and [Spring Boot Overview](/docs/topics/SpringBootOverview) 
for the Spring bean-store bridge these classes build on.
+
+## Which client class
+
+Same shape as the server side — pick the dated `McpClient` matching your 
target revision:
+
+- `org.apache.juneau.rest.client.mcp.v20260728.McpClient` — use 
`McpClient.connect(endpoint)` for the common case (see 
[Quickstart](/docs/topics/JuneauMcpQuickstart)).
+- `org.apache.juneau.rest.client.mcp.v20250618.McpClient` — use 
`builder().endpoint(...).build()` then call `initialize()` before any other 
method.
+
+See [juneau-rest-client-mcp reference](/docs/topics/JuneauRestClientMcp) for 
the full client API.
diff --git a/pages/topics/11.03.JuneauMcpRecipes.md 
b/pages/topics/11.03.JuneauMcpRecipes.md
new file mode 100644
index 0000000000..1ee82c7bc3
--- /dev/null
+++ b/pages/topics/11.03.JuneauMcpRecipes.md
@@ -0,0 +1,335 @@
+---
+title: "MCP Recipes"
+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.
+
+- [Writing a tool](#writing-a-tool)
+- [Writing a prompt](#writing-a-prompt)
+- [Serving a resource](#serving-a-resource)
+- [Serving a resource template with 
completions](#serving-a-resource-template-with-completions)
+- [Error handling](#error-handling)
+- [Elicitation / Multi-Round-Trip Requests (v2 
only)](#elicitation--multi-round-trip-requests-v2-only)
+- [Subscriptions (v2 only)](#subscriptions-v2-only)
+- [Client: call, read, and handle errors](#client-call-read-and-handle-errors)
+
+## Writing a tool
+
+**Raw, via `McpToolHandler.of(...)`** — full control over the input schema:
+
+```java
+import java.util.*;
+
+import org.apache.juneau.marshall.collections.JsonMap;
+import org.apache.juneau.rest.server.mcp.*;
+
+McpToolHandler weatherTool = McpToolHandler.of(
+    new McpToolSpec()
+        .setName("weather")
+        .setDescription("Looks up the current weather for a city.")
+        .setInputSchema(McpSchema.of(JsonMap.of(
+            "type", "object",
+            "properties", JsonMap.of("city", JsonMap.of("type", "string")),
+            "required", List.of("city")
+        ))),
+    (arguments, ctx) -> {
+        String city = String.valueOf(arguments.get("city"));
+        return McpToolOutcome.text("Sunny in " + city);
+    }
+);
+
+config.addTool(weatherTool);
+```
+
+**Typed, via `McpTypedHandlers.tool(...)`** — derives the JSON Schema from 
your argument/result classes and binds/canonicalizes automatically:
+
+```java
+import org.apache.juneau.commons.inject.BeanStore;
+import org.apache.juneau.rest.server.mcp.*;
+
+public class WeatherArgs {
+    public String city;
+}
+
+public class WeatherResult {
+    public String summary;
+    public double tempC;
+}
+
+McpToolHandler weatherTool = McpTypedHandlers.tool(
+    "weather", "Looks up the current weather for a city.",
+    WeatherArgs.class, WeatherResult.class,
+    (args, ctx) -> {
+        WeatherResult r = new WeatherResult();
+        r.summary = "Sunny";
+        r.tempC = 23.5;
+        return r;
+    }
+);
+
+config.addTool(weatherTool);
+```
+
+The typed path populates `structuredContent` on the `CallToolResult` (and 
mirrors it as a text block by default); the raw path leaves `structuredContent` 
unset unless you call `McpToolOutcome.setStructuredContent(...)` yourself.
+
+## Writing a prompt
+
+```java
+import java.util.*;
+
+import org.apache.juneau.rest.server.mcp.*;
+
+McpPromptHandler greetPrompt = McpPromptHandler.of(
+    new McpPromptSpec()
+        .setName("greet")
+        .setDescription("Greets a named person.")
+        .setArguments(List.of(new 
McpPromptArgument().setName("name").setRequired(true))),
+    (arguments, ctx) -> new McpPromptOutcome()
+        .setMessages(List.of(new McpPromptMessage()
+            .setRole(McpRole.USER)
+            .setContent(McpContentBlock.text("Hello, " + arguments.get("name") 
+ "!"))))
+);
+
+config.addPrompt(greetPrompt);
+```
+
+## Serving a resource
+
+```java
+import java.util.*;
+
+import org.apache.juneau.rest.server.mcp.*;
+
+McpResourceHandler readmeResource = McpResourceHandler.of(
+    new 
McpResourceSpec().setUri("file:///readme.txt").setName("readme").setMimeType("text/plain"),
+    (uri, ctx) -> new McpResourceOutcome()
+        .setContents(List.of(McpResourceContents.text(uri, "text/plain", 
"Welcome!")))
+);
+
+config.addResource(readmeResource);
+```
+
+## Serving a resource template with completions
+
+A template needs a named class (or anonymous class) rather than `of(...)` once 
you want a per-variable completer, since `of(...)` only wires `descriptor()` + 
`read(...)`:
+
+```java
+import java.util.*;
+
+import org.apache.juneau.commons.inject.BeanStore;
+import org.apache.juneau.rest.server.mcp.*;
+
+public class FileResourceTemplate implements McpResourceTemplateHandler {
+
+    @Override
+    public McpResourceTemplateSpec descriptor() {
+        return new McpResourceTemplateSpec()
+            .setUriTemplate("file:///{name}")
+            .setName("files")
+            .setMimeType("text/plain");
+    }
+
+    @Override
+    public McpResourceOutcome read(String uri, Map<String,String> variables, 
BeanStore ctx) {
+        String name = variables.get("name");
+        String content = loadFile(name);
+        if (content == null)
+            return null; // reported to the client as resource-not-found
+        return new 
McpResourceOutcome().setContents(List.of(McpResourceContents.text(uri, 
"text/plain", content)));
+    }
+
+    @Override
+    public McpCompleter completer(String variableName) {
+        if (! "name".equals(variableName))
+            return null;
+        return (request, ctx) -> 
McpCompletionResult.empty().setValues(matchingFileNames(request.getValue()));
+    }
+
+    private String loadFile(String name) {
+        // your lookup logic
+        return null;
+    }
+
+    private List<String> matchingFileNames(String partial) {
+        // your completion logic
+        return List.of();
+    }
+}
+
+config.addResourceTemplate(new FileResourceTemplate());
+```
+
+A registration-only template (listable but with no read/complete behavior of 
its own) can skip the interface entirely: `config.addResourceTemplate(new 
McpResourceTemplateSpec()...)`.
+
+## Error handling
+
+Throw `McpException` with a JSON-RPC 2.0 standard error code to fail a call 
explicitly:
+
+```java
+import org.apache.juneau.bean.jsonrpc.McpException;
+
+// Standard JSON-RPC 2.0 codes (no dedicated constants class in Juneau — use 
the literals):
+//   -32700  Parse error
+//   -32600  Invalid Request
+//   -32601  Method not found
+//   -32602  Invalid params
+//   -32603  Internal error
+//   -32000..-32099  Server-defined error (reserved range for application use)
+
+McpToolHandler.of(
+    new McpToolSpec().setName("weather").setDescription("Looks up the current 
weather for a city."),
+    (arguments, ctx) -> {
+        if (! arguments.containsKey("city"))
+            throw new McpException(-32602, "Missing required argument 'city'");
+        return McpToolOutcome.text("Sunny in " + arguments.get("city"));
+    }
+);
+```
+
+Any other unchecked exception a handler lets escape is automatically converted 
to a `-32603` ("Internal error") JSON-RPC response by the dispatcher — you 
don't need a catch-all yourself, but throwing `McpException` directly gets you 
a more specific code and message.
+
+## Elicitation / Multi-Round-Trip Requests (v2 only)
+
+Elicitation (SEP-2322) lets a tool pause mid-call to ask the end user a 
question, then resume once the client relays an answer. It's built on 
Multi-Round-Trip Requests (MRTR), a `2026-07-28`-only capability.
+
+```java
+import java.util.*;
+
+import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.commons.inject.BeanStore;
+import org.apache.juneau.rest.server.mcp.*;
+import org.apache.juneau.rest.server.mcp.v20260728.*;
+
+McpToolHandler confirmDeleteTool = McpToolHandler.of(
+    new McpToolSpec().setName("confirm-delete").setDescription("Deletes a 
record after confirming with the user."),
+    (arguments, ctx) -> {
+        var resume = ctx.getBean(McpMrtrResumeContext.class);
+        if (resume.isEmpty()) {
+            var schema = ElicitSchema.create()
+                .booleanField("confirm").title("Confirm deletion?")
+                .required("confirm")
+                .build();
+            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")))
+            return McpToolOutcome.text("Deletion cancelled.");
+        // ... perform the deletion here ...
+        return McpToolOutcome.text("Deleted.");
+    }
+);
+```
+
+- 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.
+
+This requires no special server config beyond a v2 servlet/mixin — MRTR 
support is on by default (see 
`AbstractMcpRestServlet.createMrtrConfig()`/`McpRestServlet.getMrtrConfig()` in 
the [server reference](/docs/topics/JuneauRestServerMcp) to customize the 
codec, TTL, or round cap).
+
+## Subscriptions (v2 only)
+
+Subscriptions (SEP-2575) let a server push resource/list-changed notifications 
to a client over a long-held `subscriptions/listen` stream. This is v2-only and 
requires `juneau-rest-server-reactive` on the server's classpath for the async 
held-open SSE transport.
+
+**Server: emit a change.** Inject `McpSubscriptions` via the handler's 
`BeanStore` and call it from any tool/resource/prompt handler:
+
+```java
+import org.apache.juneau.commons.inject.BeanStore;
+import org.apache.juneau.rest.server.mcp.*;
+
+McpToolHandler touchFileTool = McpToolHandler.of(
+    new McpToolSpec().setName("touch-file").setDescription("Marks a resource 
as updated."),
+    (arguments, ctx) -> {
+        String uri = String.valueOf(arguments.get("uri"));
+        ctx.getBean(McpSubscriptions.class)
+            .orElseThrow(() -> new IllegalStateException("McpSubscriptions 
bean missing — is this endpoint bound to v20260728?"))
+            .resourceUpdated(uri);
+        return McpToolOutcome.text("published");
+    }
+);
+```
+
+`McpSubscriptions` also exposes `toolsListChanged()`, `promptsListChanged()`, 
and `resourcesListChanged()` for list-level notifications.
+
+Optionally tune the broker (queue size, heartbeat, idle timeout) by overriding 
a hook on your servlet/endpoint:
+
+```java
+@Override
+protected McpSubscriptionsConfig createSubscriptionsConfig() {
+    return new McpSubscriptionsConfig().setIdleTimeoutMs(5 * 60_000L);
+}
+```
+
+:::caution `idleTimeoutMs` defaults to disabled
+`McpSubscriptionsConfig.getIdleTimeoutMs()` defaults to `0`, which disables 
idle-timeout reaping — a subscription stream stays open indefinitely as long as 
the connection itself stays up. If you set it to a positive value, the server 
auto-closes a stream that has delivered no notifications or heartbeats for that 
many milliseconds. A long-lived client listener should treat 
`onComplete()`/`onError(...)` as a signal to call `listen(...)` again, not 
assume the stream stays open forever once  [...]
+:::
+
+**Client: listen for changes:**
+
+```java
+import java.util.*;
+
+import org.apache.juneau.bean.mcp.v20260728.SubscriptionFilter;
+import org.apache.juneau.rest.client.mcp.v20260728.*;
+
+var client = McpClient.connect("http://localhost:8080/mcp";);
+
+var filter = new SubscriptionFilter()
+    .setResourceSubscriptions(List.of("file:///a.txt"))
+    .setResourcesListChanged(true);
+
+McpSubscriptionHandle handle = client.listen(filter, new 
McpSubscriptionListener() {
+    @Override public void onAcknowledged(SubscriptionFilter honoredFilter) {
+        System.out.println("Subscribed: " + honoredFilter);
+    }
+    @Override public void onResourceUpdated(String uri) {
+        System.out.println("Resource updated: " + uri);
+    }
+    @Override public void onListChanged(McpListChangedKind kind) {
+        System.out.println("List changed: " + kind);
+    }
+    @Override public void onComplete() {
+        System.out.println("Server closed the subscription gracefully.");
+    }
+    @Override public void onError(Throwable t) {
+        t.printStackTrace();
+    }
+});
+
+// ... later, when you're done listening:
+handle.close();
+```
+
+`listen(...)` opens the stream synchronously (a transport failure surfaces as 
a thrown `IOException` right there) then dispatches every subsequent frame to 
`listener` on a background thread — `onAcknowledged(...)` always arrives first, 
echoing the server-honored (capability-gated) subset of your requested filter.
+
+## Client: call, read, and handle errors
+
+```java
+import java.util.*;
+
+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";)) {
+    var tools = client.listTools();
+    System.out.println(tools);
+
+    var callResult = client.callTool("weather", Map.of("city", "Seattle"));
+    System.out.println(callResult.getContent());
+
+    var resource = client.readResource("file:///readme.txt");
+    System.out.println(resource.getContents());
+} catch (McpException e) {
+    System.err.println("Server returned JSON-RPC error " + e.getCode() + ": " 
+ e.getMessage());
+} catch (java.io.IOException e) {
+    System.err.println("Transport failure: " + e.getMessage());
+}
+```
+
+`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.
+
+## Next steps
+
+- **[juneau-rest-server-mcp reference](/docs/topics/JuneauRestServerMcp)** — 
capabilities, pagination, cache hints, tracing, MRTR internals.
+- **[juneau-rest-client-mcp reference](/docs/topics/JuneauRestClientMcp)** — 
full client API, including the duplex server-request channel and response 
caching.
diff --git a/pages/topics/11.JuneauRestServerMcp.md 
b/pages/topics/11.04.JuneauRestServerMcp.md
similarity index 98%
rename from pages/topics/11.JuneauRestServerMcp.md
rename to pages/topics/11.04.JuneauRestServerMcp.md
index af8a7dac2e..4ab2d2e024 100644
--- a/pages/topics/11.JuneauRestServerMcp.md
+++ b/pages/topics/11.04.JuneauRestServerMcp.md
@@ -1,9 +1,10 @@
 ---
-id: 11.juneau-rest-server-mcp
+id: 11.04.juneau-rest-server-mcp
 title: "juneau-rest-server-mcp"
 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.
 
 `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 [...]
 
@@ -56,7 +57,7 @@ This module transitively pulls in `juneau-rest-server-mcp` 
(the core) and `junea
 
 ### Drop-in servlet
 
-Subclass `org.apache.juneau.rest.server.mcp.v20250618.McpRestServlet` (not the 
abstract core `McpRestServlet` directly) and supply your config in 
`createMcpConfig()`. The base class wires up `@Rest`, 
`@SerializerConfig(addBeanTypes="true")`, and a `POST /` handler:
+Subclass `org.apache.juneau.rest.server.mcp.v20250618.McpRestServlet` (not the 
abstract core `AbstractMcpRestServlet` directly) and supply your config in 
`createMcpConfig()`. The base class wires up `@Rest`, 
`@SerializerConfig(addBeanTypes="true")`, and a `POST /` handler:
 
 ```java
 import org.apache.juneau.rest.server.*;
@@ -93,7 +94,7 @@ public class MyResource extends BasicRestServlet implements 
McpEndpoint {
 }
 ```
 
-The default `handleMcpRequest(...)` method on `McpEndpoint` (annotated 
`@RestPost("/mcp")`) takes care of dispatch, via 
`org.apache.juneau.rest.server.mcp.v20250618.McpEndpoint`'s `revision()` 
override.
+The default `handleMcpRequest(...)` method on `McpEndpoint` (inherited from 
the revision-neutral `McpEndpointMixin`, annotated `@RestPost("/mcp")`) takes 
care of dispatch, via 
`org.apache.juneau.rest.server.mcp.v20250618.McpEndpoint`'s `revision()` 
override.
 
 ## Running Under Spring Boot
 
@@ -409,8 +410,11 @@ public class FileTemplate implements 
McpResourceTemplateHandler {
     @Override
     public McpResourceOutcome read(String uri, Map<String,String> variables, 
BeanStore ctx) {
         String name = variables.get("name");
+        String content = loadFile(name);
         // return null if this template does not actually serve `uri` 
(reported as resource-not-found)
-        return McpResourceOutcome.text(uri, loadFile(name));
+        if (content == null)
+            return null;
+        return new 
McpResourceOutcome().setContents(List.of(McpResourceContents.text(uri, 
"text/plain", content)));
     }
 }
 
diff --git a/pages/topics/11.01.JuneauRestClientMcp.md 
b/pages/topics/11.05.JuneauRestClientMcp.md
similarity index 94%
rename from pages/topics/11.01.JuneauRestClientMcp.md
rename to pages/topics/11.05.JuneauRestClientMcp.md
index cbf206afd7..5f7cb606b7 100644
--- a/pages/topics/11.01.JuneauRestClientMcp.md
+++ b/pages/topics/11.05.JuneauRestClientMcp.md
@@ -1,9 +1,11 @@
 ---
-id: 11.01.juneau-rest-client-mcp
+id: 11.05.juneau-rest-client-mcp
 title: "juneau-rest-client-mcp"
 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.
+
 `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
diff --git a/sidebars.ts b/sidebars.ts
index 21f910f30d..4cf73a8b27 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1774,14 +1774,40 @@ const sidebars: SidebarsConfig = {
                                        },
                                },
                                {
-                                       type: 'doc',
-                                       id: 
'topics/11.01.juneau-rest-client-mcp',
-                                       label: '11.1. juneau-rest-client-mcp',
-                               },
-                               {
-                                       type: 'doc',
-                                       id: 'topics/11.juneau-rest-server-mcp',
-                                       label: '11. juneau-rest-server-mcp',
+                                       type: 'category',
+                                       label: '11. MCP (Model Context 
Protocol)',
+                                       collapsed: true,
+                                       items: [
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/11.01.JuneauMcpQuickstart',
+                                                       label: '11.1. 
Quickstart',
+                                               },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/11.02.JuneauMcpChoosingSetup',
+                                                       label: '11.2. Choosing 
your setup',
+                                               },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/11.03.JuneauMcpRecipes',
+                                                       label: '11.3. Recipes',
+                                               },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/11.04.juneau-rest-server-mcp',
+                                                       label: '11.4. 
juneau-rest-server-mcp (reference)',
+                                               },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/11.05.juneau-rest-client-mcp',
+                                                       label: '11.5. 
juneau-rest-client-mcp (reference)',
+                                               },
+                                       ],
+                                       link: {
+                                               type: 'doc',
+                                               id: 'topics/11.juneau-mcp',
+                                       },
                                },
                                {
                                        type: 'category',

Reply via email to