OAuth 2.0 for MCP Servers: How AI Agents Authenticate to External Services
When you register an external MCP server on a MCPWorks namespace — say, a Google Workspace server that can read emails and manage calendar events — the agent needs credentials. API keys work for simple cases, but most serious integrations require OAuth 2.0. The user has to consent, tokens expire and need refreshing, and the whole flow has to work even when the agent is running a scheduled task at 3am with no user in the loop.
Today we shipped OAuth 2.0 support for the MCP server proxy, built around RFC 8628 device authorization flow as the primary method with authorization code flow as a fallback.
Why device flow?
The standard OAuth authorization code flow requires a browser redirect to a callback URL. That works fine for web apps, but MCP clients are often terminals, IDE extensions, or agentic platforms where there's no browser context to redirect to. Self-hosted installations behind NATs can't receive callbacks at all.
RFC 8628 device flow solves this with a pattern you've probably used before — signing into a streaming app on your TV:
- The system gives you a short code (like
WDJB-MJHT) - You go to a URL on any device and enter it
- The system detects your approval automatically
This maps perfectly onto how AI agents interact with humans. When an agent needs authorization, it can naturally say: "To access Google Workspace, go to google.com/device and enter code WDJB-MJHT." No callback URLs, no browser redirects, no port forwarding.
How it works in practice
Register an OAuth-protected MCP server on your namespace:
add_mcp_server(
name="google_workspace",
url="https://google-workspace-mcp.example.com/mcp",
transport="streamable_http",
auth_type="oauth2",
oauth_config={
"client_id": "123456789.apps.googleusercontent.com",
"client_secret": "GOCSPX-...",
"scopes": ["https://www.googleapis.com/auth/gmail.readonly"],
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
"token_endpoint": "https://oauth2.googleapis.com/token",
"flow": "device"
}
)
The first time the agent calls a tool on that server, the proxy detects there's no valid token and returns a structured AUTH_REQUIRED response:
{
"auth_required": true,
"provider": "google_workspace",
"verification_uri": "https://www.google.com/device",
"user_code": "WDJB-MJHT",
"message": "Go to https://www.google.com/device and enter code WDJB-MJHT",
"expires_in": 600
}
A background asyncio task starts polling the token endpoint. When the user approves, the poller picks it up, encrypts the tokens, and stores them. The agent retries and the call goes through. Every subsequent call is transparent — no user interaction, no latency penalty.
Silent token refresh
OAuth access tokens expire. Typically after an hour for Google, sometimes less for other providers. If you wait for a 401 before refreshing, you add 1-2 seconds of latency to what should be a fast tool call — and in code-mode where multiple MCP calls chain together, that compounds.
We refresh proactively: a background check runs before token expiry (5-minute buffer), acquires a Redis lock to prevent concurrent refresh races, and swaps in the new token before any call needs it. A reactive 401 handler catches the edge case where a token is revoked between refresh cycles.
Authorization code fallback
Not every provider supports device flow. Slack and Linear only support the standard authorization code redirect. For these, the proxy returns an auth_url instead of a user_code:
{
"auth_required": true,
"provider": "slack",
"auth_url": "https://slack.com/oauth/v2/authorize?client_id=...",
"flow": "authorization_code"
}
The user clicks the URL, approves, and gets redirected to our callback endpoint at /v1/oauth/mcp-callback. The consent page includes an informed disclosure of what the token will be used for — which namespace, which scopes, and by which agent.
Security: per-namespace tokens, encrypted at rest
OAuth tokens are scoped to namespaces, not individual users. This is a deliberate design choice: when an agent runs a scheduled task at 3am, there's no "current user" to look up tokens for. The namespace is the identity boundary.
Tokens are encrypted at rest using the same AES-256-GCM envelope encryption we use for MCP server headers. Each token gets a random data encryption key (DEK), wrapped by the key encryption key (KEK). The encrypt_value() / decrypt_value() functions handle JSON serialization, per-value random IVs, and key wrapping — no new crypto infrastructure was needed because the pattern already existed.
Provider-agnostic by design
The proxy doesn't contain Google-specific or Slack-specific logic. Users supply the standard OAuth endpoints — device_authorization_endpoint, token_endpoint, client_id, client_secret, and scopes — and the proxy speaks RFC 8628 or RFC 6749 accordingly.
Any OAuth 2.0 provider works:
| Provider | Device Flow | Auth Code |
|---|---|---|
| Yes | Yes | |
| GitHub | Yes | Yes |
| Microsoft/Azure AD | Yes | Yes |
| Slack | No | Yes |
| Linear | No | Yes |
Observability
Three new Prometheus metrics track the OAuth lifecycle:
mcpworks_oauth_device_flows_total— device flow initiations by namespace and providermcpworks_oauth_token_refreshes_total— silent refresh attempts and their outcomesmcpworks_oauth_auth_required_total— how often agents hit the HITL auth wall
Combined with our existing MCP proxy latency metrics, you can see exactly how OAuth affects your agent's tool call performance — and whether token refresh is working or causing retries.
What this enables
OAuth support for MCP servers unlocks integrations that API keys can't reach. An agent on a MCPWorks namespace can now authenticate to Google Workspace, read emails, check calendars, manage Drive files — all through the standard MCP tool interface, with proper user consent and token lifecycle management.
The same pattern works for any OAuth-protected service with an MCP server: GitHub for repository management, Microsoft 365 for enterprise workflows, Slack for team communication. Register the server, approve once, and the agent handles the rest.
The full implementation — including spec documents, research decisions, and the quickstart guide — is in the MCPWorks API repository under specs/026-oauth-mcp-proxy/.
MCPWorks is the open-source standard for token-efficient AI agents. Learn more at mcpworks.io.
MCPWorks is open source.
Self-host free forever, or try MCPWorks Cloud — 14-day Pro trial, no credit card.