MCP doesn't mandate an auth scheme. The transport layer handles it, which means the spec leaves you with three real options. Here's how they compare and when each one is the right call.
API keys
The simplest pattern. The client sends a key in a header or env variable, the server validates it on each request.
{
"env": {
"MCP_API_KEY": "sk_live_..."
}
}API keys work for everything: stdio, SSE, HTTP, WebSocket. The implementation on the server is straightforward. A single middleware that looks up the key and rejects anything invalid.
The strengths are obvious. Simple to issue, simple to rotate, simple to revoke. Easy to scope: prefix the key with the server name, include permissions in the database row, and you're done. No external dependencies.
The weaknesses show up at team scale. Every user needs their own key. When someone leaves, you revoke their key, but if you've embedded keys in shared configs, you need to update those too. Rotation requires coordinated client updates.
API keys also can't express delegation cleanly. If a user wants to grant a tool limited access to act on their behalf, you end up either issuing a second more-restricted key or building an entire OAuth flow on top.
Use API keys when: the user and the client are the same person, and you don't need delegation. Personal dev tools, internal company servers, single-tenant deployments.
OAuth 2.0 with PKCE
The right pattern when your MCP server needs to act on behalf of an end user against a third-party service.
Example: an MCP server that reads a user's GitHub issues. The user owns their GitHub account. The server doesn't have permission to read those issues by default. OAuth lets the user grant scoped permission to the server, and the server gets back a token it can use to call GitHub on the user's behalf.
The flow:
- User clicks "Connect GitHub" in the client.
- Client redirects to GitHub's authorization endpoint.
- User approves the requested scopes.
- GitHub redirects back with an authorization code.
- Client exchanges the code for an access token via the token endpoint.
- Client stores the token and passes it to the MCP server on subsequent requests.
PKCE, short for Proof Key for Code Exchange, handles the public-client case where you can't safely embed a client secret. It's required for desktop and mobile clients and recommended for all public clients.
The strength of OAuth is delegation. You can grant a specific MCP server access to specific scopes: read issues but not write code, list repos but not delete them. Revocation is a single click in the user's GitHub settings.
The weakness is operational complexity. You're now running a token storage system, handling refresh flows, dealing with token expiration mid-conversation. Most teams underestimate this.
Use OAuth when: the server accesses third-party resources owned by individual users. Calendar integrations, code hosting platforms, CRM access.
JWT with short-lived tokens
The middle ground. The server issues signed JWTs containing the user identity and scopes. Tokens are short-lived (5-15 minutes) and refreshed transparently.
This pattern shines for multi-tenant SaaS products. Your auth service issues a token after the user logs in. The token contains the user's tenant ID, role, and permissions. The MCP server validates the JWT signature and uses the claims to scope every tool call.
{
"sub": "user_abc123",
"tenant": "acme_corp",
"scopes": ["postgres:read", "github:read"],
"exp": 1735689600
}The strength is that the MCP server is stateless. It doesn't need a database lookup on every request because the token contains everything needed to authorize the call. This scales horizontally without coordination.
The weakness is revocation. JWTs are valid until they expire. If a user is terminated mid-session, their token still works until it expires. Mitigations include a revocation list (which reintroduces the database lookup) or very short token lifetimes with frequent refreshes.
Use JWT when: you're building a multi-tenant SaaS where the MCP layer is one piece of a larger product. You already have an auth system and you're extending it.
What we recommend by default
For most teams, API keys are the right starting point. They handle 80% of cases with 10% of the complexity. Start there. Move to OAuth when you need delegation, JWT when you need multi-tenancy.
Avoid building a custom auth scheme from scratch. The mistakes are subtle and security-critical. If you're tempted to use HMAC signatures, request signing with timestamps, or anything that involves writing your own crypto, don't. Use one of the three patterns above, all of which have been battle-tested for a decade outside MCP.
How Toolcall handles this
We default to scoped API keys. Each server gets its own key, with optional IP allowlisting and rate limits per key.
For OAuth, we provide pre-built integrations for the major providers (GitHub, Google, Microsoft, Slack, Notion) so you don't write the flow yourself. The token storage is on us. The server gets a fresh access token on each tool call.
JWT support is on the roadmap for v0.4 and aimed at customers building their own SaaS on top of MCP.
Pick the pattern that matches your access model. Don't pick the pattern that sounds most sophisticated.





