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 sliding-window counters in Firestore:
| Limit | Default | Env var |
|---|---|---|
| Hourly generations | 10 | MCP_HOURLY_QUOTA |
| Daily generations | 60 | MCP_DAILY_QUOTA |
Quota errors surface as 429-style tool errors that the Claude client
should present to the user rather than retry silently.