Documentation

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/oauth as 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

EndpointStandardDescription
GET /.well-known/oauth-authorization-serverRFC 8414Authorization server discovery
GET /.well-known/oauth-protected-resourceRFC 9728Protected resource discovery
GET /oauth/authorizeOAuth 2.1Authorization form
POST /oauth/authorizeOAuth 2.1Bearer token validation + code issue
POST /oauth/tokenOAuth 2.1Code exchange and token refresh
POST /oauth/revokeRFC 7009Token revocation
GET /mcpMCPSSE stream (requires Bearer when OAuth enabled)
POST /mcp/messageMCPJSON-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

ClientStatus
ChatGPT PlusFull flow confirmed — discovery, PKCE, code exchange, refresh
Claude DesktopWorks via local stdio — OAuth not required
Claude.ai webFull flow confirmed — browser-based OAuth via streamable HTTP, verified 2026-06-11