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 d04f52c9b0 docs: OAuth 2.1-secured MCP example (juneau-examples-mcp)
d04f52c9b0 is described below
commit d04f52c9b0a4be6e705df172b858dd0a4a236ad8
Author: James Bognar <[email protected]>
AuthorDate: Thu Aug 6 11:15:34 2026 -0700
docs: OAuth 2.1-secured MCP example (juneau-examples-mcp)
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/10.0.0.md | 10 ++++++++++
pages/topics/11.03.JuneauMcpRecipes.md | 2 ++
pages/topics/11.04.JuneauRestServerMcp.md | 2 ++
pages/topics/11.05.JuneauRestClientMcp.md | 2 ++
4 files changed, 16 insertions(+)
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 32942e6cfd..6cc19567fd 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -1002,6 +1002,16 @@ A new example module, `juneau-examples-mcp` (under
`juneau-examples/`, alongside
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` — OAuth 2.1-secured variant of `juneau-examples-mcp`
(`secured/` package)
+
+The example module above gets a second, fully **offline-runnable** variant
demonstrating the OAuth 2.1 authorization baseline (TODO-312f) end to end:
`org.apache.juneau.examples.mcp.secured` wraps the identical notes service in a
live resource-server gate, with a demo authorization server standing in for a
real IdP so the whole walkthrough needs no external network access.
+
+- **`SecuredExampleMcpServer extends ExampleMcpServer`** — the entire security
story is one `createMcpOptions()` override layering `McpResourceServerConfig`
on top of the inherited notes surface: RS auth enabled, `setResource(...)` (the
RFC 9728 resource identity / RFC 8707 default audience), a `JwtTokenValidator`
checking `iss`/`aud`/`exp`/`nbf` against the offline AS,
`addAuthorizationServer(...)`, a baseline `addRequiredScope("mcp.read")`, and
SEP-2350 per-operation step-up (`addOpera [...]
+- **`OfflineAuthorizationServer`** — a clearly-marked DEMO-ONLY stand-in AS: a
fresh self-generated RSA signing key per run, an offline `JWKSource` handed
straight to the validator (no JWKS HTTP fetch), a real RFC 6749 §4.4
client-credentials `/token` endpoint gated by a fixed scope allowlist
(`mcp.read`/`mcp.write`, else `invalid_scope`), and an RFC 8414
`/.well-known/oauth-authorization-server` metadata document (issuer = its own
loopback base URL, plus `token_endpoint`) enabling genui [...]
+- **`SecuredExampleServer`/`SecuredExampleClient`** — a launcher plus a real,
discovery-driven end-to-end client walkthrough: an unauthenticated call
rejected with `401` + `WWW-Authenticate: Bearer`, RFC 9728 PRM discovery
(`authorization_servers`, `scopes_supported`), RFC 8414 AS-metadata discovery
of the `token_endpoint`, a client-credentials token acquisition via
`juneau-rest-client-mcp-auth`'s `McpTokenProvider`, a successful `mcp.read`
read, the same token rejected with `403 insuffi [...]
+
+See the [OAuth 2.1
recipe](/docs/topics/JuneauMcpRecipes#securing-an-mcp-client-and-server-with-oauth-21-v2-only)
for the full walkthrough, 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.
diff --git a/pages/topics/11.03.JuneauMcpRecipes.md
b/pages/topics/11.03.JuneauMcpRecipes.md
index 5613f58f42..f2dfacb5a4 100644
--- a/pages/topics/11.03.JuneauMcpRecipes.md
+++ b/pages/topics/11.03.JuneauMcpRecipes.md
@@ -448,6 +448,8 @@ try (var client =
McpClient.connect("http://localhost:8080/mcp")) {
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.
+**Prefer to start from running code?** The `secured/` package inside
[`juneau-examples-mcp`](#full-example-juneau-examples-mcp) is the canonical,
copy-me, fully offline realization of this entire recipe — and it ships as part
of the same downloadable project zip in the Apache release.
`SecuredExampleMcpServer` layers the resource-server config below onto the
plain `ExampleMcpServer` with a single `createMcpOptions()` override (baseline
`mcp.read`, plus a SEP-2350 per-operation step-up re [...]
+
**Dependencies.** On the server, add a token-validator module alongside
`juneau-rest-server-mcp-v20260728`: `juneau-rest-server-auth-jwt` (JWT/JWKS
validation) or `juneau-rest-server-auth-oauth` (RFC 7662 introspection). On the
client, add `juneau-rest-client-mcp-auth` alongside
`juneau-rest-client-mcp-v20260728`:
```xml
diff --git a/pages/topics/11.04.JuneauRestServerMcp.md
b/pages/topics/11.04.JuneauRestServerMcp.md
index de3c122c73..33a6298df8 100644
--- a/pages/topics/11.04.JuneauRestServerMcp.md
+++ b/pages/topics/11.04.JuneauRestServerMcp.md
@@ -621,6 +621,8 @@ WWW-Authenticate: Bearer realm="mcp",
error="insufficient_scope", scope="repo.de
All of the operation's required scopes are emitted in a single `scope=` param
(the SEP-2350 "single challenge" SHOULD). Sufficiency is **hierarchy-aware**
per the server MUST: a broader granted scope implies its narrower children
(granted `repo` satisfies required `repo.delete` / `repo:read`, but granted
`repo.read` does *not* satisfy required `repo`). Operations with no configured
per-operation scope are unaffected — the baseline alone governs them. The
client half of the flow (union th [...]
+**See it wired end to end.** `SecuredExampleMcpServer` in the `secured/`
package of
[`juneau-examples-mcp`](/docs/topics/JuneauMcpRecipes#securing-an-mcp-client-and-server-with-oauth-21-v2-only)
applies exactly this pattern with one `createMcpOptions()` override: a
baseline `addRequiredScope("mcp.read")` plus `addOperationScope("tools/call",
"publishNote", "mcp.write")` (and `deleteNote`) for the step-up, running fully
offline against a demo authorization server.
+
## Cache Hints (MCP `2026-07-28`, SEP-2549)
The `2026-07-28` adapter (`org.apache.juneau.rest.server.mcp.v20260728`) adds
two capabilities on top of the neutral core: SEP-2549 cache hints on five
list/read results, and a `resources/templates/list` endpoint. Both are
configured statically, at server-construction time — there is no
dynamic/per-request cache callback, and neither concept exists on the neutral
`juneau-rest-server-mcp` core or the `2025-06-18` adapter.
diff --git a/pages/topics/11.05.JuneauRestClientMcp.md
b/pages/topics/11.05.JuneauRestClientMcp.md
index dc2a061dde..f82476ee29 100644
--- a/pages/topics/11.05.JuneauRestClientMcp.md
+++ b/pages/topics/11.05.JuneauRestClientMcp.md
@@ -145,6 +145,8 @@ Fetches an authorization server's metadata as an
`OidcMetadata` record (`issuer`
The full SEP-2351 path-insertion well-known-URI construction (path-bearing
issuers, ordered fallbacks) is a deferred enhancement; `OidcDiscoveryClient`
currently implements the two-endpoint (RFC 8414 → OIDC) fallback the baseline
requires. An OIDC ID token returned alongside an access token is surfaced
verbatim on `OAuthToken.idToken()` but is **not validated** in this baseline —
do not trust its claims for auth decisions.
:::
+**See it wired end to end.** `SecuredExampleClient` in the `secured/` package
of
[`juneau-examples-mcp`](/docs/topics/JuneauMcpRecipes#securing-an-mcp-client-and-server-with-oauth-21-v2-only)
drives this exact discovery chain with nothing hardcoded — from a `401`
challenge through `McpProtectedResourceMetadataClient` (RFC 9728) and
`discoverAuthorizationServer(...)` (RFC 8414) to a discovered `token_endpoint`
it hands straight to `McpTokenProvider.clientCredentials()`.
+
### Dynamic Client Registration (SEP-837)
When you have no pre-registered `client_id`, `McpDynamicClientRegistrar`
performs RFC 7591 / OIDC Dynamic Client Registration against an authorization
server's `registration_endpoint`, built via
`McpDynamicClientRegistrar.create()`. `register()` POSTs the client metadata
and returns an immutable `McpClientRegistration` (`clientId`, `clientSecret`,
`clientSecretExpiresAt`, `registrationAccessToken`, `registrationClientUri`,
`issuer`, `redirectUris`, `applicationType`, `extras`) whose `toS [...]