AGT-38Required
Accept only tokens issued for your MCP server and never pass them on
Check every access token's signature, issuer, expiry and audience before an MCP request runs. Refuse a token whose aud does not name this server with 401. Never forward the client's token to another API.
Spec version
MCP 2026-07-28
Review by
2027-04-30
How to do it
A token issued for another service, even by your own authorization server, must not open yours. Otherwise any service that sees your users' tokens can replay them at you. When a tool calls an upstream API, use a separate token for that API, from the person's own grant with it or from token exchange. A passed-on token makes the upstream API trust a token it never meant to accept, and hides who acted from its logs. Answer an invalid or expired token with 401 and a missing scope with 403. Exchanged tokens never widen rights, as Never widen an agent's rights when you exchange its token says. The attack procedure is in Run the AI feature checks.
Examples
JavaScript example
import { createRemoteJWKSet, jwtVerify } from 'jose';
const ISSUER = 'https://auth.example.com';
const RESOURCE = 'https://mcp.example.com/mcp';
const keys = createRemoteJWKSet(new URL(`${ISSUER}/jwks.json`));
export async function verifiedClaims(bearer) {
try {
const { payload } = await jwtVerify(bearer, keys, { issuer: ISSUER, audience: RESOURCE });
return payload;
} catch {
return null;
}
}
Python example
import jwt
ISSUER = "https://auth.example.com"
RESOURCE = "https://mcp.example.com/mcp"
keys = jwt.PyJWKClient(f"{ISSUER}/jwks.json")
def verified_claims(bearer: str) -> dict | None:
try:
key = keys.get_signing_key_from_jwt(bearer).key
return jwt.decode(bearer, key, algorithms=["RS256", "ES256"], issuer=ISSUER, audience=RESOURCE)
except jwt.PyJWTError:
return None
How to check
Send a valid token from your own authorization server issued for https://other.example.com. The server answers 401 and runs no tool. A traced test tool that calls an upstream API sends it a different token from the one the client sent.
Sources
- MCP authorization security considerations (audience and passthrough)
- RFC 8707 (Resource Indicators for OAuth 2.0)
- RFC 9068 (JWT Profile for OAuth 2.0 Access Tokens)
Related rules
- AGT-7 Never widen an agent's rights when you exchange its token in Agents as users
- AGT-33 Send the issuer with every authorization response in Agents as users
- SEC-118 Run the AI feature checks in Adversarial testing
- AGT-39 Bind every token to one MCP server with resource indicators in Agents as users
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/agents-as-users/delegated-access-and-consent/mcp-token-audience-no-passthrough">Agent Wranglers agents as users playbook</a>. In code you write, add a comment next to it with the same link.