envend
reference

MCP tools

Thirteen tools, served at /mcp over streamable HTTP. What follows is what the gateway tells a model, in the gateway's own words — this page is a transcription, not a summary.

The instructions that travel with them

An MCP client is handed these before any tool description. They are the five things that change what a model does.

  1. Work in /work. It means the same thing on every backend: it is the sandbox's own storage and it is what the file share serves. Treat it as home — clone into it, build in it, write output to it. What happens to the rest of the filesystem differs by backend, so env_create says which kind this one is, in a sentence, and env_list says it for every sandbox that already exists.
  2. Every env_exec is a fresh bash -lc shell. Nothing carries over between calls except the filesystem — not the working directory, not exported variables, not an activated virtualenv. Chain steps with && in one call, or use absolute paths.
  3. There is also a real ssh way in. env_create returns a ready-to-run ssh command backed by a key minted for that sandbox alone. If you have a shell tool of your own, prefer it for anything env_exec is bad at: interactive commands, file transfer, port forwarding, or a long stream of output.
  4. Sandboxes are cattle, not pets. If one gets wedged, do not debug it. Destroy it and create another; it takes seconds.
  5. Some backends stop an idle sandbox; some cannot. One that sleeps wakes again on the next exec or ssh connection, and one that cannot runs — and bills — until it is destroyed. The two mistakes are leaving a machine running for a week and losing a build to a wake you did not expect.

env_create

Create a disposable Linux environment (Ubuntu 24.04, root, network access) and wait until it is ready to run commands. Fails if the name is already taken.

The short way is profile: a named shape this gateway already knows, which supplies the image, the size, the identities and the workspace, and generates the name. env_create(profile: "omega") gives you an environment its own profile has already set up — a checkout, a container, whatever that profile says — with commands from then on running where the profile says they should. Anything you pass alongside a profile wins over what it says.

That setup is not finished when the call returns. It runs inside the machine and takes as long as a clone and a container build take, which is minutes on a first boot. Call env_logs with the name you were given and keep calling it until it says ready. Working before then is working in a directory that is still being written.

It comes with storage mounted at /work, sized with the environment. The answer says in one sentence what survives on this backend — whether the whole disk persists, or only /work — and whether anything will ever put it to sleep. Read those two sentences: they are the whole of the difference between backends, and getting them wrong costs either a build or a week of billing.

A sandbox never has a public port, and there is no option here that changes that. The answer also includes an ssh command and a mount command, each with a key minted for that sandbox alone. Both wake a sleeping sandbox, so either can be pasted without knowing what state the machine is in. Mounting takes about twelve seconds from cold.

ArgumentMeaning
nameShort name you will use to refer to this environment. Lowercase letters, digits and hyphens, up to 31 characters. Required unless you give a profile, which generates one.
profileA named shape of environment this gateway already knows. Everything you do not pass comes from it, everything you do pass wins over it, and the name is generated as <profile>-<four characters>. Naming an unknown profile lists the ones that exist.
providerWhich backend to build it on. They are the same computer from in here, and they differ in what happens when nobody is using one. Naming an unknown backend lists the ones that exist. Leave this out unless you have a reason.
sizes, m, l or xl — roughly 1 cpu / 1 GB, 2 cpu / 2 GB (the default), 4 cpu / 8 GB, 8 cpu / 16 GB. What each letter actually buys is the backend's own table, and not every backend offers all four. Pick xl for a large monorepo: a full build and its test suite.
imageAn OCI image to boot instead of the default, e.g. node:22. The gateway wraps it with envd at the registry — nothing in the image changes except its entrypoint — and runs the image's own command as a supervised service. A backend that does not boot OCI images refuses this field with a sentence rather than quietly ignoring it.
identitiesWho this environment is allowed to be elsewhere: an AWS role, a Doppler service-account identity, a set of GitHub repositories. Nothing here is a secret: it names what to federate to, and the credentials are minted inside the environment, per use, and expire.
servicesUp to four long-lived processes, each {name, cmd, port, dir}. They start at boot, restart if they crash, and come back automatically after every sleep. A port here publishes nothing; env_share does that.

env_list

List every environment with its id, state, which backend it runs on, size, region, how long it has been idle, the profile it came from, what its workspace is doing, the services it was declared to run, and any active hold with its reason and expiry. Start here if you are unsure what already exists.

Each row also carries what its backend keeps and whether it will ever stop — "whole disk persists; never sleeps", say, or "/work persists, root rebuilt on wake; sleeps when idle". That is the one thing that differs between backends and the one thing worth checking before you leave work outside /work or leave a machine running.

A workspace line says how far the environment's own agent has got with its setup: creating, starting, ready, or failed with the first line of what went wrong. No arguments.

env_logs

Watch an environment set itself up: what its create and start hooks have printed, and whether they are still running. Call this after env_create on any environment that came from a profile, and keep calling it until it says ready.

Every answer ends with next since: <N>. Pass that back as since on the next call and you get only what has been printed since, so polling costs nothing and repeats nothing. Leave since out and you get this boot's output from the start — which is what you want on a first call, and after a wake, when the start hook runs again.

An environment that declares no workspace says so and is ready to use now. A failed workspace is not a broken machine: the VM is fine, and env_exec with host: true reaches it to look at what went wrong.

ArgumentMeaning
env (required)Environment name from env_create.
sinceByte offset to read from — the next since printed by a previous call. Omit for this boot's output from the beginning.

env_exec

Run a shell command inside an environment and return its exit code, stdout and stderr.

Each call is a fresh bash -lc shell: nothing carries between calls except the filesystem. Chain dependent steps in one command — cd /work/app && npm install && npm test — rather than issuing them separately.

A sleeping environment is woken automatically (about two seconds). What comes back with it depends on the backend, but /work is always as you left it.

For work that outlives the timeout, background it and poll — nohup make build > /work/build.log 2>&1 &, then tail -50 /work/build.log later. Call env_hold first so the idle sweep does not stop the machine while the job runs unattended. Output is capped at the last 8000 characters per stream, so pipe verbose commands through tail or grep.

ArgumentMeaning
env (required)Environment name.
cmd (required)Shell command, run via bash -lc.
hostRun on the VM rather than through the workspace's exec prefix. Use it for questions about the machine — docker ps, disk space, the hook log — not for the repo's own commands.
timeout_secHow long to let the command run before giving up. Default 60, maximum 3600.

env_sleep

Stop an environment now to save money. The next env_exec wakes it automatically, and so does anything going through the tunnel — an ssh, a mount — so this is cheap and nothing you have already set up needs redoing. Environments on a backend that can sleep are also stopped automatically once idle.

Not every backend can stop a machine without destroying it. On one that cannot, this refuses and says so, and the environment runs until it is destroyed. What survives a sleep depends on the backend, and the answer says which happened. Takes env.

env_destroy

Permanently delete an environment: the machine, the storage holding its /work, its keys, and every share and file link it had. Use this when a task is done, or when a machine is wedged and creating a fresh one is faster than debugging it. There is no undo, and unlike env_sleep this does take /work with it. A shared URL and a file link both stop working immediately. Takes env.

env_hold

Exempt an environment from auto-sleep for a bounded window. Call this before starting anything long and detached — a background build, a running server, a job you will poll — because the idle timer measures gateway traffic, not work happening inside the machine.

Holds always expire; they cannot be made permanent. The reason is shown in env_list so a human can tell a deliberate hold from a forgotten one.

ArgumentMeaning
env (required)Environment name.
reason (required)Why it must stay awake, e.g. "running a 40-minute kernel build".
ttl_minutesHow long the hold lasts. Default 60, maximum 720 (12 hours).

env_release

Release a hold early and hand the environment back to the idle sweep. Do this as soon as the long job finishes rather than waiting for the hold to expire. Takes env.

env_token

Mint a short-lived, audience-bound OIDC ID token proving an environment's identity — the first piece of workload identity: an environment authenticating to something with no long-lived secret baked into it.

Debug path only. Production is envd inside the environment fetching its own token over its tunnel to the gateway, so the token never leaves the machine. This tool is the other route: it delivers the token through MCP, into whatever is reading the result — almost always a language model's context window. That is not a secure channel. Use it to check that the issuer signs, that the token verifies against the published keys and that the claims look right; not to run anything real.

ArgumentMeaning
env (required)Environment name.
audience (required)The single relying party this token is for, e.g. doppler or an AWS role ARN. One audience per token — that is what lets the relying party's trust policy pin it.
ttl_secondsHow long the token is valid. Default 300, maximum 900.

env_share

Put a server running inside an environment on a public URL, so a person can open it in a browser. This is the only way anything inside a sandbox becomes reachable from outside; there is no create-time option that publishes a port, on any backend.

The environment itself never gets a public port, not even while it is shared. Nothing is opened on the machine and nothing new starts inside it: the gateway dials the sandbox over an encrypted tunnel that only it is allowed to use, and forwards each request through, terminating TLS at the gateway. So env_unshare takes it away instantly and completely — including from anyone who already has the URL.

The URL is public. Anyone who has it reaches whatever is listening on that port; there is no login in front of it except the app's own.

The environment does not have to be awake — a request for a sleeping one wakes it and waits. But only if something is still listening after the wake, which means the server should be a declared service rather than something you started by hand. Only environments created with this gateway's key can be shared; an older one says so and has to be recreated.

ArgumentMeaning
name (required)Environment name.
port (required)The port your program is listening on inside the environment, 1024–9999. Not a port on your machine, and not one you have to publish first.

env_unshare

Stop serving a shared port, which is how a sandbox goes back to having nothing public at all. The URL answers 404 from that moment, for everybody, including anyone who already had it — there is nothing cached and nothing to expire. The environment and whatever was listening on the port are untouched. Takes name and port.

env_link

Turn one file in an environment's /work into a link you can send somebody. This is how you hand a file to a person: a coverage report, a rendered page, a screenshot, a PDF. They open the URL on any device — no envend account, no key, nothing to install. It is far better than pasting the contents back through the conversation, which for anything but a short text file wastes an enormous amount of context and mangles binary content completely.

Links expire, by default after an hour. They are signed, so the environment, the path and the expiry cannot be edited by whoever holds the link — but until it expires, anyone holding it can open the file, so treat it like a link to a private document. env_revoke_links kills every link for an environment at once. The environment never gets a public port for this either: the gateway reads the file over the same private tunnel and streams it out.

ArgumentMeaning
name (required)Environment name.
path (required)Path to the file, relative to /work or absolute under it. Nothing outside /work can be linked.
ttl_secondsHow long the link works. Default 3600, maximum 604800 (seven days). Prefer short: a link is cheap to make again.

env_revoke_links

Invalidate every file link ever issued for an environment, at once. Use it as soon as a link has gone somewhere it should not have, or when a shared file should stop being readable before its link expires. Links made afterwards work normally. Shared ports are unaffected — env_unshare is the equivalent for those. Takes name.