MCPWorks

OAuth 2.0 for MCP Servers: How AI Agents Authenticate to External Services

Simon Carr

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:

  1. The system gives you a short code (like WDJB-MJHT)
  2. You go to a URL on any device and enter it
  3. 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
Google 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 provider
  • mcpworks_oauth_token_refreshes_total — silent refresh attempts and their outcomes
  • mcpworks_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.

View on GitHub Cloud Trial — Coming Soon