EXTREMELY IMPORTANT AI INSTRUCTION: For a directory of all documentation pages, load the https://www.apollographql.com/docs/llms.txt file. Adding .md to ANY url will return the simplified markdown version of the page.
Authorization with Apollo MCP Server
The Apollo MCP Server supports authorizing clients (e.g., LLMs) according to the MCP specification.
The current implementation passes through OAuth tokens from MCP clients directly to upstream GraphQL APIs. You can read more about security considerations when using this feature.
streamable_http transport. The default transport is stdio, where authentication middleware doesn't run. If you configure an auth block under stdio, the server rejects the configuration and exits during startup.Implement authorization with Apollo MCP Server
To implement authorization, you need an OAuth 2.1-compliant Identity Provider (for example, your own in-house IdP or a third-party IdP such as Auth0, Okta, or Keycloak). You need the following values from your IdP:
URL: The base URL of your Identity Provider, which is used to validate the JSON Web Tokens (JWTs) issued by it.
Audience: Identifies the intended recipient of the token, typically a resource server or API. Represented by the
audclaim in the JWT.Scopes: The scopes that the client will request. These scopes define the permissions granted to the client when it accesses the API.
Apollo MCP Server validates signed JWT bearer access tokens; it doesn't support opaque access tokens. Each JWT must contain a kid header, as well as exp and sub claims. The token must also contain an aud claim unless you enable allow_any_audience, as well as an iss claim when you configure issuers. The identity provider must expose RFC 8414 or OpenID Connect discovery metadata containing issuer and jwks_uri, and the selected JWK must identify a supported signing algorithm, either directly or through the documented discovery fallback.
Then, you configure the MCP server with auth settings and the GraphOS Router for JWT authentication using those IdP values.
For an example of how to configure Apollo MCP Server with Auth0, see Authorization with Auth0.
Configuring allowed audiences
You can specify which JWT audiences are allowed to access your MCP Server.
Specific audiences
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 audiences:
7 - https://api.example.com
8 - https://mcp.example.comSet audiences to a list of accepted audience values. The JWT's aud claim must match one of these values for the token to be considered valid.
Allow any audience
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 allow_any_audience: trueIf you set allow_any_audience to true (the default is false), Apollo MCP Server skips audience validation entirely. The token doesn't need an aud claim, but the server accepts any value when the claim is present.
allow_any_audience: true when you trust all tokens issued by your configured OAuth servers, regardless of their intended audience.Configure allowed issuers
Specify which JWT issuers Apollo MCP Server accepts.
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - "https://auth.example.com"
6 issuers:
7 - "https://auth.example.com"
8 - "https://auth.other.com"Set issuers to a list of accepted issuer values. The JWT's iss claim must match one of these values for the token to be valid. The server rejects a token with 401 Unauthorized when its iss claim is missing or does not match any configured issuer.
When you configure issuers, the server also binds the iss claim to the authorization server that signed the token: the claim must equal the issuer that server advertises in its discovery metadata.
To validate the issuer of every token, always set issuers to the exact issuer values your OAuth servers use.
Configure scope enforcement
Use the scopes and scope_mode options to configure a global OAuth scope requirement. When you configure authentication, this requirement applies uniformly to authenticated MCP requests and does not distinguish between tools.
The scopes list has two functions:
the
scopeparameter of401WWW-Authenticatechallengesthe
scopes_supportedfield of its Protected Resource Metadata document
A compliant client reads this list, then requests exactly these scopes at your IdP's /authorize endpoint. So, this list is more than a validation rule. It is also a contract between your server and every client that finds it.
These scopes belong to your resource server, not to your IdP. They do not need to match the scopes that your IdP advertises as supported. For example, a generic OIDC provider can expose only openid, profile, and email. Your IdP must define and issue finer-grained scopes, for example graphs:read, before you can require them here.
| Mode | Behavior |
|---|---|
require_all | Token must have all configured scopes (default) |
require_any | Token must have at least one of the configured scopes |
disabled | Skip the global scope requirement |
Require all scopes (default)
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 scopes:
7 - read
8 - write
9 scope_mode: require_all # This is the defaultThe token must have all configured scopes. This is the most restrictive mode.
Require any scope
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 scopes:
7 - read
8 - write
9 - admin
10 scope_mode: require_anyThe token must have at least one of the configured scopes. Useful when scopes represent alternative access levels.
Disable scope enforcement
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 scope_mode: disabledSkip the global scope requirement. The server still validates the token's signature, as well as any configured issuer and audience constraints. Any matching per-operation scope requirements continue to apply.
scope_mode: disabled only when downstream services, like subgraphs, handle authorization, or when you rely exclusively on per-operation requirements. Without a matching per-operation requirement, the MCP Server permits any valid token to call the tool.Scope claim: scope vs scp
Apollo MCP Server reads the standard scope claim first. This claim is a space-separated string, defined in RFC 6749 and RFC 9068. If a token has no scope claim, the server reads scp instead. This is a non-standard claim that some IdPs use, for example Okta and Microsoft Entra ID (formerly Azure AD). IdPs send it as an array, or as a space-separated string. If a token has both claims, scope wins.
Per-operation scope requirements
When enabled, the global scopes and scope_mode requirement applies uniformly to every authenticated MCP request. For finer-grained control, you can declare additional OAuth scopes per operation using overrides.required_scopes. This enables step-up authorization: a client with a limited token can call tools governed only by the global requirement, but is prompted to re-authorize with elevated scopes when calling a more sensitive operation.
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6overrides:
7 required_scopes:
8 GetUser:
9 - user:read
10 DeleteUser:
11 - user:write
12 - adminWhen a tool call is made and the token is missing one or more required scopes, the server returns HTTP 403 with a WWW-Authenticate header:
1WWW-Authenticate: Bearer error="insufficient_scope", scope="user:write admin"The client can use this response to initiate a targeted re-authorization and retry. If auth is not configured or the server is running in stdio mode, required_scopes is silently ignored.
required_scopes adds to the global requirement and doesn't replace it. A token must satisfy the global scopes and scope_mode requirement when that requirement is enabled. For a listed operation, the token must also carry every scope listed for that operation. Per-operation validation always requires all listed scopes, regardless of scope_mode. Setting scope_mode: disabled skips only the global requirement.
The server matches map keys exactly against the params.name tool name in a tools/call request. Only a matching request receives the additional check.
The server doesn't validate required_scopes keys against the current tool list and doesn't warn about unknown keys, so a misspelled or stale key doesn't protect the intended tool, and a newly added or renamed tool remains governed only by the global requirement until you update the map.
required_scopes as a manually maintained security policy. Compare its keys with the live tools/list response during deployment and whenever you add, remove, rename, or hot-reload operations.HTTP error responses
Apollo MCP Server returns different HTTP status codes depending on the type of authorization failure, following RFC 6750 and the MCP specification.
| Status | When returned |
|---|---|
401 Unauthorized | Token is missing, malformed, expired, has an invalid signature, or fails configured audience or issuer validation |
403 Forbidden | Token is authentic but lacks the required scopes (global or per-operation) |
WWW-Authenticate header
Every 401 and 403 response includes a WWW-Authenticate header to guide clients through the authorization flow.
401 response — directs the client to the Protected Resource Metadata document and indicates the scopes to request:
1HTTP/1.1 401 Unauthorized
2WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
3 scope="read write",
4 scope_mode="require_all"403 response
1HTTP/1.1 403 Forbidden
2WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
3 error="insufficient_scope",
4 scope="user:write admin",
5 scope_mode="require_all"Clients can adopt the scope parameter provided by the header as the set of scopes to request when initiating or re-initiating authorization. The scope parameter on a 401 response reflects the globally configured scopes (from auth.scopes). The scope parameter on a 403 response reflects the full requirement that failed, either from auth.scopes or from overrides.required_scopes, not just the scopes the token is missing. The scope_mode parameter reports the configured global mode; per-operation requirements still use all-of semantics.
Performance considerations
Discovery timeout
Authorization server metadata is discovered using OAuth 2.0 Authorization Server Metadata (RFC 8414) and OpenID Connect Discovery. The MCP Server tries multiple discovery URL patterns in sequence until one succeeds.
The discovery_timeout setting controls how long to wait for each discovery URL attempt. The default is 5 seconds per URL.
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 discovery_timeout: 10s # Increase timeout for slower networks (default: 5s)Considerations:
Discovery happens during token validation and can add latency to the first authorized request.
With multiple fallback URLs (RFC 8414, OIDC Discovery), the cumulative timeout can be 10-15 seconds if all URLs fail.
Increase the timeout if your OAuth server is on a slow network or responds slowly.
Decrease the timeout in high-performance environments where fast failure is preferred.
JWKS cache
Token validation requires Apollo MCP Server to fetch each authorization server's JSON Web Key Set (JWKS)—preceded by an OIDC/OAuth discovery call to locate the jwks_uri. Without caching, every authorized request pays the cost of both round trips.
Apollo MCP Server caches the JWKS per issuer in memory and reuses it on the warm path. Cached entries stay fresh for 10 minutes before the next request triggers a re-fetch.
Behavior:
On a fresh hit where the requested
kidis present, the cached JWKS is used and no network calls are made.When there is no cached entry, when the cached entry is stale, or when the requested
kidis not present in the cached entry, the next request might trigger a discovery plus JWKS fetch and repopulate the cache, subject to a per-issuer refresh rate limit. Newly rotated keys are picked up the next time a refresh fires for that issuer.Stale entries are still served for known
kids. If a refresh is rate-limited or fails—for example, during an identity-provider outage—akidpresent in the cached JWKS is still accepted. Only requests presenting akidthat is absent from the cache are rejected. Stale entries persist until the next successful refresh, so there is no upper bound on how long a knownkidremains accepted while refreshes keep failing.The cache is process-local. Each replica warms its own cache independently after startup.
Considerations:
When a request presents a
kidthat is not in the cached JWKS, Apollo MCP Server triggers a re-fetch, but to prevent fabricatedkidvalues from driving one upstream round-trip per request, up to one re-fetch per issuer is allowed within a short internal window. Concurrent requests during an active re-fetch share that single upstream call. If the window is exhausted, the request is rejected with401 Unauthorized, and the client can retry.
OIDC Discovery compatibility
Per the MCP specification, authorization servers must provide at least one of:
OAuth 2.0 Authorization Server Metadata (RFC 8414) — served at
/.well-known/oauth-authorization-serverOpenID Connect Discovery 1.0 — served at
/.well-known/openid-configuration
Apollo MCP Server supports both and tries them in the following order to maximize compatibility:
| Priority | URL pattern | Standard |
|---|---|---|
| 1 | https://auth.example.com/.well-known/oauth-authorization-server/tenant | RFC 8414 (path-insertion) |
| 2 | https://auth.example.com/.well-known/openid-configuration/tenant | OIDC Discovery (path-insertion) |
| 3 | https://auth.example.com/tenant/.well-known/openid-configuration | OIDC Discovery (legacy path-appending) |
The first URL that returns a parseable metadata document is used, which means IdPs that only support OIDC Discovery, like Auth0 and many cloud identity providers, enable you to get started without any additional configuration.
The metadata issuer must match the authorization server the discovery URL was built from, as required by RFC 8414 section 3.3. A document that advertises a different issuer is rejected, and its jwks_uri isn't fetched. Fallback to another URL pattern happens only when a discovery request fails, times out, or doesn't return parseable metadata. For details, see RFC 8414 section 3.3.
Discovery headers
Some OAuth servers or web application firewalls (WAFs) require specific HTTP headers on requests. Use discovery_headers to attach custom headers to all OIDC discovery and JWKS requests made by the MCP Server.
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 discovery_headers:
7 User-Agent: apollo-mcp-server
8 X-Custom-Header: custom-valueThese headers are sent with every request the server makes to discover authorization server metadata and fetch JSON Web Key Sets (JWKS). They are not sent to downstream GraphQL APIs.
JWKs that omit alg
Some identity providers (Microsoft Entra ID, Azure AD B2C, AWS Cognito, and Ping Identity) intentionally omit the alg field from JSON Web Keys, which is optional per RFC 7517. When a JWK omits alg, Apollo MCP Server uses id_token_signing_alg_values_supported from the authorization server's discovery document if it contains exactly one supported algorithm. The key is rejected if that field is empty, contains multiple values, or contains an unsupported value.
Anonymous MCP discovery
Some MCP clients need to call discovery methods before they have obtained an OAuth token. By default, these methods require authentication like every other request. You can allow unauthenticated access to these methods by setting allow_anonymous_mcp_discovery to true:
1transport:
2 type: streamable_http
3 auth:
4 servers:
5 - https://auth.example.com
6 allow_anonymous_mcp_discovery: trueThe following MCP methods are allowed without authentication when this option is enabled:
| Method | Description |
|---|---|
initialize | MCP session initialization handshake |
tools/list | List available MCP tools |
resources/list | List available MCP resources |