Authentication
mcp supports multiple authentication methods. For most services, authentication is automatic — you just connect and mcp handles the rest.
How it works
When you call a tool on an HTTP server, mcp follows this sequence:
Check for saved token — Look in
~/.config/mcp/auth.jsonfor a valid, non-expired tokenCheck config headers — Use
Authorizationheader from config if presentOn 401 response — Start the authentication flow:
Try OAuth 2.0 (discovery + PKCE flow)
Fall back to manual token prompt
Tokens are stored per server URL and refreshed automatically when they expire.
OAuth 2.0 (automatic)
Services like Sentry support OAuth 2.0 with the MCP protocol. When you first connect:
mcp sentry --listAuthenticating with https://mcp.sentry.dev...
Opening browser for authorization...Your browser opens, you authorize the app, and the token is saved. Next time, it just works.
What happens under the hood
mcpchecks the server for OAuth Protected Resource Metadata to find the authorization serverFetches the OAuth Authorization Server Metadata (
.well-known/oauth-authorization-server)Registers as a client using Dynamic Client Registration if supported
Generates a PKCE challenge (S256) for security
Opens your browser to the authorization URL
Listens on a local port for the callback (default range
localhost:8085-8099, configurable viaMCP_OAUTH_CALLBACK_PORT)Exchanges the authorization code for tokens
Saves tokens to
~/.config/mcp/auth.json
Token refresh
When a token expires, mcp automatically tries to refresh it using the refresh token. If that fails, it starts the OAuth flow again.
Manual token (interactive)
If a server doesn't support OAuth, mcp falls back to asking for a token. It recognizes popular services and shows helpful instructions:
Paste your token, press Enter. It's saved and used for future requests.
Recognized services
mcp has built-in hints for these services:
Honeycomb
API Key
Account → API Keys
GitHub
Personal Access Token
Settings → Developer settings → Personal access tokens
Sentry
Auth Token
Settings → Account → API → Auth Tokens
Linear
API Key
Settings → API → Personal API Keys
Notion
Integration Token
My Integrations → Create integration
Slack
Bot/User Token
Your app → OAuth & Permissions
Grafana
Service Account Token
Administration → Service Accounts
GitLab
Personal Access Token
User Settings → Access Tokens
Jira
API Token
Manage profile → Security → API tokens
Cloudflare
API Token
Profile → API Tokens
Datadog
API Key
Organization Settings → API Keys
PagerDuty
API Key
User Settings → Create API User Token
For unrecognized services, you get a generic prompt.
Config-based authentication
You can set auth headers directly in the config file:
Use ${ENV_VAR} to avoid hardcoding secrets. Set the env var in your shell profile:
Empty tokens are skipped
If an env var is not set, the Authorization header resolves to Bearer (empty token). mcp detects this and skips the header, falling back to OAuth or manual auth instead.
Token storage
Tokens are saved in ~/.config/mcp/auth.json:
clients— OAuth client registrations (client ID per server)tokens— Access tokens, refresh tokens, and expiry timestamps
Clearing tokens
To re-authenticate, delete the entry from auth.json or delete the whole file:
Next connection will trigger a fresh authentication flow.
Authentication priority
When multiple auth sources exist, the priority is:
Config headers —
Authorizationheader fromservers.json(if non-empty)Saved token — Token from
auth.json(loaded on connect)OAuth flow — Triggered on 401 response
Manual prompt — If OAuth registration fails
Server-side authentication (proxy mode)
The sections above cover client-side authentication — how mcp authenticates when calling remote MCP servers. When running mcp serve --http, the proxy itself can also authenticate incoming requests from clients.
This is configured via the serverAuth key in servers.json. See the proxy mode guide for full details.
Schema change. As of the OAuth Authorization Server feature,
serverAuthtakes aproviders: ["..."]array instead of a singleprovider: "..."string. The new shape lets a single instance accept multiple kinds of bearer credentials in parallel — for example, static tokens for local CLI tools and OAuth-issued JWTs for Claude.ai web. Configs using the oldproviderfield will silently boot asNoAuth; rewrite them as shown below.
Quick example
Each entry in tokens supports two shapes:
Legacy (string):
"tok-alice": "alice"— subject only, no roles.Extended (object):
"tok-bob": { "subject": "bob", "roles": ["dev", "oncall"] }— subject plus a list of roles that will flow into ACL evaluation.
Both forms can coexist in the same config file.
Role-based ACL (recommended for new setups)
See proxy mode ACL docs for the full schema, access expansion table, and evaluation model.
Available providers
none (default when providers is empty)
No auth — all requests are anonymous
bearer
Static token-to-user mapping, with optional per-token roles
forwarded
Trust reverse proxy X-Forwarded-User header (and optional X-Forwarded-Groups for roles)
oauth_as
Run an OAuth 2.0 Authorization Server with Dynamic Client Registration so Claude.ai, ChatGPT, Cursor and other AI clients can connect as Custom Connectors. See the OAuth AS how-to.
providers is a list — combine multiple in the same instance:
The chain tries each provider in order; the first one that accepts the request wins. When all reject, the chain returns the error of the first configured provider — which avoids leaking which token format hit a closer-to-success path. Order is a performance hint (cheap lookups first), not a correctness one.
Forwarded provider and roles
With "forwarded" in providers, the proxy reads the user from the configured user header (default x-forwarded-user) and, optionally, a groups header (default x-forwarded-groups, following the oauth2-proxy convention) to populate roles.
Groups header value is parsed as a comma-separated list: each entry is trimmed and empty entries are dropped. Missing header yields an empty role list (not an error). Role matching is case-sensitive.
Only use
forwardedbehind a trusted reverse proxy that strips these headers from incoming client requests — otherwise clients could forge identities and roles.
Access control (ACL)
The ACL controls which authenticated users can access which tools. It supports two schemas:
Role-based (recommended) — define reusable roles with server-aware, read/write-aware grants. Evaluation is union-based and order-independent. Deny always wins.
Legacy — flat rules list with first-match-wins semantics, fully backward compatible.
See proxy mode ACL docs for the full schema reference and examples.
Last updated
Was this helpful?