ToolsExplainer
MCP security and authentication: how the OAuth flow works and where attacks land
MCP security under the 2026-07-28 spec: how OAuth 2.1 authentication works step by step, the attacks it does not stop, and a hardening checklist for teams.

MCP security has two halves that are easy to confuse. The first is authentication: proving that a client may talk to a Model Context Protocol server at all, which the specification now defines as a precise OAuth 2.1 profile. The second is everything that happens after a valid token is presented: which tools an agent can see, what the descriptions of those tools tell the model to do, which credentials the server holds for upstream APIs, and what a locally installed server can read on a laptop. Most incidents so far have landed in the second half.
This explainer is for engineers who build or operate MCP servers and the platform teams who approve them. It walks through how MCP authentication works in the current revision of the specification, 2026-07-28, what changed from earlier revisions, which attacks the spec’s own security pages describe, and which controls actually stop each one. It ends with a checklist that can be applied to a server, a client or a fleet.
Everything here is checked against the published specification and the primary documentation of each project named, as of October 2026. Where the spec uses MUST, SHOULD or MAY, the article keeps that distinction, because it decides what a team can rely on from other people’s implementations.
Where MCP security sits: transport auth versus tool trust
MCP security splits along the transport. Remote servers reached over Streamable HTTP are expected to use the OAuth-based authorization flow. Local servers launched as a subprocess over stdio are not: the authorization spec says stdio implementations “SHOULD NOT follow this specification, and instead retrieve credentials from the environment”. And authorization as a whole is OPTIONAL. A server that skips it is still a conforming MCP server.
That produces three distinct trust problems:
- Is this caller allowed to reach this server? OAuth answers this for HTTP servers, through access tokens bound to the server’s URL.
- Should the model trust what this server says? Tool names, descriptions, schemas and results all flow into the model’s context. The tools page says clients MUST treat tool annotations as untrusted unless they come from trusted servers, and that there SHOULD always be a human in the loop able to deny tool invocations.
- What can this server touch? A local server is, in the words of the project’s local server security guide, “an ordinary operating system process” with the user’s environment variables, files and network access. No protocol feature governs that.
The OWASP MCP Top 10 shows how lopsided the risk is. Of its ten categories, only two are about authentication directly (MCP01 token mismanagement and secret exposure, MCP07 insufficient authentication and authorisation). The rest cover scope creep, tool poisoning, supply chain tampering, command injection, prompt injection via contextual payloads, missing audit and telemetry, shadow MCP servers, and context over-sharing.
How the authorization spec got here
The OAuth profile has been rebuilt in almost every revision. Teams supporting older clients will meet all of these versions in production, so it helps to know which rule arrived when.
| Revision | What changed for authentication |
|---|---|
| 2025-03-26 | First authorization framework based on OAuth 2.1 |
| 2025-06-18 | MCP servers classified as OAuth resource servers; protected resource metadata for discovery; RFC 8707 resource indicators required; first security best-practices page |
| 2025-11-25 | OpenID Connect Discovery supported; incremental scope consent via WWW-Authenticate; Client ID Metadata Documents added as the recommended registration method; WWW-Authenticate made optional with .well-known fallback |
| 2026-07-28 (current) | Dynamic Client Registration deprecated; clients must validate iss (RFC 9207) before redeeming a code; credentials bound to the issuing authorization server; application_type required in registration; protocol made stateless, with sessions removed |
Sources: the changelogs for 2026-07-28, 2025-11-25, 2025-06-18 and 2025-03-26 on modelcontextprotocol.io. The 2026-07-28 revision is marked “current” on the project’s versioning page.
The stateless change matters for security too. Earlier revisions had an Mcp-Session-Id header, and the old best-practices page had a section on session hijacking. In 2026-07-28 there are no protocol sessions. Servers that need state across calls mint their own handles and receive them back as ordinary tool arguments, and the security best practices now cover “state handle hijacking” instead.
How MCP authentication works, step by step
MCP authentication is a standard OAuth 2.1 authorization code flow with a fixed set of discovery and binding rules on top. Three roles take part: the MCP server is the OAuth resource server, the MCP client is the OAuth client, and a separate authorization server, which may or may not be hosted with the MCP server, issues the tokens. The spec deliberately says nothing about how that authorization server is built.

Figure 1: The MCP OAuth flow in the 2026-07-28 revision. Each step is a normative requirement, and the two red branches are places where the spec tells the client to stop, not to fall back.
Discovery starts with a 401 and protected resource metadata
The client first calls the server with no token. The server answers 401 Unauthorized with a WWW-Authenticate: Bearer header carrying a resource_metadata URL, and optionally a scope parameter listing the scopes this request needs. MCP servers MUST implement RFC 9728 protected resource metadata, and the document MUST list at least one entry in authorization_servers.
If the header has no resource_metadata, clients MUST fall back to well-known URIs, first at the path of the MCP endpoint (/.well-known/oauth-protected-resource/mcp) and then at the root. Clients MUST support both mechanisms.
Authorization server metadata, and the issuer check
From the protected resource metadata the client picks an authorization server and fetches its metadata, trying RFC 8414 OAuth metadata and OpenID Connect Discovery endpoints in a defined order. Clients MUST support both. After fetching, the issuer in the document MUST be identical to the issuer used to build the URL; if not, the client MUST NOT use it. The spec’s own example: a document fetched from attacker.example claiming to be honest.example must be rejected.
PKCE support is checked here too. If code_challenge_methods_supported is absent from the metadata, the security considerations say the client MUST refuse to proceed. Clients MUST use the S256 method when technically capable.
Getting a client ID: pre-registration, CIMD, then DCR
An MCP client usually has no prior relationship with the server it is about to call, which is the problem OAuth client registration was never designed for. The client registration page defines three mechanisms and a priority order:
- Pre-registered credentials, if the client has them for this authorization server.
- Client ID Metadata Documents (CIMD), if the authorization server advertises
client_id_metadata_document_supported. The client ID is an HTTPS URL, such ashttps://app.example.com/oauth/client-metadata.json, pointing to a JSON document the client hosts with at leastclient_id,client_nameandredirect_uris. The authorization server fetches it, checks thatclient_idmatches the URL exactly, and validates redirect URIs against it. - Dynamic Client Registration (DCR), the RFC 7591
POST /registerapproach, as a fallback. It is deprecated in 2026-07-28 and kept only for authorization servers that do not support CIMD. - Asking the user to type client details, if nothing else works.
The switch from DCR to CIMD is a security decision as much as a convenience one. DCR lets anyone mint a client ID with arbitrary redirect URIs, which is one ingredient of the confused deputy attack described below. A CIMD client ID is tied to a domain the client controls, and an authorization server can apply domain-based trust policies, such as allow-lists for internal servers or reputation checks for unknown domains.
CIMD does not solve everything. The spec is explicit that it “cannot prevent localhost URL impersonation”: a malicious local process can present a legitimate client’s metadata URL and listen on any localhost port. Authorization servers SHOULD warn on localhost-only redirect URIs and MUST display the redirect hostname during consent.
Clients that persist credentials MUST key them by the authorization server’s issuer, MUST NOT reuse them with a different authorization server, and MUST re-register if the protected resource metadata starts pointing somewhere else.
PKCE, the resource parameter and the iss check
The authorization request carries a PKCE challenge and a resource parameter. Under RFC 8707, resource names the canonical URI of the MCP server, such as https://mcp.example.com/mcp. It MUST appear in both the authorization request and the token request, and clients MUST send it “regardless of whether authorization servers support it”. This is what lets the authorization server put the right audience in the token.
Before redirecting, the client MUST record the expected issuer alongside the PKCE verifier. When the code comes back, the client applies RFC 9207: if iss is present it is compared to the recorded value by exact string match, with no case folding or trailing-slash normalisation; if the server advertises iss support and it is missing, the response is rejected. This defends against mix-up attacks, in which a malicious authorization server the client also talks to tricks it into sending an honest server’s code to the wrong token endpoint. The best-practices page notes that PKCE alone does not stop this, because the client would hand its code_verifier to the attacker too.
Using the token, and what the server must check
The access token goes in an Authorization: Bearer header on every HTTP request, never in a query string. The server MUST validate it under OAuth 2.1 section 5.2 and MUST check that it was issued specifically for this server as the audience. Invalid or expired tokens get a 401.
Three sentences in the spec carry most of the weight:
- “MCP servers MUST only accept tokens that are valid for use with their own resources.”
- “MCP servers MUST NOT accept or transit any other tokens.”
- If the server calls upstream APIs, “the access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server MUST NOT pass through the token it received from the MCP client.”
Authorization servers SHOULD issue short-lived access tokens and MUST rotate refresh tokens for public clients. All authorization endpoints MUST be HTTPS, and redirect URIs MUST be either localhost or HTTPS.
Scopes and step-up
Scope handling is where the spec tries to keep tokens small. Servers SHOULD put the scopes a request needs in the scope parameter of the 401 challenge, and clients SHOULD request exactly those. Only if no scope is given does the client fall back to everything in scopes_supported, which is why the best-practices page says scopes_supported should be “the minimal set of scopes necessary for basic functionality”.
When a later operation needs more, the server answers 403 with error="insufficient_scope" and the scopes required. The client then runs a step-up flow, requesting the union of what it already has and what was challenged, and retries a bounded number of times. The intended pattern is a token that starts at something like mcp:tools-basic and grows to files:write only when a write is actually attempted.
MCP OAuth mistakes the spec explicitly forbids
The security best practices page reads as a list of ways real implementations got OAuth wrong. Each attack below has a normative mitigation.
Token passthrough
Token passthrough is an MCP server accepting whatever token a client sends, without checking that it was issued to the server, and forwarding it to a downstream API. It is the most common shortcut in proxy-style servers that wrap a SaaS API, because the client already has a token for that API and passing it through looks harmless.
The spec lists why it is not: the server’s own rate limits and validation are bypassed, the server cannot tell clients apart, downstream logs show the wrong caller, and a stolen token can be laundered through the server for data exfiltration. The mitigation is blunt: MCP servers MUST NOT accept any tokens not explicitly issued for them.
The confused deputy at an OAuth proxy
The confused deputy problem applies to an MCP server that acts as a proxy to a third-party API using one static client ID with that API’s authorization server, while letting MCP clients register dynamically. The attack chain in the best-practices page needs four conditions: a static upstream client ID, dynamic registration at the proxy, a consent cookie set by the third-party authorization server, and no per-client consent at the proxy.
An attacker registers a new client with a malicious redirect URI and sends the victim a crafted authorization link. The upstream server sees its own consent cookie and skips the consent screen, and the proxy’s authorization code lands at the attacker’s redirect URI. The attacker redeems it and acts as the user.
The required fix is per-client consent at the proxy, before the upstream flow starts: a registry of approved client_id values per user, a consent page that names the client, shows the upstream scopes and the redirect URI, and resists clickjacking, __Host- prefixed and signed consent cookies bound to a specific client, exact-match redirect URI validation, and single-use state values set only after consent. The authorization security considerations repeat the core rule: proxies using static client IDs MUST obtain user consent for each dynamically registered client.
SSRF through discovery and client metadata
OAuth discovery makes the client fetch URLs supplied by the server: the resource_metadata URL, the authorization_servers list and every endpoint in the authorization server metadata. A malicious MCP server can point these at http://169.254.169.254/, internal IP ranges or localhost services. For an MCP client running on a server, such as an agent platform, that is a server-side request forgery primitive.
The best-practices page says server-side MCP clients MUST consider SSRF and SHOULD require HTTPS, block private and link-local ranges, apply the same checks to each redirect hop and route discovery through an egress proxy. It also warns against hand-written IP validation, because octal, hex and IPv4-mapped IPv6 encodings defeat naive parsers.
CIMD moves the same risk to the authorization server, which now fetches a URL supplied by an unknown client. The spec says authorization servers SHOULD consider SSRF when fetching metadata documents, with the same mitigations.
Malicious authorization URLs on the client
A server can hand the client a javascript: authorization URL, or one crafted to inject shell commands when the client opens it through a shell. This is not theoretical. CVE-2025-6514, rated 9.6 by the reporting CNA, describes mcp-remote as “exposed to OS command injection when connecting to untrusted MCP servers due to crafted input from the authorization_endpoint response URL”. The spec now says clients MUST accept only http and https schemes for authorization URLs (with http only for loopback in development), MUST reject javascript:, data:, file: and similar, and MUST NOT open URLs through cmd.exe, sh or PowerShell.
The same page describes how a client-side XSS can escalate to code execution through a local proxy that spawns stdio servers. CVE-2025-49596 is the canonical case: MCP Inspector versions below 0.14.1 allowed unauthenticated requests to its proxy to “launch MCP commands over stdio”. Local proxies need authentication between their own components, and the commands they spawn need sandboxing and logging.
Open redirects and state handles
Two smaller rules round out the OAuth section. Authorization servers MUST validate redirect URIs by exact match against registered values, and clients SHOULD use and verify state. And in the stateless protocol, servers MUST NOT treat possession of a state handle as authentication: handles should be random, should expire, and SHOULD be bound server-side to the user ID taken from the verified token, so a guessed handle is useless to another principal.
The MCP security threats OAuth does not touch
A perfectly implemented OAuth flow guarantees that a legitimate client with a correctly scoped token is talking to the right server. It says nothing about whether that server is honest, whether its tool descriptions are safe for a model to read, or whether the tool results contain instructions. These are the threats that have produced most public MCP incidents.
Tool poisoning and shadowing
Tool poisoning is hiding instructions in a tool’s name, description or schema. OWASP lists it as MCP03. Because clients load every configured server’s tool list into the same model context, a poisoned description can steer how the agent uses another server’s tools, which the local server security guide calls tool shadowing. The guide’s warning is the important part: this works “without the poisoned tool ever being invoked”. Researchers at Invariant Labs published proof-of-concept attacks for both patterns in 2025.
The controls are review and reduction. Read tool definitions before approving a server, for example with the MCP Inspector, which lists tools without a model in the loop. Keep the set of configured servers small, since each one widens the surface the others are exposed to. And give each caller only the tools it needs, so a poisoned definition from a server it does not use never reaches its context.
Rug pulls
A rug pull is a server changing its tool definitions after approval. Package pinning does not help when the server is remote, and a one-time consent dialog does not either. The local server security guide recommends clients that record tool definitions at approval and ask again when they change, and notes that “the protocol does not require this, so it is a property worth choosing a client for”. At fleet level, the same effect comes from routing tools through a single point that holds the approved definition set.
Prompt injection through tool results
Tool results are untrusted input. A GitHub issue, a web page or a support ticket returned by a tool can carry instructions that the model then follows using whatever other tools it holds, the pattern OWASP files under LLM01 prompt injection and MCP06. The tools spec asks clients to show tool inputs to the user before calling, validate results before passing them to the model, and prompt for confirmation on sensitive operations; servers MUST validate inputs, implement access controls, rate-limit invocations and sanitise outputs.
No filter catches every injection. The defensible goal is to make a successful injection boring: the agent that reads untrusted content should not also hold a tool that can send data out or delete things without a person approving it.
Over-broad scopes and over-broad tool lists
Excessive permission is the multiplier on every other threat. The best-practices page lists the common scope mistakes: publishing every scope in scopes_supported, wildcard scopes such as * or full-access, bundling unrelated privileges to avoid future prompts, and treating scopes in a token as sufficient without server-side authorisation logic. OWASP calls the result “privilege escalation via scope creep” (MCP02).
The same logic applies one layer up, to tools. An agent that only needs to search a knowledge base should not see the ticketing server’s delete_ticket tool at all, regardless of what the upstream token would allow.
Local server supply chain
Local servers are downloaded code running with the user’s privileges. The local security guide lists the threats: typosquatted or taken-over packages, legitimate servers that over-collect, compromised dependencies and vulnerable bridges. It also notes that pinning a package with npx or uvx fixes only the top-level package, while transitive dependencies resolve fresh at install time; pinning a container image by digest freezes the whole tree.
Even well-maintained reference servers ship vulnerabilities. CVE-2025-53109 describes the reference Filesystem server allowing “access to unintended files via symlinks within allowed directories” before versions 0.6.4 and 2025.7.01. A container with only the intended directory mounted would have limited that to the directory anyway.
For one-click installs, the best-practices page says clients MUST show the exact command, untruncated, require explicit approval and allow cancellation, and SHOULD highlight sudo, rm -rf, network calls and access to sensitive paths. Local servers using HTTP rather than stdio SHOULD require an authorisation token or use restricted IPC, since otherwise any process, or a web page via DNS rebinding, can reach them.
OWASP categories at a glance
| OWASP MCP Top 10 (2025) | Covered by MCP OAuth? | Main control |
|---|---|---|
| MCP01 Token mismanagement and secret exposure | Partly | Audience-bound short-lived tokens, secrets out of config files |
| MCP02 Privilege escalation via scope creep | Partly | Minimal scopes, step-up, per-caller tool allow-lists |
| MCP03 Tool poisoning | No | Definition review, pinning, small curated tool sets |
| MCP04 Supply chain attacks and dependency tampering | No | Provenance checks, version or digest pinning, SBOMs |
| MCP05 Command injection and execution | No | Input validation, URL scheme checks, sandboxing |
| MCP06 Prompt injection via contextual payloads | No | Result guardrails, human approval for sensitive tools |
| MCP07 Insufficient authentication and authorisation | Yes, for HTTP servers | The spec’s OAuth 2.1 profile, implemented fully |
| MCP08 Lack of audit and telemetry | No | Per-call logs with caller, tool, arguments and outcome |
| MCP09 Shadow MCP servers | No | Inventory of client configs, approved-server list |
| MCP10 Context injection and over-sharing | No | Narrow tool sets and scoped data per caller |
Threat-to-control map: who owns each fix
The useful question for a team is not “is MCP secure” but “for each threat, whose code or configuration holds the control”. Some controls live in the MCP server, some in the client, some in the authorization server, and some only exist if someone puts a policy point between agents and servers.

Figure 2: OAuth and the spec’s MUSTs cover the first column. The other three depend on review, policy and isolation that someone has to own.
| Threat | Owner | Control |
|---|---|---|
| Token passthrough | MCP server | Validate audience; obtain a separate upstream token |
| Confused deputy | MCP proxy server | Per-client consent before upstream authorisation |
| Mix-up attack | MCP client, authorization server | Record issuer, emit and validate iss |
| SSRF in discovery or CIMD fetch | Client on a server, authorization server | HTTPS only, private-range blocking, egress proxy |
| Malicious authorization URL | MCP client | Scheme allow-list, no shell to open URLs |
| Tool poisoning, shadowing | Whoever approves servers | Review definitions; curate per-caller tool sets |
| Rug pull | Client or gateway | Pin approved definitions; re-prompt on change |
| Injection via results | Client, gateway, application | Guardrails on results; approval for sensitive tools |
| Over-broad scopes | MCP server, authorization server | Minimal scopes_supported; step-up |
| Over-broad tools | Platform team | Deny-by-default tool allow-lists per caller |
| Local supply chain | User or endpoint team | Provenance, pinning, containers, inventory |
The pattern in the second half of the table is that the owner is rarely the person writing the agent. It is whoever decides which servers exist in the organisation and who may call what, which is a platform function. That is where a gateway comes in.
Running MCP behind a central gateway
An MCP gateway is a proxy that sits between agents and MCP servers: agents connect to one endpoint, the gateway connects to the approved upstream servers, and it decides per caller which tools appear, which credential is used upstream and what gets logged. GenAI Brief’s MCP gateway explainer covers the general mechanics and the token-cost side; this section looks at it only as a security control, mapped to the threats above.
In terms of the spec, a gateway is an MCP server to its clients and an MCP client to upstream servers. That means it has to implement both halves of the OAuth profile correctly, and above all it must not become a token-passthrough proxy. Done properly, it moves several controls from “every developer should” to “configured once”:
- An approved server list. Agents cannot reach a server the gateway does not know about, which addresses shadow remote servers and limits where poisoned definitions can come from.
- Per-caller tool filtering. Each key or team sees an allow-list, ideally deny-by-default, which shrinks both the injection blast radius and the poisoning surface.
- Credentials held centrally. Upstream tokens live in the gateway, not in config files on laptops, and per-user OAuth keeps each user’s grant separate.
- Checks and logs on every call. Arguments and results can be inspected, and there is one record of who called which tool with what.
How Bifrost implements this
Bifrost, the open-source Go AI gateway from Maxim AI, is a useful worked example because its documentation covers each of these controls and both directions of MCP authentication, and because the same process also routes LLM traffic. Its overhead figure is published too: 11 µs added per request at 5,000 RPS on an AWS t3.xlarge, in Bifrost’s benchmark.
Inbound authentication follows the MCP OAuth profile. The gateway authentication docs describe a mcp_server_auth_mode setting with three values: headers (virtual keys or API keys only, OAuth endpoints return 404), both, and oauth (Bifrost-issued JWTs only). In the OAuth modes Bifrost is a full OAuth 2.1 authorization server for its /mcp resource: a tokenless request gets a 401 pointing to RFC 9728 metadata, clients self-register with RFC 7591, PKCE uses S256, and tokens are bound to the /mcp resource through the aud claim per RFC 8707. A request carrying both a JWT and a header credential is rejected as conflicting. The docs also require an explicit issuer URL, because otherwise it would be derived from the unauthenticated Host header, a detail that matters for the issuer checks described earlier.
Upstream authentication avoids passthrough. The MCP authentication docs list six upstream auth types: none, static headers, shared admin oauth, per_user_headers, per_user_oauth and token_exchange. With per-user OAuth, each user authorises the upstream service once through a consent screen, the credential is stored against that identity, and a tool call with no valid credential returns an mcp_auth_required link instead of executing. Token exchange, an enterprise feature, swaps the caller’s identity-provider token for a new one minted for the upstream audience via RFC 8693 on every call, with nothing stored per user. In both cases the upstream server receives a token issued for it, which is what the spec’s anti-passthrough rule requires.
Tools are deny-by-default. Tool filtering stacks three levels: a per-server tools_to_execute list, where an empty list means no tools; per-request headers that can narrow but never widen; and per-virtual-key MCP configurations, where a key with none sees no tools except from servers explicitly marked allow-by-default. A support team’s key might see search and get_article from a knowledge base and nothing from the filesystem server.
Execution is explicit. By default Bifrost does not execute tool calls returned by a model; the application calls a separate tool execution endpoint, which leaves room for approval steps on sensitive tools. Automatic execution exists in Agent Mode but has to be configured.
Guardrails and logs cover the call itself. Guardrails can target MCP tool executions, inspecting or redacting arguments before the tool runs and results after it returns, with rules written in CEL against the MCP client, tool name and arguments. Request logs record MCP calls alongside LLM calls. The Maxim team’s write-ups on MCP audit logging requirements and MCP guardrails go further into what to capture.
Enterprise tier. Guardrails, SSO-backed user identity, token exchange and signed admin audit logs are part of Bifrost Enterprise.
A summary of the approach is on the Bifrost MCP gateway page, and the broader policy model is on its AI governance page.
Other approaches, and the gap a server-side gateway leaves
Bifrost is not the only way to add this layer. Docker MCP Gateway runs each server in an isolated container “with restricted privileges, network access, and resource usage” and injects credentials, which addresses the local-server column directly; its access control works at the level of server profiles. Microsoft, IBM, Red Hat and LiteLLM all ship MCP gateways with different emphases. GenAI Brief’s ranking of MCP gateways and the open-source shortlist compare them on these criteria.
Every server-side gateway shares one blind spot: it cannot see MCP servers that developers configure locally in Claude Code, Cursor or Claude Desktop, which never send traffic through it. That needs something on the endpoint. Bifrost pairs the gateway with Bifrost Edge, which inventories the MCP servers configured in supported apps on each machine and enforces allow or deny decisions on the device, while the gateway remains the control plane. The shadow AI piece covers why blocking alone fails there.
Three scenarios, and what each team should do
Abstract checklists are easier to apply with a concrete setup in mind. These three cover most of the deployments readers will meet.
A SaaS company shipping a remote MCP server
A company exposes its product API as a hosted MCP server for customers’ agents. This team owns the resource-server half of the spec. It should publish protected resource metadata, return precise scope values in 401 and 403 challenges, keep scopes_supported minimal, validate the audience of every token, and support CIMD at its authorization server with sensible trust policies. If the server calls the company’s own internal APIs, it should use a separate internal credential, never the customer’s MCP token.
It should also treat its tool descriptions as security-relevant text. Descriptions should describe the tool and nothing else, change only with versioned releases, and never include instructions about other tools. Rate limits, input validation and output sanitisation are MUSTs on the server side, not suggestions.
A platform team rolling MCP out to several hundred developers
Here the risk is volume and drift: many developers, many servers, many tokens in config files. The best return comes from routing remote servers through one gateway with a short approved list, per-team keys with deny-by-default tool sets, per-user OAuth for SaaS tools such as GitHub or Jira, and logs of every call. For local servers, the local security guide’s fleet advice applies: publish an allow-list with pinned versions, set a default posture such as “third-party servers run in containers with no home-directory mounts”, inventory client configuration files continuously, and be ready to block egress and rotate credentials for a compromised server across the fleet.
GenAI Brief’s guide to the most-used MCP servers lists the permissions the popular ones actually need, which is a reasonable starting point for the allow-list.
An internal MCP server wrapping an internal API
A team builds an MCP server over an internal system that already trusts the company’s identity provider. The cleanest design reuses that trust rather than inventing a new credential: either the MCP server is a resource server for tokens from the corporate authorization server, or a gateway in front performs a token exchange so the server receives a token minted for its own audience. Long-lived shared API keys in a config file are the pattern to avoid, because they hide which person made each call.
A hardening checklist for MCP security
These lists condense the normative requirements in the 2026-07-28 spec and the project’s security guides, ordered by impact. Items marked MUST are spec requirements; the rest are recommendations.
For MCP server builders
- Implement RFC 9728 metadata and return
WWW-Authenticatewithresource_metadataand the neededscopeon 401 (MUST for metadata). - Validate audience on every token and reject anything not issued for this server (MUST).
- Never pass client tokens upstream. Obtain a separate token from the upstream authorization server (MUST NOT pass through).
- Keep
scopes_supportedminimal and use 403insufficient_scopechallenges for step-up; avoid wildcard scopes. - Validate tool inputs, enforce access control, rate-limit and sanitise outputs (MUST, per the tools spec).
- Bind state handles to the user from the verified token, and use random, expiring handles.
- If you proxy a third-party API with a static client ID, implement per-client consent before the upstream flow (MUST).
- Keep tool descriptions factual and versioned. Treat any change as a release.
For MCP client and agent builders
- Use PKCE with S256 and refuse authorization servers that do not advertise it (MUST).
- Send
resourceon authorization and token requests (MUST). - Record the issuer and validate
issbefore redeeming a code (MUST); check metadataissuermatches the URL (MUST). - Prefer CIMD over DCR, and key stored credentials by issuer (MUST).
- Allow only
http(s)authorization URLs and never open them through a shell (MUST). - On servers, block SSRF in discovery: HTTPS only, private ranges blocked, egress proxy.
- Show the full startup command before installing a local server (MUST for one-click installs), and re-prompt when tool definitions change.
- Keep a human in the loop for sensitive or destructive tools, and show tool inputs before execution.
For platform and security teams
- Maintain an approved server list with published criteria: identifiable publisher, readable source, pinned version or digest.
- Route remote MCP traffic through one policy point with deny-by-default tools per key or team.
- Hold upstream credentials centrally, per-user where the upstream supports it, never in plaintext laptop configs.
- Run third-party local servers in containers with only the needed directory mounted and egress limited.
- Inventory MCP client configurations across the fleet, and treat unlisted servers as unapproved software.
- Log every tool call with caller, server, tool, arguments and outcome, and keep admin changes in a separate audit trail.
- Rehearse the fleet incident: find every instance of a vulnerable server, block its egress, rotate the credentials it held and push the fixed version.
What to watch next
Three changes are signposted in the current documents. The spec says a future revision is expected to make iss in authorization responses a MUST for authorization servers, closing the gap where mix-up protection depends on servers that choose to emit it. Dynamic Client Registration is now formally deprecated, and under the project’s feature lifecycle policy deprecated features stay for at least twelve months before they can be removed, so CIMD support is worth adding to authorization servers now. And the official authorization extensions repository lists an enterprise-managed authorization extension as stable and a client-credentials extension as a draft, which points to more machine-to-machine and identity-provider-driven patterns in core deployments.
Our judgement is that the OAuth half of MCP security is close to solved on paper. The 2026-07-28 profile is careful, it borrows well-tested RFCs, and the remaining work is implementation discipline: audience checks, no passthrough, PKCE, iss. The half that is not solved is trust in tools themselves, and no protocol revision will solve it, because the model reads tool definitions and results as instructions by design. Teams that treat MCP security as an OAuth project will pass an audit and still be exposed. Teams that also decide centrally which servers exist, which tools each caller sees, where credentials live and what gets logged will be in a position to answer the question that matters after an incident: which agent called which tool, with whose authority, and what did it do.
Sources
- MCP specification 2026-07-28: Authorization Model Context Protocol
- MCP specification 2026-07-28: Authorization Server Discovery Model Context Protocol
- MCP specification 2026-07-28: Client Registration Model Context Protocol
- MCP specification 2026-07-28: Authorization Security Considerations Model Context Protocol
- MCP specification 2026-07-28: Key Changes Model Context Protocol
- MCP specification 2026-07-28: Tools Model Context Protocol
- MCP Security Best Practices (2026-07-28) Model Context Protocol
- MCP Local Server Security (2026-07-28) Model Context Protocol
- RFC 9728: OAuth 2.0 Protected Resource Metadata IETF
- RFC 8707: Resource Indicators for OAuth 2.0 IETF
- RFC 9207: OAuth 2.0 Authorization Server Issuer Identification IETF
- OAuth Client ID Metadata Document (draft-ietf-oauth-client-id-metadata-document-00) IETF
- OWASP MCP Top 10 (2025) OWASP Foundation
- LLM01:2025 Prompt Injection OWASP GenAI Security Project
- CVE-2025-6514 (mcp-remote OS command injection) NIST National Vulnerability Database
- CVE-2025-49596 (MCP Inspector remote code execution) NIST National Vulnerability Database
- CVE-2025-53109 (Filesystem MCP server symlink escape) NIST National Vulnerability Database
- Bifrost MCP gateway authentication (inbound OAuth 2.1) Maxim AI
- Bifrost MCP authentication (upstream auth types) Maxim AI
- Bifrost token exchange for MCP Maxim AI
- Bifrost MCP tool filtering Maxim AI
- Bifrost MCP tool execution Maxim AI
- Bifrost guardrails (LLM and MCP) Maxim AI
- Bifrost Edge: govern MCP servers Maxim AI
- Docker MCP Gateway Docker
Frequently asked questions
How does MCP authentication work?
For HTTP-based MCP servers, authentication follows OAuth 2.1. The server returns a 401 pointing to its protected resource metadata, the client discovers the authorization server, obtains a client ID, runs an authorization code flow with PKCE and a resource parameter, and then sends the access token as a Bearer header on every request. The server must check that the token was issued for it.
Does MCP require OAuth?
No. Authorization is optional in the MCP specification. When an HTTP server does implement it, it should follow the OAuth 2.1 profile in the spec. Servers using the stdio transport should not use the OAuth flow at all and instead take credentials from the environment, which is why local servers need different controls.
What is token passthrough in MCP and why is it forbidden?
Token passthrough is when an MCP server accepts a token that was not issued for it and forwards it to a downstream API. The spec forbids it because it bypasses the server's own controls, breaks audit trails and lets a stolen token be replayed through the server. An MCP server must validate the audience and use a separate token, issued by the upstream authorization server, for upstream calls.
What is the difference between Dynamic Client Registration and Client ID Metadata Documents?
With Dynamic Client Registration, a client POSTs to a registration endpoint and receives a new client ID from each authorization server. With Client ID Metadata Documents, the client ID is an HTTPS URL pointing to a JSON document the client hosts, which the authorization server fetches and validates. The 2026-07-28 spec deprecates Dynamic Client Registration and recommends metadata documents.
What is MCP tool poisoning?
Tool poisoning is hiding instructions in a tool's name, description or schema so that the model follows them, for example reading a private file and passing it as an argument. The model reads tool definitions as instructions, so the poisoned tool does not even need to be called. OWASP lists it as MCP03 in its MCP Top 10.
How do you secure local MCP servers?
Install only servers from identifiable publishers, pin exact versions or container digests, run third-party servers in a container with nothing mounted beyond the directory they need, pass each one its own narrowly scoped token, restrict network egress and use a client that re-prompts when tool definitions change. Fleets also need an inventory of client configuration files.
Can an MCP gateway replace OAuth on MCP servers?
No, it complements it. A gateway can act as the OAuth authorization server for its own endpoint and hold upstream credentials, but each upstream server still has to validate tokens issued for it. What the gateway adds is the policy layer OAuth lacks: which servers are approved, which tools each caller sees, guardrails on arguments and results, and a log of every call.


