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 2fcfc66d7d docs: MCP 2026-07-28 _meta / trace-context + Spring Boot
setup (TODO-312c3)
2fcfc66d7d is described below
commit 2fcfc66d7d603626a24f4b75342e6ad7d1450f49
Author: James Bognar <[email protected]>
AuthorDate: Sat Aug 1 10:36:35 2026 -0400
docs: MCP 2026-07-28 _meta / trace-context + Spring Boot setup (TODO-312c3)
- Page 11 (juneau-rest-server-mcp): add "Running Under Spring Boot"
(recommend
SpringMcpRestServlet / the BasicSpringRestServlet + McpEndpoint mixin;
caution
that subclassing McpRestServlet under Spring Boot loses the
SpringBeanStore
bridge) and a Trace-Context Propagation section (params._meta carrier,
_meta-wins precedence, result._meta echo, gen_ai.*/mcp.* attributes,
recordRpcError mapping).
- Page 05.07 (juneau-bean-mcp): document the reshaped v2 _meta beans
(RequestParams/RequestMeta/Result/ResultMeta/Icon/PingResult, resultType,
ServerDiscoverResult -> CacheableResult); fix a broken cache-hints anchor.
- Page 10.40 (observability): neutral tracing seam + OTel bridge + pinned
experimental semconv snapshot.
- Release notes 10.0.0: MCP _meta reshape, trace-context propagation,
SpringMcpRestServlet.
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/10.0.0.md | 10 ++
pages/topics/05.07.JuneauBeanMcp.md | 36 +++++-
pages/topics/10.40.RestServerObservability.md | 100 ++++++++++++++++
pages/topics/11.JuneauRestServerMcp.md | 157 ++++++++++++++++++++++++++
4 files changed, 301 insertions(+), 2 deletions(-)
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 633feb1fa5..e39debca39 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -908,6 +908,16 @@ Building on the `resources/templates/list` endpoint above,
10.0.0 makes register
See
[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#resource-templates-reads-and-completions)
and
[juneau-bean-mcp-v20250618](/docs/topics/JuneauBeanMcp#completions-completioncomplete-new-in-1000)
for the full topics.
+### MCP `2026-07-28` — `_meta` wire compliance + trace-context propagation +
Spring Boot convenience class
+
+`2026-07-28`-only. Brings `juneau-bean-mcp-v20260728`'s per-request/per-result
metadata into exact alignment with the MCP `2026-07-28` schema, adds
distributed trace-context propagation through that same `_meta` carrier, and
adds a Spring Boot convenience base class. `2025-06-18` is untouched by all
three changes.
+
+- **`_meta` wire-compliance reshape.** The neutral JSON-RPC envelope beans
(`JsonRpcRequest`/`JsonRpcResponse`) carry no `_meta` property; it now lives
under `params._meta` (new `RequestMeta`, via a new `RequestParams<T>` CRTP base
on
`CallToolRequest`/`GetPromptRequest`/`ReadResourceRequest`/`CompleteRequest`,
plus `RequestParamsOnly` for the metadata-only methods) with the exact
`io.modelcontextprotocol/`-prefixed negotiation keys the schema mandates, and
under `result._meta` (new `Res [...]
+- **Trace-context propagation.** `params._meta` may additionally carry bare
(unprefixed) W3C `traceparent`/`tracestate`/`baggage` keys. When an active
`TracerHook` is registered, an explicit `params._meta` value wins over the
equivalent HTTP header (independently per field); the active trace context is
echoed back into `result._meta` on success; and the OpenTelemetry bridge (when
present) enriches the span with pinned, experimental `gen_ai.*`/`mcp.*`
attributes and maps JSON-RPC errors ( [...]
+- **`SpringMcpRestServlet` convenience class (both revisions).** A new
`SpringMcpRestServlet`
(`org.apache.juneau.rest.server.mcp.v20250618.SpringMcpRestServlet` and
`.v20260728.SpringMcpRestServlet`) combines `BasicSpringRestServlet` with the
revision's `McpEndpoint` mixin — the simplest way to expose MCP from a Spring
Boot resource with full Spring dependency-injection support in
tool/prompt/resource handlers; implement only `getMcpConfig()`. **Don't**
subclass the plain `McpRestServle [...]
+
+See
[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#trace-context-propagation-mcp-2026-07-28)
(Spring Boot + trace-context sections),
[juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-metadata-and-trace-context-_meta)
(bean catalog), and
[Observability](/docs/topics/RestServerObservability#mcp-trace-context-propagation-2026-07-28)
(framework-level seam + OTel bridge) for the full topics.
+
### Bug Fixes
- **Fixed RRPC method calls never dispatching over POST.** Every HTTP POST to
an `@RestOp(method="RRPC")` operation previously returned a 404 instead of
reaching the target method. `RrpcRestOpSession` derived the RRPC method key by
splitting the request path on the last `/`, but RRPC keys are of the form
`methodName/(paramTypes)` and themselves contain a `/`, so the method name was
stripped off and the lookup always fell through to `NotFound`. The key is now
derived from the already-comp [...]
diff --git a/pages/topics/05.07.JuneauBeanMcp.md
b/pages/topics/05.07.JuneauBeanMcp.md
index 799cb4cb2c..f41750a501 100644
--- a/pages/topics/05.07.JuneauBeanMcp.md
+++ b/pages/topics/05.07.JuneauBeanMcp.md
@@ -265,7 +265,7 @@ String yaml = Yaml.of(r);
Set `addBeanTypes` and the discriminator property names on the corresponding
builders to round-trip polymorphic content correctly.
-## MCP `2026-07-28` — Cache Hints & Resource Templates
+## MCP `2026-07-28`: Cache Hints, Resource Templates
The sibling `juneau-bean-mcp-v20260728` module (package
`org.apache.juneau.bean.mcp.v20260728`) adds SEP-2549 cache hints and a
`resources/templates/list` result to its own revision of the wire beans covered
above. Both are opt-in, top-level, nullable fields/beans — omitted entirely
from the wire when unset.
@@ -288,7 +288,39 @@ A compact example showing both features on the same result:
### `2026-07-28` completions (`completion/complete`, new in 10.0.0)
-`juneau-bean-mcp-v20260728` gained the same nine-bean completion family
described above for `2025-06-18` — `CompleteRequest`, `CompletionReference`
(with `PromptReference`/`ResourceTemplateReference` subtypes,
`ref/prompt`/`ref/resource` discriminators), `CompletionArgument`,
`CompletionContext`, `CompleteResult`, `Completion`, and `CompletionCapability`
— as an independent copy in `org.apache.juneau.bean.mcp.v20260728`, with
`McpMethods.COMPLETION_COMPLETE` (`"completion/complete"`). Fi [...]
+`juneau-bean-mcp-v20260728` gained the same nine-bean completion family
described above for `2025-06-18` — `CompleteRequest`, `CompletionReference`
(with `PromptReference`/`ResourceTemplateReference` subtypes,
`ref/prompt`/`ref/resource` discriminators), `CompletionArgument`,
`CompletionContext`, `CompleteResult`, `Completion`, and `CompletionCapability`
— as an independent copy in `org.apache.juneau.bean.mcp.v20260728`, with
`McpMethods.COMPLETION_COMPLETE` (`"completion/complete"`). Fi [...]
+
+## MCP `2026-07-28` Metadata and Trace-Context (`_meta`)
+
+10.0.0 also brings `juneau-bean-mcp-v20260728`'s per-request/per-result
metadata into exact alignment with the MCP `2026-07-28` schema. This is a
correction to the wire shape (an unreleased-10.0.0 fix, not a compatibility
mode — there's no old-shape fallback) and is `2026-07-28`-only; `2025-06-18`
has no `_meta` concept and is untouched by it.
+
+The neutral JSON-RPC envelope beans (`JsonRpcRequest`/`JsonRpcResponse` in
[juneau-bean-jsonrpc](/docs/topics/JuneauBeanJsonRpc)) carry **no** `_meta`
property. Per-request metadata instead lives one level down, under
`params._meta`; per-result metadata lives under `result._meta`.
+
+| Bean | Purpose |
+|---|---|
+| `RequestParams<T>` | Abstract CRTP base for the four concrete request-params
beans (`CallToolRequest`, `GetPromptRequest`, `ReadResourceRequest`,
`CompleteRequest`), contributing the optional `_meta` (`RequestMeta`) property.
|
+| `RequestParamsOnly` | Concrete params bean for the methods whose only member
is `_meta` — `*/list`, `ping`, `server/discover`. |
+| `RequestMeta` | Per-request metadata: required
`io.modelcontextprotocol/protocolVersion`, optional
`io.modelcontextprotocol/clientInfo` (an `Implementation`), required
`io.modelcontextprotocol/clientCapabilities`, deprecated-but-accepted
`io.modelcontextprotocol/logLevel`, and bare (unprefixed) W3C trace-context
keys `traceparent`/`tracestate`/`baggage`. Declares the wire-key names as
`public static final String` constants (`KEY_PROTOCOL_VERSION`,
`KEY_CLIENT_INFO`, `KEY_CLIENT_CAPABIL [...]
+| `Result<T>` | Abstract CRTP base for every successful result, contributing
`resultType` (an open string; defaults to `"complete"`) and optional `_meta`
(`ResultMeta`). `CacheableResult<T>` (see above) extends it, so the five
cacheable results inherit it transitively; `CallToolResult`, `GetPromptResult`,
`CompleteResult`, `PingResult`, and `ServerDiscoverResult` all extend it
directly or transitively. |
+| `ResultMeta` | Per-result metadata: `io.modelcontextprotocol/serverInfo` (an
`Implementation`, the server's own identity) plus bare
`traceparent`/`tracestate`/`baggage`, populated when an active tracer echoes
the request's trace context back to the caller. Declares `KEY_SERVER_INFO` as
its wire-key constant; same extension-map shape as `RequestMeta`. |
+| `PingResult` | Typed, empty success result for `ping` (previously an untyped
`JsonMap`). |
+| `Icon` | One entry of `Implementation.icons`: required `src`, optional
`mimeType`, ordered `sizes`, and `theme` (`Icon.Theme`: `light`/`dark`). |
+
+`Implementation` gains optional `title`, `description`, and ordered `icons`
(`List<Icon>`) alongside its existing `name`/`version`. `ClientCapabilities`
and `ServerCapabilities` both gain a schema-defined `extensions` map (with a
`put*Extensions(name, value)` convenience setter on each) — distinct from the
pre-existing free-form `experimental` bag, which stays client-side only.
+
+`ServerDiscoverResult` (the `server/discover` result) now extends
`CacheableResult<ServerDiscoverResult>`, inheriting `resultType`/`_meta`/cache
hints, and adds required `supportedVersions` (`["2026-07-28"]`),
`capabilities`, and optional `instructions`. Server identity moved off a
top-level `serverInfo` field onto the inherited `_meta`
(`ResultMeta.serverInfo`) — the same place every other result now carries it.
+
+A representative `tools/call` round trip showing the full `_meta` shape on
both sides:
+
+```json
+// Request
+{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","arguments":{"text":"hello"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"my-client","version":"1.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}
+
+// Response
+{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"my-server","version":"1.0.0"}},"content":[{"type":"text","text":"hello"}]}}
+```
+
+Trace-context propagation (the `traceparent`/`tracestate`/`baggage` keys
above) is a REST-server-adapter behavior, not a bean-level one — see
[Trace-Context
Propagation](/docs/topics/JuneauRestServerMcp#trace-context-propagation-mcp-2026-07-28)
on the `juneau-rest-server-mcp` topic and
[Observability](/docs/topics/RestServerObservability) for the full mechanism.
## Related Modules
diff --git a/pages/topics/10.40.RestServerObservability.md
b/pages/topics/10.40.RestServerObservability.md
index b5d98c72a4..f6413ab30f 100644
--- a/pages/topics/10.40.RestServerObservability.md
+++ b/pages/topics/10.40.RestServerObservability.md
@@ -309,6 +309,106 @@ Set the `RestContext.responseTraceparent` environment /
system property to `fals
The default is `true` (on-when-tracer). A per-resource `@Rest`-annotation
opt-out is tracked as a follow-on.
+## MCP Trace-Context Propagation (`2026-07-28`)
+
+MCP `2026-07-28` endpoints are ordinary `@RestOp`s — `POST /` on
`McpRestServlet`, `POST /mcp` on `McpEndpoint` — so everything above (the
off-by-default contract, `TracerHook`, the OpenTelemetry bridge,
response-header injection) already applies to them with **no MCP-specific
wiring**. This section documents the one MCP-specific extension: a second,
non-HTTP place a trace context can travel — the JSON-RPC request's own
`params._meta` — and the neutral seam that makes that possible witho [...]
+
+**v2-only.** Trace-context propagation is a `2026-07-28` feature; `2025-06-18`
keeps its original HTTP-header-only tracing behavior unchanged — no `_meta`, no
trace echo.
+
+### The neutral carrier seam
+
+`RestOpInvoker` already resolves every `@RestOp` handler argument before it
asks the active `TracerHook` for a span. Three neutral types in
`juneau-rest-server` let a non-HTTP argument (like a parsed MCP
`JsonRpcRequest`) participate in that decision without OpenTelemetry — or any
tracer implementation — knowing what MCP or JSON-RPC are:
+
+```java
+public interface TraceContextCarrier {
+ String get(String key);
+ Iterable<String> keys();
+ void set(String key, String value);
+}
+
+public interface TraceContextExtractor {
+ Optional<TraceContextCarrier> extract(RestRequest request, Object[]
resolvedArguments);
+ default TraceOperation operation(RestRequest request, Object[]
resolvedArguments) {
+ return TraceOperation.DEFAULT;
+ }
+}
+```
+
+- **`TraceContextCarrier`** is a read/write key-value view over W3C fields
(`traceparent`/`tracestate`/`baggage`) from a source that need not be HTTP
headers.
+- **`TraceContextExtractor`** is a bean an adapter (like the `2026-07-28` MCP
binding) publishes; it inspects the already-resolved handler arguments and, if
it recognizes one, hands back a carrier plus a **`TraceOperation`** — an
immutable, low-cardinality span name and attribute set the bridge should use
instead of its own HTTP-derived defaults.
+- **Extraction only happens when a non-no-op `TracerHook` is active.** A
resource with no registered `TracerHook` never resolves a
`TraceContextExtractor` either — the existing zero-allocation no-tracer fast
path is unchanged.
+
+`TracerHook` gains two source-compatible default methods so every existing
implementation — including a bare lambda implementing only
`startSpan(RestRequest)` — keeps compiling unchanged:
+
+```java
+default Scope startSpan(RestRequest request, TraceContextCarrier carrier,
TraceOperation operation) {
+ return startSpan(request); // ignores carrier/operation unless overridden
+}
+
+default void inject(TraceContextCarrier carrier) {
+ // no-op unless overridden
+}
+```
+
+`Scope` gains one more source-compatible default, for observing a
JSON-RPC-level error on an already-HTTP-200 response (see below):
+
+```java
+default void recordRpcError(int code, String message) {
+ // no-op unless overridden
+}
+```
+
+### `params._meta` — a second carrier, not a new protocol
+
+The `2026-07-28` MCP adapter (`juneau-rest-server-mcp-v20260728`) publishes a
stable `TraceContextExtractor` from both `McpRestServlet` and `McpEndpoint` (as
a `@Bean mcpTraceContextExtractor()`). It recognizes the resolved
`JsonRpcRequest` argument and reads its `params._meta`, which may carry the
bare (unprefixed) W3C fields `traceparent`, `tracestate`, and `baggage`
alongside the `io.modelcontextprotocol/`-prefixed negotiation keys. The
extractor hands the bridge a **composite** carrier:
+
+```text
+lookup(key) = params._meta[key] if present, otherwise the HTTP header of the
same name
+```
+
+**An explicit `params._meta` value wins over the equivalent HTTP header,
independently for each of the three W3C fields.** An absent metadata key simply
falls back to the HTTP header. The extractor also derives a `TraceOperation`
naming the span after the MCP method/target (`"tools/call echo"`, `"prompts/get
greet"`, bare `"resources/read"`, or the exact method otherwise) with
`mcp.*`/`gen_ai.*` attributes — see
[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#trace-context-prop
[...]
+
+### How the OpenTelemetry bridge implements the composite
+
+`OtelTracerHook` overrides the three-argument `startSpan` overload above. It
runs the configured `TextMapPropagator` twice against the same `Context`: first
against ordinary HTTP headers, then — only when the extractor recognized a
carrier — a second pass against that carrier. Per the propagator contract,
extraction that finds nothing usable leaves the `Context` from the previous
pass unchanged, so an explicit `params._meta` value wins while an
absent/invalid one falls back to whatever t [...]
+
+The bridge also overrides `inject(TraceContextCarrier)` (backing the neutral
`TraceContexts.inject(TracerHook, TraceContextCarrier)` helper below) using the
same configured propagator against `Context.current()`.
+
+### Result `_meta` echo
+
+At span-start, the OTel bridge already stashes the rendered
`traceparent`/`tracestate`/(non-empty) `baggage` as request attributes for
`TraceContextResponseProcessor` to write as HTTP response headers (see above).
Before returning any successful `2026-07-28` result, the MCP adapter reads
those same request-scoped attributes and copies them into the result's own
`_meta`, alongside the server identity every success already carries:
+
+```json
+{
+ "traceparent": "00-0af7651916cd43dd8448eb211c80319c-<server-span-id>-01",
+ "io.modelcontextprotocol/serverInfo": {"name": "my-server", "version":
"1.0.0"}
+}
+```
+
+So an MCP client reading the JSON-RPC result body sees the same trace
identifiers an HTTP-level observer reads off the response headers. JSON-RPC
errors have no `result`, so there's no error-response echo.
+
+### JSON-RPC errors on an HTTP-200 response
+
+A dated MCP adapter always answers over HTTP `200`, even for a JSON-RPC-level
error (an unknown tool, a malformed request) — so `Scope.setStatusCode(int)`
never sees anything but the adapter's uniform success status for those cases.
Before returning a JSON-RPC error response, `McpRevision` calls the neutral
`Scope.recordRpcError(code, message)` default introduced above on the request's
active tracing scope. `OtelTracerHook`'s override sets
`rpc.response.status_code`, a low-cardinality `e [...]
+
+### Neutral injection helper
+
+A dependency-free static helper stamps the active tracer's current trace
context into an arbitrary carrier, without the caller depending on
OpenTelemetry:
+
+```java
+TraceContexts.inject(TracerHook tracer, TraceContextCarrier carrier);
+```
+
+This has no first-party MCP call site in 10.0.0 — it exists so a future MCP
*client* (or any other non-HTTP caller) can inject the active trace context
into whatever carrier it builds (for example a `RequestMeta` it's about to send
as `params._meta`) using only this class and the installed `TracerHook`.
+
+### Pinned experimental semantic conventions
+
+The `gen_ai.*`/`mcp.*` span attribute names, the tool/prompt/resource
span-naming rules, and the `error.type` mapping above are pinned to the
OpenTelemetry GenAI semantic-conventions snapshot at commit
[`c739977ae690961f36e435504e5c1febaef1f7f3`](https://github.com/open-telemetry/semantic-conventions-genai/tree/c739977ae690961f36e435504e5c1febaef1f7f3),
plus core OpenTelemetry semantic conventions v1.43.0. **All of these
attributes are experimental/development** — the dedicated GenAI sem [...]
+
+### `2025-06-18` is unaffected
+
+None of the above exists on `2025-06-18`: its wire beans have no
`_meta`-carried trace fields, its adapter publishes no `TraceContextExtractor`,
and it continues to rely solely on the HTTP-header propagation and
response-header injection described earlier on this page.
+
## Custom `MetricsRecorder` / `TracerHook` implementations
The SPIs are deliberately tiny — implement them directly when you need a
backend the shipped bridges don't cover (Dropwizard Metrics, an internal
time-series store, a structured-log appender, …):
diff --git a/pages/topics/11.JuneauRestServerMcp.md
b/pages/topics/11.JuneauRestServerMcp.md
index 5c108c6868..7c9eb9ca72 100644
--- a/pages/topics/11.JuneauRestServerMcp.md
+++ b/pages/topics/11.JuneauRestServerMcp.md
@@ -95,6 +95,91 @@ 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.
+## Running Under Spring Boot
+
+Both dated adapters ship a Spring Boot convenience base class,
`SpringMcpRestServlet` —
`org.apache.juneau.rest.server.mcp.v20250618.SpringMcpRestServlet` and
`org.apache.juneau.rest.server.mcp.v20260728.SpringMcpRestServlet` — combining
the revision's `McpEndpoint` mixin with `BasicSpringRestServlet`. This is the
simplest way to expose MCP from a Spring Boot resource with full Spring
dependency-injection support inside your tool/prompt/resource handlers.
+
+### Recommended: extend `SpringMcpRestServlet`
+
+```java
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
+import org.apache.juneau.rest.server.mcp.v20260728.*;
+
+@Rest(path="/mcp")
+public class MyMcpResource extends SpringMcpRestServlet {
+
+ @Override
+ public McpServerConfig getMcpConfig() {
+ return new McpServerConfig().addTool(new MySpringAwareTool());
+ }
+}
+```
+
+You get MCP dispatch (`McpEndpoint`) plus the Spring bean-store bridge
(`BasicSpringRestServlet`, backed by `SpringBeanStore`) in one class — just
implement `getMcpConfig()`. A handler like `MySpringAwareTool` can then resolve
Spring-managed beans via `BeanStore.getBean(MySpringService.class)`.
+
+### General mixin pattern
+
+If you already have your own Spring Boot base resource — one that already
extends `BasicSpringRestServlet` (or a subclass of it) — compose the same mixin
directly instead of going through `SpringMcpRestServlet`:
+
+```java
+@Rest(path="/api")
+public class MyResource extends BasicSpringRestServlet implements McpEndpoint {
+
+ @Override
+ public McpServerConfig getMcpConfig() {
+ return new McpServerConfig().addTool(new MySpringAwareTool());
+ }
+}
+```
+
+This is exactly the shape `SpringMcpRestServlet` uses internally — reach for
it directly when your resource needs to extend some other Spring Boot base
class.
+
+:::caution Don't subclass `McpRestServlet` under Spring Boot
+It's tempting to reuse the plain drop-in servlet (`extends McpRestServlet`,
see "Drop-in servlet" above) from a Spring Boot app. **Don't.** `McpRestServlet
extends BasicRestServlet` — a *sibling* of `SpringRestServlet`, not a subclass
of it — so a class can never extend both, and even setting that aside, only the
mixin path (`BasicSpringRestServlet implements McpEndpoint`) carries the Spring
bean-store bridge MCP handlers rely on. A servlet that extends `McpRestServlet`
inside a Spring B [...]
+:::
+
+### Maven dependencies
+
+```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>
+<!-- and/or juneau-rest-server-mcp-v20250618, if you also need that revision
-->
+```
+
+Which dated adapter you depend on — and therefore which
`SpringMcpRestServlet`/`McpEndpoint` you extend/implement
(`org.apache.juneau.rest.server.mcp.v20250618.*` or
`org.apache.juneau.rest.server.mcp.v20260728.*`) — is a compile-time choice,
exactly like every other MCP binding on this page. Both revisions ship the same
convenience class.
+
+### Servlet registration
+
+Registration is standard Juneau-on-Spring-Boot — nothing MCP-specific. Either
enable opt-in auto-registration:
+
+```properties
+juneau.rest.auto-register=true
+```
+
+or register explicitly with the self-declared-path helper:
+
+```java
+@Bean
+ServletRegistrationBean<?> mcpServlet(BeanStore beanStore) {
+ return JuneauServletRegistrations.forServlet(new MyMcpResource(),
beanStore);
+}
+```
+
+See [REST Server — Self-Registration](/docs/topics/RestServerSelfRegistration)
for the full mechanism (Jetty microservice and `web.xml` equivalents included)
and [juneau-rest-server-springboot](/docs/topics/JuneauRestServerSpringboot) /
[Spring Boot Overview](/docs/topics/SpringBootOverview) for the Spring
bean-store bridge these classes build on.
+
+### Contrast: the Juneau Microservice runtime
+
+Under the Jetty-based Juneau Microservice runtime there is no Spring bean
store at all, so the caveat above doesn't apply: `MyMcpServlet extends
McpRestServlet` (see "Drop-in servlet" above) is the ordinary, correct recipe
there — there's no second bean-resolution path to silently miss.
+
## Writing Handlers
### Raw tool handler
@@ -468,6 +553,78 @@ Registering resource templates through the neutral
`McpServerConfig`/`McpResourc
See
[juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-cache-hints-resource-templates)
for the wire-bean side of cache hints (`McpCacheScope`, `CacheableResult<T>`)
and resource templates (`ResourceTemplate`, `ListResourceTemplatesResult`).
+## Trace-Context Propagation (MCP `2026-07-28`)
+
+**v2-only.** Every `2026-07-28` MCP request participates in distributed
tracing exactly the way any other `@RestOp` invocation does — there is no
MCP-specific wiring to turn on. This section covers the MCP-specific *carrier*:
how `params._meta` interacts with HTTP trace-context headers, and how the
result echoes the active trace back to the caller. For the framework-level seam
(`TraceContextExtractor`, `TraceContextCarrier`, `TraceOperation`,
`TracerHook`, the OpenTelemetry bridge), see [...]
+
+### `params._meta` as a second trace-context carrier
+
+Per the `2026-07-28` schema, `params._meta` may carry the bare (unprefixed)
W3C fields `traceparent`, `tracestate`, and `baggage` alongside the
`io.modelcontextprotocol/`-prefixed negotiation keys:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/call",
+ "params": {
+ "name": "echo",
+ "arguments": {"text": "hi"},
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {"name": "my-client", "version":
"1.0"},
+ "io.modelcontextprotocol/clientCapabilities": {},
+ "traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
+ }
+ }
+}
+```
+
+Both `McpRestServlet` and `McpEndpoint` publish this revision's stable
`TraceContextExtractor` (a `@Bean mcpTraceContextExtractor()`), which — only
when an active `TracerHook` is registered — recognizes the resolved
`JsonRpcRequest` argument before the span opens and builds a composite carrier
over `params._meta`:
+
+```text
+lookup(key) = params._meta[key] if present, otherwise the HTTP header of the
same name
+```
+
+**An explicit `params._meta` value wins over the equivalent HTTP header —
independently for `traceparent`, `tracestate`, and `baggage`.** An absent
metadata key simply falls back to the HTTP header, letting an MCP client
propagate its own trace context even when the transport-level HTTP headers
belong to some other hop (a gateway, a load balancer, etc.). With no tracer
registered, this extraction never runs at all — the same zero-allocation
no-tracer fast path described on the observabil [...]
+
+### Result `_meta` echo
+
+Before returning any successful result, the adapter copies the active trace
context — whatever the `TracerHook` bridge rendered at span-start, whether that
came from `params._meta` or the HTTP fallback — into the result's own `_meta`,
alongside the server identity every success already carries:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "_meta": {
+ "io.modelcontextprotocol/serverInfo": {"name": "my-server", "version":
"1.0.0"},
+ "traceparent": "00-0af7651916cd43dd8448eb211c80319c-<server-span-id>-01"
+ },
+ "content": [{"type": "text", "text": "hi"}]
+ }
+}
+```
+
+The same values also reach the ordinary HTTP `traceparent`/`tracestate`
response headers (see the observability page) — an MCP client and an HTTP-level
observer read the identical trace identifiers, whichever surface they look at.
`tracestate`/`baggage` are only added when non-empty. JSON-RPC errors have no
`result`, so there is no error-response echo — see below for how an error is
still observed on the span.
+
+### Span naming, attributes, and JSON-RPC errors
+
+When the OpenTelemetry bridge (`juneau-rest-server-tracing-otel`) is active as
the `TracerHook`, it enriches the span with pinned, **experimental**
`gen_ai.*`/`mcp.*` attributes derived from the request, and names the span
after the MCP operation rather than the bare HTTP method:
+
+| Method | Span name | Notable attributes |
+|---|---|---|
+| `tools/call` (tool `echo`) | `"tools/call echo"` | `gen_ai.tool.name=echo`,
`gen_ai.operation.name=execute_tool` |
+| `prompts/get` (prompt `greet`) | `"prompts/get greet"` |
`gen_ai.prompt.name=greet` |
+| `resources/read` | `"resources/read"` (the URI stays an attribute, never a
span-name suffix) | `mcp.resource.uri=<uri>` |
+| everything else | the exact method, e.g. `"tools/list"` | — |
+
+Every span additionally carries `mcp.method.name`, `mcp.protocol.version`, and
(when present) `jsonrpc.request.id`.
+
+Because a dated MCP adapter always answers over HTTP `200` — even for a
JSON-RPC-level error like an unknown tool — the HTTP status alone can never
mark a span failed. Before returning a JSON-RPC error response, `McpRevision`
calls a neutral `Scope.recordRpcError(code, message)` on the request's active
tracing scope; the OpenTelemetry bridge maps that into
`rpc.response.status_code`, a low-cardinality `error.type` category, and
`StatusCode.ERROR` with the JSON-RPC message as the status d [...]
+
+All of the above attribute names and span-naming choices are pinned to a
specific, **experimental** OpenTelemetry GenAI semantic-conventions snapshot —
see [Observability — Micrometer +
OpenTelemetry](/docs/topics/RestServerObservability#mcp-trace-context-propagation-2026-07-28)
for the exact pin, the full attribute reference, and the neutral seam these
classes are built on.
+
## 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.