Skip to content

Authentication & authorization

The CloudQuell MCP server is an OAuth 2.1 resource server. Access is gated by standard, short-lived OAuth tokens minted by the CloudQuell identity service (Amazon Cognito), and every write is protected by a double gate — the admin:write scope plus an explicit confirm: true argument and a backend org-role check.

  • Grant: Authorization Code with PKCE (S256) — mandated by the MCP authorization spec for remote HTTP servers.
  • Public client: the MCP app client has no secret; PKCE protects the code exchange. Your MCP client obtains the client id automatically via the /register (Dynamic Client Registration) shim — you never paste a client id or secret.
  • Identity provider: the CloudQuell Hosted UI (cloudquell-prod-auth.auth.us-west-2.amazoncognito.com), supporting email/password and Google sign-in — the same accounts as the dashboard.
  • Token lifetimes: access tokens are short-lived (~1 hour); a refresh token keeps the connection alive without re-prompting. Scheduled/agentic use is supported by a long refresh-token lifetime.

Clients configure themselves from these (all public, unauthenticated):

Route Spec Purpose
GET /.well-known/oauth-protected-resource RFC 9728 Declares the resource URI + supported scopes; points clients at the authorization server.
GET /.well-known/oauth-authorization-server RFC 8414 Maps OAuth endpoints onto the CloudQuell Hosted UI (/authorize, /token) and advertises /register.
POST /register RFC 7591 Dynamic Client Registration shim — returns the shared public PKCE client id.

The server’s canonical resource URI — and the OAuth audience — is:

https://mcp.cloudquell.com/mcp

Clients send resource=https://mcp.cloudquell.com/mcp on the authorize and token requests (RFC 8707). The server validates the token’s audience against a small allowlist — its own dedicated MCP client and the CloudQuell dashboard client — and rejects anything outside it. That is the confused-deputy defense the spec calls for; accepting the dashboard client as well is deliberate, so a token from your existing CloudQuell session also works. This URL is both the OAuth audience/resource identifier and the live endpoint you connect to (the same https://mcp.cloudquell.com/mcp used everywhere in these docs).

The server defines two custom scopes on its resource server:

Scope Fully-qualified name Grants
read https://mcp.cloudquell.com/mcp/read All 29 read tools. Default — granted unless you opt into more.
admin:write https://mcp.cloudquell.com/mcp/admin:write The 14 write tools, in addition to read.

Plus the standard OIDC scopes (openid, email, profile) used to identify you.

  • read is the baseline. A token that carries only read can analyze everything and mutate nothing — every write tool refuses with insufficient_scope before any backend call.
  • admin:write typically rides the connector token. There is no separate consent screen or second connection that toggles it on. The Dynamic Client Registration shim registers your client for the server’s full advertised scope set, so in practice the token your client receives already includes admin:write. Because of that, the real protection on any change is not the scope alone — it is the double gate below (confirm: true and a backend org-role check), so holding admin:write does not by itself let an agent alter anything.

Mutations require admin:write and confirm: true

Section titled “Mutations require admin:write and confirm: true”

Every write tool enforces two independent checks, server-side, before any downstream call:

  1. The caller’s access token carries admin:write. Missing → insufficient_scope.
  2. The tool input includes confirm: true. Missing → confirmation_required.

If either check fails, the tool returns an error and nothing changes — no budget is created, no invite is sent, no channel is touched. This is stricter than relying on the MCP client’s allow/deny prompt: even a misbehaving client cannot mutate without both the scope and an explicit confirmed argument.

// Refused — has admin:write but no confirm:
{ "name": "Prod AWS monthly", "amount": 50000, "start_date": "2026-07-01" }
// → "confirmation_required: pass confirm:true to perform this write"
// Performed — scope present AND confirm:true:
{ "name": "Prod AWS monthly", "amount": 50000, "start_date": "2026-07-01", "confirm": true }

The MCP annotations (destructiveHint, etc.) shown in the Tool Reference are UI hints only; they never gate behavior. The scope + confirm check is the real control.

Authorization (RBAC) is enforced by CloudQuell

Section titled “Authorization (RBAC) is enforced by CloudQuell”

OAuth scope decides read vs. write at the MCP layer. Your role and organization membership still apply downstream:

  • The server forwards a token that identifies you (by your Cognito sub); the CloudQuell backend resolves your organization and role from the database on every request.
  • Admin-only operations (e.g. renaming the org, managing invites) still require the appropriate org role server-side. Holding admin:write does not elevate your CloudQuell role — if your account isn’t an org admin, an admin-only call is rejected (403 Admin access required) by the backend regardless of scope.

So a write tool needs: the admin:write scope + confirm: true + the backend permission for that action under your role.

The agent operates within your organization only. Identity is derived from your token’s sub, mapped to your org membership in CloudQuell — not from anything the client or agent can set. There is no parameter to “switch” to an org you don’t belong to. See Access, limits & data scope.

For headless / scheduled agents that can’t do an interactive browser login, paid organizations can mint service-account keys (prefix cak_) from Settings → API Keys in the dashboard. A key is presented as Authorization: Bearer cak_…, is read-only, org-scoped, and valid for one year (revocable immediately). No sign-in, refresh, or client registration is needed — the key is the credential. See API keys for the full lifecycle and usage guidance.