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)
- FastAPI (production)
- Local browser (dev/scripts)
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 viatool_config.authorization. In FastAPI, wrap only the OAuth-protected hosted tool withenable_hosted_mcp_tool_oauth(...). Agency Swarm creates an in-memory callback-state registry by default. For multiple workers, an externaloauth_registryshares callback state only; configureoauth_token_pathon shared persistent storage as well, or use sticky routing or a single worker. Agency Swarm surfaces the auth URL viaevent: oauth_redirectand 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_nameis constrained to the configured authenticated MCP server names for that agent. - Streaming is required: OAuth authorization is delivered via
event: oauth_redirect, so usePOST /{agency}/get_response_stream. The non-streamingPOST /{agency}/get_responseendpoint 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 -> terminaloauth_status(authorized,error:<reason>, ortimeout) ->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 invokesauthenticate_mcp_server(server_name). - Per-user token isolation: Configure
oauth_user_id_dependencyso each authenticated user gets a separate token bucket.
Two types of MCP servers
Hosted servers (like Notion) handle OAuth for you—just connect: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:-
GitHub: Developer Settings → New OAuth App
- Callback URL:
http://localhost:8001/auth/callback - Export:
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET
- Callback URL:
-
Google: Cloud Console → Create Credentials → OAuth client ID
- Callback URL:
http://localhost:8002/auth/callback - Export:
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET
- Callback URL:
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, setredirect_uri(orOAUTH_CALLBACK_URL/<SERVER>_REDIRECT_URI) to your publicly reachable/auth/callbackURL.
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:
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:
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 usingrun_fastapi), you can send the auth URL to your frontend and wait for the callback code there:
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_IDand*_CLIENT_SECRET, then delete the cached folder for that server. - Wrong storage path: set
AGENCY_SWARM_MCP_CACHE_DIRor passcache_dir=Path(...)to keep tokens in a specific location.