Skip to main content

What is this?

This feature lets your users connect their personal accounts to AI agents you build. When a user wants your agent to access their Gmail, GitHub, or Notion, they simply click “Authorize” in their browser—just like signing into any app. The agent then stores their credentials and can access their data on their behalf. Example: You build a productivity agent. A user asks it to “summarize my unread emails.” The agent prompts the user to authorize Gmail access. They click approve, and the agent can now read their inbox. Next time, it just works—no login required until the token expires.

Why use this?

  • Users authenticate themselves - You don’t need their passwords or API keys
  • Tokens are stored per user - Each user’s credentials are isolated
  • Automatic refresh - Tokens renew automatically; users only re-authorize when needed
  • Works with any OAuth provider - GitHub, Google, Notion, and more

Where OAuth is used (supported run modes)

Use this when your agency runs on a server and users authenticate from a web/mobile UI.
  • Mode: saas_stream
  • Hosted MCP (Notion): Configure MCPServerOAuth(url=..., name=...) and deploy your agency.
  • Self-hosted MCP (GitHub/Google): Run your OAuth-enabled MCP server + deploy your agency.
  • HostedMCPTool (remote MCP): If you use HostedMCPTool, OAuth tokens must be provided via tool_config.authorization. In FastAPI, wrap only the OAuth-protected hosted tool with enable_hosted_mcp_tool_oauth(...). Agency Swarm creates an in-memory callback-state registry by default. For multiple workers, an external oauth_registry shares callback state only; configure oauth_token_path on shared persistent storage as well, or use sticky routing or a single worker. Agency Swarm surfaces the auth URL via event: oauth_redirect and injects the access token once authorized. Public hosted MCP servers should stay unwrapped.
  • Deferred auth trigger (all modes): Startup never forces OAuth discovery. The model gets an authenticate_mcp_server(server_name) tool and OAuth starts only when that tool is invoked for a specific authenticated MCP server.
  • Tool parameter contract: server_name is constrained to the configured authenticated MCP server names for that agent.
  • Streaming is required: OAuth authorization is delivered via event: oauth_redirect, so use POST /{agency}/get_response_stream. The non-streaming POST /{agency}/get_response endpoint is not compatible with OAuth-enabled MCP servers.
  • Deterministic stream contract after activation: meta -> oauth_redirect -> oauth_status (pending) -> keepalive comments every 15s while waiting -> terminal oauth_status (authorized, error:<reason>, or timeout) -> messages -> end.
  • Bounded wait: SaaS OAuth waits up to 10 minutes before emitting oauth_status = timeout.
  • Discovery-time auth after activation: with standard FastMCP auth, MCP schema discovery (list_tools) is protected, so OAuth starts during discovery after the model invokes authenticate_mcp_server(server_name).
  • Per-user token isolation: Configure oauth_user_id_dependency so each authenticated user gets a separate token bucket.
See the FastAPI OAuth section: OAuth-enabled agencies.

Two types of MCP servers

Hosted servers (like Notion) handle OAuth for you—just connect:
Self-hosted servers (GitHub, Google) require you to run a FastMCP server with your OAuth credentials:
First run opens the browser for consent. Tokens are cached under ~/.agency-swarm/mcp-tokens/ and reused automatically. Users only re-authorize when tokens expire.

OAuth flow at a glance

In FastAPI mode, OAuth still happens at MCP discovery time (list_tools), but only after the model explicitly invokes authenticate_mcp_server(server_name).

Setup requirements

Self-hosted: Register an OAuth app

For GitHub/Google, you need to create an OAuth app and run a FastMCP server:
  1. GitHub: Developer Settings → New OAuth App
    • Callback URL: http://localhost:8001/auth/callback
    • Export: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
  2. Google: Cloud Console → Create Credentials → OAuth client ID
    • Callback URL: http://localhost:8002/auth/callback
    • Export: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
Then run the FastMCP server (see examples/mcp_oauth/github_server.py for GitHub, examples/mcp_oauth/google_server.py for Google).

Configuration reference

  • Naming drives env vars: name="github" -> GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET.
  • Scopes are explicit: pass only what the tool needs (e.g., ["repo", "user"]).
  • Redirects: local runs default to http://localhost:8000/auth/callback. For FastAPI, set redirect_uri (or OAUTH_CALLBACK_URL / <SERVER>_REDIRECT_URI) to your publicly reachable /auth/callback URL.

Token storage and isolation

Tokens default to ~/.agency-swarm/mcp-tokens/{user}/{server-client}/. Set AGENCY_SWARM_MCP_CACHE_DIR for a global override or pass cache_dir per server. In multi-worker FastAPI deployments, oauth_registry and token storage solve different problems: the registry coordinates the pending browser callback, while oauth_token_path stores the resulting token. An external registry does not share worker-local token files. Put oauth_token_path on storage mounted by every worker, or use sticky routing or a single worker. User-scoped buckets use a collision-resistant filesystem segment derived from the authenticated user ID or set_oauth_user_id(). Each server bucket also includes the effective OAuth client configuration, so different client registrations cannot reuse each other’s tokens. Legacy buckets that do not identify the user or OAuth client unambiguously are intentionally not auto-migrated; those users must authorize again instead of risking credential reuse. For FastAPI, pass a trusted dependency that authenticates the request and returns a stable, non-secret user ID:
Do not read the user ID directly from a request header or body. If browsers reach your app with a cookie or session, also pass verify_oauth_callback_user=True so /auth/callback rejects a code whose pending flow belongs to a different user. See Binding the callback to a user. For local or other non-FastAPI runs, either provide oauth_token_path with user_context, or call set_oauth_user_id() before the run:
Per-user layout (conceptual):
Access tokens refresh automatically via the MCP SDK; if a refresh token expires, the flow will prompt for authorization again.
Tools activated by authenticate_mcp_server hold a live authenticated session, not just a token file. When one shared Agency serves several users, the activated tools are dropped as soon as the OAuth user changes, so the next user’s agent starts from authenticate_mcp_server against its own token bucket instead of reusing the previous user’s session. Identify every run: call set_oauth_user_id() before it, or pass user_id through user_context (or context_override).

Multiple OAuth servers

Advanced: Custom handlers (when you are not using FastAPI)

If you are building your own UI/server (not using run_fastapi), you can send the auth URL to your frontend and wait for the callback code there:
See OAuth-enabled agencies for the FastAPI production flow.

Troubleshooting

  • Browser doesn’t open or redirect fails: paste the full callback URL when prompted; confirm it matches the provider config.
  • Credentials rejected: re-export *_CLIENT_ID and *_CLIENT_SECRET, then delete the cached folder for that server.
  • Wrong storage path: set AGENCY_SWARM_MCP_CACHE_DIR or pass cache_dir=Path(...) to keep tokens in a specific location.

See Also