Class McpResourceServerSupport
401/403 challenge emission, and RFC 9728 Protected Resource Metadata construction —
driven by an McpResourceServerConfig.
Bearer extraction and token validation are delegated to the reusable
OAuthFilter (RFC 6750) from
juneau-rest-server-auth-oauth; this class layers on the MCP-specific resource_metadata challenge
parameter, RFC 8707 audience matching (McpAudienceValidator), and baseline required-scope enforcement.
- Since:
- 10.0.0
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringRequest attribute under whichauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)stashes the caller's granted scopes (the validated token's roles) so the POST-parse dispatch point can enforce SEP-2350 per-operation step-up scopes. -
Method Summary
Modifier and TypeMethodDescriptionstatic Principalauthenticate(McpResourceServerConfig cfg, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res) Authenticates the request against the RS config, establishing the authenticated principal.static McpProtectedResourceMetadataBuilds the RFC 9728 Protected Resource Metadata document from the config.static voidenforceOperationScopes(McpResourceServerConfig cfg, Collection<String> grantedScopes, McpOperationContext ctx) Enforces SEP-2350 per-operation step-up scopes at the POST-parse dispatch point.static voidgateMcpEndpoint(McpResourceServerConfig cfg, RestContext ctx, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res) Router-identity bearer gate for theMcpEndpointmixin's MCP JSON-RPC endpoint.static voidgateMcpServlet(McpResourceServerConfig cfg, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res) Router-aligned bearer gate for theMcpRestServlet'sPOST /endpoint.grantedScopes(jakarta.servlet.http.HttpServletRequest req) Returns the granted scopes stashed byauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)for this request.static StringBuilds theWWW-Authenticatechallenge for a token missing a required baseline scope (error="insufficient_scope", with the requiredscopeset).static StringBuilds theWWW-Authenticatechallenge for an invalid/expired token or an audience mismatch (error="invalid_token").static McpProtectedResourceMetadataServes this endpoint's PRM from the RFC 9728 path-inserted well-known location, but only when the requested suffix matches this resource's expected well-known path; otherwise404.static McpProtectedResourceMetadataServes this endpoint's PRM from the SEP-2351 root well-known location (fallback), failing fast on a non-root or re-mounted mount; a no-op-throwing404when RS auth is disabled.static URIReturns the path-inserted well-known PRM URI advertised in theresource_metadatachallenge parameter.static StringBuilds theWWW-Authenticatechallenge for a request that presented no bearer credentials (RFC 6750 §3: noerrorcode).static Principalprincipal(jakarta.servlet.http.HttpServletRequest req) Returns the authenticatedPrincipalauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)stashed for this request.static booleansatisfies(Collection<String> grantedScopes, Collection<String> requiredScopes) Returns whether the granted scopes satisfy every required scope, using exact string equality.static StringstepUpChallenge(McpResourceServerConfig cfg, Collection<String> operationScopes) Builds the SEP-2350 scoped step-upWWW-Authenticatechallenge naming the scopes required to invoke a specific operation (error="insufficient_scope", with the operation'sscopeset).
-
Field Details
-
GRANTED_SCOPES_ATTR
Request attribute under whichauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)stashes the caller's granted scopes (the validated token's roles) so the POST-parse dispatch point can enforce SEP-2350 per-operation step-up scopes.- See Also:
-
-
Method Details
-
gateMcpEndpoint
public static void gateMcpEndpoint(McpResourceServerConfig cfg, RestContext ctx, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res) Router-identity bearer gate for theMcpEndpointmixin's MCP JSON-RPC endpoint.A no-op unless RS auth is
enabledand this request resolves to the MCP JSON-RPC dispatch operation. SeeresolvesToMcpDispatch(RestContext, HttpServletRequest)for how the target operation is identified without any hand-rolled path reconstruction — the key change that makes this gate immune to@Mixin(path/paths)re-mounts and to trailing-slash / percent-encoding / method-override drift.Fails fast with the same origin-root
500diagnostic as the well-known route (seeassertWellKnownMountReachable(RestRequest)) — rather than authenticating — whenever the resolved MCP dispatch operation is itself mounted under a@Mixin(path/paths)re-mount prefix: a401on such a mount would advertise aresource_metadataURL that the re-mount has already made unreachable, so the two routes must agree rather than leaving a broken half-state.- Parameters:
cfg- The RS config. Must not benull .ctx- The hostRestContexthandling this request (injected into the@RestStartCallhook). Must not benull .req- The raw HTTP request. Must not benull .res- The raw HTTP response (used to emit the challenge header on rejection). Must not benull .
-
gateMcpServlet
public static void gateMcpServlet(McpResourceServerConfig cfg, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res) Router-aligned bearer gate for theMcpRestServlet'sPOST /endpoint.A no-op unless RS auth is
enabledand this request resolves to aPOST(the dedicated MCP servlet's only writable surface). Gating every effectivePOSTis correct for a dedicated MCP servlet: there is no non-MCP writable surface to distinguish, so no per-operation identity check is needed.- Parameters:
cfg- The RS config. Must not benull .req- The raw HTTP request. Must not benull .res- The raw HTTP response (used to emit the challenge header on rejection). Must not benull .
-
metadataForWellKnownRoot
public static McpProtectedResourceMetadata metadataForWellKnownRoot(McpResourceServerConfig cfg, RestRequest req) Serves this endpoint's PRM from the SEP-2351 root well-known location (fallback), failing fast on a non-root or re-mounted mount; a no-op-throwing404when RS auth is disabled.- Parameters:
cfg- The RS config.req- The REST request.- Returns:
- The PRM document.
-
metadataForWellKnownPathInserted
public static McpProtectedResourceMetadata metadataForWellKnownPathInserted(McpResourceServerConfig cfg, RestRequest req) Serves this endpoint's PRM from the RFC 9728 path-inserted well-known location, but only when the requested suffix matches this resource's expected well-known path; otherwise404. Fails fast on a non-root or re-mounted mount; a404when RS auth is disabled.- Parameters:
cfg- The RS config.req- The REST request.- Returns:
- The PRM document.
-
buildMetadata
Builds the RFC 9728 Protected Resource Metadata document from the config.- Parameters:
cfg- The RS config. Must beenabledand valid.- Returns:
- The PRM bean.
-
metadataUri
Returns the path-inserted well-known PRM URI advertised in theresource_metadatachallenge parameter.- Parameters:
cfg- The RS config.- Returns:
- The PRM document URI.
-
authenticate
public static Principal authenticate(McpResourceServerConfig cfg, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res) Authenticates the request against the RS config, establishing the authenticated principal.On success the principal is stashed under
"juneau.principal"and returned. On failure theWWW-Authenticate: Bearer ...challenge (carrying theresource_metadatapointer) is written directly tores— because an exception thrown from a@RestStartCallhook does not route through the response processors that would otherwise copy anAuthenticationException's headers — and then a401 Unauthorized(AuthenticationException) or403 Forbidden(insufficient scope) is thrown.- Parameters:
cfg- The RS config. Must beenabledand valid.req- The HTTP request. Must not benull .res- The HTTP response, used to write the challenge header on failure. Must not benull .- Returns:
- The authenticated principal. Never
null .
-
grantedScopes
Returns the granted scopes stashed byauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)for this request.- Parameters:
req- The HTTP request. May benull .- Returns:
- The granted scopes, or an empty set when none were stashed (RS auth disabled / gate not run).
-
principal
Returns the authenticatedPrincipalauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)stashed for this request.Reads the same framework-standard
"juneau.principal"attributeauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)writes on success, so this is the single source of truth for "who is calling this MCP request". It is the seam the2026-07-28dispatcher threads intoRequestStateCodec.seal(org.apache.juneau.rest.server.mcp.v20260728.McpRequestState, java.lang.String, java.security.Principal)/RequestStateCodec.unseal(java.lang.String, java.lang.String, java.security.Principal)for principal-boundrequestStateAAD. MirrorsgrantedScopes(HttpServletRequest): anull request, an absent attribute, or a non-Principalvalue all returnnull — the anonymous / RS-auth-disabled path, which every caller must handle without an NPE. UnlikegrantedScopes(HttpServletRequest), which falls back to an emptySetin that same situation, this returnsnull rather than an empty value in that case; the divergence is intentional — a scalar identity has no natural "empty" representative the way a collection does.- Parameters:
req- The HTTP request. May benull .- Returns:
- The authenticated principal, or
null when none was stashed (RS auth disabled / gate not run / anonymous caller).
-
enforceOperationScopes
public static void enforceOperationScopes(McpResourceServerConfig cfg, Collection<String> grantedScopes, McpOperationContext ctx) Enforces SEP-2350 per-operation step-up scopes at the POST-parse dispatch point.A no-op unless RS auth is
enabledand the resolved operation has a non-emptyrequired-scope set. When the caller'sgrantedScopesdo notsatisfythe operation's required scopes, throws a403 Forbiddencarrying aWWW-Authenticate: Bearer ..., error="insufficient_scope", scope="..."step-up challenge that names exactly the scopes required for this operation.This is the enforcement point the security review mandated: unlike the
@RestStartCallbearer gate (which fires before the JSON-RPC body is parsed), this runs once the MCP method — and thus the operation — is known, so the challenge can be scoped to the specific operation. It layers on top of, and never weakens, the baseline gate (op-identity, origin-root fail-fast, audience, and baseline required-scope enforcement all still run first inauthenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)).- Parameters:
cfg- The RS config. Must not benull .grantedScopes- The caller's granted scopes. May benull (treated as empty).ctx- The resolved operation. Must not benull .- Throws:
Forbidden-403 insufficient_scopewhen the granted scopes do not satisfy the operation's required scopes.
-
stepUpChallenge
public static String stepUpChallenge(McpResourceServerConfig cfg, Collection<String> operationScopes) Builds the SEP-2350 scoped step-upWWW-Authenticatechallenge naming the scopes required to invoke a specific operation (error="insufficient_scope", with the operation'sscopeset).- Parameters:
cfg- The RS config.operationScopes- The scopes required to invoke the operation.- Returns:
- The challenge value.
-
satisfies
public static boolean satisfies(Collection<String> grantedScopes, Collection<String> requiredScopes) Returns whether the granted scopes satisfy every required scope, using exact string equality.A required scope
ris satisfied only when some granted scopegis exactly equal to it. OAuth scopes have no universal hierarchy, so a grantedrepodoes not imply a requiredrepo:writeorrepo.admin— a broad-but-low-privilege scope must never authorize a differently named privileged operation. Applications that want scope implication must expand their granted scopes themselves before calling this method; this method does not provide a hierarchical-implication SPI.- Parameters:
grantedScopes- The caller's granted scopes. Nevernull .requiredScopes- The scopes required to invoke the operation. Nevernull .- Returns:
true if every required scope is satisfied.
-
missingTokenChallenge
Builds theWWW-Authenticatechallenge for a request that presented no bearer credentials (RFC 6750 §3: noerrorcode).Carries a
scopehint listing the endpoint-wide baseline required scopes when any are configured, so a client can request them on its first authorization; the hint is omitted (noscope="") when no baseline scope is required.- Parameters:
cfg- The RS config.- Returns:
- The challenge value.
-
invalidTokenChallenge
Builds theWWW-Authenticatechallenge for an invalid/expired token or an audience mismatch (error="invalid_token").- Parameters:
cfg- The RS config.- Returns:
- The challenge value.
-
insufficientScopeChallenge
Builds theWWW-Authenticatechallenge for a token missing a required baseline scope (error="insufficient_scope", with the requiredscopeset).- Parameters:
cfg- The RS config.- Returns:
- The challenge value.
-