Skip to main content
Agentgateway implements the MCP Authorization specification, allowing you to require valid JWT bearer tokens before granting access to MCP servers. The mcpAuthentication policy handles token validation, exposes OAuth resource metadata, and optionally adapts non-spec-compliant identity providers.

How it works

When mcpAuthentication is configured on a route:
  1. The gateway exposes an OAuth protected resource metadata endpoint at /.well-known/oauth-protected-resource/<path>
  2. Unauthenticated requests receive 401 Unauthorized with a WWW-Authenticate header pointing to the metadata endpoint
  3. MCP clients follow the OAuth flow to obtain a token from your authorization server
  4. Requests with a valid bearer token are forwarded to the upstream MCP server

Provider scenarios

Use this configuration when your authorization server fully implements the MCP Authorization spec. Agentgateway acts as the resource server and validates tokens directly.
policies:
  cors:
    allowHeaders:
    - mcp-protocol-version
    - content-type
    allowOrigins:
    - '*'
    exposeHeaders:
    - "Mcp-Session-Id"
  mcpAuthentication:
    mode: strict
    issuer: http://localhost:9000
    audiences:
    - http://localhost:3000/stdio/mcp
    jwks:
      url: http://localhost:9000/.well-known/jwks.json
    resourceMetadata:
      resource: http://localhost:3000/stdio/mcp
      scopesSupported:
      - read:all
      bearerMethodsSupported:
      - header
      - body
      - query
      resourceDocumentation: http://localhost:3000/stdio/docs
      resourcePolicyUri: http://localhost:3000/stdio/policies
The route must also match the well-known metadata path:
matches:
- path:
    exact: /stdio/mcp
- path:
    exact: /.well-known/oauth-protected-resource/stdio/mcp

JWKS configuration

The gateway loads JSON Web Key Sets (JWKS) either from a URL or from a local file:
mcpAuthentication:
  issuer: https://your-idp.example.com
  jwks:
    url: https://your-idp.example.com/.well-known/jwks.json

JWT validation options

By default, the exp (expiration) claim is required in every token. You can customize which RFC 7519 registered claims must be present using jwtValidationOptions.requiredClaims. Only the following claim names are recognized: exp, nbf, aud, iss, sub. Any other value is silently ignored.
This setting only enforces presence. Standard claims like exp are always validated when present — an expired token is rejected regardless of requiredClaims.
mcpAuthentication:
  issuer: https://enterprise-idp.example.com
  audiences:
  - https://api.mycompany.com/mcp
  jwks:
    url: https://enterprise-idp.example.com/.well-known/jwks.json
  jwtValidationOptions:
    requiredClaims: []
Tokens without exp remain valid until the signing key is rotated. Only use requiredClaims: [] when your identity provider intentionally omits expiration and you have a key rotation strategy in place.

Running the authentication example

1

Start the demo dependencies

The authentication example uses Keycloak and a mock authorization server. Start them with:
make run-validation-deps
This starts the mock authorization server on http://localhost:9000 and Keycloak on http://localhost:7080.
2

Start the gateway

cargo run -- -f examples/mcp-authentication/config.yaml
3

Test unauthenticated access

A request without a token should return 401 Unauthorized:
curl -i http://localhost:3000/stdio/mcp
The response includes a WWW-Authenticate header with a link to the resource metadata endpoint.
4

Test with MCP Inspector

npx @modelcontextprotocol/inspector
Set transport to Streamable and URL to http://localhost:3000/stdio/mcp. The MCP Authorization flow starts automatically after the initial 401 response. For Keycloak, use credentials testuser / testpass.

What the provider adapter does

When you set a provider, the gateway acts as an Authorization Server facade for MCP clients:
  • Exposes resource metadata at /.well-known/oauth-protected-resource/...
  • Exposes authorization server metadata at /.well-known/oauth-authorization-server/... — pointing back to the gateway itself
  • Fetches the real AS metadata from your issuer and rewrites it per-provider to smooth over protocol gaps
  • Proxies client registration (Keycloak only) at .../client-registration
Omit the provider block entirely when your authorization server is already spec-compliant.

Troubleshooting

  • Ensure issuer matches the iss claim in your tokens exactly.
  • Ensure each entry in audiences matches the aud claim clients request.
  • Verify the resource metadata is reachable at /.well-known/oauth-protected-resource/... and that the resource value matches the audiences entry.
  • Check that the JWKS URL is accessible from the gateway process.