Choosing your MCP setup
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.
Module shape: 3 shared cores, 6 per-revision adapters
The MCP ecosystem is 9 Maven artifacts, not 6 independent ones. Three of them are revision-neutral cores — each has zero compile-time knowledge of 2025-06-18 vs 2026-07-28 — and each core carries two thin adapter modules on top, one per protocol revision:
juneau-bean-jsonrpc JSON-RPC 2.0 envelope (revision-neutral)
├── juneau-bean-mcp-v20250618 2025-06-18 wire beans
└── juneau-bean-mcp-v20260728 2026-07-28 wire beans
juneau-rest-server-mcp server dispatch core (revision-neutral)
├── juneau-rest-server-mcp-v20250618
└── juneau-rest-server-mcp-v20260728
juneau-rest-client-mcp client core (revision-neutral)
├── juneau-rest-client-mcp-v20250618
└── juneau-rest-client-mcp-v20260728
The three decisions below only concern which adapter coordinate you pick per side — the neutral cores come along transitively; you don't depend on or configure them directly.
Why a separate artifact per revision, instead of one artifact with a runtime revision switch: Maven/Gradle dependency resolution happens at build time, so keeping a 2025-06-18-only consumer off 2026-07-28-only dependencies has to be a compile-time artifact choice, not a runtime flag. The 2026-07-28 server adapter alone pulls in juneau-rest-server-reactive (reactive-streams SSE, for subscriptions), juneau-rest-server-auth-oauth (OAuth 2.1), and juneau-rest-server-auth-jwt (JWT) — none of which land on the classpath of a 2025-06-18-only deployment.
Why the two adapters keep diverging in size rather than converging: 2025-06-18 is maintained for bug fixes and spec-correctness only, indefinitely. All new MCP capability work — Multi-Round-Trip Requests, subscriptions, sampling, elicitation, OAuth 2.1 hardening, cache hints, structured tool output, resource templates, _meta/trace-context propagation — lands only in 2026-07-28. The capability table below reflects that split as of today; expect the gap to widen, not close.
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 page for the v2-only features and the juneau-rest-server-mcp reference for the full breakdown.
2. Dedicated servlet vs mixin
Both paths bind the same revision and expose the same config surface — getMcpConfig()/createMcpConfig() for tool/prompt/resource registration, plus (on 2026-07-28) a single getMcpOptions()/createMcpOptions() override for behavior configuration (capabilities/instructions/cache/MRTR/subscriptions/broker, all on one McpOptions bean) — 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:
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:
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 envelope or dispatch machinery.
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(...):
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());
}
}
<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).
McpRestServlet under Spring BootMcpRestServlet 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 for servlet registration details (self-registration, web.xml, Spring Boot auto-registration) and Spring Boot Overview 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— useMcpClient.connect(endpoint)for the common case (see Quickstart).org.apache.juneau.rest.client.mcp.v20250618.McpClient— usebuilder().endpoint(...).build()then callinitialize()before any other method.
See juneau-rest-client-mcp reference for the full client API.