Skip to main content

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. iss and aud both set to MCP_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 expiresAt shrunk on rotation, not deleted, so a concurrent in-flight request doesn't fail).
  • Resource indicator (RFC 8707): every /authorize and /token request must include resource=<MCP_PUBLIC_URL>. Trailing slash is tolerated.

Discovery endpoints

All at the server root:

EndpointReturns
/.well-known/oauth-authorization-serverRFC 8414 metadata (authorization_endpoint, etc.)
/.well-known/oauth-protected-resourceRFC 9728 resource metadata
/.well-known/jwks.jsonActive 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:

LimitDefaultEnv var
Hourly generations10MCP_HOURLY_QUOTA
Daily generations60MCP_DAILY_QUOTA

Quota errors surface as 429-style tool errors that the Claude client should present to the user rather than retry silently.