Use Drums from Claude Code, Cursor and Codex (MCP)
One MCP server, two ways to reach it. Install it in Claude Code, Cursor or Codex, then drive desktops, snapshots, tasks, runs and keys from the agent.
Drums publishes one MCP server. It exposes the workspace (computers, snapshots, tasks, runs and evaluations, API keys, the audit log) and the desktop itself (GUI actions, screenshots, shell, files, verify, fork, reset) as tools, so an agent in Claude Code, Cursor, Codex or any other MCP client works on Drums desktops the way the SDK does. Every tool call is scoped to the workspace of the API key, is checked against the key's scopes, and is written to the audit log.
Two ways to reach it
| Transport | Where it runs | Address | When to use it |
|---|---|---|---|
| Remote (Streamable HTTP) | Inside the gateway | https://beta.drums.run/gw/mcp (your workspace's gateway URL plus /mcp) | Nothing to install. Clients that speak HTTP MCP: Claude Code, Cursor |
| Local (stdio) | drums mcp, from the Python SDK (pip install drums-sdk, 0.3.0 or later) | Your machine, talking to DRUMS_URL with DRUMS_TOKEN | Clients that only start a command: Codex, or a Claude Code setup you want to keep local |
Both servers list exactly the same tools with the same schemas and give the same answers; the local one calls the same gateway routes. Authentication is the workspace API key in either case (Authorization: Bearer on the remote endpoint, DRUMS_TOKEN for the local command). There is no OAuth flow: create a key under API keys and put it where the snippet says drums_sk_.... The key is never shown in the console again, so treat the client's configuration file like a secret.
Print the snippet for your client
With the SDK installed and DRUMS_URL exported as in the quickstart, drums mcp --print-config prints the configuration for a client, with your gateway URL filled in and drums_sk_... where the key goes. It never prints a key. The four forms are claude, claude-local, cursor and codex; the sections below show each command and its output as printed by SDK 0.3.0. The console's Connect sheet (MCP tab) shows the same snippets for your workspace.
Claude Code
drums mcp --print-config claudeprints the registration command for the remote endpoint; Claude Code keeps it in its settings:
claude mcp add --transport http drums https://beta.drums.run/gw/mcp --header "Authorization: Bearer drums_sk_..."Put your key in place of drums_sk_... (or write $DRUMS_TOKEN there and let the shell expand it) and run it. To run the server locally instead:
drums mcp --print-config claude-localclaude mcp add drums -e DRUMS_URL=https://beta.drums.run/gw -e DRUMS_TOKEN=drums_sk_... -- drums mcpThen ask Claude Code for the tools: list_templates, create_computer and the rest appear under the drums server. The SDK repository also ships a Claude Code plugin (see The Claude Code plugin).
Cursor
drums mcp --print-config cursorprints the object for ~/.cursor/mcp.json (every project) or .cursor/mcp.json (one project); put your key in place of drums_sk_...:
{
"mcpServers": {
"drums": {
"url": "https://beta.drums.run/gw/mcp",
"headers": { "Authorization": "Bearer drums_sk_..." }
}
}
}Codex or any stdio client
Codex starts MCP servers as commands, so it uses the local server:
drums mcp --print-config codexprints the table for ~/.codex/config.toml; put your key in place of drums_sk_...:
[mcp_servers.drums]
command = "drums"
args = ["mcp"]
env = { DRUMS_URL = "https://beta.drums.run/gw", DRUMS_TOKEN = "drums_sk_..." }drums mcp is the same as python -m drums.mcp. drums mcp --env followed by a computer id binds the desktop tools to that computer, so the agent can leave computer_id out. drums mcp --create makes one computer, binds it and closes it when the server exits (--task loads a task JSON file into it).
What the local server refuses
- A key over plain HTTP.
DRUMS_URLmust behttps://, orhttp://to loopback (localhost,127.0.0.0/8,::1: a local drumsd). Any otherhttp://URL makesdrums mcpexit 2 before any request, with a one-line message that names the host, never the key. For a trusted network, opt in withdrums mcp --allow-insecure-httporDRUMS_ALLOW_INSECURE_HTTP=1. - A redirect to another origin. The client follows a redirect only to the same scheme, host and port, at most 5 hops; anything else is
RedirectRefused, so the key is never re-sent elsewhere.
Tools
Every tool, in the order the servers list them. workspace tools act on the workspace through the gateway; desktop tools act inside one computer and take its computer_id (the id that create_computer and list_computers return). The scope is the API-key scope the call needs; "any key" means any active key of the workspace, with the rules in the tool's description. A destructive tool is refused unless the call passes confirm: true.
| Tool | Layer | What it does | Scope | Destructive |
|---|---|---|---|---|
| list_templates no arguments | workspace | List the templates (base desktop images) this workspace can start a computer from. Use a template's checkpoint as the template argument of create_computer. | computers:read | No |
| list_computers include_destroyed?, limit? | workspace | List the live computers (desktops) of this workspace: id, name, state, validity, task, created_at. Pass include_destroyed to also see closed ones. | computers:read | No |
| create_computer template, task_id?, task?, ttl_s?, setup? | workspace | Start a new isolated desktop (computer) from a template or a snapshot, optionally with a task loaded. Returns the computer record; its id is the computer_id every desktop tool takes. The desktop is ready when state is ready and validity is valid. | computers:write | No |
| get_computer computer_id | workspace | Read one computer's record: state, validity verdict and reasons, task, lease, epoch. | computers:read | No |
| destroy_computer computer_id, confirm | workspace | Close a computer for good (its desktop state is lost). Destructive: the call is refused unless confirm is true. | computers:write | Yes: needs confirm: true |
| list_snapshots no arguments | workspace | List the saved desktop states (snapshots) of this workspace. A snapshot id can be the template of create_computer. | computers:read | No |
| create_snapshot computer_id, name? | workspace | Save a computer's current desktop state as a snapshot (the computer keeps running). Returns the snapshot record. | computers:write | No |
| delete_snapshot snapshot_id, confirm | workspace | Delete a snapshot. Destructive: refused unless confirm is true. | computers:write | Yes: needs confirm: true |
| list_tasks domain?, limit? | workspace | List the verified computer-use tasks available on this host (id, domain, instruction). Load one with create_computer(task_id) or load_task. | computers:read | No |
| get_task task_id | workspace | Read one task document (instruction, setup, checker). | computers:read | No |
| load_task computer_id, task_id?, task? | desktop | Load a task into a running computer (runs its setup and starts a new episode). After this, verify scores the desktop against this task's checker. | computers:write | No |
| list_runs status?, limit? | workspace | List the runs (training, scripted tests) of this workspace, newest first: id, name, kind, status, counts. | runs:read | No |
| create_run spec, idempotency_key? | workspace | Submit a run (RunSpec: name, kind, task_set_id, task_ids, template_version_id, agent, execution, budget). It is queued and the host's runner picks it up. Returns run_id and operation_id. | runs:write | No |
| get_run run_id | workspace | Read a run's status and results: status, stop reason, attempt counts (done, succeeded, verified valid), metrics and the last events. | runs:read | No |
| list_evaluations status?, limit? | workspace | List evaluation runs of this workspace, newest first. | runs:read | No |
| create_evaluation spec, idempotency_key? | workspace | Submit an evaluation (a run with kind evaluation): the same RunSpec as create_run; kind is set to evaluation. | runs:write | No |
| get_evaluation run_id | workspace | Read an evaluation's status and results (per-task success and verified validity). | runs:read | No |
| list_api_keys no arguments | workspace | List this workspace's API keys (metadata only: id, name, prefix, scopes, created, last used, expiry; never a secret). | any key | No |
| create_api_key name, scopes, expires_in_days? | workspace | Create an API key in this workspace. The plaintext key is in this reply once and nowhere else. Scopes cannot exceed the calling key's own scopes. | any key | No |
| revoke_api_key key_id, confirm | workspace | Revoke an API key; it is refused on its next call. Destructive: refused unless confirm is true. | any key | Yes: needs confirm: true |
| read_audit_log action?, limit?, cursor? | workspace | Read this workspace's audit log, newest first: who did what, when, with what outcome. MCP calls appear with actor mcp and action mcp.<tool>. | audit:read | No |
| gui computer_id, code | desktop | Run a GUI action in the desktop: Python using pyautogui (e.g. pyautogui.click(100, 200), pyautogui.write('text'), pyautogui.hotkey('ctrl', 's')). Screen is 1920x1080 pixels. Take a screenshot afterwards to see the result. | computers:write | No |
| screenshot computer_id | desktop | Take a screenshot of the desktop (PNG image). | computers:read | No |
| shell computer_id, command, timeout_s? | desktop | Run a bash command in the desktop as the desktop user (no root, no internet). Returns returncode, stdout, stderr. | computers:write | No |
| read_file computer_id, path | desktop | Read a file in the desktop (~ is /home/user). Text comes back as text, binary as base64. | computers:write | No |
| write_file computer_id, path, content | desktop | Write a text file in the desktop (creates folders; overwrites). | computers:write | No |
| list_dir computer_id, path | desktop | List a folder in the desktop: name, dir, size. | computers:write | No |
| office_convert computer_id, path, format, outdir? | desktop | Convert a document with LibreOffice (e.g. docx->pdf, xlsx->csv, csv->xlsx). Writes next to the source unless outdir is given. | computers:write | No |
| spreadsheet_read computer_id, path | desktop | Read the first sheet of a spreadsheet (xlsx, ods, csv) as rows of text, through LibreOffice. | computers:write | No |
| browser_tabs computer_id | desktop | List the open Chrome tabs (url, title). Needs Chrome started with --remote-debugging-port=1337. | computers:write | No |
| browser_open computer_id, url | desktop | Open a URL in a new Chrome tab (Chrome must be running with --remote-debugging-port=1337). | computers:write | No |
| verify computer_id | desktop | Score the desktop's current state with the loaded task's checker: score (0..1), valid (every gate of the episode passed), trusted, audit flags. | computers:write | No |
| fork computer_id, n? | desktop | Fork a running desktop into n children that each continue from this exact state (apps open, files as they are). Returns the children's computer ids. The parent keeps running. | computers:write | No |
| reset computer_id | desktop | Reset a desktop to its clean template state and start a new episode (the loaded task's setup runs again). | computers:write | No |
Tool results are JSON text, except screenshot, which returns a PNG image. A failed call returns a tool error with a one-line reason (for example refused: pass confirm: true to destroy_computer), never a protocol error, so the agent can read it and recover.
The workflow
- Create.
list_templates(orlist_snapshots) andlist_tasks, thencreate_computerwith a template and atask_id. Pollget_computeruntilstateisreadyandvalidityisvalid; before that the desktop may still be restoring. Keep the computer id. - Act.
screenshotto see the desktop;guifor pyautogui actions (click, type, hotkeys) with a screenshot after each one;shell,read_file,write_fileandlist_dirfor anything a terminal does better than a mouse;browser_openandbrowser_tabsfor Chrome;office_convertandspreadsheet_readfor documents. The desktop user has no root and no internet. - Verify.
verifyscores the desktop with the loaded task's checker and reports whether the episode is still valid (every gate passed).load_taskswaps the task on a running computer. - Fork or reset.
forkmakes N children that continue from this exact state (the parent keeps running) to branch at a decision point;resetreturns the desktop to the clean template and runs the task's setup again for another attempt. See Checkpoints, snapshots and forks. - Results.
get_runandget_evaluationfor runs submitted withcreate_runorcreate_evaluation;read_audit_logfor what happened, by whom.destroy_computerwithconfirm: truewhen the desktop is no longer needed (ttl_scloses it anyway when it expires).
Safety
- Destructive tools need
confirm: truefrom the caller.destroy_computer,delete_snapshotandrevoke_api_keyare refused before any network call unless the tool arguments carryconfirm: true. Drums never prompts a human itself: the MCP client (Claude Code, Cursor, Codex) owns the user's approval of such a call, and a well-behaved agent tells you before it passes the flag. - Workspace scoping. A key sees only its own workspace. A computer, snapshot, run or key of another workspace does not exist for it: the tool returns a not-found error, never a hint that the id is real.
- Scopes. Each tool needs the scope in the table; a key without it gets a
forbiddenerror.create_api_keycannot grant more than the calling key has; only an owner or admin key may create or revoke keys; a key cannot revoke itself. - Audit log. The gateway records every
tools/callasmcp.<tool>with actormcpand originmcp: tool name, duration, computer id and error code, never the arguments, screenshots or file contents. The underlying actions (computer.create,api_key.revoke, ...) keep their own rows. Filter the Audit log page orread_audit_logby actionmcp. - Rate limits are the gateway's. Each MCP request takes one token from the key's bucket, and failed authentications count per source IP, exactly as for
/v1; see Limits. - Secrets. The server never prints a key.
create_api_keyreturns the new key once, in that reply only; the agent's transcript then holds it, so use it, store it and do not paste it elsewhere.
The Claude Code plugin
The SDK repository is also a Claude Code plugin: .claude-plugin/plugin.json, a .mcp.json that starts drums mcp with DRUMS_URL and DRUMS_TOKEN from your environment, and the drums-workflow skill. The skill teaches the workflow above (templates, create, wait until ready and valid, act with screenshots after GUI actions, verify, fork at decision points, reset for another attempt, destroy with confirm when done, read results) and the rules: never destroy without telling the user, prefer shell and read_file over the GUI for checks, keep computer ids. Install it from the repository the way you install any Claude Code plugin, with the two environment variables set.
Troubleshooting
| Symptom | Meaning | What to do |
|---|---|---|
The client reports 401 when connecting or listing tools | The key is missing, mistyped, expired or revoked | Check the Authorization header or DRUMS_TOKEN; create a new key under API keys |
404 or "not found" on a computer id that exists in the console | The id belongs to another workspace than the key | Use a key of that workspace, or the id of a computer in this one |
429 with Retry-After | The key's request bucket is empty, or too many failed authentications from this address | Wait the number of seconds in Retry-After; slow the agent down |
refused: pass confirm: true to ... | A destructive tool was called without confirmation | Confirm with the user, then call again with confirm: true |
not available on this server (needs the Drums gateway) | drums mcp points at a plain runtime (DRUMS_URL is a drumsd, not the gateway) | Runs, evaluations, keys and the audit log exist only behind the gateway; point DRUMS_URL at it |
forbidden | The key lacks the tool's scope | Create a key with the scope the table lists (Manage API keys safely) |
drums mcp exits 2: InsecureTransport | DRUMS_URL is plain http:// to a host that is not loopback | Use the https:// gateway URL; on a trusted network only, --allow-insecure-http or DRUMS_ALLOW_INSECURE_HTTP=1 |
RedirectRefused | The server redirected to another origin | Point DRUMS_URL at the final URL (the gateway's own address) |
Every gateway reply carries a request id (X-Request-Id on the remote endpoint); include it when you report a problem.