Skip to content

MCP OAuth Stage 2: well-known PRM, AS metadata, JWKS endpoints #92

@heskew

Description

@heskew

MCP OAuth Stage 2: well-known PRM, AS metadata, JWKS endpoints

Sub-issue of #86. Adds the three discovery documents under /.well-known/* that MCP clients fetch to find the authorization server and the public keys for verifying tokens.

Context

PR #89 (Stage 1) shipped the RFC 7591 Dynamic Client Registration endpoint. This stage adds the documents that MCP clients consume to discover that endpoint and the rest of the OAuth surface. Without these, a client that hits the protected resource and receives a 401 + WWW-Authenticate: Bearer resource_metadata="..." (issued by Stage 5's withMCPAuth) has nowhere to follow up.

What this adds

  1. /.well-known/oauth-protected-resource — RFC 9728 Protected Resource Metadata. The discovery entry point. Advertises the canonical resource URI and the authorization server.
  2. /.well-known/oauth-authorization-server — RFC 8414 Authorization Server Metadata. Advertises authorize / token / register / JWKS endpoints, supported algorithms and methods, and RFC 8707 resource parameter support.
  3. /.well-known/jwks.json — placeholder returning an empty key set until Stage 4 generates and persists signing keys in harper_oauth_mcp_keys.
  4. New MCP config fields mcp.issuer and mcp.resource — derived from the request when unset, configurable for production deployments behind proxies or with pinned identity requirements.
  5. CORS headers on all three documents so browser-based MCP clients and discovery tools can fetch cross-origin.
  6. Mounting via server.http(handler, { urlPath }) — well-known paths don't fit the existing Resource API mounted at /oauth/*, so they use Harper's middleware mechanism directly. Config read via getter at request time so live config changes apply without re-registering routes.

Spec requirements

  • RFC 9728 (MUST): PRM document at /.well-known/oauth-protected-resource with resource + authorization_servers fields
  • RFC 8414 (MUST): AS metadata at /.well-known/oauth-authorization-server; issuer field required, must match request origin
  • MCP 2025-06-18 (MUST): AS metadata advertises code_challenge_methods_supported: ["S256"], resource_parameter_supported: true (RFC 8707)
  • Best practice: CORS-accessible discovery documents so browser clients work

Acceptance

  • PRM endpoint at /.well-known/oauth-protected-resource serves spec-compliant JSON with resource and authorization_servers
  • AS metadata endpoint at /.well-known/oauth-authorization-server serves spec-compliant JSON including registration_endpoint, code_challenge_methods_supported: ["S256"], jwks_uri, id_token_signing_alg_values_supported: ["RS256", "EdDSA"], resource_parameter_supported: true
  • JWKS endpoint at /.well-known/jwks.json serves {"keys": []} placeholder; Stage 4 populates
  • All three documents include Access-Control-Allow-Origin: * and Access-Control-Allow-Methods: GET, OPTIONS
  • mcp.issuer and mcp.resource config fields exist; defaults derive from request origin
  • Documented in docs/configuration.md including Host-header security caveat for issuer derivation
  • Unit tests cover URI resolution, each document's required fields, CORS headers, handler registration, fall-through paths, and exact-path matching

All acceptance items shipped in PR #90.

Dependencies

Out of scope

  • Populated JWKS — Stage 4 generates signing keys
  • Authorize/token endpoints (Stages 3, 4)
  • Bearer-token validation contract (Stage 5)

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions