MCP Keys API
The MCP Keys API manages the same user-owned MCP keys shown on the dashboard MCP page. Keys support two server-enforced authorization modes:
SCOPESgrants the tools covered byenabledScopes. This is the legacy behavior and remains the default.TOOLSgrants only the tool names stored inenabledTools.
Tool groups are presentation metadata only. The API stores explicit tool names, not group grants, so a tool added to a group later is not automatically granted to an existing key.
Security and validation
- Regular user tokens can manage keys owned by the authenticated user, matching the dashboard behavior.
- MCP callers must be authorized for the specific MCP key-management tool they call. In scope mode this comes from
quave:manage:mcp-keys; in tool mode the management tool itself must be selected. - Deleting MCP keys still carries both the MCP-key-management and dangerous-operation requirements.
- Returning or revealing raw key plaintext still carries the secret-read requirement and emits a secret-decrypt audit event for reveal.
- Account, app, and environment permissions, admin checks, confirmations, previews, MFA, and audit logging continue to apply in both modes.
- Tool mode rejects an empty allowlist and unknown tool names, and normalizes duplicate names.
- Send only the active grant field.
enabledToolsrequires explicitauthorizationMode: TOOLS, while tool mode rejectsenabledScopesinstead of silently interpreting either payload as a scope-mode key. - Tool-mode records persist
enabledScopes: []. This inactive legacy field makes a rollback to a pre-tool-aware server fail closed instead of broadening the key to read access. - Omit
enabledScopesfrom tool-mode create/update requests. Pre-tool-aware app instances reject the missing legacy field during a rolling deploy instead of silently creating a scope-mode read key. - Name, authorization, security-level, and session-duration validation is shared with dashboard methods.
For a tool-mode request, Quave ONE derives only the scopes declared by the current selected tool. Every unconditional requiredScopes entry and every declared secondaryScopes entry for that tool is available to that request. This means selecting a tool authorizes its complete declared parameter surface, including optional secret-returning behavior where documented. The derived scopes do not authorize any other unselected tool, and the normal secret/admin/audit protections still run.
Backward compatibility
Records with a missing or unrecognized authorizationMode are treated as SCOPES. Migration 156 marks those records as SCOPES without changing enabledScopes, key IDs or secrets, enabled state, security level, session duration, timestamps, or any other field. It does not create enabledTools, so existing keys keep the same discovery and execution behavior and require no reconfiguration.
Delegating keys from an MCP key
A calling MCP key can list or manage only keys with the same or less effective access:
SCOPEStoSCOPES: the target scopes must be a subset of the caller scopes.SCOPEStoTOOLS: every scope declared by every selected target tool must be available to the caller.TOOLStoTOOLS: the target allowlist must be a subset of the caller allowlist.TOOLStoSCOPES: denied. A finite allowlist cannot safely grant a scope whose effective tool set may grow when new tools ship.
The same comparison applies when changing a key's authorization mode. An MCP caller cannot reveal, rotate, toggle, or delete a target key outside its effective access.
Restrictions
MCP keys authenticate only through MCP. A direct Public API call with an MCP
key answers 401; scripts and services use an
API token or OAuth.
MCP-only controls, such as tool allowlists and security-level confirmations,
never apply to a direct call, so the key cannot be reused there.
The optional restriction field limits a key to one account and, optionally,
to some of its apps, regions, and sandbox templates:
| Field | Required | Description |
|---|---|---|
accountId | Yes | The only account the key can reach. You must be a member of it. |
appIds | No | Apps of that account the key can use. Omit for every app. |
regions | No | Regions where the key can create sandboxes. Omit for every region. |
templateIds | No | Sandbox templates the key can use. Omit for every template. |
A restricted key can call only the Agent Sandboxes
and sandbox template tools; every other tool answers 403. A sandbox,
template, region, or accountId outside the restriction answers 403. An
app-restricted key must pass appId when it lists sandboxes, can use but not
change account-wide templates, and can create templates only for its apps. A
key with templateIds cannot create templates.
The key still acts as its owner: every request also needs the owner's own account permissions, so a key can never do more than its owner.
Create a restricted sandbox key:
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "sandbox-agent",
"enabledScopes": [
"quave:read",
"quave:write:config",
"quave:write:deploy",
"quave:write:dangerous"
],
"securityLevel": "balanced",
"sessionDuration": 1800,
"restriction": {
"accountId": "ACCOUNT_ID",
"appIds": ["APP_ID"],
"regions": ["us-5"]
},
"returnSecret": true
}' \
https://api.quave.cloud/api/public/v1/mcp-key
On update, omit restriction to keep the stored value or send
"restriction": null to remove it. Metadata responses include restriction.
The dashboard MCP page keeps it when you edit a key there.
Limit accounts
By default an MCP key reaches every account its owner is a member of. Set
limitAccounts: true with allowedAccountIds to limit the key to chosen
accounts. In the dashboard this is the Limit accounts switch on the MCP key
form.
| Field | Description |
|---|---|
limitAccounts | true limits the key. On update, false removes the limit and omitting it keeps the stored value. |
allowedAccountIds | Accounts the key can reach. Required with limitAccounts: true. You must be a member of each one. |
The limit is enforced on every MCP tool call:
- A call that names, or resolves to, any other account answers
403. This coversaccountId,appId,appEnvId, the current account, and resources such as alerts, credentials or backups that belong to another account. list-accountsreturns only the allowed accounts.- The key never uses its owner's internal Quave roles, so it acts only as a member of the allowed accounts.
get-user-tokenandcreate-accountanswer403, because their results would not be limited. Personal LLM provider keys and AI subscriptions also answer403, because they are outside every account.- Agent automations and agent profiles can use only MCP keys limited to the same or fewer accounts. Automations whose key reaches further are hidden.
- A limited key can see and manage only keys limited to the same or fewer
accounts. It can create keys only with
limitAccounts: trueand accounts within its own limit, and it cannot remove a limit.
When the owner loses membership of an allowed account, that account is removed from the key. A key left without accounts is disabled and stays limited; it never becomes a key for every account. Choose an account again before enabling it.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "prod-read-only",
"enabledScopes": ["quave:read"],
"securityLevel": "balanced",
"sessionDuration": 1800,
"limitAccounts": true,
"allowedAccountIds": ["PRODUCTION_ACCOUNT_ID"]
}' \
https://api.quave.cloud/api/public/v1/mcp-key
If the key also has a restriction, its accountId must be one of the
allowedAccountIds.
List and get MCP keys
curl -H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/mcp-keys'
curl -H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/mcp-key?keyId=MCP_KEY_ID'
Metadata responses include authorizationMode, enabledScopes, enabledTools, limitAccounts, and allowedAccountIds. Raw key plaintext is never included.
Create a scope-mode key
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "automation-read",
"authorizationMode": "SCOPES",
"enabledScopes": ["quave:read"],
"securityLevel": "balanced",
"sessionDuration": 1800,
"returnSecret": true
}' \
https://api.quave.cloud/api/public/v1/mcp-key
Omitting authorizationMode preserves the legacy SCOPES default. quave:read is always included in a scope-mode key.
Create a tool-mode key
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "health-check",
"authorizationMode": "TOOLS",
"enabledTools": [
"get-current-account",
"list-apps",
"get-account-observability-summary"
],
"securityLevel": "balanced",
"sessionDuration": 1800,
"returnSecret": true
}' \
https://api.quave.cloud/api/public/v1/mcp-key
Only the explicit names in enabledTools are discoverable and callable. New tools remain denied until the key is updated. If returnSecret is true and authorized, the response includes rawKey once; store it immediately.
Update or toggle an MCP key
Send the full authorization configuration when updating. Changing modes is validated with the same delegation rules as creation.
curl -X PATCH \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"keyId": "MCP_KEY_ID",
"enabled": true,
"authorizationMode": "TOOLS",
"enabledTools": ["list-apps", "list-app-envs"],
"securityLevel": "strict",
"sessionDuration": 900
}' \
https://api.quave.cloud/api/public/v1/mcp-key
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "keyId": "MCP_KEY_ID", "enabled": false }' \
https://api.quave.cloud/api/public/v1/mcp-key/toggle
Set or clear the default MCP key
A user has at most one default MCP key. Setting a new default clears the previous one. A disabled key cannot be the default; disabling or deleting the default key clears it. A new web agent chat preselects this key when the chat was not opened from an automation run that still has its own enabled key.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "keyId": "MCP_KEY_ID", "isDefault": true }' \
https://api.quave.cloud/api/public/v1/mcp-key/default
Send "isDefault": false to clear the default without choosing another key.
Regenerate, reveal, or delete
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "keyId": "MCP_KEY_ID", "name": "automation-read-2", "returnSecret": true }' \
https://api.quave.cloud/api/public/v1/mcp-key/regenerate
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/mcp-key/reveal?keyId=MCP_KEY_ID'
curl -X DELETE \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/mcp-key?keyId=MCP_KEY_ID'