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). Writes are OFF by default for every organization: a write
additionally needs an org-level switch that only a human admin can turn on, an
admin role, and confirm: true — all checked server-side before anything moves.
OAuth 2.1 + PKCE
Section titled “OAuth 2.1 + PKCE”- 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.
Discovery endpoints
Section titled “Discovery endpoints”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. |
Resource indicator / audience
Section titled “Resource indicator / audience”The server’s canonical resource URI — and the OAuth audience — is:
https://mcp.cloudquell.com/mcpClients send resource=https://mcp.cloudquell.com/mcp on the authorize and token
requests (RFC 8707). The server validates that tokens were minted for it and
rejects tokens issued for anything else — the confused-deputy defense the spec
requires. 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.
Scopes: read and admin:write
Section titled “Scopes: read and admin:write”The server defines two custom scopes on its resource server:
| Scope | Fully-qualified name | Grants |
|---|---|---|
read |
https://mcp.cloudquell.com/mcp/read |
All 40 read tools. The only scope a cak_ service key can hold. |
admin:write |
https://mcp.cloudquell.com/mcp/admin:write |
Lets the 14 write tools be attempted, in addition to read. It is not on its own permission to write — see below. |
Plus the standard OIDC scopes (openid, email, profile) used to identify you.
-
readis the baseline. A token that carries onlyreadcan analyze everything and mutate nothing — every write tool refuses withinsufficient_scopebefore any backend call. -
An interactive connector’s token carries BOTH scopes, always. There is no per-scope consent toggle: the Dynamic Client Registration shim advertises the full scope set for every registration, so the token your client receives includes
admin:writeno matter who signed in — including a viewer. Cognito cannot express per-scope consent, so this is not something the server can fix by asking for less.That is exactly why
admin:writeis not what protects a write. The scope only says “this token may attempt a write tool”. What decides whether the write actually happens is the per-organization switch and your org role, both checked below.
Want a genuinely read-only connection? Use a
cak_service key (API keys) — those are minted read-only and cannot carryadmin:writeat all.
Writes are OFF by default
Section titled “Writes are OFF by default”A new CloudQuell organization cannot be changed over MCP at all. Every organization starts with MCP writes disabled, and that includes existing paid organizations — nothing was grandfathered in. Until an admin turns the switch on, every write tool refuses and the connector is, in practice, a read-only analyst.
Turn it on in the CloudQuell app: Settings → Organization, in the AI agent access card, using the toggle “Allow AI agents to make changes via MCP”. Only an org admin or super_admin can — the toggle is visible but disabled for other roles — and only in the app: an agent cannot enable it for itself (see below).
The four checks
Section titled “The four checks”Every write tool enforces these server-side, before any downstream call, in this order:
| # | Check | Refusal when it fails |
|---|---|---|
| 1 | The access token carries admin:write |
insufficient_scope |
| 2 | The organization has MCP writes enabled | writes_disabled_for_org |
| 3 | Your org role is admin or super_admin |
insufficient_role |
| 4 | The tool input includes confirm: true |
confirmation_required |
If any check fails the tool returns that error and nothing changes — no budget
is created, no invite is sent, no channel is touched. If the server cannot read
your organization’s switch or your role, it refuses with
org_context_unavailable rather than guessing: the gate fails closed.
The order is deliberate. The switch is checked before your role so that an
organization which has not opted in gets told about the switch — the thing an
admin can act on — rather than about the caller’s role. And confirm is checked
last, so a caller who would be refused anyway is never told that all they need is
confirm: true.
// Refused — the organization has not enabled MCP writes:{ "name": "Prod AWS monthly", "amount": 50000, "start_date": "2026-07-01", "confirm": true }// → "writes_disabled_for_org: MCP writes are turned off for this organization.// An org admin can enable them in Settings → Organization → AI agent access."
// Refused — writes are on, but you are a viewer:// → "insufficient_role: your role is viewer; MCP writes require admin or super_admin"
// Refused — everything else passes, but confirm is missing:// → "confirmation_required: pass confirm:true to perform this write"
// Performed — switch on, admin role, confirm:true:{ "name": "Prod AWS monthly", "amount": 50000, "start_date": "2026-07-01", "confirm": true }This is stricter than relying on the MCP client’s allow/deny prompt: even a misbehaving client cannot mutate anything unless the organization has opted in and the signed-in human is an admin.
An agent can never enable writes for itself
Section titled “An agent can never enable writes for itself”update_organization_preferences refuses to set mcp_writes_enabled. It
strips the key, tells you it did, and applies the rest of your preference change.
Otherwise the whole model would be circular — “MCP writes are off” would be
fixable by an MCP write. Only a human admin, in the app, can flip it.
Reads are unaffected
Section titled “Reads are unaffected”None of this touches reads. The organization lookup that backs checks 2 and 3 is made lazily, on the first write of a request, so a read-only session never performs it and pays nothing for it.
The MCP annotations (destructiveHint, etc.) shown in the
Tool Reference are UI hints only; they never gate behavior.
The four checks above are the real control.
Authorization (RBAC) is enforced by CloudQuell
Section titled “Authorization (RBAC) is enforced by CloudQuell”Your role and organization membership apply at two layers, and both still hold:
- At the MCP server (check 3 above): writes require
adminorsuper_admin. - Downstream, in CloudQuell, as before. The server forwards a token that
identifies you (by your Cognito
sub); the backend resolves your organization and role from the database on every request, and admin-only operations still require the appropriate org role there. Holdingadmin:writedoes not elevate your CloudQuell role.
The MCP-layer role check does not replace the downstream one — it narrows what
reaches it, so a refusal is explained in terms you can act on instead of arriving
as a bare 403 from a backend you never called directly.
So a write needs: the admin:write scope + the organization switch +
an admin org role + confirm: true + the backend permission for that
action under your role.
Org scoping
Section titled “Org scoping”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.
Service-account keys (cak_)
Section titled “Service-account keys (cak_)”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 always read-only — it is minted with the
read scope alone and cannot carry admin:write, so it fails check 1 no matter
what the organization switch says — 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.