Syed Hasibur Rahman22 min read

MCP Authorization: How the AI Agent Protocol Learned OAuth the Hard Way

In its first four months, the Model Context Protocol asked every server author to build a miniature identity provider from scratch. It took a blog post, a security review, and a cross-tenant data leak at Asana before the spec was rewritten around OAuth's oldest lesson: separate the party that authenticates users from the party that serves data. This post traces MCP authorization from its March 2025 draft through the confused-deputy fix, RFC 9728 resource metadata, the token-passthrough ban, and the real incidents — Asana, CVE-2025-6514, WhatsApp tool poisoning — that turned abstract spec language into MUST NOT clauses.

Somewhere in early 2025 a developer at a mid-sized SaaS company wired up an MCP server so their internal chatbot could read from the company’s GitHub organization. The chatbot already had a GitHub OAuth token — the one issued when the user signed into the internal tool. So the developer did the obvious thing: when the MCP server received a tools/call request, it took the Authorization header off the incoming request and forwarded it, unmodified, to api.github.com. It worked immediately. It shipped. Code review didn’t flag it, because nothing about it looked wrong — the token was valid, the user was authenticated, the API call succeeded.

It was also, per the letter of the specification that exists today, a security bug serious enough that the Model Context Protocol’s own documentation now spends an entire page explaining why it must never happen1. The token that GitHub issued was scoped, audited, and rate-limited on the assumption that it would only ever be presented by the client GitHub itself recognized. The MCP server that forwarded it wasn’t that client. Every downstream log entry, every rate-limit bucket, every anomaly-detection heuristic on GitHub’s side now attributed the chatbot’s traffic to the wrong actor. If the MCP server had a bug — a path traversal, a prompt injection that convinced the model to call the wrong tool — the blast radius wasn’t “whatever the MCP server was supposed to expose.” It was “everything the user’s GitHub token could do.”

This pattern is called token passthrough, and it is one of two anti-patterns — the other is the confused deputy problem — that drove a near-total rewrite of MCP’s authorization model between March 2025 and today. The story of that rewrite is a good case study in something OAuth practitioners have relearned every decade since 2012: when you build a new protocol on top of bearer tokens without importing OAuth’s separation of concerns, you will eventually rediscover why that separation exists, usually through an incident report.


What MCP is, and why its authorization model matters now

Anthropic open-sourced the Model Context Protocol on November 25, 2024, describing it as “an open standard that enables developers to build secure, two-way connections between their data sources and AI-powered tools”2. The pitch was straightforward: instead of every AI application writing a bespoke integration for every tool, database, and API it needs to reach, MCP defines one interface — the AI client speaks MCP, the tool exposes an MCP server, and any client can talk to any server without custom glue code.

It worked, and it spread fast. Within a year, MCP had first-class support in ChatGPT, Claude, Cursor, Gemini, Microsoft Copilot, and VS Code, with over 97 million monthly SDK downloads and roughly 10,000 active public servers2. OpenAI announced support across its products in March 2025; Google DeepMind confirmed Gemini support that April2. On December 9, 2025, Anthropic donated the protocol to the newly formed Agentic AI Foundation, a directed fund under the Linux Foundation with Anthropic, OpenAI, Block, AWS, Google, Microsoft, Bloomberg, and Cloudflare as founding platinum members — moving MCP from a single vendor’s spec to vendor-neutral, community-governed infrastructure3.

That growth curve is exactly why the authorization story matters. An MCP server is, almost by construction, a confused deputy waiting to happen: it sits between an AI agent (acting on a user’s behalf, with unpredictable input in the form of model-generated tool calls) and a real backend API that has its own trust assumptions. Every one of the ~10,000 public MCP servers is a new node in that graph. Get the identity model wrong at this scale and you don’t get one vulnerable app — you get a vulnerability class.


Part 1: The original sin — MCP servers as their own identity providers

MCP’s first authorization specification, dated 2025-03-26, made a design choice that reads, in hindsight, like a beginner’s OAuth mistake: it expected the MCP server itself to act as the OAuth authorization server. The server that served your data was also the server that logged users in, issued authorization codes, and minted access tokens.

Aaron Parecki — an OAuth working group participant and co-author of RFC 9728 — wrote the post that crystallized why this was wrong, on April 3, 2025, under the title “Let’s Fix OAuth in MCP”4. His core argument: “requiring the MCP Server to be both an authorization server and resource server is too much of a burden”4. OAuth 2.0 has separated these roles since 2012 for a reason. The authorization server is the “token factory” — it authenticates the resource owner, manages consent, and issues tokens. The resource server is the API that validates and honors those tokens. Collapsing the two into one component means every MCP server author — someone who wants to expose, say, a company wiki as a tool — now also has to correctly implement login flows, session management, refresh token rotation, and token issuance. That is a lot of security-critical surface area to ask of people whose actual job is “expose a wiki.”

Parecki traced the root cause to a discovery problem: MCP clients fetched OAuth metadata directly from the MCP server’s own URL, which structurally forced the server to answer as both parties4. His proposed fix borrowed a spec that, at the time, was itself still in draft: OAuth 2.0 Protected Resource Metadata. Under that model, the MCP server publishes a metadata document that says, in effect, “I am a resource server; here is the authorization server you should actually talk to.” The client discovers that server, runs the OAuth dance there, and only ever presents the resulting token to the MCP server for validation. The MCP server never touches a password, a login form, or a client registration record.

This is not a novel insight for OAuth generally. It is, in fact, exactly the separation RFC 6749 assumed from day one. What’s notable is that a brand-new, extremely fast-growing protocol managed to reinvent the pre-separation mistake within its first four months, in full public view, and then fix it in public — a useful reminder that “obviously how OAuth works” knowledge does not automatically transfer into new protocol designs unless someone with OAuth scar tissue is in the room.


Part 2: The June 2025 fix — MCP servers become resource servers, full stop

The 2025-06-18 revision of the MCP authorization spec adopted Parecki’s fix wholesale. It reclassified the MCP server as a pure OAuth 2.1 resource server and mandated RFC 9728 Protected Resource Metadata, which had itself been published as an RFC that April, co-authored by Michael Jones, Phil Hunt, and Aaron Parecki5. The architectural line, once drawn, is now absolute in every subsequent spec revision through the current 2025-11-25 and 2026-07-28 editions:

“A protected MCP server acts as an OAuth 2.1 resource server, capable of accepting and responding to protected resource requests using access tokens. … The implementation details of the authorization server are beyond the scope of this specification. It may be hosted with the resource server or a separate entity.”6

The MCP server’s job shrank to exactly one thing an OAuth resource server does: validate a bearer token and decide whether to honor the request. Login, consent, and token minting moved to a dedicated authorization server — which might be Auth0, Okta, Keycloak, a company’s existing IdP, or (for testing) a small standalone service, but is never the MCP server code itself.

This is a small sentence with a large practical consequence: an MCP server author no longer needs to think about password storage, session fixation, or refresh token theft. They need to think about one thing — is this bearer token valid, and was it issued for me? That second clause turns out to be where almost everything interesting in the rest of this post lives.


Part 3: The authorization flow, end to end

Here is the full discovery-to-access flow as the current spec defines it, walking a client that has never talked to a given MCP server before.

Step 1 — Unauthenticated probe. The client sends an MCP request without a token. The server replies 401 Unauthorized with a WWW-Authenticate header carrying a resource_metadata URL:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

Step 2 — Protected Resource Metadata. The client fetches that URL (or falls back to the well-known path if the header is absent) and receives a JSON document whose authorization_servers field names at least one authorization server the MCP server trusts6. This is RFC 9728 doing exactly what it was designed to do: let a resource server point at its authorization server without the client having to know that relationship in advance.

Step 3 — Authorization server metadata. The client fetches the authorization server’s own metadata — via RFC 8414’s /.well-known/oauth-authorization-server7 or OpenID Connect Discovery — to learn the authorization and token endpoints, and critically, whether the server supports PKCE (code_challenge_methods_supported). If that field is absent, the spec is blunt: “the authorization server does not support PKCE and MCP clients MUST refuse to proceed”6. There is no fallback to unprotected authorization codes.

Step 4 — Client identification. The client identifies itself to the authorization server by one of three mechanisms, tried in priority order: pre-registered client credentials (an existing relationship), Client ID Metadata Documents (an HTTPS URL that resolves to a JSON document describing the client — useful because MCP clients and servers routinely have zero prior relationship), or Dynamic Client Registration under RFC 75918, kept for backwards compatibility.

Step 5 — Authorization request. The client generates PKCE parameters, opens a browser to the authorization endpoint, and — this is the part that didn’t exist before June 2025 — includes a resource parameter identifying the canonical URI of the MCP server it intends to use the token with, per RFC 8707 Resource Indicators9:

GET /authorize?response_type=code
    &client_id=...
    &code_challenge=...&code_challenge_method=S256
    &resource=https%3A%2F%2Fmcp.example.com
    &scope=files%3Aread HTTP/1.1

Step 6 — Token exchange. The user authenticates and consents at the authorization server (not the MCP server — the MCP server is never in this loop). The client exchanges the authorization code for an access token, again including the same resource parameter in the token request.

Step 7 — Resource access. The client calls the MCP server with Authorization: Bearer <token> on every request, including requests within the same logical session — MCP is explicitly stateless at the protocol level, so there is no session cookie standing in for re-authentication6.

Step 8 — Server-side validation. The MCP server, acting purely as a resource server, validates the token’s signature, expiry, and — the clause that matters most for everything below — its audience: “MCP servers MUST validate that access tokens were issued specifically for them as the intended audience, according to RFC 8707 Section 2”96.

Notice what the resource parameter buys you across this whole flow: it is the mechanism by which a token minted by a general-purpose authorization server becomes cryptographically scoped to one specific MCP server, rather than being usable wherever a bearer token happens to be accepted.


Part 4: The confused deputy problem, mechanically

“Confused deputy” is not an MCP-specific term — it names a class of vulnerability first described in 1988 by Norm Hardy, where a program with more authority than the entity invoking it is tricked into misusing that authority on the invoker’s behalf10. MCP’s security documentation walks through a specific, mechanically precise instance of it that arises in MCP proxy servers — MCP servers that connect clients to a third-party API by acting as a single OAuth client to that third party1.

The vulnerable configuration requires four conditions simultaneously:

  1. The MCP proxy uses a static client ID when talking to the third-party authorization server (common when that authorization server doesn’t support dynamic registration).
  2. The MCP proxy lets MCP clients register dynamically, each getting their own client ID.
  3. The third-party authorization server sets a consent cookie after the first successful authorization.
  4. The MCP proxy does not enforce its own per-client consent step before forwarding to the third party.

The attack: a legitimate user authorizes normally, and the third-party authorization server drops a consent cookie scoped to the proxy’s static client ID. Later, an attacker dynamically registers a malicious client against the same proxy, pointing its redirect_uri at a server the attacker controls, and sends the victim a crafted authorization link. Because the victim’s browser still carries the consent cookie for that static client ID, the third-party authorization server skips the consent screen entirely — it has no way to know this is a different logical client behind the same static ID. The resulting authorization code gets redirected straight to the attacker’s server. The attacker exchanges it for an MCP access token and now acts as the victim against the MCP server, without the victim ever seeing a second consent prompt1.

The fix the spec mandates is a second, independent consent step, owned entirely by the MCP proxy, that runs before the third-party redirect: the proxy maintains its own per-client_id consent registry, checked before every forward to the third party, with the consent-tracking state (cookies, state parameter) never set until after that consent screen has been explicitly approved1. The subtlety worth internalizing: PKCE does not save you here, because the attacker is exploiting a cookie the third-party server set, not intercepting the authorization code in transit. This is a session-management bug wearing an OAuth costume, and it required the spec authors to write out state-cookie handling requirements in a level of procedural detail — set the cookie after consent, not before; bind it to the specific client_id, not just “user has consented” — that reads more like a web-framework security checklist than a protocol spec.


Part 5: Token passthrough — the ban and the reasoning behind it

Where the confused deputy problem is about authorization codes, token passthrough is about access tokens, and it’s the anti-pattern the opening anecdote committed. The current spec states it in the flattest possible terms:

“If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. 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.”6

The security-best-practices document lists four concrete reasons this MUST NOT exists, and they’re worth separating because “just don’t do it” tends not to survive contact with a deadline unless the reasoning sticks:

  • Security control circumvention. Rate limiting, request validation, and traffic monitoring on the downstream API are often keyed to the token’s audience or the calling client’s identity. A passthrough token lets the MCP client bypass every one of those controls, because as far as the downstream API’s logs are concerned, the traffic is the original, trusted client1.
  • Broken audit trails. The downstream resource server’s logs attribute the request to whatever identity minted the passthrough token — not to the MCP server that’s actually the one making the call. Incident investigation degrades immediately: you cannot tell, after the fact, which MCP server touched what.
  • Trust boundary violations. Downstream services grant trust based on assumptions about who’s calling — often implicit ones about calling patterns or origin. A token accepted by multiple services without per-service validation means a single compromised service can pivot laterally using the same token.
  • Foreclosed evolution. Even an MCP server that starts as a “pure proxy” today will likely need its own security controls later. Passthrough forecloses that option by design, because there’s no MCP-server-issued token to attach anything to.

The correct alternative, which several MCP gateway implementations have converged on, is RFC 8693 Token Exchange: the MCP server receives the client’s token, validates its audience is itself, and then — as its own OAuth client — exchanges it at the upstream authorization server for a new, audience-bound token scoped specifically to the upstream API, in an on-behalf-of flow that preserves the original user’s identity without ever forwarding their literal token bytes downstream. The upstream API’s logs then correctly show “MCP server X, acting for user Y” rather than a token whose provenance is opaque.


Part 6: A minimal resource server, done correctly

Here’s what audience validation and RFC 9728 discovery look like on the server side, in Python. This is deliberately minimal — no library abstracts away the two checks that matter most: metadata publication, and rejecting anything not addressed to you.

import json
import time
from functools import wraps

import jwt
from jwt import PyJWKClient
from flask import Flask, request, jsonify

app = Flask(__name__)

MCP_CANONICAL_URI = "https://mcp.example.com"          # this server's own identity
AUTH_SERVER_ISSUER = "https://auth.example.com"
JWKS_URL = f"{AUTH_SERVER_ISSUER}/.well-known/jwks.json"
RESOURCE_METADATA_URL = f"{MCP_CANONICAL_URI}/.well-known/oauth-protected-resource"

jwk_client = PyJWKClient(JWKS_URL)


@app.route("/.well-known/oauth-protected-resource")
def protected_resource_metadata():
    # RFC 9728: tell clients which authorization server to use.
    # This is the ONLY identity-provider-shaped thing this server does.
    return jsonify({
        "resource": MCP_CANONICAL_URI,
        "authorization_servers": [AUTH_SERVER_ISSUER],
        "scopes_supported": ["mcp:tools-basic", "files:read", "files:write"],
        "bearer_methods_supported": ["header"],
    })


def require_valid_token(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        auth_header = request.headers.get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            return _unauthorized("missing bearer token")
        token = auth_header.removeprefix("Bearer ")

        try:
            signing_key = jwk_client.get_signing_key_from_jwt(token)
            claims = jwt.decode(
                token,
                signing_key.key,
                algorithms=["RS256", "ES256"],
                issuer=AUTH_SERVER_ISSUER,
                options={"verify_aud": False},   # we check aud/resource manually below
            )
        except jwt.PyJWTError as exc:
            return _unauthorized(f"invalid token: {exc}")

        # RFC 8707 Section 2 / MCP spec: reject anything not issued FOR this server.
        # This is the single check that prevents this server from being used
        # as a confused deputy for tokens minted for some other resource.
        audience = claims.get("aud")
        audiences = audience if isinstance(audience, list) else [audience]
        if MCP_CANONICAL_URI not in audiences:
            return _unauthorized("token audience does not include this resource")

        if claims.get("exp", 0) < time.time():
            return _unauthorized("token expired")

        request.token_claims = claims
        return f(*args, **kwargs)

    return wrapper


def _unauthorized(reason: str):
    resp = jsonify({"error": "invalid_token", "error_description": reason})
    resp.status_code = 401
    resp.headers["WWW-Authenticate"] = (
        f'Bearer resource_metadata="{RESOURCE_METADATA_URL}"'
    )
    return resp


@app.route("/mcp/tools/read_file", methods=["POST"])
@require_valid_token
def read_file():
    # This handler NEVER forwards request.headers["Authorization"] anywhere.
    # If it needs to call an upstream API, it exchanges the validated claims
    # for a fresh, upstream-audience-bound token (RFC 8693) — never the
    # client's original bearer token.
    user_id = request.token_claims["sub"]
    path = request.json.get("path")
    return jsonify({"content": f"[contents of {path} for {user_id}]"})

Two things to flag. First, the verify_aud=False plus manual audience check is deliberate, not sloppy — PyJWT’s built-in audience check expects a single expected value, but RFC 8707 tokens can legitimately carry multiple audiences, and the spec requires checking membership, not equality. Second, notice what’s absent: no login route, no session store, no password hashing, no refresh-token table. That absence is the entire point of the June 2025 rewrite — this file is smaller and has a smaller attack surface than the March 2025 model would have permitted, because none of that logic belongs here anymore.


Part 7: The 2026-07-28 additions — what a year of production traffic taught the spec

The most recent spec revision, dated 2026-07-28, added three sections that read like they were written in direct response to incident reports rather than abstract threat modeling1.

State handle hijacking. MCP is stateless by design — no protocol-level sessions — so servers that need continuity across requests (a shopping cart ID, a workflow ID) mint an explicit handle and receive it back as an ordinary tool argument. The spec now explicitly forbids treating possession of that handle as authentication: “MCP servers MUST NOT treat possession of a state handle as authentication,” and recommends binding handles server-side to the authenticated user by keying stored state as <user_id>:<handle>, where the user ID comes from the verified token, never from client-supplied data1. This is the same lesson as predictable session IDs in classic web apps, transplanted into agent tool-calling.

Mix-up attacks. An MCP client that talks to many authorization servers over its lifetime can be tricked, by a malicious authorization server it also happens to trust, into sending an authorization code intended for an honest server to the malicious one instead1. The mitigation binds the authorization response to the specific authorization server the client recorded before redirecting, via the iss parameter — and the spec is candid that PKCE alone doesn’t help, since the client hands its code_verifier straight to whichever token endpoint it’s talking to, honest or not.

OAuth authorization URL validation. This one has a direct real-world antecedent. A malicious MCP server can hand a client a javascript: URL as its “authorization endpoint,” and a client that passes that straight to window.open() or a shell command executes it. The spec now mandates strict scheme allowlisting (http:///https:// only, with http:// permitted solely for loopback addresses) and forbids opening URLs via shell commands at all1. The reason this section exists is CVE-2025-6514.


Part 8: What happens when you get it wrong — three incidents

CVE-2025-6514 (mcp-remote, CVSS 9.6). JFrog’s security research team disclosed, on July 9, 2025, an OS command injection vulnerability in mcp-remote, a popular bridge that lets local MCP clients connect to remote MCP servers11. The root cause: mcp-remote took the authorization_endpoint URL returned by a remote MCP server’s OAuth metadata and passed it, insufficiently sanitized, into a shell command used to open the user’s browser. A malicious or compromised MCP server could therefore achieve full remote code execution on the client’s machine — not by exploiting a bug in AI reasoning or prompt injection, but by weaponizing exactly the “open this authorization URL” step every OAuth flow requires. Versions 0.0.5 through 0.1.15 were affected, with over 437,000 downloads recorded before the fix in 0.1.1611. This is the incident the 2026-07-28 spec’s “OAuth Authorization URL Validation” section exists to prevent.

The Asana cross-tenant leak. Asana introduced an MCP server for its AI integration on May 1, 2025. A logic flaw in the server’s access-control layer — not an authorization-protocol failure exactly, but a case study in what happens when the resource-server boundary isn’t enforced carefully — allowed users in one organization to see project names, task descriptions, comments, and files belonging to entirely separate tenants. Asana discovered the issue on June 4, took the MCP server offline immediately, and kept it down until June 17 while resetting every connection12. Roughly 1,000 customers were affected. The incident is a reminder that “validate the token audience” is necessary but not sufficient — a resource server also has to enforce tenant isolation within its own data layer, something no OAuth clause can do for you.

WhatsApp tool poisoning. Invariant Labs demonstrated a distinct but related attack class: a malicious MCP server registers a tool whose description — the text the AI model reads to decide how and when to call it — contains hidden instructions invisible to the human user but fully visible to the model. In their proof of concept, a poisoned tool description on an unrelated, seemingly benign server instructed the connected LLM to exfiltrate the user’s entire WhatsApp message history through the tool’s own parameters, with no visible indication of exfiltration in the output the human sees13. Tool poisoning isn’t an OAuth failure at all — no token was misused — but it belongs in the same conversation because it exploits the same structural fact the rest of this post has been circling: an MCP server, once connected, sits inside the trust boundary of everything else the agent can reach.


Part 9: What to actually do

If you are building or operating an MCP server today, the prescription that falls out of eighteen months of spec revisions and incident reports is short:

  1. Never let your MCP server issue its own tokens. If you find yourself writing a login route inside your MCP server, stop — that logic belongs in a real authorization server, and the June 2025 spec rewrite exists specifically so you don’t have to build one.
  2. Validate audience, not just signature. A cryptographically valid token that wasn’t issued for you is not a valid request. Reject it, and don’t be tempted to accept it “just this once” because a client is misconfigured — that’s exactly the crack token passthrough incidents grow from.
  3. Never forward a client-presented token to a downstream API. If you need to call an upstream service, act as an OAuth client to it and use token exchange (RFC 8693) to get a fresh, upstream-audience-bound token. This is the single highest-leverage rule in this entire post.
  4. If you run a proxy to a third party with a static client ID, implement your own per-client consent gate before you ever redirect there — and set any tracking cookie only after that consent, not before.
  5. Treat every URL a remote MCP server hands you as attacker-controlled. Allowlist schemes, never shell out to open a URL, and apply the same SSRF discipline (block private IP ranges, pin DNS between check and use) to every metadata-discovery fetch your client performs.
  6. Bind server-minted state handles to the authenticated user, server-side. Never treat “the caller knows this ID” as proof the caller owns it.
  7. Read tool descriptions as untrusted input, because that’s what they are. If your client renders or reasons over tool descriptions from third-party servers, the description text deserves the same suspicion as any other content an attacker could plant in front of a model.

Closing

MCP’s authorization spec has now been rewritten, extended, and hardened four times in under two years — March 2025, June 2025, November 2025, July 2026 — and every one of those revisions removed a piece of implicit trust the previous version had assumed without examining it. The pattern underneath all four rewrites is the same one OAuth itself learned over its first decade: separating who authenticates a user from who serves their data isn’t bureaucratic overhead, it’s the mechanism that makes every other security property — audit trails, scoped tokens, revocation, blast-radius containment — possible to reason about at all. MCP servers collapsed that separation in their first spec because building a new protocol from scratch makes it easy to forget lessons that live in scar tissue rather than in a checklist. It took a security researcher’s blog post, a formal spec section on the confused deputy problem, an RCE with a CVSS score of 9.6, and roughly a thousand Asana customers’ project data to relearn it in public.

None of that makes MCP unusual — every protocol that lets agents act with real credentials against real APIs will eventually have this argument with itself. What’s worth taking from it is the shape of the fix, which is boring in the best way: stop minting your own tokens, check the audience claim, don’t forward what wasn’t issued to you, and treat every URL a peer hands you as hostile until proven otherwise. None of that is novel cryptography. It’s just OAuth, applied with the discipline that a decade of incidents eventually forces onto every protocol that skips it the first time.


References


  1. Model Context Protocol, “Security Best Practices”, Documentation version 2026-07-28. https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Anthropic, “Introducing the Model Context Protocol”, November 25, 2024. https://www.anthropic.com/news/model-context-protocol ↩︎ ↩︎ ↩︎

  3. The Linux Foundation, “Linux Foundation Announces the Formation of the Agentic AI Foundation (AAIF)”, December 9, 2025. https://www.linuxfoundation.org/press/linux-foundation-announces-the-formation-of-the-agentic-ai-foundation ↩︎

  4. Parecki, A., “Let’s Fix OAuth in MCP”, April 3, 2025. https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol ↩︎ ↩︎ ↩︎

  5. Jones, M.B., Hunt, P., and Parecki, A., “OAuth 2.0 Protected Resource Metadata”, IETF RFC 9728, April 2025. https://datatracker.ietf.org/doc/html/rfc9728 ↩︎

  6. Model Context Protocol, “Authorization”, Specification version 2025-11-25. https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  7. Jones, M.B., Sakimura, N., and Bradley, J., “OAuth 2.0 Authorization Server Metadata”, IETF RFC 8414, June 2018. https://datatracker.ietf.org/doc/html/rfc8414 ↩︎

  8. Richer, J. (Ed.), Jones, M., Bradley, J., Machulak, M., and Hunt, P., “OAuth 2.0 Dynamic Client Registration Protocol”, IETF RFC 7591, July 2015. https://datatracker.ietf.org/doc/html/rfc7591 ↩︎

  9. Campbell, B., Bradley, J., and Tschofenig, H., “Resource Indicators for OAuth 2.0”, IETF RFC 8707, February 2020. https://www.rfc-editor.org/rfc/rfc8707 ↩︎ ↩︎

  10. Wikipedia, “Confused Deputy Problem”, based on Hardy, N., “The Confused Deputy: (or why capabilities might have been invented)”, ACM SIGOPS Operating Systems Review, 1988. https://en.wikipedia.org/wiki/Confused_deputy_problem ↩︎

  11. JFrog Security Research, “OS Command Injection in mcp-remote When Connecting to Untrusted MCP Servers”, CVE-2025-6514, July 9, 2025. https://research.jfrog.com/vulnerabilities/mcp-remote-command-injection-rce-jfsa-2025-001290844/ ↩︎ ↩︎

  12. BleepingComputer, “Asana Warns MCP AI Feature Exposed Customer Data to Other Orgs”, June 2025. https://www.bleepingcomputer.com/news/security/asana-warns-mcp-ai-feature-exposed-customer-data-to-other-orgs/ ↩︎

  13. Invariant Labs, “MCP Security Notification: Tool Poisoning Attacks”. https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks ↩︎