Skip to content

MCP server

Quivvo has a hosted MCP server, so coding agents like Claude Code, Cursor and Codex can create and drive cloud machines directly. It speaks Streamable HTTP at https://tryquivvo.com/api/mcp. You sign in through your browser, or pass a Quivvo API key.

How do I set it up?

Add the server and sign in when your client asks. No key to paste:

claude mcp add --transport http quivvo https://tryquivvo.com/api/mcp

In Cursor or VS Code: add to Cursor, or install in VS Code. Each opens the client with this server filled in.

In Claude Code, run /mcp to sign in. Your browser shows which app is asking and where it will send you back. Read and write are ticked, and deleting is off unless you tick it. The app gets a key that works only on this server, listed under API keys until you revoke it. It doesn't expire, and there's no refresh token: if it's revoked, the client asks you to sign in again.

For CI, or a client that can't sign in, use an API key. The CLI writes that configuration for you:

quivvo mcp init claude
quivvo mcp init cursor
quivvo mcp init codex

The config refers to $QUIVVO_API_KEY rather than holding your key, so it's safe in a repo and the key stays out of transcripts. Set that variable wherever your client starts from, then start a new session. If you've signed in with the CLI, this reads the saved key without showing it:

export QUIVVO_API_KEY="$(node -p "require('$HOME/.config/quivvo/credentials.json').api_key")"

Add --project to write a project file instead of your user configuration. --inline-key writes the key itself instead of a reference, and that file must stay out of git. quivvo init does the same for every AI tool it finds, and installs the skill too.

To add it by hand, Claude Code takes:

claude mcp add --transport http --scope user quivvo https://tryquivvo.com/api/mcp --header 'Authorization: Bearer ${QUIVVO_API_KEY}'

Cursor, in ~/.cursor/mcp.json:

{ "mcpServers": { "quivvo": { "url": "https://tryquivvo.com/api/mcp", "headers": { "Authorization": "Bearer ${env:QUIVVO_API_KEY}" } } } }

Codex, in ~/.codex/config.toml. Codex gives up on a tool call after 60 seconds by default, so raise that if you'll ask exec to wait longer than its default:

[mcp_servers.quivvo]
url = "https://tryquivvo.com/api/mcp"
bearer_token_env_var = "QUIVVO_API_KEY"
tool_timeout_sec = 300

For a client that only supports stdio, quivvo mcp start runs a local server that forwards to the hosted one.

These setups follow each client's own documentation, and clients change. We test the server with the MCP SDK's client. If one of the above doesn't work in yours, tell us with report_problem or quivvo feedback.

How do I call it by hand?

A client does this for you. With curl, tools/list gives each tool's argument names, such as cmd for exec and confirm for delete_machine. A request with no protocol version needs no more than the key:

curl -s https://tryquivvo.com/api/mcp -H "Authorization: Bearer $QUIVVO_API_KEY" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"exec","arguments":{"machine":"work","cmd":"uname -a"}}}'

2026-07-28 wants its version in the headers and in params._meta, along with the client's capabilities. Mcp-Method and, for tools/call, Mcp-Name have to repeat what the body says, or the answer is error -32020:

curl -s https://tryquivvo.com/api/mcp -H "Authorization: Bearer $QUIVVO_API_KEY" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' -H 'Mcp-Method: tools/call' -H 'Mcp-Name: exec' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"exec","arguments":{"machine":"work","cmd":"uname -a"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

What tools does it have?

Tool What it does
create_machine Creates a machine and waits until it runs. Takes env, disk_gib, auto_suspend_minutes and delete_after_minutes
list_machines, get_machine Lists machines, or shows one, including how its setup script went and what its last suspend turned into
exec Runs a shell command as root and returns its exit code and output
job_status, job_logs, job_kill, list_jobs Follows, reads, stops and finds commands that are still running
read_file, write_file Reads a file or lists a directory, or writes a text file up to 4 MB
start_machine, stop_machine, restart_machine Boots, shuts down or reboots a machine
suspend_machine, resume_machine Suspends, falling back to a stop when it can't keep memory, or resumes. suspend_outcome and resume_outcome say which happened
fork_machine Copies a machine's disk into new machines. failed lists any that couldn't be made
publish_port, unpublish_port, list_ports Gives a port a public HTTPS URL, or takes it away. The URL serves one port, the last published, and replaced_port says which it stopped serving
save_snapshot, list_snapshots, delete_snapshot Saves, lists or deletes disk snapshots. Deleting needs the exact name to confirm
delete_machine Deletes a machine and its disk; needs its exact name to confirm
get_usage, get_catalog, whoami Shows cost and credit; templates, sizes, regions and prices; what this key may do and its place in the queue
report_problem Tells our team something went wrong

Before anyone signs in, a client can see all of these tools, and the first call to one starts sign-in. After that, an agent only sees the tools its key's scopes allow. A key from agent sign-up can't manage keys or billing.

How do long commands work?

exec always starts a job, so the command outlives the call, and waits for its result:

  • wait_seconds is 50 by default, at most 280, and 0 answers once it has started. Keep it under your client's tool timeout.
  • If it finishes in time, you get its result. If not, you get job_id, the output so far, next_offset, and a next instruction. Don't run it again.
  • timeout_seconds kills it after that long: 3600 by default, at most 86400.
  • If your client hangs up during the wait, the command is stopped with TERM, then KILL, and history marks it cancelled. Otherwise it couldn't be found again, and a retry would run it twice.
  • Output is one merged output field, stdout and stderr together. tail_bytes returns only the end. Pass secrets in env: it stays out of history, and the command text doesn't.

Follow it with job_status (wait_seconds up to 60, default 0), job_logs (offset, tail_bytes, and next_offset to read on) and job_kill (TERM, INT or KILL). list_jobs finds one whose id you lost.

Other long calls, such as create_machine, start, resume, restart, stop, suspend, save_snapshot and fork_machine, answer at about 50 seconds with where things got to and a next field, and the work carries on.

Which tools wake a machine?

exec, write_file, start_machine, resume_machine and restart_machine do. Reading never does, because a tool marked read-only shouldn't start a bill. read_file, job_status, job_logs, list_jobs and job_kill answer machine_not_running on a machine that isn't running, naming the tool to call. This differs from the REST API, where reading files and jobs wakes a machine.

If a machine is still waking, exec and write_file answer machine_not_running with retry_after_seconds and say nothing has run. When one of them did wake it, the answer has resume_outcome, and warnings if it was a cold boot.

How big are the answers?

Default answers stay under 32,000 characters. Long command output keeps its start and end, with [... N bytes omitted ...] where the middle was cut. truncated and more say how to fetch the rest, and lists page with offset. max_bytes and tail_bytes can ask for up to 1 MiB. read_file returns encoding as utf-8 or base64, with size, offset and next_offset.

What do errors look like?

JSON text with isError set, and the same fields as the REST API: code, message, retryable, retry_after_seconds, docs_url, owner_action and request_id. Unknown arguments are refused, naming the valid ones. A workspace waiting in the queue gets queued from create_machine and fork_machine: pass owner_action to your owner, and don't retry.

How does sign-in work?

It's OAuth 2.1 with PKCE (S256 only), for public clients that register themselves. A call with no valid token answers HTTP 401 with WWW-Authenticate: Bearer resource_metadata=..., which leads to the protected resource metadata and then the authorization server metadata. Redirect URIs must be https, or http on localhost, 127.0.0.1 or [::1], where any port works. state is required, and a token is bound to https://tryquivvo.com/api/mcp, so it doesn't work on the REST API.

Which protocol versions does it speak?

Both eras of the protocol share the endpoint: 2026-07-28, where every request carries its own version and server/discover needs no key, and the older initialize handshake from 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05. It's stateless, with JSON responses and no sessions. Its server card says how to connect.

Updated 2 November 2026

Give your agent a computer

npx quivvo@latest sh
Get a free VM