MCP Authentication: Securing How Agents and Servers Connect
Every MCP server you connect to expects a credential. Stripe wants an API key. A GitHub server wants a token. An internal server wants a bearer string your platform team minted. The Model Context Protocol carries those credentials but defines almost nothing about how they should be issued, scoped, or revoked. So they end up where credentials always end up without a system: hard-coded into client config on every machine that runs an agent.
That is the real MCP authentication problem. Not how a single client proves itself once, but how you manage identity across a fleet of agents, people, and servers without leaking long-lived secrets into dotfiles.
How MCP authentication works today
MCP itself is transport. Authentication is delegated to the underlying connection. In practice that means one of two things.
For local servers launched over stdio, there is often no authentication at all. The server runs as a subprocess with whatever permissions the user has, and trust is implicit.
For remote servers over HTTP, the client attaches a credential, usually a bearer token or API key, in a header on every request:
{
"mcpServers": {
"stripe": {
"url": "https://mcp.stripe.com",
"headers": { "Authorization": "Bearer sk_live_..." }
}
}
}
That works for one developer and one server. It does not survive contact with a team.
Where it breaks
Secrets live in client config. A live Stripe key in a JSON file on a laptop is a live Stripe key in a laptop’s backups, shell history, and any screen share. Multiply by every server and every engineer.
No per-person identity. The server sees a key, not a person. When an agent makes a call, there is no record of who was driving it. Audit and incident response both start from nothing.
Shared keys cannot be revoked cleanly. One key shared across a team means revoking one person rotates everyone. So nobody rotates, and access outlives the people who had it.
Every client reimplements it. Claude Code, Cursor, and Codex each store credentials their own way. There is no single place to see or cut off access.
Authentication that only answers “is this a valid key” is not enough once more than one human is involved. You need to answer “which person, with what scope, that I can revoke in one click”.
Fixing authentication at the gateway
An MCP gateway moves the credential off the client and onto the boundary. The pattern is two-sided.
Downstream: per-person grant tokens. Each person or agent gets their own grant token to the proxy. It is scoped to the servers and tools they are allowed to reach, and you can revoke it without touching anyone else. The client config holds a grant token, never the real upstream secret:
{
"mcpServers": {
"stripe": {
"url": "https://proxy.policylayer.com/mcp/<server-uuid>/",
"headers": { "Authorization": "Bearer <grant-token>" }
}
}
}
Upstream: brokered credentials. The gateway holds the real Stripe key or GitHub token and attaches it when it forwards an allowed call. The secret never reaches the client, so it cannot leak from a machine that never had it.
The result: one place that knows every identity, issues scoped access, brokers upstream secrets, and logs which person made which call. Revoking a departing engineer is one token, not a key rotation across five services.
Authentication proves who is calling. The next question, what they are then allowed to do, is MCP authorization, and it is where scoped tokens turn into real limits.
Related reading
- MCP gateway: what it is and why agent fleets need one
- MCP authorization: scoping what agents can do
- We scanned open-source MCP configs
- Safely connect Claude Code to upstream MCP servers
Control what your agents can do through MCP.
Get started now →. The product is live.
Browse the policy library →. Pre-classified tools across thousands of MCP servers.
Read the MCP security reference →. What the boundary looks like.
Questions.
MCP is only a transport, so it delegates authentication to the underlying connection. Local stdio servers often have none; remote HTTP servers expect a bearer token or API key in a header on every request. The protocol does not define how those credentials are issued, scoped, or revoked.
A long-lived key in a client config file also lives in that machine's backups, shell history, and any screen share. The server sees a key, not a person, so calls cannot be attributed, and a shared key cannot be revoked for one person without rotating it for everyone.
Move the credential off the client and onto a gateway. Each person gets a scoped, revocable grant token to the gateway, while the gateway holds the real upstream secret and attaches it only to allowed calls. Revoking someone is one token, not a key rotation across every service.
Authentication proves who is calling. Authorization decides what that caller may do, on which tool, with which arguments. A bearer token answers the first question and contributes nothing to the second.