Skip to main content

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 artifactjuneau-rest-server-mcp-v20250618juneau-rest-server-mcp-v20260728
Client artifactjuneau-rest-client-mcp-v20250618juneau-rest-client-mcp-v20260728
Server packageorg.apache.juneau.rest.server.mcp.v20250618org.apache.juneau.rest.server.mcp.v20260728
Client packageorg.apache.juneau.rest.client.mcp.v20250618org.apache.juneau.rest.client.mcp.v20260728
Client handshakeMcpClient.initialize()McpClient.serverDiscover() (also via McpClient.connect(...))
Structured tool outputObject-rooted schemas only; structuredContent must be a JSON objectArbitrary JSON Schema Draft 2020-12 shapes; structuredContent may be any JSON value
Resource templates, template reads, completionsYes (cross-revision core feature)Yes (cross-revision core feature)
Cache hints (SEP-2549)NoYes
Multi-Round-Trip Requests / elicitation (SEP-2322)NoYes
Subscriptions (SEP-2575)NoYes
Trace-context propagation via params._metaNoYes
Spring Boot convenience (SpringMcpRestServlet)YesYes

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).

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 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 — use McpClient.connect(endpoint) for the common case (see Quickstart).
  • 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 for the full client API.