A tool accepts customer_id and returns a customer’s invoices. Its schema is valid, the access token has a valid signature, and the requested customer exists.
The call may still be unauthorized. The token could belong to another resource server. The user could belong to a different tenant. The MCP client might not have the consent required to act through a proxy.
A schema defines what a call looks like. Authorization determines whether this caller may perform it on this object.
Keep the identities distinct
For a protected remote service, the path is roughly:
user → MCP client → MCP server → downstream API
↘ authorization server ↗
There can be more than one authorization server in a proxy deployment. The MCP server is a resource server for incoming calls and may also be an OAuth client when calling a downstream API. Those roles do not share a token by default.
The MCP authorization specification, version 2026-07-28, scopes this authorization flow to HTTP transports. A local stdio server has a different process and operating-system trust boundary; do not paste the remote flow onto it without explaining the distinction.
For an incoming HTTP request, established authorization middleware should validate the token’s signature or introspection result, issuer, intended audience, expiry and relevant scopes. Decoding JWT claims is not validation. A correctly signed token for another API must not become a credential for this MCP server.
The second check belongs to your application
After authentication, derive user and tenant identity from trusted authorization context. Resolve the requested object and enforce the application’s policy before reading or mutating it.
If a tool takes tenant_id as an argument, that field is a requested target, not proof of membership. The SDK cannot infer that invoice 913 belongs to tenant B while the authenticated user belongs to tenant A. Your application must bind the identities to the resource.
Scopes such as invoices:read describe a class of action. They do not necessarily mean “every invoice in every tenant.” Conversely, an object ownership check does not compensate for accepting a token with the wrong audience.
The proxy consent trap has specific conditions
The official security guidance describes a confused-deputy scenario involving a proxy with a static upstream OAuth client ID, dynamic registration of downstream clients, an upstream consent cookie, and missing per-client consent at the proxy.
A user may previously have approved the proxy upstream. That does not mean the user approved every newly registered MCP client that can reach it. Reusing upstream consent without enforcing the client-specific decision can let the proxy act on behalf of an unapproved client.
This is a documented threat model, not a report that every MCP proxy is vulnerable or that a particular breach occurred. Preserve client identity and enforce the consent step at the boundary described by the protocol and your deployment.
A session ID is not a credential
Session state can associate related requests. It cannot replace authorization on each HTTP request. A caller that knows a session identifier should not inherit the authority of the user who created that session.
Likewise, the MCP security guidance forbids token passthrough: accepting a token intended for a downstream service and simply relaying it obscures the resource boundary. Obtain downstream credentials through the supported authorization flow and keep them separate from credentials issued for the MCP server.
Prove denials do not reach the tool
A useful integration test checks both the response and the absence of the side effect:
| Request | Expected result | Additional assertion |
|---|---|---|
| Correct signature, wrong audience | Denied | No downstream call |
| Expired or wrong-issuer token | Denied | No downstream call |
| Valid identity, missing required scope | Denied | No protected operation |
| Tenant A user requests tenant B object | Denied | No data or existence details leaked beyond policy |
| Session ID without authorization | Denied | No inherited access |
| Unapproved client reuses proxy consent | Consent enforced or request denied | No protected upstream action |
Add positive controls, so a completely broken service does not “pass” by denying everything. Exercise revocation, expiry during a session and writes as well as reads. Keep test credentials out of logs.
This article is a standards-based design review and test plan. It does not ship a custom OAuth implementation or claim a tested SDK integration. The official authorization tutorial is a starting point for a pinned implementation, which still needs these application-specific tests.
The agent security chapter covers containment and prompt injection. Those controls limit capabilities, but a sandbox can still call an allowed endpoint with excessive authority. Put identity, consent and object policy at the tool boundary where the effect occurs.