Use froog as an MCP server
froog is also an MCP server, so Claude Code, Codex, Cursor, Claude Desktop and any other MCP client can create, run, fork and snapshot sandboxes as tools, without shell commands. There are two ways to connect, with the same tools:
- Local:
froog mcpruns the server on stdio, signed in like the CLI (froog login, orFROOG_API_KEYin its environment). - Hosted:
https://api.froogcorp.com/mcp(Streamable HTTP) with an API key in the headerAuthorization: Bearer froog_…, or, for clients that sign in through the browser (Claude's custom connectors, ChatGPT), OAuth: see Connect with a sign-in. Nothing to install.
Claude Code
$ claude mcp add froog -- froog mcp
# or hosted:
$ claude mcp add --transport http froog https://api.froogcorp.com/mcp --header "Authorization: Bearer $FROOG_API_KEY"
Add --scope user to have it in every project.
Codex
$ codex mcp add froog -- froog mcp
Hosted, in ~/.codex/config.toml (the key is read from FROOG_API_KEY):
[mcp_servers.froog]
url = "https://api.froogcorp.com/mcp"
bearer_token_env_var = "FROOG_API_KEY"
Cursor: in ~/.cursor/mcp.json (or .cursor/mcp.json in a project), one of:
{ "mcpServers": { "froog": { "command": "froog", "args": ["mcp"] } } }
{ "mcpServers": { "froog": {
"url": "https://api.froogcorp.com/mcp",
"headers": { "Authorization": "Bearer ${env:FROOG_API_KEY}" } } } }
Claude Desktop: Settings, Developer, Edit Config, then in claude_desktop_config.json (Claude Desktop doesn't see your shell's PATH, so give the full path that which froog prints):
{ "mcpServers": { "froog": {
"command": "/Users/you/.local/bin/froog",
"args": ["mcp"],
"env": { "FROOG_API_KEY": "froog_…" } } } }
Any other client: run froog mcp as a stdio server, or point it at https://api.froogcorp.com/mcp with the Authorization header.
Connect with a sign-in (OAuth)
Claude (claude.ai and Claude Desktop: Settings, Connectors, Add custom connector), ChatGPT and other clients that can't send a header connect to https://api.froogcorp.com/mcp with just the URL. froog follows the MCP authorization spec (OAuth 2.1 with PKCE):
- The client gets
401withWWW-Authenticate: Bearer resource_metadata="https://api.froogcorp.com/.well-known/oauth-protected-resource/mcp", reads that and/.well-known/oauth-authorization-server, and registers itself (POST /oauth/register, dynamic client registration, public clients only, redirect URIs over https, tolocalhost, or an app's own scheme). - Your browser opens froog's sign-in (Google, as on the dashboard), then a page that names the client and where it sends you back, and asks what it may do:
ci(the default: create sandboxes, run commands, stop and start them, delete the ones it created),full-sandbox(use existing sandboxes only) orread-only. - On Allow, the client gets an access token: a new API key with that preset, labeled
mcp: CLIENT NAME, that expires in 30 days. It's infroog api-key listand the dashboard like any key. Revoke it there (or the client callsPOST /oauth/revoke). Its creation is in the audit log. There are no refresh tokens: after 30 days the client asks you to sign in again.
A malformed authorization request (a bad response_type, no PKCE, another resource) shows an error page. froog sends the browser back to the client's redirect URI only after you answer the consent page (with access_denied on Deny). API keys keep working as bearer tokens on the same URL. Self-hosted control planes turn OAuth on with FROOG_SERVER_MCP_OAUTH=on (it needs Google sign-in set up). While it's off, the OAuth URLs answer 404.
| Tool | Does |
|---|---|
list_sandboxes | Sandboxes with state, size, OS and labels. Filter by label, state, name_prefix. |
create_sandbox | A new sandbox (name, size, os, from a saved snapshot, env, labels, ttl_secs, setup). Waits until it runs. |
sandbox_info | One sandbox in full. |
exec | Run a bash command (command, cwd, env, timeout_secs up to 3600). Returns the exit code, stdout and stderr. |
start_sandbox, stop_sandbox | Start (waits until it runs) or stop (keeps the disk). |
delete_sandbox | Delete for good. Needs confirm: true. |
fork_sandbox | Copy a sandbox, or one of its snapshots, into new_name. Waits until the copy runs. |
save_snapshot, list_snapshots | Save a snapshot (with a label, a template for create_sandbox from). List a sandbox's snapshots, or the account's saved ones. |
read_file, write_file, list_dir | Files in a running sandbox: text as is, binary as base64. |
host_port | An HTTPS URL for a port (private with a token unless public). |
desktop_link | A 10-minute link to watch and use the desktop. |
prompt_agent | Run a coding agent inside the sandbox on a prompt (agent, model, effort) and return its answer. |
start_job | Start a bash command in the background (command, cwd, env), like froog exec --detach. Returns the job id at once. |
list_jobs, job_logs, kill_job | A sandbox's background commands. One job's state, exit code and output from offset (pass the returned next_offset to read only what's new). Stop one. |
create_sandbox,fork_sandboxandstart_sandboxwait up to 120 seconds (wait: falsereturns at once).execandread_filereturn at most 256 KiB of output (the end of a long output, the start of a long file).- A failed call is a tool error whose text is the API's error,
{"error":{"code":…,"message":…,"request_id":…}}, so the agent sees why (not_found,api_key_action_forbidden, a full plan) and you can quote the request id. - Give the agent a scoped key. The tools obey the key's scope exactly like the HTTP routes they stand for: with
froog api-key create agent --preset ci, an agent can create sandboxes and delete only the ones it created. With--preset read-only, it can only look. - Output as it runs. An
execcall that sends_meta.progressTokengets its output asnotifications/progresswhile the command runs (progress: the bytes so far,message: the new output), then the usual result.froog mcpwrites them on stdout. The hosted endpoint answers that call as server-sent events when the client acceptstext/event-stream. - The hosted endpoint is stateless: each
POSTgets one JSON answer (or, for a call with a progress token, one event stream that ends with the answer), with no session id (GETanswers 405). It speaks MCP 2025-06-18, 2025-03-26 and 2024-11-05. Tool calls count toward the same per-IP request limit as the API.