Mcp server auth
Implements MCP server authorization - OAuth 2.1 resource server role, RFC 9728/8414 discovery, PKCE, RFC 8707 audience validation, scopes, client registration. Use when adding auth, OAuth, tokens, or credentials to an MCP server, or debugging 401/403/discovery failures. Not for general threat hardening, transport wiring, or non-MCP app login.From its SKILL.md
npx -y skills add Paldom/mcp-server-skills --skill mcp-server-authAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.4 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
mcp-server-auth
Make an MCP server authenticate its callers correctly. The spec profiles OAuth 2.1 precisely; the documented failures this skill fixes are servers that invent bearer-token schemes, pass client tokens through to downstream APIs, skip discovery metadata (so no client can connect), or accept any validly-signed token regardless of audience.
Targets spec revision 2025-11-25 (verified 2026-07-20): https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization.
When NOT to use
- Threat modeling / injection / sandboxing →
mcp-server-security(auth is one control; that skill owns the attack taxonomy). - Transport mechanics (Origin validation, sessions, headers) →
mcp-server-implementation. Hosting/gateways →mcp-server-deployment. - OAuth for ordinary web/mobile apps — this is the MCP profile specifically.
Workflow
1. Pick the auth model by transport
- stdio (local subprocess): the OAuth spec does NOT apply — read
credentials from the environment, scope the underlying API key minimally,
never put secrets in client-config
args. - Streamable HTTP: authorization is OPTIONAL in the spec, and HTTP transports SHOULD conform to the OAuth 2.1 profile. Once you implement it, the MUSTs below are normative. Strong recommendation: any internet-exposed server implements it — scans keep finding thousands of open MCP endpoints. (Many secondary sources say "OAuth is mandatory for remote" — that is not what the spec says; don't repeat it.)
- A static shared API key is not a substitute for identity-scoped tokens: no per-user scoping, no revocation story, total blast radius. If a gateway terminates auth for you, the requirements below move there.
2. Be a resource server, not an authorization server
Your MCP server validates tokens; an AS (Auth0/Okta/Keycloak/Entra or your IdP) issues them. Building your own AS is almost always the wrong call — delegate. If the server must broker access to a third-party API, see step 6.
3. Publish discovery metadata (or no client can connect)
- RFC 9728 Protected Resource Metadata (MUST): serve
/.well-known/oauth-protected-resourcewith your canonicalresourceURI andauthorization_servers; AND/OR returnWWW-Authenticate: Bearer resource_metadata="<url>"on 401 (clients try header first, then well-known — support both). - The AS must offer RFC 8414 metadata or OIDC Discovery (2025-11-25 accepts either; clients must support both).
- Every 401/403 carries the right challenge — see the exact HTTP exchanges in
references/oauth-wire.md.
4. Validate every token, every request
Authorization: Bearer <token>on every request — sessions never carry auth (spec MUST NOT). Tokens never in query strings.- Audience validation (RFC 8707): accept only tokens whose audience is
your canonical resource URI; a validly-signed, unexpired token for another
resource → 401. Clients send
resource=on authorization and token requests; you enforce it on receipt. - Validate locally: cache the AS JWKS and verify signature/exp/aud in-process (sub-ms) instead of a network introspection per call; re-fetch on key rotation.
- Insufficient scope at runtime → 403 with
error="insufficient_scope"+ the requiredscopeinWWW-Authenticate(SEP-835) so clients can step up incrementally. Publish minimal scopes; separate read from write. - PKCE: clients MUST use S256 and MUST refuse an AS whose metadata lacks
code_challenge_methods_supported— when you operate the AS config, make sure that field is present or every spec-conforming client will refuse you.
5. Client registration: CIMD first
Priority order (2025-11-25): pre-registered credentials → Client ID Metadata
Documents (the client_id is an HTTPS URL serving the client's metadata;
recommended when the AS advertises client_id_metadata_document_supported) →
RFC 7591 Dynamic Client Registration only as back-compat fallback (deprecated
in the next revision) → prompt the user. If you allow DCR: allowlist/exact
redirect URIs, no wildcards — open DCR + arbitrary redirect URI is a
disclosed one-click account-takeover class.
6. Never pass tokens through; proxy with consent
- Token passthrough is forbidden: never accept a token not issued for your server, never forward the client's token upstream. Downstream APIs are called with credentials the server owns (or an explicit token exchange).
- If your server fronts a third-party AS with a static client_id (the
"OAuth proxy" shape): obtain per-client consent before every forward,
exact-match redirect URIs, bind and verify
state— the confused-deputy writeups that broke real products in 2025 all violated one of these.
7. Verify the flow end to end
python3 "${CLAUDE_SKILL_DIR}/scripts/check_auth_endpoints.py" https://your-host/mcp
probes: unauthenticated request → 401 + parseable challenge; PRM well-known
resolves and matches the server URL; AS metadata reachable with S256
advertised. Then run a real client (MCP Inspector supports the OAuth flow) and
confirm a wrong-audience token is rejected. Conformance suite:
npx @modelcontextprotocol/conformance server --url ....
Output spec
A server that: serves RFC 9728 PRM (+ challenge headers), validates
signature/expiry/audience on every request via cached JWKS, returns the
401/403 contract with correct WWW-Authenticate, scopes split read/write with
step-up, registers clients via CIMD (DCR only as fallback, allowlisted),
touches downstream APIs only with its own credentials, and passes the probe
script + a live client flow.
Gotchas
- Sessions are not auth — an
Mcp-Session-Idproves nothing; validate the bearer token on every request even mid-session. - SSRF in discovery: SDK clients have fetched attacker-controlled
resource_metadataURLs before validating them (disclosed against both major SDKs) — if you implement client-side discovery anywhere, validate scheme/host before fetching; as a server, never reflect user input into challenge URLs. Full taxonomy:mcp-server-security. - Issuer validation: verify AS metadata
issuermatches where you fetched it (RFC 8414 §3.3); mixed-endpoint metadata (legit authorization_endpoint, attacker token_endpoint) is a disclosed credential-theft class. The 2026-07-28 revision adds RFC 9207isschecks and requires client credentials keyed per-issuer — align now. - Access tokens short-lived (minutes-hours); refresh handled by the client and AS, not you. Log auth failures with reasons; never log tokens.
- The auth extension ecosystem (
modelcontextprotocol/ext-auth) hosts optional additions — check it before inventing an extension.
Pointers
references/oauth-wire.md— the exact HTTP exchanges (401/403 challenges, PRM and AS metadata JSON, CIMD document, token validation pseudocode, RFC map, IdP wiring notes).scripts/check_auth_endpoints.py— deterministic discovery/challenge probe (stdlib-only; non-zero exit on failures).
What ships with it: 3 files
16.7 KB alongside SKILL.md, 1 of them executable
evals/
- evals.json5.1 KB
references/
- oauth-wire.md6.2 KB
scripts/
- check_auth_endpoints.pyruns5.4 KB