The Docs MCP Server can require an OAuth 2.0 access token on its MCP endpoint. Authentication is off by default, so local use stays frictionless.
When enabled, the server is an OAuth 2.0 resource server. It does not issue tokens, register clients, or host sign-in pages. An external identity provider (Clerk, Auth0, Keycloak, Microsoft Entra ID, and others) does all of that. MCP clients discover the provider from the server, sign the user in there, and send the token they receive with every request. The server accepts a token only when it is a JWT that the configured provider issued for this server.
This follows the MCP authorization specification (protocol revision 2026-07-28).
| Surface | Protected by authentication | How to protect it |
|---|---|---|
MCP endpoint (/mcp) over HTTP |
Yes | Bearer token, as described here |
Deprecated SSE transport (/sse, /messages) |
Not served | Only available while authentication is off |
| MCP over stdio | No | The host that launches the process is the trust boundary. Enabling auth logs a warning. |
| Web UI | No | A reverse proxy with its own sign-in (OAuth2-Proxy, Authelia, Traefik forward-auth, Cloudflare Access), or a private network |
HTTP API (/api) and its WebSocket |
No | Same as the web UI. On a loopback bind the server also rejects requests from foreign browser origins. |
Worker link (--server-url) |
No | A private network between coordinator and worker |
At startup the server states this boundary, naming the protected MCP endpoint. When authentication is enabled on a command that serves no MCP endpoint over HTTP (web, worker), it warns that authentication protects nothing in that process.
For putting the web UI and API behind a reverse proxy, see Reverse Proxy Deployment.
sequenceDiagram
participant Client as MCP Client
participant Server as docs-mcp-server
participant Provider as Identity Provider
Client->>Server: MCP request without a token
Server-->>Client: 401 + WWW-Authenticate: Bearer resource_metadata="…"
Client->>Server: GET protected resource metadata
Server-->>Client: resource = public URL + /mcp, authorization_servers = [issuer]
Client->>Provider: Discover, register, sign the user in (PKCE)
Provider-->>Client: Access token with aud = public URL + /mcp
Client->>Server: MCP request with Authorization: Bearer token
Server-->>Client: MCP response
- A request without a valid token gets
401withWWW-Authenticate: Bearer resource_metadata="<public URL>/.well-known/oauth-protected-resource/mcp". When the request carried a token that was rejected, the challenge also carrieserror="invalid_token". - The protected resource metadata (RFC 9728) names the MCP endpoint's public URL as
resourceand the configured issuer as the only authorization server. Itsresource_nameis the server's title, or its name when there is no title, so clients that show it at sign-in use the same name as everywhere else. It is served at the URL in the challenge and at the RFC 9728 well-known location, and it is readable from any origin. - The client talks to the provider directly. It asks for a token for the
resourceit just learned, per RFC 8707. - The client sends
Authorization: Bearer <token>on every request.
| Setting | CLI | Environment | Required | Description |
|---|---|---|---|---|
auth.enabled |
--auth-enabled |
DOCS_MCP_AUTH_ENABLED |
– | Turns authentication on for the MCP endpoint. |
auth.issuerUrl |
--auth-issuer-url |
DOCS_MCP_AUTH_ISSUER_URL |
yes | The provider's issuer identifier, an https URL. It must match the issuer the provider publishes exactly, including any trailing slash. |
server.publicUrl |
--public-url |
DOCS_MCP_SERVER_PUBLIC_URL |
yes | The URL clients use to reach the server, e.g. https://docs.example.com or https://example.com/docs. |
auth.audience |
--auth-audience |
DOCS_MCP_AUTH_AUDIENCE |
no | The audience tokens must carry, an absolute URL or a URN. It defaults to <public URL>/mcp. |
The --auth-* flags exist on the commands that serve MCP: the default command and mcp. The environment variables and the configuration file apply to every command.
The advertised resource and the expected audience both derive from server.publicUrl. The server never guesses them from its bind address or from the request's Host header. That is why a public URL is required, and startup fails with an error naming the missing setting.
npx @arabold/docs-mcp-server@latest \
--auth-enabled \
--auth-issuer-url "https://your-app.clerk.accounts.dev" \
--public-url "https://docs.example.com"Tokens must then carry aud = https://docs.example.com/mcp.
At startup the server fetches the provider's metadata from the same well-known locations MCP clients try, RFC 8414 first and then OpenID Connect Discovery. Startup fails when no document answers, when the published issuer differs from auth.issuerUrl, or when the metadata has no jwks_uri. The error lists the URLs it tried.
Whatever provider you use, it needs to:
- Issue JWT access tokens signed with keys published in its
jwks_uri. The server rejects opaque tokens. - Put this server in
aud. Ideally the provider setsaudfrom the RFC 8707resourceparameter the client sends, which is<public URL>/mcp. A provider that issues a fixed audience works too: setauth.audienceto that value. The override must name an API or resource identifier, never a client ID, or the provider's ID tokens would be accepted as access tokens. - Let MCP clients register themselves, through Client ID Metadata Documents (preferred in MCP 2026-07-28) or Dynamic Client Registration (RFC 7591).
- Support PKCE with S256.
Enable these OAuth application settings on the Clerk instance:
| Setting | Value | Why |
|---|---|---|
JWT access tokens (oauth_jwt_access_tokens) |
on | Clerk issues opaque tokens otherwise |
aud claim (aud_claim_enabled) |
on | Clerk sets aud to the requested resource |
PKCE required (pkce_required) |
on | MCP clients always use PKCE |
| Dynamic client registration | on | Lets MCP clients register themselves |
Client ID Metadata Documents (client_id_metadata_documents_advertised) |
recommended | The registration method MCP 2026-07-28 prefers |
With the Clerk CLI:
clerk api /instance/oauth_application_settings --app <app_id> --instance dev \
-X PATCH -d '{"oauth_jwt_access_tokens":true,"aud_claim_enabled":true,"pkce_required":true}'Then run the server with --auth-issuer-url https://<your-app>.clerk.accounts.dev and your public URL. You don't need auth.audience.
The server accepts a request only when its bearer token:
- is a JWT signed with a key the configured issuer publishes,
- has
issequal to the configured issuer, - has an
audthat contains the expected audience, - and has not expired.
There is no other path to access. A token issued for another application on the same provider is rejected, and so is any opaque token. If the provider's keys can't be retrieved, the request fails with 500 rather than 401, so clients don't restart sign-in because of a provider outage.
Access is binary: any valid token for this server grants every MCP tool.
Startup fails with "no public URL is configured". Set server.publicUrl (or --public-url) to the URL clients use.
Startup fails with "Auth configuration validation failed". auth.issuerUrl must be an https URL, and auth.audience, when set, an absolute URL or a URN.
Startup fails because auth.issuerUrl "does not match the issuer the authorization server publishes". Copy the issuer value from the provider's metadata exactly. A trailing slash counts.
Clients get 401 with error="invalid_token". Decode the token (for example at jwt.io) and compare:
audshould equal<public URL>/mcp, orauth.audienceif you set one;issshould equalauth.issuerUrl;expshould be in the future.
With Clerk, a missing aud or an opaque token means the settings above aren't enabled.
A client can't find the provider. Check that the resource_metadata URL from the challenge is reachable from the client. Behind a reverse proxy it must pass through: see Reverse Proxy Deployment.
Debug logging. Run with --verbose to see token verification failures.
Serve the MCP endpoint over HTTPS, through TLS termination at a reverse proxy: the server itself speaks plain HTTP, and bearer tokens travel in every request.
The host and origin checks apply whether authentication is on or off. Authentication controls who may call MCP tools, not where the scraper connects or which files it reads; Infrastructure Security covers those limits.