Skip to content
Back to Docs
MCP Reference

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.

json
{ "issuer": "https://www.promptot.com", "authorization_endpoint": "https://www.promptot.com/oauth/authorize", "token_endpoint": "https://mcp.promptot.com/api/oauth/token", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"], "scopes_supported": [ "prompts:read", "prompts:write", "prompts:delete", "blocks:read", "blocks:write", "variables:read", "variables:write", "versions:read", "versions:write", "versions:publish", "test_cases:read", "test_cases:write", "evals:read", "evals:run", "playground:run", "ai:import", "ai:rewrite", "ai:copilot", "projects:read", "gallery:read" ] }

GET /api/oauth/.well-known/oauth-protected-resource

RFC 9728 — Protected Resource Metadata.

json
{ "resource": "https://mcp.promptot.com/mcp", "authorization_servers": ["https://www.promptot.com"], "bearer_methods_supported": ["header"], "scopes_supported": [ "prompts:read", "prompts:write", "prompts:delete", "blocks:read", "blocks:write", "variables:read", "variables:write", "versions:read", "versions:write", "versions:publish", "test_cases:read", "test_cases:write", "evals:read", "evals:run", "playground:run", "ai:import", "ai:rewrite", "ai:copilot", "projects:read", "gallery:read" ] }

The flow, step by step

1

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.

response
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="promptot", resource_metadata="https://mcp.promptot.com/api/oauth/.well-known/oauth-protected-resource"
2

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.

url
https://www.promptot.com/oauth/authorize ?client_id=claude.ai &redirect_uri=https://claude.ai/api/mcp/auth_callback &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &state=abc123randomopaque
3

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.

4

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.

redirect
https://claude.ai/api/mcp/auth_callback ?code=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... &state=abc123randomopaque
5

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.

bash
curl -X POST https://mcp.promptot.com/api/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "code": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", "redirect_uri": "https://claude.ai/api/mcp/auth_callback", "client_id": "claude.ai" }'
6

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.

json
{ "access_token": "pot_YOUR_KEY_HERE", "token_type": "Bearer", "scope": "prompts:read prompts:write blocks:read blocks:write versions:publish ...", "expires_in": null }
7

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.

header
Authorization: Bearer pot_YOUR_KEY_HERE

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

bash
# Generate a 43-char URL-safe verifier VERIFIER=$(openssl rand -base64 32 | tr '/+' '_-' | tr -d '=') # Derive the SHA-256 challenge (base64url, no padding) CHALLENGE=$(printf '%s' "$VERIFIER" \ | openssl dgst -sha256 -binary \ | openssl base64 \ | tr '/+' '_-' \ | tr -d '=') echo "verifier: $VERIFIER" echo "challenge: $CHALLENGE"

2. Open the consent screen in a browser

bash
open "https://www.promptot.com/oauth/authorize\ ?client_id=demo-cli\ &redirect_uri=http://localhost:8765/cb\ &code_challenge=$CHALLENGE\ &code_challenge_method=S256\ &state=demo123"

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.

bash
CODE="paste-the-code-here"

4. Exchange the code for an access token

bash
curl -s -X POST https://mcp.promptot.com/api/oauth/token \ -H "Content-Type: application/json" \ -d "{ \"grant_type\": \"authorization_code\", \"code\": \"$CODE\", \"code_verifier\": \"$VERIFIER\", \"redirect_uri\": \"http://localhost:8765/cb\", \"client_id\": \"demo-cli\" }" # => { "access_token": "pot_YOUR_KEY_HERE", "token_type": "Bearer", "scope": "..." }

5. Call the MCP server with the bearer token

bash
curl -s -X POST https://mcp.promptot.com/mcp \ -H "Authorization: Bearer pot_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

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.