Class McpResourceServerSupport

java.lang.Object
org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerSupport

public final class McpResourceServerSupport extends Object
Request-time glue for the OAuth 2.1 resource-server baseline — bearer validation, RFC 8707 audience enforcement, 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 Details

  • 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 the McpEndpoint mixin's MCP JSON-RPC endpoint.

      A no-op unless RS auth is enabled and this request resolves to the MCP JSON-RPC dispatch operation. See resolvesToMcpDispatch(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 500 diagnostic as the well-known route (see assertWellKnownMountReachable(RestRequest)) — rather than authenticating — whenever the resolved MCP dispatch operation is itself mounted under a @Mixin(path/paths) re-mount prefix: a 401 on such a mount would advertise a resource_metadata URL 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 be null.
      ctx - The host RestContext handling this request (injected into the @RestStartCall hook). Must not be null.
      req - The raw HTTP request. Must not be null.
      res - The raw HTTP response (used to emit the challenge header on rejection). Must not be null.
    • gateMcpServlet

      public static void gateMcpServlet(McpResourceServerConfig cfg, jakarta.servlet.http.HttpServletRequest req, jakarta.servlet.http.HttpServletResponse res)
      Router-aligned bearer gate for the McpRestServlet's POST / endpoint.

      A no-op unless RS auth is enabled and this request resolves to a POST (the dedicated MCP servlet's only writable surface). Gating every effective POST is 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 be null.
      req - The raw HTTP request. Must not be null.
      res - The raw HTTP response (used to emit the challenge header on rejection). Must not be null.
    • metadataForWellKnownRoot

      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-throwing 404 when RS auth is disabled.
      Parameters:
      cfg - The RS config.
      req - The REST request.
      Returns:
      The PRM document.
    • metadataForWellKnownPathInserted

      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; otherwise 404. Fails fast on a non-root or re-mounted mount; a 404 when 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 be enabled and valid.
      Returns:
      The PRM bean.
    • metadataUri

      Returns the path-inserted well-known PRM URI advertised in the resource_metadata challenge 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 the WWW-Authenticate: Bearer ... challenge (carrying the resource_metadata pointer) is written directly to res — because an exception thrown from a @RestStartCall hook does not route through the response processors that would otherwise copy an AuthenticationException's headers — and then a 401 Unauthorized (AuthenticationException) or 403 Forbidden (insufficient scope) is thrown.

      Parameters:
      cfg - The RS config. Must be enabled and valid.
      req - The HTTP request. Must not be null.
      res - The HTTP response, used to write the challenge header on failure. Must not be null.
      Returns:
      The authenticated principal. Never null.
    • grantedScopes

      public static Set<String> grantedScopes(jakarta.servlet.http.HttpServletRequest req)
      Parameters:
      req - The HTTP request. May be null.
      Returns:
      The granted scopes, or an empty set when none were stashed (RS auth disabled / gate not run).
    • principal

      public static Principal principal(jakarta.servlet.http.HttpServletRequest req)
      Returns the authenticated Principal authenticate(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" attribute authenticate(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 the 2026-07-28 dispatcher threads into RequestStateCodec.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-bound requestState AAD. Mirrors grantedScopes(HttpServletRequest): a null request, an absent attribute, or a non-Principal value all return null — the anonymous / RS-auth-disabled path, which every caller must handle without an NPE. Unlike grantedScopes(HttpServletRequest), which falls back to an empty Set in that same situation, this returns null 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 be null.
      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 enabled and the resolved operation has a non-empty required-scope set. When the caller's grantedScopes do not satisfy the operation's required scopes, throws a 403 Forbidden carrying a WWW-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 @RestStartCall bearer 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 in authenticate(org.apache.juneau.rest.server.mcp.v20260728.McpResourceServerConfig, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)).

      Parameters:
      cfg - The RS config. Must not be null.
      grantedScopes - The caller's granted scopes. May be null (treated as empty).
      ctx - The resolved operation. Must not be null.
      Throws:
      Forbidden - 403 insufficient_scope when 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-up WWW-Authenticate challenge naming the scopes required to invoke a specific operation (error="insufficient_scope", with the operation's scope set).
      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 r is satisfied only when some granted scope g is exactly equal to it. OAuth scopes have no universal hierarchy, so a granted repo does not imply a required repo:write or repo.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. Never null.
      requiredScopes - The scopes required to invoke the operation. Never null.
      Returns:
      true if every required scope is satisfied.
    • missingTokenChallenge

      Builds the WWW-Authenticate challenge for a request that presented no bearer credentials (RFC 6750 §3: no error code).

      Carries a scope hint 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 (no scope="") when no baseline scope is required.

      Parameters:
      cfg - The RS config.
      Returns:
      The challenge value.
    • invalidTokenChallenge

      Builds the WWW-Authenticate challenge for an invalid/expired token or an audience mismatch (error="invalid_token").
      Parameters:
      cfg - The RS config.
      Returns:
      The challenge value.
    • insufficientScopeChallenge

      Builds the WWW-Authenticate challenge for a token missing a required baseline scope (error="insufficient_scope", with the required scope set).
      Parameters:
      cfg - The RS config.
      Returns:
      The challenge value.