Hosted OAuth Flow
The hosted MCP server at https://mcp.promptot.com/mcp speaks OAuth 2.1 + PKCE. This page documents the complete handshake, discovery endpoints, design rationale, and a curl-based example.
When you need OAuth
Only browser-based MCP clients use OAuth. If you're running a stdio-based client locally, you skip OAuth entirely and paste a raw API key from the dashboard.
Uses OAuth
- claude.ai (web)
- ChatGPT custom connectors
- Cursor (web)
Pastes raw API key
- Claude Desktop (stdio)
- Cursor desktop (stdio)
- Codex CLI (stdio)
Discovery endpoints
The MCP server exposes the two standard OAuth discovery documents. Conformant MCP clients fetch these automatically — you should not need to hand-configure URLs.
GET /api/oauth/.well-known/oauth-authorization-server
RFC 8414 — Authorization Server Metadata.
GET /api/oauth/.well-known/oauth-protected-resource
RFC 9728 — Protected Resource Metadata.
The flow, step by step
Client hits /mcp with no auth
The MCP client makes its first POST /mcp request without an Authorization header. The server returns 401 with a WWW-Authenticate header pointing the client at the protected resource metadata document.
Redirect the user to the consent screen
The client opens a browser window pointing at PromptOT's authorization endpoint with PKCE parameters and the OAuth client identifier.
User picks a project + AI features, clicks Approve
The user (already signed in to PromptOT) sees the consent screen, picks which project to grant the MCP key against, optionally ticks Allow AI features, and clicks Approve.
PromptOT mints an HMAC-signed auth code and redirects back
The backend signs a short-lived (60 second TTL) auth code containing the chosen project and granted scopes. The user is redirected to the client's registered redirect_uri with the code and the original state parameter.
Client exchanges the code at /api/oauth/token
The client posts the auth code, the PKCE verifier, and the original redirect URI to the token endpoint. The server verifies the HMAC signature, checks the PKCE challenge, and confirms the code is unused and unexpired.
Server mints a fresh API key with the granted scopes
The server creates a new row in api_keys with the requested scopes, name MCP — claude.ai, and returns the raw key as the access token. The access_token IS a raw PromptOT API key — there is no separate token store. Revocation reuses the existing api_keys.revoked_at flag.
Client uses the bearer token on every /mcp call
From this point on the MCP client attaches the access token as a standard bearer token to every request to the MCP server.
Why this design
- Stateless auth codes. Codes are HMAC-signed JWT-style strings with embedded expiry — no DB table is needed for the OAuth flow itself, which keeps the handshake fast and trivial to scale horizontally.
- Access token = raw API key. There is no separate OAuth token store. Revocation, scoping, audit logs, and rate limiting all reuse the existing API key infrastructure.
- Thin proxy. The hosted MCP server is a thin proxy in front of the existing
/api/v1/*routes. There is no per-request DB lookup beyond the standard API key auth.
End-to-end example with curl
A complete OAuth flow you can run from a terminal. The PKCE verifier and challenge below are deterministic for clarity — generate fresh random bytes for real clients.
1. Generate a PKCE verifier and challenge
2. Open the consent screen in a browser
3. Capture the auth code from the redirect
After approval, the browser is redirected to http://localhost:8765/cb?code=...&state=demo123. Copy the value of code.
4. Exchange the code for an access token
5. Call the MCP server with the bearer token
Revoking access
Open the dashboard's API Keys page for the project the key was scoped to. Find the row named MCP — claude.ai (or whichever client_id approved the consent) and click Revoke. The next request from that client will fail with 401 Unauthorized, which prompts the client to start a fresh OAuth handshake.