MCP server authentication checklist: OAuth, tokens, scopes and transports
An MCP server is properly authenticated when every HTTP request carries an OAuth access token issued for that server alone, with narrow scopes, and the server never forwards that token anywhere else. AdminKit's checklist walks through each requirement in the MCP authorization specification, from transport choice to scopes, so you can confirm your server meets it.
By AdminKit team, Netbase JSC · Updated
Key takeaways
- Pick the transport first. A STDIO server takes credentials from its environment. A server on HTTP should use OAuth as the MCP authorization specification describes.
- Make the server discoverable: answer a missing token with
401and aWWW-Authenticateheader that points to its OAuth Protected Resource Metadata. - Accept only tokens issued for this server. Check the audience on every request, not once per session.
- Never forward the caller’s token. Use a separate token for any downstream API.
- Keep scopes narrow, ask for more with a
403 insufficient_scopechallenge, and keep tokens out of URLs and logs.
What does authentication mean for an MCP server?
The Model Context Protocol (MCP) lets an AI agent call tools on a server. Authentication answers “who is calling?” Authorization answers “what may they do?” MCP handles both with OAuth, the open standard for delegated access tokens.
In the MCP authorization specification, a protected MCP server acts as an OAuth 2.1 resource server. That means it receives access tokens and checks them; it does not issue them. A separate authorization server, often your existing identity provider, issues the tokens. The MCP client, such as an AI app or agent, gets a token and sends it with each request. The glossary entry on OAuth for MCP summarizes the flow.
Authorization is optional in the specification. A server with no authentication can still be spec-compliant. This checklist is for servers that hold anything worth protecting, which is most of them.
This page is one step of AdminKit’s checklist to vet and secure an MCP server before connecting it to an AI agent. That guide covers the consumer side. This one is for whoever runs the server.
Is your server on STDIO or HTTP?
The transport decides which rules apply. STDIO means the client starts the server as a local process and talks to it over standard input and output. Streamable HTTP means the server listens on a URL and any client that can reach it can send requests.
The specification is explicit: implementations using an HTTP-based transport “SHOULD conform to this specification”, and implementations using STDIO “SHOULD NOT follow this specification, and instead retrieve credentials from the environment.”
| STDIO transport | Streamable HTTP transport | |
|---|---|---|
| Who can reach it | Only the client process that started it | Anyone who can reach the URL |
| How it gets credentials | From the environment, such as variables or a local file | OAuth access tokens on every request |
| OAuth discovery and tokens | Not used | Required by this checklist |
| Main risks | The launch command, and what the process can read | Stolen or wrong-audience tokens, open endpoints, DNS rebinding |
| What to check | Pinned version, sandbox, least-privilege credentials | Every item below |
The transports page adds HTTP rules that sit next to OAuth. Servers “MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks”. A server meant for local use only “SHOULD bind only to localhost (127.0.0.1)”. See MCP transport security for how the two transports differ.
The checklist at a glance
Each row is one requirement from the MCP specification or a standard it builds on. “MUST” and “SHOULD” are the specification’s own words.
| # | Check | Level | Source |
|---|---|---|---|
| 1 | HTTP server validates Origin; local-only server binds to 127.0.0.1 | MUST / SHOULD | MCP Transports |
| 2 | Server publishes Protected Resource Metadata with at least one authorization server | MUST | MCP Authorization, RFC 9728 |
| 3 | Missing or invalid token gets 401 with WWW-Authenticate | MUST | MCP Authorization |
| 4 | Server accepts only tokens issued for itself (audience check) | MUST | MCP Authorization, RFC 8707 |
| 5 | Server never passes the client’s token to another API | MUST NOT | MCP Authorization |
| 6 | Every request is authenticated; the session is never the credential | MUST | MCP Security Best Practices |
| 7 | Missing scope gets 403 with error="insufficient_scope" | SHOULD | MCP Authorization |
| 8 | Narrow scopes; no * or full-access | Best practice | MCP Security Best Practices, RFC 9700 |
| 9 | Tokens never in the URL query string; stored securely, not logged | MUST | MCP Authorization |
| 10 | Authorization server: HTTPS, PKCE S256, exact redirect URIs, short-lived tokens | MUST / SHOULD | MCP Authorization, OAuth 2.1 |
The sections below explain each item in plain terms.
Can a client discover how to authenticate?
A client meeting your server for the first time needs to know where to get a token. The specification answers this with OAuth 2.0 Protected Resource Metadata, defined in RFC 9728. It is a small JSON document that names the authorization servers your server trusts.
- Publish the metadata (item 2). MCP servers “MUST implement” RFC 9728, and the document “MUST include the
authorization_serversfield containing at least one authorization server.” Serve it from the/.well-known/oauth-protected-resourcepath. See OAuth protected resource metadata for the fields. - Point to it from the 401 (item 3). Invalid or expired tokens “MUST receive a HTTP 401 response”. Put the metadata URL in the
WWW-Authenticateheader asresource_metadata, so the client does not have to guess.
An illustrative exchange for a server at https://mcp.example.com/mcp; the hostnames are placeholders:
POST /mcp HTTP/1.1
Host: mcp.example.com
Content-Type: application/json
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp",
scope="tickets:read"
The scope parameter is optional. The specification says servers “SHOULD include” it, so the client asks for only what the first request needs.
Does the server accept only tokens issued for itself?
This is the most important item (item 4). A token can be perfectly valid and still be meant for a different service. The specification says MCP servers “MUST only accept tokens specifically intended for themselves and MUST reject tokens that do not include them in the audience claim”.
The audience is the field in a token that names its intended recipient. The client asks for it with the resource parameter from RFC 8707, set to your server’s canonical URL. The specification makes that parameter mandatory for clients. This is general OAuth advice too: the OWASP OAuth 2.0 Cheat Sheet says bearer tokens should be audience-restricted, preferably to a single resource server, to limit the impact of a leaked token.
On the server, check on every request:
- Signature and issuer. The token comes from an authorization server you listed in your metadata. For a JWT, verify the signature against that server’s published keys.
- Audience. The audience names this server’s canonical URL, such as
https://mcp.example.com/mcp. Reject anything else, even from your own identity provider. - Expiry. Reject expired tokens with
401. - Scopes. The token carries the scope this tool needs. Enforce it in your own code; a scope in the token is a claim, not a permission check.
A quick test: get a token from your identity provider for a different API and send it to your MCP server. It must fail with 401.
Does the server ever pass a token through?
Token passthrough means accepting the client’s token and forwarding it unchanged to another API (item 5). The specification is direct: “The MCP server MUST NOT pass through the token it received from the MCP client.” If your server calls another API, it acts as an OAuth client and uses “a separate token, issued by the upstream authorization server.”
Passthrough turns your server into a confused deputy: a program that misuses its own access on someone else’s behalf. The downstream API cannot tell whether your server checked the token. The MCP confused-deputy problem and token passthrough walks through the attack and the fix, with a code example.
Search your code for any place that copies the incoming Authorization header into an outgoing request. Each one is a finding.
Is every request authenticated, not just the session?
Streamable HTTP servers can hand out an MCP-Session-Id header to tie requests into one conversation. It is tempting to check the token once and then trust the session ID. Don’t (item 6).
The authorization specification says authorization “MUST be included in every HTTP request from client to server, even if they are part of the same logical session.” The MCP security best practices add: “MCP Servers MUST NOT use sessions for authentication.” They also require “secure, non-deterministic session IDs”, and recommend binding session data to the user, with a key such as <user_id>:<session_id> where the user ID comes from the token.
Are scopes narrow, and can a client ask for more later?
A scope is a named permission in a token, such as tickets:read. Narrow scopes limit what a stolen token can do (item 8). RFC 9700, the IETF’s OAuth security best practice, says the privileges of an access token “SHOULD be restricted to the minimum required for the particular application or use case.”
The MCP security best practices list common scope mistakes, including “Publishing all possible scopes in scopes_supported”, “Using wildcard or omnibus scopes (*, all, full-access)” and “Treating claimed scopes in token as sufficient without server-side authorization logic.”
Start each client with a small, read-only set. When a tool needs more, return a scope challenge (item 7). The specification says the server “SHOULD respond with” HTTP 403 Forbidden and a WWW-Authenticate header carrying error="insufficient_scope" and the scopes required:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="tickets:read tickets:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
The client can then ask the user to approve just the extra scope. This is called step-up authorization.
Where do tokens travel, and where are they stored?
Tokens leak through the places they pass, not only through attacks on the protocol (item 9).
- Header only. Clients send the token in the
Authorization: Bearerheader. The specification says access tokens “MUST NOT be included in the URI query string”, because URLs end up in proxy logs, browser history and analytics. - Never log tokens. The specification warns that “tokens cached or logged on the server” can be reused by attackers. Log the token’s subject, client ID and scopes instead.
- Store securely. Clients and servers “MUST implement secure token storage and follow OAuth best practices.”
- Expire quickly. Authorization servers “SHOULD issue short-lived access tokens to reduce the impact of leaked tokens.” For public clients, they must rotate refresh tokens.
Is the authorization server configured to the same standard?
Your MCP server trusts whatever the authorization server issues, so check it as part of the same review (item 10). The MCP specification builds on the OAuth 2.1 draft and requires:
- HTTPS everywhere. “All authorization server endpoints MUST be served over HTTPS”, and redirect URIs must be
localhostor HTTPS. - PKCE with S256. PKCE stops a stolen authorization code from being exchanged for a token. Clients must use it, and if the authorization server metadata has no
code_challenge_methods_supportedfield, clients “MUST refuse to proceed.” Make sure yours publishes it. - Exact redirect URIs. Authorization servers “MUST validate exact redirect URIs against pre-registered values to prevent redirection attacks.”
- A way for new clients to register. The 2025-11-25 specification lists three: pre-registration, OAuth Client ID Metadata Documents for clients with no prior relationship, and Dynamic Client Registration as a fallback. Pick the ones your authorization server supports and advertise them in its metadata.
- Per-client consent for proxies. If your MCP server is a proxy that uses one static client ID with a third-party authorization server, it “MUST obtain user consent for each dynamically registered client”. The confused-deputy guide explains why.
How do you test your server’s authentication from outside?
Start with what an anonymous caller sees. AdminKit’s free MCP endpoint check sends an unauthenticated initialize to your server URL. It reports the status code, the WWW-Authenticate challenge and any OAuth Protected Resource Metadata your server publishes. It only sends tools/list if initialize works without credentials, and it never calls a tool. How to read an MCP endpoint check result explains each field.
That covers items 2 and 3 and shows whether the server is open at all. Items 4 to 9 happen inside the server, so test them yourself on a server you own: send a token for a different audience, a token in the query string, a request with only a session ID, and a call that needs a missing scope. Each must fail with the status code from the checklist.
For the rest of the operator’s work, such as tool allowlists and untrusted tool output, see how to secure an MCP server. For the wider topic, AdminKit’s AI agents and MCP cluster collects every guide and glossary term on agent and MCP security.
When should a gateway handle authentication for you?
If you run several MCP servers, or one you cannot change, enforcing this checklist in each one is repetitive and easy to get wrong. A security gateway sits in front of them and does it once: it checks that each token is meant for it, never passes the caller’s token through, allowlists tools and keeps one audit log. Read our guide to a security gateway for MCP servers to see how it works and when it beats building authentication into every server. Netbase deploys and runs AdminKit’s MCP & Agent Gateway as a custom engagement; talk to our team to scope one for your servers.
Defensive use only
Run the MCP endpoint check and the token tests above only against servers you own or are authorized to test. The HTTP examples on this page are illustrative, with placeholder hostnames. See our Acceptable Use Policy for the full terms.
This guide is general information, not a security audit of your specific network — see our disclaimer for what AdminKit's guidance does and does not commit to.
Frequently asked questions
Does a local MCP server that uses STDIO need OAuth?
No. The MCP authorization specification says implementations using an STDIO transport should not follow it and should instead retrieve credentials from the environment. A STDIO server only talks to the client process that started it, so protect the credentials it reads and limit what the process can reach.
Is a valid token from our identity provider enough for the MCP server to accept it?
No. The token must also be issued for this specific MCP server. The specification says servers must reject tokens that do not include them in the audience claim. A token your identity provider issued for another API is valid, but not for your MCP server.
Can the MCP session ID replace the access token after the first request?
No. The MCP security best practices say servers must not use sessions for authentication, and the authorization specification says authorization must be included in every HTTP request, even within the same logical session. A session ID only identifies a conversation.
What should the server return when a token lacks a scope?
HTTP 403 Forbidden with a WWW-Authenticate header that sets error to insufficient_scope and lists the scopes the operation needs. A missing, invalid or expired token gets 401 Unauthorized instead. The client can then ask the user to approve just the extra scope.
Can my MCP server reuse the user's token to call a downstream API?
No. That is token passthrough, which the specification forbids. If the server calls another API, it acts as an OAuth client to that API and uses a separate token issued by that API's authorization server.
Can AdminKit test my server's authentication?
Partly. AdminKit's free MCP endpoint check shows from outside whether your server answers initialize without credentials, what its 401 challenge says and what OAuth metadata it publishes. It cannot see how the server validates tokens inside, so the audience and passthrough items still need a code review or a test with a wrong-audience token.
Sources
- MCP specification — Authorization (2025-11-25)
- MCP Security Best Practices (2025-11-25)
- MCP specification — Transports (2025-11-25)
- RFC 9728 — OAuth 2.0 Protected Resource Metadata
- RFC 8707 — Resource Indicators for OAuth 2.0
- RFC 9700 — Best Current Practice for OAuth 2.0 Security
- IETF — The OAuth 2.1 Authorization Framework (draft)
- OWASP Cheat Sheet Series — OAuth 2.0 Cheat Sheet
Want AdminKit to watch this for you?
Free checks tell you the state today. Protective DNS and monitoring tell you when it changes.
Talk to an expert
Huy Nguyen (David), Founder & CEO, Netbase JSC
Ask whether this fix applies to your setup, or get a second pair of eyes on your DNS and mail records.