MCP authentication
The MCP server speaks OAuth 2.1 with PKCE over an HTTP transport (Streamable HTTP). It is stateless: no in-memory session map, no sticky sessions, no per-client transport state. State lives in Firestore and the client carries a bearer JWT.
Workspace gate
Only Google identities in the cloudpilots.com Workspace can sign in. The
gate is enforced via the Google ID token's hd (hosted domain) claim,
checked against MCP_ALLOWED_DOMAIN (default cloudpilots.com). An email
suffix check is not used — service-account-issued tokens won't have the
hd claim and are rejected.
Token shape
- Access token: RS256 JWT, 15-minute TTL.
issandaudboth set toMCP_PUBLIC_URL. Public keys exposed at/.well-known/jwks.json(cached 1 h on the verifier side). - Refresh token: opaque, 30-day TTL with a 30-second rotation grace
window (old token's
expiresAtshrunk on rotation, not deleted, so a concurrent in-flight request doesn't fail). - Resource indicator (RFC 8707): every
/authorizeand/tokenrequest must includeresource=<MCP_PUBLIC_URL>. Trailing slash is tolerated.
Discovery endpoints
All at the server root:
| Endpoint | Returns |
|---|---|
/.well-known/oauth-authorization-server | RFC 8414 metadata (authorization_endpoint, etc.) |
/.well-known/oauth-protected-resource | RFC 9728 resource metadata |
/.well-known/jwks.json | Active signing keys (RS256) |
Audit log
Every tool invocation emits a structured JSON line to stdout with
channel: "mcp_audit". Includes uid, clientId, tool name, start /
end, duration, success / failure, pipeline ids on success. Does not
include user prompt content — those would be PII and routine logs aren't
the right place for them.
Quotas
Per-uid counters in Firestore (usage/${uid}_${YYYY-MM-DD}), incremented
once per tool call inside a Firestore transaction.
| Limit | Default | Env var |
|---|---|---|
| Hourly tool calls | 60 | MCP_HOURLY_QUOTA |
| Daily tool calls | 300 | MCP_DAILY_QUOTA |
One tool call = one unit. generate_image_async with iterations: 4
still counts as a single unit — iterations do not multiply quota.
propose_concepts, generate_image, and generate_image_async are the
metered tools; get_image is free (read-only).
Quota errors surface as tool errors with messages like
Daily generation limit (300) exceeded. Try again tomorrow. — the Claude
client should present these to the user rather than retry silently.