-
Notifications
You must be signed in to change notification settings - Fork 3
oauth
Complete guide for OAuth authentication with MCP servers.
- Quick Start
- Auto-Detection
- UI Integration
- API Reference
- Testing
- Whose login: platform, Code and place connections
- Architecture
- Troubleshooting
{
"mcpServers": {
"Notion": {
"url": "https://mcp.notion.com/mcp"
}
}
}No manual OAuth configuration needed - it's auto-detected from the 401 response.
cd agent_go
./run_server_with_logging.sh- Open http://localhost:5173
- Click MCP Servers in sidebar
- Expand server details
- Click orange Login button next to Notion
- Complete authentication in browser
- Badge turns green: OAuth ✓
OAuth servers advertise themselves via 401 Unauthorized responses per OAuth 2.0 spec. We auto-detect this automatically.
┌──────────┐
│ Frontend │ 1. Loads MCP servers
└────┬─────┘
│
│ 2. Triggers discovery
▼
┌──────────┐
│ Backend │ 3. Tries to connect to each server
└────┬─────┘
│
│ 4. GET https://mcp.notion.com/mcp
▼
┌──────────┐
│ Notion │ 5. Returns 401 Unauthorized
│ MCP │ WWW-Authenticate: Bearer realm="..."
└────┬─────┘ Link: <...>; rel="token_endpoint"
│
│ 6. OAuth endpoints extracted
▼
┌──────────┐
│ Backend │ 7. Sets requires_oauth = true
└────┬─────┘
│
│ 8. Returns ToolStatus with OAuth info
▼
┌──────────┐
│ Frontend │ 9. OAuthStatusBadge appears automatically
└──────────┘
- RFC 9728 - OAuth Protected Resource Metadata (for Smithery-style servers)
-
RFC 8414 - OAuth Authorization Server Metadata (
.well-known/oauth-authorization-server) -
401 Response Headers -
WWW-AuthenticateandLinkheaders
The /api/tools endpoint returns OAuth info:
{
"name": "Notion",
"server": "Notion",
"status": "error",
"error": "OAuth authentication required",
"requires_oauth": true,
"oauth_endpoints": {
"auth_url": "https://auth.notion.com/oauth/authorize",
"token_url": "https://auth.notion.com/oauth/token"
}
}- No Manual Configuration - Just add server URL
- Works for Any OAuth Server - Follows OAuth 2.0 spec (RFC 6750)
- Graceful Fallback - No badge shown if OAuth not detected
| State | Appearance | Description |
|---|---|---|
| Not Authenticated |
[ ! Login ] (orange) |
Click to start OAuth flow |
| Authenticating | [ ⟳ ... ] |
Flow in progress |
| Authenticated |
[ ✓ OAuth ] [ ↻ ] [ ✕ ] (green) |
Valid token, with refresh and logout buttons |
In the MCP Servers section (sidebar), next to each OAuth-enabled server:
┌─────────────────────────────────────────────────┐
│ Notion 15 tools ● [OAuth ✓] │
│ [▶ Show] [Toggle] │
└─────────────────────────────────────────────────┘
User clicks "Login"
│
▼
Browser opens automatically to auth page
│
▼
User completes authentication
│
▼
Returns to app
│
▼
Badge updates to green "OAuth ✓"
│
▼
Tools become available
import { OAuthStatusBadge } from '@/components/OAuthStatusBadge';
function MyComponent() {
return (
<OAuthStatusBadge
serverName="Notion"
requiresOAuth={true} // Optional - auto-detected if not provided
onAuthChange={(valid) => {
console.log('Auth changed:', valid);
if (valid) refreshTools();
}}
/>
);
}POST /api/oauth/start
Content-Type: application/json
{"server_name": "Notion"}Response:
{
"server_name": "Notion",
"auth_url": "",
"state": "",
"message": "OAuth flow started - browser will open automatically"
}GET /api/oauth/status?server_name=NotionResponse:
{
"server_name": "Notion",
"valid": true,
"expires_in": "23h59m30s",
"token_path": "/Users/you/.config/mcpagent/tokens/notion.json"
}POST /api/oauth/logout
Content-Type: application/json
{"server_name": "Notion"}Response:
{
"status": "success",
"message": "Successfully logged out from Notion"
}import { oauthApi } from '@/services/oauthApi';
// Check status
const status = await oauthApi.getOAuthStatus('Notion');
console.log(status.valid); // true/false
console.log(status.expires_in); // "23h59m30s"
// Start login
await oauthApi.startOAuthFlow('Notion');
// Logout
await oauthApi.logout('Notion');- Start backend:
./run_server_with_logging.sh - Start frontend:
cd frontend && npm run dev - Open http://localhost:5173
- Click MCP Servers → Notion → Login
- Complete auth → Badge turns green
# Start flow
curl -X POST http://localhost:8000/api/oauth/start \
-H "Content-Type: application/json" \
-d '{"server_name": "Notion"}'
# Check status
curl "http://localhost:8000/api/oauth/status?server_name=Notion"
# Logout
curl -X POST http://localhost:8000/api/oauth/logout \
-H "Content-Type: application/json" \
-d '{"server_name": "Notion"}'# Backend logs will show:
✅ Auto-detected OAuth for Notion: auth=https://auth.notion.com/oauth/authorize, token=https://auth.notion.com/oauth/tokenThe same OAuth flow serves three kinds of connection. They differ in whose login is used and where it is stored:
| Connection | Added by | Login used by | Token stored at |
|---|---|---|---|
| Platform (Connectors page) | an admin | every workflow, Crew and chat that selects it | <tokens>/_platform/<server>.json |
| Code personal (code_private_mcp.md) | any person, in a Code | only that person's own Code chats | their personal store, sealed |
| Place (personal_mcp_attach.md) | someone who can edit a workflow or Crew | everyone who uses that workflow or Crew, Slack channels included | the personal store under a (person, place) id, sealed |
Sign-in apps. Providers without dynamic client registration (Google,
GitHub, Slack, Asana, Box, ...) need an OAuth app. An admin sets one per
provider under Sign-in apps, or with server set-mcp-app --key <k> < client_secret.json, run as the service user and never as root. Personal and
place connections then sign in with one click. The app's redirect URI must
include <public URL>/api/oauth/callback. Google's human-feedback callback
is for the Gmail bot channel, not MCP. The app is stored sealed at
<tokens>/_platform/apps/<key>.json and read live at every connect.
sequenceDiagram
participant UI as Frontend
participant API as Backend API
participant OAuth as OAuth Manager
participant Provider as OAuth Provider
participant Cache as Cache Manager
participant MCP as MCP Server
UI->>API: POST /api/oauth/start {server_name}
API->>Provider: Discover endpoints (.well-known)
Provider-->>API: auth_url, token_url
API->>Provider: Register client (DCR)
Provider-->>API: client_id
API->>OAuth: GenerateAuthURL() with PKCE
OAuth-->>API: auth_url, state
API-->>UI: {auth_url, state}
UI->>Provider: Open browser to auth_url
Provider->>Provider: User authorizes
Provider->>API: GET /callback?code=XXX&state=YYY
API->>OAuth: ExchangeCodeForToken(code)
OAuth->>Provider: POST /token with PKCE verifier
Provider-->>OAuth: access_token, refresh_token
OAuth->>OAuth: SaveToken()
API->>Cache: InvalidateByServer(server_name)
Cache-->>API: Cache cleared
API-->>UI: Success (via polling)
UI->>API: Use MCP tools
API->>OAuth: LoadToken()
OAuth-->>API: Valid token
API->>MCP: Request with OAuth token
MCP-->>API: Tools available
API->>Cache: Cache tools
| Component | File | Description |
|---|---|---|
| OAuth Routes | agent_go/cmd/server/oauth_routes.go |
API endpoints |
| OAuth Manager | mcpagent/oauth/manager.go |
Token exchange |
| Discovery | mcpagent/oauth/discovery.go |
Endpoint discovery |
| Token Storage | mcpagent/oauth/token_store.go |
Token persistence |
| Component | File | Description |
|---|---|---|
| OAuth API | frontend/src/services/oauthApi.ts |
API client |
| OAuth Badge | frontend/src/components/OAuthStatusBadge.tsx |
Smart badge component |
~/.config/mcpagent/tokens/{server}.json
0600 (owner read/write only)
{
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expiry": "2024-01-15T12:00:00Z"
}- Auto-Refresh - Tokens refresh automatically when expired using refresh token
-
Config Persistence - OAuth endpoints saved to
_user.jsonafter successful auth - Zero-Expiry Handling - Tokens with no expiry treated as indefinitely valid
- Manual Refresh - Click ↻ button to force status check
{
"mcpServers": {
"Notion": {
"url": "https://mcp.notion.com/mcp"
}
}
}If auto-detection doesn't work:
{
"mcpServers": {
"Notion": {
"url": "https://mcp.notion.com/mcp",
"oauth": {
"auth_url": "https://api.notion.com/v1/oauth/authorize",
"token_url": "https://api.notion.com/v1/oauth/token",
"use_pkce": true,
"token_file": "~/.config/mcpagent/tokens/notion.json"
}
}
}
}| Field | Required | Default | Purpose |
|---|---|---|---|
auto_discover |
No | true |
Auto-discover OAuth endpoints |
use_pkce |
No | true |
Use PKCE for enhanced security |
auth_url |
No* | - | Authorization endpoint |
token_url |
No* | - | Token endpoint |
client_id |
No | - | OAuth client ID (DCR can provide) |
redirect_url |
No | http://localhost:8000/api/oauth/callback |
OAuth callback URL |
token_file |
No | Auto-generated | Where to store OAuth tokens |
scopes |
No | [] |
OAuth scopes to request |
*Required if auto_discover is false
Cause: Server didn't return 401 or headers are missing
Check:
- Backend logs for "Auto-detected OAuth" message
- Run:
curl -I https://mcp.notion.com/mcp
Solution: Server might not support OAuth, or add manual config
Cause: Backend not running or CORS issue
Solution:
# Check backend is running
lsof -i :8000
# Restart backend
cd agent_go
./run_server_with_logging.shCause: Token expired or invalid
Solution:
- Click "✕" to logout
- Click "Login" again
Cause: System doesn't have default browser configured
Solution: Check backend logs for auth URL and open manually
Cause: Cannot reach MCP server or no WWW-Authenticate header
Solution:
- Check internet connection
- Try:
curl -I https://mcp.notion.com/mcp
Cause: User didn't complete auth in browser
Solution: Complete the auth flow or click "✕" to cancel and try again
Cause: Redirect URL not registered with OAuth provider
Solution: Ensure server allows http://localhost:8000/api/oauth/callback
Cause: Waiting for cache invalidation
Solution: Automatic - wait 1-2 seconds and retry. Cache invalidates after successful auth.
| Feature | Description |
|---|---|
| RFC 9728 Support | OAuth Protected Resource Metadata discovery |
| RFC 8414 Support | Standard OAuth discovery |
| PKCE | Proof Key for Code Exchange for security |
| Auto-Discovery | No manual configuration needed |
| Token Persistence | Tokens saved securely to disk |
| Auto-Refresh | Tokens refresh automatically |
| Config Persistence | OAuth config saved after successful auth |
| Manual Refresh | Button to check status on demand |
| Zero-Expiry Handling | Tokens with no expiry treated as valid |
| Cache Invalidation | Automatic cache clearing after auth |
| DCR Support | Dynamic Client Registration when available |
- PKCE is recommended (no client secret needed)
- State parameter validates callbacks (CSRF protection)
- Tokens auto-refresh if refresh_token available
- Cache auto-invalidates after OAuth success
- Don't store client secrets in config (use PKCE instead)
- Don't skip state validation
Auto-synced from docs/ on main. Edit there, not here.