Third-Party Credentials in an MCP Server: Account Linking Without a Confused Deputy

Most writing about MCP authorization stops at the front door: the client gets a token, your server validates it, the tool call runs. That is the easy half when your server only touches data you already hold.

The harder half appears when a tool needs a credential for a system you do not own. A support-desk MCP server that has to read a customer's repository. An analytics server that queries the client's warehouse. An internal server that calls three SaaS APIs on behalf of whoever is asking. Now your server holds, or can obtain, credentials for a third party, and every one of those credentials is an authorization decision made once and reused many times.

This is where MCP servers turn into confused deputies. The pattern is worth naming precisely, because reviewers name it.

The confused deputy, stated plainly

Your server holds a credential that grants access to a third-party system. A caller reaches your server with their own MCP access token, calls a tool, and the tool uses the stored credential. If the server does not tie the stored credential to the caller's identity, the caller has just borrowed someone else's access, or the whole organization's access, through your deputy.

The MCP authorization specification's security guidance calls this out alongside token passthrough for a reason: both are cases of a server exercising authority that the request in front of it did not actually carry. The MCP token proves who is asking. It proves nothing about which third-party account they may use.

So the rule is one sentence. Every third-party credential is stored against a principal, and a tool call may only use credentials stored against the principal in the validated MCP token.

Decide the credential model before you write the tool

There are three models in practice, and they are not interchangeable.

Per-user linked accounts. Each user completes an OAuth authorization with the third party, and your server stores that user's refresh token. Access in the third-party system is exactly what that user has. This is the model that survives a security review, and it is the one to choose unless it is impossible.

One shared service credential. Your server holds a single account or app credential for the third party, usually with broad access. Every user's tool calls run through it. This is defensible only when your server enforces the per-user decision itself, before the downstream call, from data it can trust: the caller's identity and entitlements from the MCP token, checked against an authorization model you can show a reviewer. Write down that the downstream system is not enforcing anything. Reviewers will find it if you do not.

Caller-supplied credentials. The user passes a key or token as a tool argument. Do not do this. Tool arguments are chosen by a model that reads untrusted content, the value lands in transcripts and logs, and you have no revocation story. If a client insists, the answer is a linking flow, not an argument.

The deliverable here is a short table: for each tool, which third-party system it touches, which credential model it uses, and who is the effective principal downstream. That table is a question on the questionnaire whether or not the questionnaire asks it in those words.

Building the linking flow

Account linking is a plain OAuth authorization code flow between your server and the third party, run in a browser, on behalf of one user. The unfamiliar part is that it happens inside a product the user is reaching through an agent.

A few things the flow has to get right.

Do not run it inside a tool call. A tool call is not a browser context and should not be a place where consent happens. Have the tool detect that no credential is linked and return a structured error containing a link URL and a machine-readable reason. The host renders it; the human decides. Where the client supports elicitation with a URL, that is the same idea with better ergonomics.

Bind the linking session to the MCP identity. Generate a single-use, short-lived linking state value, store it against the user and tenant from the validated MCP token, and use it as the OAuth state parameter. On callback, look the state up, use it once, and delete it. This is what prevents a link from being redeemed into the wrong account. Use PKCE (RFC 7636) as well; OAuth 2.1 and the security best current practice in RFC 9700 both require it for authorization code flows.

Pin the redirect URI to a registered, exact value. No wildcards, no paths built from request input. Open redirects in the callback are how linking flows get turned into token theft.

Request the narrowest downstream scopes the tools actually need, and record which scopes each linked account granted. When a tool later fails because the user linked with read-only scopes, you want a clear error rather than a stack trace.

Verify the account you got. After the exchange, call the third party's identity endpoint and store the downstream account identifier alongside the credential. If your product requires that the linked account be in the same organization, check that here. This is also where you catch a user linking a personal account to a corporate workspace.

Storing and using the tokens

Store refresh tokens encrypted with a key from a managed key service, in a table whose access path is the credential service and nothing else. Do not put them in the same store as application data that a tool can read. Keep access tokens in memory or in a short-lived cache keyed by user; do not persist them past their lifetime.

The lookup should be structurally incapable of returning the wrong row. In the handler, the identity comes from the token context, never from a tool argument, and the credential service takes that identity as a required parameter. A tool that accepts a connection_id or account argument and looks it up without an ownership check is the exact bug this article exists to prevent. Test for it explicitly: two tenants, valid tokens, each one attempting to name the other's connection.

Emit an audit event when a credential is used, not only when it is created. The event should carry the MCP user and tenant, the third-party system, the downstream account identifier, the tool that requested it, and the outcome. Link, relink, and unlink are separate event types. A security team investigating an incident in the third-party system needs to be able to work backwards from a downstream account to the MCP session that used it.

Revocation and expiry

Revocation is the part that gets skipped and the part that gets asked about.

  • Give users and administrators a way to unlink, and make unlink call the third party's revocation endpoint (RFC 7009) rather than just deleting your row. A deleted row with a live refresh token is not revoked.
  • When a user is deprovisioned in your product, delete and revoke their linked credentials as part of that flow. Test the whole path.
  • Handle the refresh failure honestly. When the third party returns invalid_grant, mark the connection as broken, stop retrying, and surface a re-link prompt. Silent retry loops against an identity provider get your app rate limited, and sometimes get it suspended.
  • Expire idle connections on a defined schedule and document that schedule. "Credentials are kept until the user unlinks" is an answer, but it is a worse one.

What a reviewer will ask

Four questions, in roughly this order.

  1. Whose access is used when a tool touches system X? Answer with the credential model table.
  2. How do you prevent one tenant from using another tenant's connection? Answer with the identity-from-token rule and the cross-tenant test result.
  3. Where are the refresh tokens, who can read them, and with what key? Answer with the store, the key service, and the access path.
  4. What happens when you offboard a user? Answer with the deprovisioning flow, including revocation calls.

If your MCP server touches only your own data, none of this applies and you should not build it. If it holds credentials for someone else's system, this is the section of the review that decides the outcome, and it is easier to design in the first week than to retrofit after a questionnaire arrives.