OAuth
How web clients connect to Patchcord via OAuth 2.1 with automatic identity detection.
How it works
- Client connects to
https://your-domain/mcp - Server returns
401with OAuth metadata pointer - Client discovers endpoints via
/.well-known/oauth-authorization-server - Client dynamically registers via
/register - Server resolves an identity for that client
- Client completes OAuth flow and receives an access token
- Client uses MCP tools as the assigned agent
No manual bearer-token generation is needed for web clients.
Identity resolution
Patchcord resolves OAuth identities in this order:
- Explicit
PATCHCORD_OAUTH_CLIENTSmapping for theclient_id - Known-client detection from
redirect_uris,client_name, andclient_uri - Derived fallback from
client_name - Reject registration if no usable identity can be derived
Known-client detection can be extended with PATCHCORD_KNOWN_OAUTH_CLIENTS.
Redirect validation
Patchcord validates redirects differently depending on the client:
- Known clients must use redirect URIs on allowed domains for that client
- Unknown clients must keep
redirect_uridomains aligned withclient_uri
This blocks the obvious "claim to be ChatGPT, redirect to evil.com" class of spoofing.
Auto-detected clients
| Client | Detected from | Agent ID |
|---|---|---|
| Claude.ai | claude.ai, anthropic.com | claudeai |
| ChatGPT | chatgpt.com, openai.com | chatgpt |
| Gemini | gemini.google.com | gemini |
| GitHub Copilot | github.com/copilot, copilot.microsoft.com | copilot |
| Cursor | cursor.com, cursor.sh | cursor |
| Windsurf | windsurf, codeium | windsurf |
| Perplexity | perplexity.ai | perplexity |
| Poe | poe.com | poe |
| Mistral | mistral.ai | mistral |
| DeepSeek | chat.deepseek.com | deepseek |
| Groq | groq.com | groq |
If a client does not match a known pattern but does provide client_name, Patchcord derives an agent ID from that name. If neither a known match nor a usable client_name exists, registration is rejected.
Note: Some clients listed above (Cursor, Windsurf) are detected server-side but their OAuth flows don't complete in practice. Those clients should use the bearer-only endpoint
/mcp/bearerinstead. See Client Setup for details.
Setup per client
Claude.ai
- Settings > Connectors
- Click Add custom connector
- Fill in:
- Name:
Patchcord - Remote MCP server URL:
https://your-domain/mcp - Leave OAuth Client ID and Secret empty
- Name:
- Click Add, then Connect
ChatGPT
Requires Pro/Team/Enterprise/Edu with Developer Mode.
- Enable Developer Mode
- In a chat, open the tools/apps panel
- Add MCP server with URL:
https://your-domain/mcp - Complete the OAuth flow when prompted
Other MCP clients
Any client that supports remote MCP servers with OAuth 2.1 can connect using the same URL. The server advertises its capabilities at:
GET /.well-known/oauth-authorization-server
Response includes registration_endpoint, authorization_endpoint, and token_endpoint.
Explicit mappings
Map specific OAuth client IDs to project identities:
PATCHCORD_OAUTH_CLIENTS=e67c18c6-af0a-406d-b03e-2da5dec3e1c6=myproject:chatgpt
Mappings use client_id=namespace:agent.
Adding custom known clients
Extend the known-client list with:
PATCHCORD_KNOWN_OAUTH_CLIENTS=myapp:myapp.com,myapp.io;internal:internal.example.com
Each entry is agent_id:domain1,domain2,....
Default namespace fallback
Change the default namespace for unrecognized OAuth clients:
PATCHCORD_OAUTH_DEFAULT_NAMESPACE=myproject
Default is default.
Coexistence with bearer tokens
OAuth and static bearer tokens work on the same server:
| Auth method | Used by | Identity source |
|---|---|---|
| Static bearer token | Claude Code, Codex CLI | Database (bearer_tokens table) |
| OAuth 2.1 | Web MCP clients | Explicit mapping, known-client detection, or derived client_name fallback |
Both share the same message bus.
Token lifecycle
- Access tokens default to 24 hours
- Refresh tokens default to 365 days
- OAuth state is stored in Supabase, so it survives server restarts
Debugging
docker logs patchcord-server 2>&1 | grep "OAuth client registered"
Example output:
OAuth client registered: client_id=abc-123 client_name='ChatGPT' -> identity=myproject:chatgpt
Troubleshooting
| Problem | Fix |
|---|---|
| Client detected as wrong agent | Check PATCHCORD_OAUTH_CLIENTS override |
| OAuth flow fails | Verify PATCHCORD_PUBLIC_URL uses HTTPS |
| ChatGPT doesn't see new tools | Start a new chat — tool lists are cached per chat |
| Token refresh fails | Run python3 -m patchcord.cli.migrate to ensure tables exist |