Server data from the Official MCP Registry
Cloud desktops for AI agents: create a Linux computer, see its screen, click, type and run tasks.
About
Cloud desktops for AI agents: create a Linux computer, see its screen, click, type and run tasks.
Remote endpoints: streamable-http: https://app.mandala.computer/mcp
Security Report
Valid MCP server (2 strong, 1 medium validity signals). 1 known CVE in dependencies Package registry verified. Imported from the Official MCP Registry.
Endpoint verified · Requires authentication · 3 issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
What You'll Need
Set these up before or after installing:
Environment variable: MANDALA_API_KEY
Environment variable: MANDALA_MODEL_KEY
Environment variable: MANDALA_PROFILE
Environment variable: MANDALA_BASE_URL
Environment variable: MANDALA_COMPUTER_ID
Environment variable: MANDALA_NO_LIFECYCLE
Environment variable: MANDALA_READ_ONLY
Environment variable: MANDALA_TAGS
How to Install & Connect
Available as Local & Remote
This plugin can run on your machine or connect to a hosted endpoint. during install.
Documentation
View on GitHubFrom the project's GitHub README.
mandala-computer-mcp
An MCP server for Mandala Computer — cloud desktops for AI agents.
Point Claude Code, Claude Desktop, or anything else that speaks MCP at a real Linux desktop it can see and drive. Screenshots come back as images, so the model looks at the screen and clicks what it sees.
Status: alpha. The tool surface is settling; expect breaking changes before 1.0. Tracks the platform's
/api/v1, which is itself still moving.
Install
MCP requires Node 20.3 or newer. The TypeScript CLI login setup requires
Node 22 or newer (mandala-computer on npm). Sign in once, then add the MCP
server:
npm install -g mandala-computer
mandala login
claude mcp add mandala -- npx -y mandala-computer-mcp
On Node 20, MCP can use an existing saved profile or an environment key.
The TypeScript CLI guides you through browser approval and saves an API key in
~/.mandala/credentials.json. MCP only reads that file; it never issues or
approves a device, writes credentials, or starts login automatically. The
Python distribution also provides a mandala command; use the TypeScript
CLI for login.
To select a separate profile and workspace:
mandala login --profile Work --workspace Research
claude mcp add mandala -- npx -y mandala-computer-mcp --profile Work
--profile takes precedence over MANDALA_PROFILE, then the saved default.
Names are case-sensitive. An explicit key or nonempty MANDALA_API_KEY takes
precedence over every profile and avoids accessing the credential store.
Empty explicit keys fail; an empty or whitespace-only environment key is absent.
You can still use a key from Settings → Credentials → API keys with
MANDALA_API_KEY.
Saved credentials require POSIX protection: a real directory owned by you with
mode 0700, and an owned regular file with mode 0600 and one link. Symlinks,
hardlinks, unsafe permissions, malformed files and unsupported protection are
refused before any API request. Windows file loading is unsupported; explicit
or environment keys remain available. Only your home directory's store is read.
A saved profile also binds its API base URL. --base-url or MANDALA_BASE_URL
must match that stored base after canonicalization, including the entire path
prefix and port. An explicitly empty base flag is invalid with saved credentials.
Each stdio session resolves once: restart to pick up another saved key. Revoking
the key in Settings → Credentials → API keys makes later calls fail; MCP
does not switch profiles, reread the file, or retry a refused action. Run login
explicitly when new credentials are needed.
A saved profile can also carry a default workspace, set with
mandala workspaces use <workspace> (and removed with --clear); the CLI keeps
it in ~/.mandala/defaults.json, beside credentials.json, which it never
changes. Started from that profile, the secret tools (list_secrets,
get_secret, create_secret, set_secret, replace_secret,
delete_secret), create_computer and list_computers use it when a call
leaves out workspace_id, as mandala computers create and computers list
do; an explicit workspace_id always wins. Their descriptions say so and name
the workspace, list_secrets, list_computers and create_computer say when
the default chose the workspace, and startup prints it on stderr. There is no
"every workspace" value: to list elsewhere, pass another workspace_id (or
'unassigned'), or start the server from a profile with no default. MCP
only reads that file, once at startup, with the same checks as
credentials.json. It is not applied when the profile's key is confined to a
workspace (that key's own scope applies), when the default was saved for
another account than the profile is logged in to now, or with an explicit or
environment key. A file that cannot be read (not valid JSON, another version,
or readable by others) is noted on stderr: list_secrets, get_secret and
list_computers then use account-wide, but create_secret, set_secret,
replace_secret, delete_secret and create_computer refuse a call without
workspace_id, sending nothing, rather than act account-wide; pass
workspace_id, or fix or delete the file and restart the server. The hosted
HTTP server never reads it, and its tools are unchanged.
Claude Code — as a plugin, which installs the server and a skill together:
# Run mandala login first, or export MANDALA_API_KEY in this shell.
/plugin marketplace add mandalacomputer/mcp
/plugin install mandala-computer@mandala
The skill — plugin/skills/mandala-computer/SKILL.md
— is the part the tools cannot say for themselves: when a cloud desktop is the
right answer at all, that it consumes running capacity until suspended or stopped, that
run_agent is usually the right level and a screenshot per click is not, and
which refusals are worth a second try. It is a description of when and how,
not a second client; once the server is installed it stays out of the way.
MANDALA_MODEL_KEY, if exported alongside, is passed through and turns on
run_agent when the configured filters permit it. Without it, run_agent runs
on the account's AI credits when they can pay (see Agents on AI
credits).
To use an environment key instead of a saved profile:
claude mcp add mandala -e MANDALA_API_KEY=com_… -- npx -y mandala-computer-mcp
Claude Desktop — in claude_desktop_config.json:
{
"mcpServers": {
"mandala": {
"command": "npx",
"args": ["-y", "mandala-computer-mcp"]
}
}
}
Cursor, Windsurf and the rest take the same three fields — command,
args, env — in whichever file they keep their MCP servers in.
Your MCP client starts this as a subprocess. It uses the saved profile’s API
base, or https://app.mandala.computer/api/v1 by default with an environment key.
Programmatic local hosts can call runStdio({ profile: 'Work' }); the exported
StdioConfig allows a local key or profile. createServer still requires an
explicit API key, and HttpConfig has no local credential options.
Use
Ask for what you want. A first session usually goes:
Create a computer from the base template, open example.com, and show me what's on the screen.
Under that, the model is doing roughly this:
create_computer(template="base") → builds it and selects it for the session
wait_for_computer(until="guest") → the desktop inside is answering, not just the VM
open_url(url="https://example.com") → puts the page on the screen
screenshot() → an image the model can point at
click(x=640, y=400) → clicks what it saw
screenshot() → looks again
create_computer(size="large") is the fast path: the named shapes from
list_sizes are the ones the platform keeps pre-booted, so one of those is
usually answered in about a second where a custom cpu/ram_mb/disk_gb
shape boots cold. A size sets the template and the numbers together, so send
it alone or the explicit fields alone.
use_computer binds a machine to the session, so every later call can leave
computer_id out. Pass computer_id explicitly on any call to override it
without changing the binding, which is how you drive two machines at once.
Set MANDALA_COMPUTER_ID to bind one at startup and skip use_computer
entirely.
The tools
This is the unfiltered inventory. The filters below can withhold tools from both listing and calling; workflows in this README apply only when the needed tools are available. The offline fixture checks exercised operation coverage; verification against the live publication is a separate required CI gate. Parameter and response-mode support remains a separate contract.
Choosing a machine — list_templates, list_sizes, list_computers, get_computer,
use_computer, wait_for_computer, get_desktop_url
Lifecycle — create_computer, start_computer, stop_computer,
suspend_computer, restart_computer, update_computer, clone_computer,
delete_computer, move_computer_to_workspace, move_computer, list_moves, get_operation,
list_operations, wait_for_operation — see Lifecycle operations
move_computer_to_workspace changes workspace membership and who can access a computer.
It requires an account-wide member or owner credential and explicit computer_id and
workspace_id arguments (null means unassigned). The selected computer and default
workspace are not used. The computer must be stopped, with no saved session, snapshots
or snapshot history copies, secret bindings, or proxy credentials. Refusals leave
those resources intact; the tool never removes them to force a move. This is separate
from move_computer, which relocates a computer to different hardware.
Driving the desktop — screenshot, click, type_text, paste_text,
press_key, scroll, drag, move_mouse, mouse_button, cursor_position,
wait
Every one of these that acts — all but screenshot and cursor_position —
takes context: true and then answers the desktop as it stands just after the
action: the windows list_windows would list and the one with focus, without a
second call. They are read once, straight after the action, so a window still
opening may not be listed yet; when they cannot be read the action still
happened, and the answer says why. When the focused window is Chromium the
answer also carries page: the page's URL and title and one line per
interactive element visible in it — what it is, what it says (and its
accessible name when that is something else), and the point to click. Firefox, the default browser, has no page; then page_not_read gives
the platform's reason beside the windows.
Inside the guest — exec, exec_poll, exec_kill, get_execution,
read_execution_output, open_url,
list_windows, window_action, read_clipboard, write_clipboard,
read_file, write_file, write_file_chunk, get_file_upload, cancel_file_upload, list_directory
write_file writes straight to the path by default: a file already there is
truncated as the upload starts, so a failed or cut-off write can leave only part
of the new contents, with the old ones gone. With overwrite: false it creates
the file only if nothing is there, and a path that is taken is refused without
that attempt writing anything (Linux computers only). Those contents are written
beside the path and published only once complete, so incomplete contents never
appear at it, but a failure while publishing or answering can leave the complete
file there. After any error read the path before retrying or overwriting. When a
partial file must never appear at the path, use overwrite: false or
write_file_chunk. read_file and write_file take no_wake: true
to refuse (409) rather than resume a computer that is not running. For
embedders, the Api raises that refusal as FileExistsError, and a
create-only 409 whose reason could not be read as CreateOnlyConflictError,
which says nothing about the path; isTransient is false for both.
Retained versions — retain_execution_output, get_result, read_result_output,
delete_result, publish_artifact, get_artifact, read_artifact, delete_artifact
Passive metadata — list_activities, get_activity, get_activity_results, read_signals
Being told rather than asking — wait_for_event, poll_events,
wait_for_file_change
Snapshots — list_snapshots, snapshot_holdings, create_snapshot,
restore_snapshot, clone_snapshot, snapshot_schedule, get_retention,
delete_snapshot
Your own templates — get_template_schema, check_template,
publish_template, get_template, retire_template
Building one — build_template, list_builds, get_build, watch_build
Account quota — get_account
Who you are — whoami, list_api_keys, list_workspaces,
get_workspace, list_workspace_members, create_workspace,
rename_workspace, delete_workspace. Minting and revoking API keys are
deliberately not tools — see Who you are, and API keys.
Deliberately not tools — five API operations, each because it would put a
credential in a model's context: minting and revoking API keys (above), and the
three browser-connection routes (create, renew and revoke). A browser
connection is a raw capability to drive the guest's browser; the TypeScript
and Python SDKs' browser drivers create, renew and revoke it and keep it to
themselves. Every other API operation has a tool. One body form is left out as
well: the input route's batch, and with it the batch wait_for check, which
exists only inside one. The input tools take one action per call, so a model
sees each answer before it chooses the next.
A key that expires has no desktop or event stream. The platform withholds a
computer's desktop credentials from such a key, so get_desktop_url,
wait_for_event, poll_events and wait_for_file_change answer that a key
without an expiry is needed, once the key's own record confirms it expires.
Every tool refuses an argument it does not declare. A misspelt or unknown key is an input error naming the key, and nothing is sent — so a misspelt flag can never be dropped and the call go ahead without it.
Spending — get_usage, get_credits
Being told somewhere else — list_webhooks, create_webhook,
get_webhook, update_webhook, rotate_webhook_secret, test_webhook,
list_webhook_deliveries, delete_webhook
SSH access — list_ssh_keys, add_ssh_key, remove_ssh_key,
get_computer_ssh, set_computer_ssh. Keys belong to the person the API key
was issued to, not to the account. A key added with add_ssh_key (an API key
or connected app) is accepted only by computers with SSH on in the account that
credential acts on, and is removed when the credential is revoked; a key added
from the dashboard is accepted on every account where that person is an owner
or member. list_ssh_keys marks each key with where it works from the account
the API key acts on: every account, this account only, or another account only
(refused here; which account is not said). A key is registered once, so a key
bound to another account cannot be added again here. remove_ssh_key removes
only a key marked this account only: one added from the dashboard (every
account) or bound to another account answers as if there were no such key, and
the person removes it from the dashboard. A workspace-scoped key can read keys
but not add or remove them.
Showing an app on the internet — list_public_urls, create_public_url,
update_public_url, delete_public_url. A port inside a computer served at
https://<label>.<base_domain> (the domain is in the list_public_urls
answer, e.g. mandala.dev), on 443 with no port in the address. Omitted access
defaults to public on paid plans and private on trials. Public means anyone
with the link can open it; trials requesting public receive
402 PUBLIC_ACCESS_REQUIRES_PLAN. Private URLs accept an account API key as
Authorization: Bearer (workspace-scoped keys must match the computer's workspace),
or browser sign-in through the dashboard's Open button. The key never reaches the app. The
app must listen on 0.0.0.0, not 127.0.0.1. Labels that read as the
platform's or another company's address are refused. A deleted URL's label stays
with the account: no other account can ever take it, and the account may use it
again unless the platform has blocked it. A computer over its plan's limit
serves only its oldest URLs, and the listing marks the rest.
A browser opening a new account's URL may first see a one-time notice page. It
goes to any request that looks like a browser opening a page — a navigation by
its fetch metadata, or, without fetch metadata, Accept: text/html from a
Mozilla/ user agent — so a program sending browser headers sees it too. A
client that sets headers skips it with mandala-skip-warning: 1; in the
computer's own browser, where open_url cannot send headers, click Continue.
Calling an API without handing the computer the key —
list_credential_gateway_bindings, create_credential_gateway_binding,
delete_credential_gateway_binding. A binding names a secret from the
account's store, an exact https:// origin, a path prefix and the HTTP methods
allowed. Programs inside the computer send requests to the binding's base_url
followed by the provider path, with the placeholder as the API key —
Authorization: Bearer <placeholder> or x-api-key: <placeholder> (both values
are in the answer) — and the platform sends them on with the secret in their
place: the secret never reaches the computer. The secret goes upstream as
Authorization: Bearer <secret> by default, or as x-api-key: <secret> with
auth_header: "x-api-key". An Anthropic key binding uses x-api-key (origin
https://api.anthropic.com, path prefix /v1), and Claude Code inside the
computer then runs with ANTHROPIC_BASE_URL=<base_url> and
ANTHROPIC_API_KEY=mandala-placeholder. Answers stream, server-sent events
included; a request is limited to 7 MiB, an answer to 64 MiB and a call to 10
minutes. Up to 8 per computer; a
binding cannot be changed, only deleted and made again. Owners create and
delete them; members can list them. Origins, paths and methods that can only
be refused are refused before any request.
Secrets — list_secrets, get_secret, create_secret, set_secret,
replace_secret, delete_secret for the account's secret store, and
get_computer_secrets, set_computer_secrets and create_computer's secrets
for which of them a computer receives. A value goes in through create_secret,
set_secret (create the name, or replace its value if the scope holds it —
names match ignoring ASCII case) or replace_secret, and never comes back out. No route answers one, and a store tool's result
holds only the decoded documented fields (success) or the status, the reason
word and a sentence of its own (refusal). It never includes the platform's
response text, and the Api keeps none for these routes. The bindings carry
only secret ids, revisions and names. A computer receives a secret as an environment variable (env) or as a
file under /run/mandala-secrets/user/files (file). A set replaces the whole
binding list ([] removes every binding) and reaches the guest at the
computer's next start or restart; send the version a read answered to have it
refused with 409 if the list changed since. A replaced value also reaches a
running computer: a file binding's file is rewritten in place, and on an image
that supports it an env binding reaches new shells, and every exec, plain
or with desktop: true, runs with the bound variables as they are at that
moment (programs already running keep the old value until a restart).
replace_secret
and delete_secret need the current revision_id, and a stale one is a 409.
delete_secret also needs confirm: true: a computer still bound to a deleted
secret cannot start again until that binding is removed. A bound computer runs,
and its guest answers, a few seconds before its secrets land, so
wait_for_computer(until="guest") on one also waits until they have;
get_computer shows the gap as "its secrets are still on their way in".
A proxy for the browsers — browser_proxy: {server, bypass} on
create_computer and update_computer sends a Linux computer's browsers
(Chromium, Chrome, Firefox) through a proxy; nothing else on the computer uses
it. On update_computer it goes alone, replaces the setting whole, and null
removes it. Which proxies are accepted is the platform's rule, and its refusal
comes back as it is. A running computer has a change within seconds:
wait_for_computer(until="guest") waits until its browsers have it, and
get_computer shows the gap as "its browser proxy is still being applied".
A proxy for all outbound traffic — egress_proxy: {server, credentials_secret_id} on create_computer and update_computer sends ALL of
a computer's outbound TCP (exec, terminals, package managers and browsers
alike) through a proxy, taken on its host so nothing inside the computer can
opt out. The server is http://, https:// or socks5:// with an explicit
port; there is no bypass list. credentials_secret_id names a secret holding
user:password that is not bound to the computer and never reaches it: the
computer's host signs in to the proxy with it. It fails closed (proxy down or
refusing, or credentials not on the host yet: the connection fails, nothing goes
direct), drops UDP to the internet and ICMP, and does not proxy DNS lookups.
On update_computer it goes alone — beside any other field it is refused
before a request is sent — replaces the setting whole, and null removes it.
get_computer names the proxy and, while the host waits for its credentials,
says "its egress proxy is waiting for credentials; connections are closed until
they arrive".
An end time — ends_at (an RFC 3339 time) or ends_in_hours (hours from
now, fractions allowed) on create_computer and update_computer makes the
computer stop by itself at that time, as stop_computer would; its disk is
kept. It fires once and is then cleared. On create the time must be at least
10 minutes and at most 366 days away; on update_computer it only has to be in
the future, it goes alone — beside any other field it is refused before a
request is sent — and null clears it. Send one spelling, not both.
get_computer shows it as ends_at, and its summary says "stops by itself at
…". A host that cannot keep an end time answers 409 with reason unsupported.
An answer that does not confirm the end time asked for (no ends_at, a
malformed or different one, or one still there after clearing it) is an error
naming the computer; the computer is left as it is. A same-key retry answered
with the first request's stored answer can only have an ends_in_hours end
time checked to be no later than asked for, so it succeeds with a line saying
the end time is UNCONFIRMED; read it back with get_computer. So does an
answer with no Date header, which leaves only this machine's clock to check
an ends_in_hours end time against. A clear is confirmed by the absence of
ends_at in the answer.
Delegating — run_agent, start_agent_run, run_agent_chat, registered when a
model key is present: MANDALA_MODEL_KEY on stdio, or the caller's own X-Model-Key
header over HTTP. Without a key, run_agent and start_agent_run are registered
when the account's AI credits can pay for a run (see
Agents on AI credits). All of them must also survive the
configured filters.
Background runs and kept conversations — get_agent_run, list_agent_runs,
cancel_agent_run, list_agent_threads, get_agent_thread, delete_agent_thread,
registered whether or not anything can pay for a run, since a run or a thread can
come from any client — see Background runs and threads
Tool filters
Set MANDALA_READ_ONLY=1 to register only tools whose existing
annotations.readOnlyHint is exactly true. It accepts 1, true, yes,
on for on and 0, false, no, off, empty or unset for off, ignoring
surrounding whitespace and letter case. Any other value fails at startup.
Safety is not inferred from a tool's name or HTTP method: read_file and
cursor_position can wake a computer and consume running capacity, so they are withheld. So are
mixed read/write tools such as wait_for_computer and snapshot_schedule.
screenshot, read_clipboard and list_windows retain their read-only hints.
Set MANDALA_TAGS=input,guest to select a union of named tool groups. Names
are lowercase only, comma-separated, trimmed and deduplicated. Empty or
unset means no tag filter; empty comma-separated entries are ignored. An
unknown nonempty tag fails before stdio connects or HTTP starts listening,
with an error listing all valid tags.
| Tag | Tools |
|---|---|
account | get_account, whoami, list_api_keys, list_workspaces, get_workspace, list_workspace_members, create_workspace, rename_workspace, delete_workspace |
computers | list_computers, get_computer, use_computer, wait_for_computer, get_desktop_url, list_sizes |
lifecycle | create_computer, start_computer, stop_computer, suspend_computer, restart_computer, update_computer, clone_computer, delete_computer, move_computer_to_workspace, move_computer, list_moves, get_operation, list_operations, wait_for_operation |
input | screenshot, click, type_text, paste_text, press_key, scroll, drag, move_mouse, mouse_button, cursor_position, wait |
guest | exec, exec_poll, exec_kill, open_url, list_windows, window_action, read_clipboard, write_clipboard |
files | list_directory, read_file, write_file, write_file_chunk, get_file_upload, cancel_file_upload, wait_for_file_change |
executions | get_execution, read_execution_output |
results | get_activity_results, retain_execution_output, get_result, read_result_output, delete_result |
artifacts | publish_artifact, get_artifact, read_artifact, delete_artifact |
snapshots | All snapshot tools listed above, including get_retention |
templates | list_templates, all your-own-template tools and all build tools listed above |
events | wait_for_event, poll_events, wait_for_file_change |
usage | get_usage, get_credits |
webhooks | All webhook tools listed above |
ssh | All SSH tools listed above |
public_urls | list_public_urls, create_public_url, update_public_url, delete_public_url |
credential_gateway | list_credential_gateway_bindings, create_credential_gateway_binding, delete_credential_gateway_binding |
secrets | list_secrets, get_secret, create_secret, set_secret, replace_secret, delete_secret, get_computer_secrets, set_computer_secrets |
agent | run_agent, run_agent_chat, start_agent_run, get_agent_run, list_agent_runs, cancel_agent_run, list_agent_threads, get_agent_thread, delete_agent_thread |
activities | list_activities, get_activity, get_activity_results |
signals | read_signals |
The selected tags form a union, then intersect with read-only and the existing
lifecycle and model-key restrictions. MANDALA_NO_LIFECYCLE=1 still withholds
its five tools even when their tags are selected; selecting agent cannot
enable run_agent_chat without a per-session model key, nor run_agent or
start_agent_run without one or credits that can pay. The tools that start a
run, cancel_agent_run and delete_agent_thread are withheld by read-only, but
remain available with lifecycle disabled alone; an agent selection with
read-only keeps the four reads of runs and threads.
MANDALA_TAGS=files MANDALA_READ_ONLY=1 exposes only list_directory.
activities,results includes get_activity_results once; signals is separate
from the active events socket. Filters never grant API privileges: the API
checks current member/owner and workspace authorization on every request.
use_computer is not read-only. When it is withheld, pass computer_id
explicitly, or bind a computer with MANDALA_COMPUTER_ID at stdio startup.
HTTP callers must supply their own computer selection. These variables apply
to both transports and the plugin forwards them. Embedders can pass
readOnly: true and tags: ['input', 'guest'] in ServerConfig; the server
does not read the environment itself.
Lifecycle operations
Every accepted create, clone, start, stop, suspend, restart, snapshot restore,
resize, host move and delete records a lifecycle operation, and the tool that made the call
says its operation_id — in its sentence, as (operation op_…), and in the
JSON. move_computer carries it onto the outcome it reports. It is absent where
the platform could not record one; the call happened either way.
get_operation reads one, and list_operations pages through them newest
first (computer_id, limit, cursor, idempotency_key; computer_id is not
defaulted to the selected computer, and idempotency_key finds the operation a
call sent with that key recorded, even when the call's answer was lost). wait_for_operation polls one until it is final: it
answers on succeeded, and a failed one is an error carrying the platform's
error.code (start_failed, build_failed, computer_gone, move_failed,
resize_not_applied, lost, and more may be added) and its sentence.
succeeded means the platform finished its step, not that the desktop has
booted: wait_for_computer is still the wait for a desktop that answers. Most
operations are already succeeded when their tool answers; a clone is
running until its disk is copied, and a move until it lands. All three are
reads, and all three stay registered when the lifecycle tools are withheld,
since start, stop, suspend and restart record operations too.
The eleven lifecycle tools (create_computer, clone_computer,
start_computer, stop_computer, suspend_computer, restart_computer,
update_computer, move_computer, delete_computer, restore_snapshot,
clone_snapshot) take an optional idempotency_key. Leave it out and a fresh
one is sent for you; a failure whose outcome is unknown names the key it went
with. What to do next depends on who answered:
- The answer was lost (the connection dropped after the request went out,
or a proxy in front of the platform gave up), or the platform answers
idempotency_in_progress: call the same tool again with the sameidempotency_key. The platform answers with the first call's result, or says it is still running; the step is not done twice. - The platform answered a
5xxthat names nooperation_id: nothing may have been done. A refusal given before the call was sent anywhere — another launch already in progress, no host to place it on, the template catalogue incomplete — names none, and the platform releases its key. Call the same tool again with the sameidempotency_key: the platform carries it out if the key was released, or answersidempotency_outcome_unknownif not, and only then does the case below apply. - The platform answered a
5xxthat names anoperation_id, oridempotency_outcome_unknown: the key is spent, and resending with it only answersidempotency_outcome_unknown. The platform then keeps the call's operationpendingfor up to an hour, whatever happened, while its host may be carrying it out. What to read depends on the tool:start_computer,stop_computer,suspend_computer,delete_computer,move_computerandupdate_computer: readget_computer(andget_operationwith theoperation_idthe error named:succeededmeans the step happened,runningmeans wait on it, andpendingalone is no reason to wait). If the step did not take effect, send the call again with a new key, or none.restart_computer, whichget_computercannot show: a computer readsrunningbefore and after a reset. Read the operation instead (get_operationwith theoperation_idthe error named, orlist_operationswith the key):succeededmeans the restart happened,runningmeans wait on it. Anything else leaves the restart possibly done, so ask the user before sending it again with a new key, or none; a second restart resets the guest again.create_computer,clone_computer,clone_snapshotandrestore_snapshot, where a read of the computer made straight away can show no effect for a build or restore that then lands, and a second one is a second computer or a second overwrite: read the operation first —get_operationwith theoperation_idthe error named, orlist_operationswith the key — and while it ispendingorrunning, wait (wait_for_operation) and do not resend with any key. Only once it is final asfailed(error.codelost, for one) or no longer found, ANDget_computer(list_computersafter a create or a clone) shows the step did not happen, send the call again with a new key, or none. If no operation is found at all, read the computer again after a few minutes before resending.
idempotency_key_reused: a different call already used that key, and nothing was done. Send this one with a new key, or none.
Keys last 24 hours.
Who you are, and API keys
whoami takes no arguments and reads GET /api/v1/whoami: the person this
server's API key was issued to, the account and role it acts with (the role as
it is now), the workspace it is confined to, and the key itself — including
manage_keys, whether it may manage API keys. It needs no permission and any
role, and a suspended account can call it; the answer says so when the account
is suspended. Behind the hosted server's OAuth sign-in, the key is the Connected
app's (prefix oauth).
list_api_keys lists the key holder's API keys on the account, newest first —
never a raw key. Each carries minted_by_key_id: the key that minted it over
the API, or null for one minted from the dashboard. Revoking a key does not
revoke the keys it minted. It needs this server's key to be allowed to manage
keys, which only a person can turn on in the dashboard (Settings → Credentials →
API keys → the key's menu → Allow managing keys); without it the
tool answers the platform's 403, whose sentence says exactly that. A Connected
app's key never has the permission.
Minting and revoking keys are not offered here, on purpose. A mint answers
the new key in full, once, and a model's context — the conversation, the
client's logs, whatever the transcript is shared with — is the worst place for a
long-lived credential and one nobody can take it back from. A revoke is
irreversible, and on a model's reading of a list it can cut off the person's CI,
another agent, or this very session. People do both from the dashboard or with
the mandala api-keys CLI.
list_workspaces, get_workspace and list_workspace_members read the
account's workspaces, which partition its computers. A key confined to a
workspace lists only that one, and is refused (403) the member list, which is
the whole account's: workspaces do not divide people. All three are reads.
create_workspace, rename_workspace and delete_workspace change them, and
need an owner's key that is not confined to a workspace: any key confined to
one is refused (403), so none of them can touch the workspace this server's own
key stands in. delete_workspace needs confirm: true and REVOKES every API
key confined to the workspace, whoever holds it — a person's CI or another agent
may be using one — and answers how many. Only an empty workspace can be
deleted: one that still holds computers or active secrets is refused (409),
saying how many, and nothing is deleted or revoked. Move its computers out with
move_computer_to_workspace (or delete them) and delete its secrets with
delete_secret, then send it again.
None of the three is offered to a read-only session (MANDALA_READ_ONLY).
An account-wide key can work in one workspace without a key confined to it:
create_computer(workspace_id: ...) creates the computer there, and names its
secrets and an egress proxy's credentials_secret_id from that workspace's
scope (its own secrets and the account-wide ones), and
list_computers(workspace_id: ...) lists that workspace's computers, or with
'unassigned' the ones in none. A workspace the key cannot reach is not found,
and so is 'unassigned' from a confined key. Left out, these two tools apply
the saved profile's default workspace, as the secret tools do (see above);
with no default, the computer goes in the key's own workspace or in none, and
the listing is every computer the key can see.
Current account quota
get_account takes no arguments and reads GET /api/v1/account once with the
caller's account credential. Viewer or stronger access is required. It reports
account-wide aggregates, including for a workspace-scoped key, without resource
identities. It needs no selected computer or model key, opens no event stream,
and remains available with read-only and no-lifecycle filters. Select the
account tag to expose it on its own.
The report includes the effective plan, pool ceilings, per-computer maxima,
capabilities, current consumption and remaining quota. capabilities.windows
says whether the plan permits Windows computers; capabilities.macos says
whether this account may create macOS computers (os: "macos"), which are
available on request rather than with a plan, and reads false against a
platform that does not send it. Configured CPU and
disk include kept computers regardless of power state; running or reserved RAM
includes current reservations. CPU, MB, GB and snapshot bytes retain the API's
units. get_usage separately reports historical metered consumption over time.
Quota is advisory: observed_at is an observation time, not a reservation or
consistency token. Later create, resize, start or snapshot requests can still be
refused, and existing 402 messages remain unchanged. Snapshot headroom is against
indexed stored bytes; it does not include in-flight capture reservations and
does not establish that a new capture will fit.
complete.computers and complete.snapshots are independent. An incomplete group
has null for all its consumption and remaining figures, meaning unknown,
while the other group can retain numeric values. Complete zero usage and zero
remaining quota remain numeric zeros. Verified plan ceilings remain available in
a partial report, including a no-plan account that still has retained usage.
Malformed reports and HTTP failures remain errors, never an empty account.
The tool prints unknown and advisory guidance before the projected public fields.
Things worth knowing
Every computer is a Linux desktop today. Windows guests are not offered on any plan; where this README mentions Windows it is describing behaviour the client already supports for when they are.
Running computers consume running capacity until suspended or stopped.
Runtime and individual exec calls do not add a charge to the monthly subscription;
AI-credit usage is charged separately.
create_computer says so in its own description, and so does everything that
starts a machine by a side door — restore_snapshot boots a stopped computer,
and write_clipboard, read_file and cursor_position each resume a suspended
one, because each has to reach the guest agent to do its job. All of them are
subject to running-capacity limits, and on a plan at its limit can come back 402
rather than a result. The two reads are the surprising half of that list, and
are why neither is annotated readOnlyHint: a host that auto-approves
read-only tools would otherwise wake a machine and consume running capacity with nobody asked.
screenshot, read_clipboard and list_windows genuinely do not start
anything and keep the hint. When a stretch of work is over, suspend_computer (a pause:
start_computer brings the same session back in about a second) or
stop_computer (a shutdown: the disk is kept, the session is not). Idle
suspend catches the ones a model forgets, but only after 30 minutes untouched.
get_usage reports the resources used over time, not an invoice amount.
Documentation truncated — see the full README on GitHub.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Rea
Freeby Morluto · Developer Tools
Reverse engineer anything from your terminal or agent with one CLI and MCP server.
Paperclip
Freeby Paperclipai · Developer Tools
Trending hip-hop artist momentum scores across four cultural dimensions.
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
