OAuth 2.1 — connecting ChatGPT and Claude
The MCP specification requires OAuth 2.1 for remote connections. smeldr.dev/oauth ships a full OAuth 2.1 authorization server — authorization code flow, mandatory PKCE, refresh tokens, and revocation. It wires into an existing Smeldr site in four lines of code.
Claude Desktop connects to an MCP server by spawning a local process; it does not need OAuth. OAuth is required for browser-based AI clients such as ChatGPT that connect remotely over HTTP.
Prerequisites
- Add
smeldr.dev/oauthas a module dependency. - Provision a separate SQLite file (or implement
oauth.Store) for OAuth tokens.
OAuth tokens are kept in their own store — they are not written to the Smeldr content DB.
- The setup reuses your existing Smeldr bearer tokens as the authorization
credential. No separate user table or user management is needed.
Setup
import (
"smeldr.dev/core"
"smeldr.dev/mcp"
"smeldr.dev/oauth"
)
// 1. Create the OAuth token store.
store, err := oauth.NewSQLiteStore("./oauth.db")
if err != nil {
log.Fatal(err)
}
// 2. Create the OAuth server.
oauthSrv := oauth.New(oauth.Config{
Issuer: "https://cms.example.com",
VerifyBearer: func(token string) bool {
_, ok := smeldr.VerifyTokenString(token, app.Secret(), app.TokenStore())
return ok
},
}, store)
// 3. Wire OAuth into the MCP server.
mcpSrv := mcp.New(app, mcp.WithOAuth(oauthSrv))
// 4. Mount all endpoints.
mcpSrv.Register(app)
Config.Issuer is the public HTTPS base URL of your site. VerifyBearer receives the Smeldr bearer token the user pastes into the OAuth authorization form; return true if it is valid.
Access token TTL defaults to 1 hour; auth code TTL defaults to 5 minutes. Both are configurable via Config.AccessTokenTTL and Config.AuthCodeTTL.
What Register mounts
| Endpoint | Standard | Description |
|---|---|---|
GET /.well-known/oauth-authorization-server | RFC 8414 | Authorization server discovery |
GET /.well-known/oauth-protected-resource | RFC 9728 | Protected resource discovery |
GET /oauth/authorize | OAuth 2.1 | Authorization form |
POST /oauth/authorize | OAuth 2.1 | Bearer token validation + code issue |
POST /oauth/token | OAuth 2.1 | Code exchange and token refresh |
POST /oauth/revoke | RFC 7009 | Token revocation |
GET /mcp | MCP | SSE stream (requires Bearer when OAuth enabled) |
POST /mcp/message | MCP | JSON-RPC message endpoint |
How ChatGPT connects
1. ChatGPT hits GET /mcp without a token. 2. The server returns 401 with a WWW-Authenticate: Bearer resource_metadata="..." header. 3. ChatGPT fetches GET /.well-known/oauth-protected-resource to discover the authorization server. 4. ChatGPT fetches GET /.well-known/oauth-authorization-server and starts the PKCE flow. 5. The user pastes their Smeldr bearer token into the authorization form. 6. ChatGPT exchanges the code for an access token at POST /oauth/token. 7. Subsequent requests carry Authorization: Bearer <access_token>.
No client registry — CIMD
Most OAuth servers require registering each client with a client_id and client_secret. smeldr.dev/oauth skips the registry: at authorization time it fetches the client's own metadata URL (CIMD — Client ID Metadata Document):
GET <client_id URL> → { "client_name": "ChatGPT", "redirect_uris": [...] }
Any AI assistant that publishes a CIMD document is automatically a valid client. No registration step is needed.
PKCE and refresh tokens
PKCE is mandatory in OAuth 2.1 and handled automatically. The client generates a code_verifier, sends its hash as code_challenge in the authorization request, and proves it holds the verifier at token exchange.
Refresh tokens are issued when the client requests the offline_access scope. Without a refresh token, the client must re-authorize when the access token expires (default: 1 hour). Most AI assistants request offline_access automatically.
Revocation
POST /oauth/revoke
Per RFC 7009, the response is always 200 OK regardless of whether the token existed.
Status
| Client | Status |
|---|---|
| ChatGPT Plus | Full flow confirmed — discovery, PKCE, code exchange, refresh |
| Claude Desktop | Works via local stdio — OAuth not required |
| Claude.ai web | Full flow confirmed — browser-based OAuth via streamable HTTP, verified 2026-06-11 |