omg/docs

MCP — drive omg from Claude Code

OAuth-authenticated MCP server at mcp.omg.dev. Install, tool surface, how the auth dance works.

mcp.omg.dev is an OAuth-protected Model Context Protocol server. Hook it up to Claude Code (or any MCP client that speaks Streamable HTTP + OAuth 2.1) and you get a small toolbelt for driving your omg account from the editor: create apps from a prompt, steer existing apps with follow-up instructions, inspect runs.

The Worker is hosted on Cloudflare with a custom-domain route. The Authorization Server is auth.omg.dev (the same Better Auth instance that mints session JWTs for omg.dev). Tokens are JWT access tokens with aud=https://mcp.omg.dev/.

Install in Claude Code

claude mcp add --transport http omg https://mcp.omg.dev/mcp

That's the whole install. On first use Claude Code does Dynamic Client Registration against auth.omg.dev/api/auth/oauth2/register, runs PKCE-authorize, opens a browser for sign-in + consent, and stores the resulting access + refresh tokens. Subsequent sessions are silent.

Verify:

claude mcp list
# omg: https://mcp.omg.dev/mcp (HTTP) - ✓ Connected

If you see ! Needs authentication, run a tool from any session and Claude Code will trigger the OAuth flow. The refresh token (issued under the offline_access scope) lives for 30 days, so reauthentication is rare.

Tools

ToolArgsPurpose
omg_statusHealth + configured-subsystems probe. Use first when something seems off.
omg_create_apppromptSpin up a fresh project and kickoff agent run. Returns {projectId, runId, slug}.
omg_steerrunId, textSend a natural-language follow-up to an existing app. Not a code diff.
omg_get_runrunIdCurrent run state (queued/running/done/failed) + most recent message.
omg_tail_runrunId, afterMessageCreatedAt?Incremental tail of assistant + tool messages.

All tool calls forward the same JWT to Convex's /api/cli/* endpoints, which accept dual-audience tokens (legacy aud=vibes from auth.omg.dev/token and new aud=https://mcp.omg.dev/ from this flow). See Auth for the JWT model.

OAuth flow

// .well-known discovery, per MCP spec
// Step 1 — Resource Server metadata (RFC 9728)
GET https://mcp.omg.dev/.well-known/oauth-protected-resource
→ {
  "resource": "https://mcp.omg.dev",
  "authorization_servers": ["https://auth.omg.dev"],
  "scopes_supported": ["omg:apps"]
}

// Step 2 — Authorization Server metadata (RFC 8414)
GET https://auth.omg.dev/.well-known/oauth-authorization-server
→ {
  "issuer": "https://auth.omg.dev",
  "authorization_endpoint": "https://auth.omg.dev/api/auth/oauth2/authorize",
  "token_endpoint":         "https://auth.omg.dev/api/auth/oauth2/token",
  "registration_endpoint":  "https://auth.omg.dev/api/auth/oauth2/register",
  "jwks_uri":               "https://auth.omg.dev/api/auth/jwks",
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["openid","profile","email","offline_access","omg:apps"]
}

PKCE is mandatory (S256 only). Dynamic Client Registration is open and unauthenticated, per MCP-client-onboarding norms. Consent is shown the first time a given client requests a given scope; later authorize calls from the same client + user return the redirect immediately.

Scopes

ScopeWhat the token can do
omg:appsRead + write the user's omg apps (everything the tool surface above exposes)
offline_accessMint refresh tokens — without this the access token expires in 1h and the user has to re-authorize
openid / profile / emailOIDC scopes; not currently required by the Worker but available

The Worker validates aud=https://mcp.omg.dev/ and iss=https://auth.omg.dev on every /mcp request via the JWKS at /api/auth/jwks (EdDSA Ed25519). 401 responses include WWW-Authenticate: Bearer resource_metadata=… so clients can auto-reauthenticate on stale tokens.

Token shape

// Decoded access token issued by /api/auth/oauth2/token
{
  "sub": "PmcFDWIjUum…",                          // omg user id
  "email": "you@example.com",                     // via customAccessTokenClaims
  "name": "",
  "aud": ["https://mcp.omg.dev/",                 // the resource indicator
          "https://auth.omg.dev/api/auth/oauth2/userinfo"],
  "azp": "gJHYnPVlNx…",                           // DCR-issued client_id
  "scope": "openid email omg:apps",
  "iss": "https://auth.omg.dev",
  "iat": 1780050232,
  "exp": 1780053832                               // 1h default
}

The email claim is added by apps/auth/src/auth.ts via customAccessTokenClaims. Convex's requireSessionUserId uses it to look up the omg user — keeping legacy /token-minted JWTs and the new OAuth tokens interchangeable at the Convex boundary.

Troubleshooting

! Needs authentication in claude mcp list. No tokens stored yet, or the refresh token expired (30d). Call any omg_* tool from a Claude Code session — it'll trigger the OAuth flow.

401 from /mcp with WWW-Authenticate: Bearer resource_metadata=…. Access token expired and refresh failed (rare). Same fix: trigger any tool to reauthorize.

Tool call works but returns Unauthorized from Convex. The token's aud doesn't match Convex's accepted audiences. The deployed Convex verifier accepts both https://mcp.omg.dev and the trailing-slash form https://mcp.omg.dev/. If a custom client is sending something else, that won't match.

requested resource invalid at the token endpoint. The resource parameter (RFC 8707) wasn't in validAudiences. Two forms are accepted (…dev and …dev/). Anything else is rejected. Update the client to match.

Request path

Claude CodeMCP client
POST /mcp · Bearer
MCP servermcp.omg.dev (Worker)
forwards bearer
Convex /api/cli/*dual-aud JWT verifier

The Worker verifies each Bearer token's EdDSA signature against auth.omg.dev's JWKS before forwarding to Convex. The tokens themselves come from the OAuth flow above — a separate Claude-Code-to-auth.omg.dev handshake (PKCE authorize + token), not part of this hot path.

Source lives at apps/mcp/ in the monorepo. The contributor-facing README at apps/mcp/README.md covers how to add a new tool, the deploy pipeline (push → deploy-mcp.ymlwrangler deploy), and the trailing-slash / validAudiences gotcha.