# Ample Computer remote MCP

Endpoint: `https://api.ample.computer/mcp` (Streamable HTTP).
Local clients can continue using `ample mcp` over stdio.

The catalog comes from Clap's public command tree in the same release as the
worker CLI. `ample server list` becomes `ample_server_list`. Adding an ordinary
command or flag requires no MCP schema, routing, or argument mapping. The catalog
is available through `tools/list`, `/mcp/catalog`, or `ample docs mcp`.
`/mcp/server.json` and `ample docs mcp --registry` publish generated registry
metadata, including the catalog digest. Process management (`mcp`, `update`) and
local credential replacement (`auth login`, `auth redeem`) are excluded.
The original seven MCP names and `service_or_path`/`answers` spellings remain.

## Register and authenticate

Every request without a token, the `initialize` handshake and `tools/list`
included, gets 401 with `WWW-Authenticate: Bearer resource_metadata=...` pointing
at `/.well-known/oauth-protected-resource/mcp`. Most MCP clients start OAuth only
when connecting fails, so this is what makes them sign in. The one anonymous call
is `tools/call` of `ample_auth_signup`. MCP clients a person uses (ChatGPT,
Claude, Claude Code, Codex, Cursor, VS Code and other coding agents) sign in with
the browser authorization-code flow; see "Browser sign-in" below.

A signed-in person's prompt from ample.computer carries a connect code
(`ample_connect_...`). Pass it as `connect_code` to `POST /v1/auth/signup` or
`ample_auth_signup` and the credentials are for the person's own account: no
claim URL, no expiry. A code works once, for an hour.

An agent host that provisions and retains its own credentials needs no CAPTCHA,
payment, or browser interaction for a normal first deployment:

Send `POST /v1/auth/signup` with JSON:

```json
{"name":"my-agent","request_key":"<fresh-random-UUID-v4>"}
```

Save the random request key before sending. Reuse it after a disconnect to retrieve
the same encrypted registration receipt for ten minutes. Never share it. The
response includes account identity, free quotas and budget, a claim URL, fixed
account expiry, `access_token`, and `credentials.client_id` / `client_secret`.
The `ample_auth_signup` MCP tool provides the same flow; its `execution_key` must
be a UUID v4 when no explicit `request_key` is supplied. `ample auth signup`
saves the new login on the machine, so tokens renew automatically, and refuses
to replace a local CLI login that still works.

Renew access by sending a form-encoded `POST /oauth/token`:

```text
grant_type=client_credentials
client_id=<issued client ID>
client_secret=<issued secret>
resource=https://api.ample.computer/mcp
```

HTTP Basic client authentication is also supported. Access tokens last at most
15 minutes and are audience-bound. Send `Authorization: Bearer <access_token>` on
every protected MCP/REST request. Existing scoped bearer tokens also work.
Operator/admin tokens are rejected by remote MCP and never enter worker VMs.
Authorization metadata is at `/.well-known/oauth-authorization-server` and
`/.well-known/oauth-protected-resource/mcp`. The optional extension identifier is
`io.modelcontextprotocol/oauth-client-credentials`.

Renewal never extends the account's original unclaimed lifetime (48 hours by
default) or clears suspension. Use the returned claim URL to claim the account.
An expired, deleted or revoked credential gets 401 `invalid_client`; a suspended
account gets 403 `account_suspended`, and its access tokens get the same code on
every request. Signing up again is the fix only for expiry: on
`account_suspended`, stop and tell the owner to contact Ample Computer.
`ample_auth_agent_create/list/revoke` manages agent credentials. Revocation fences
access tokens and descendant credentials, including already admitted workers.

## Browser sign-in

`/.well-known/oauth-authorization-server` also lists `authorization_endpoint`
(`/oauth/authorize`), `registration_endpoint` (`/oauth/register`), PKCE `S256`,
client ID metadata documents and `iss` in authorization responses (RFC 9207).
The person signs in with an emailed code (a new email gets a free account) and
approves; the client exchanges the code with its `code_verifier` and receives a
rotating refresh token. Access tokens are the same audience-bound 15-minute tokens.
The grant is an agent credential named after the client, so `ample auth agent
list/revoke` covers it. Agents that keep their own credentials (above) never need
this flow.

## Panel for app hosts

Hosts that render MCP Apps (ChatGPT, Claude, Codex) can show Ample Computer's panel:
`resources/list` and `resources/read` serve `ui://ample/app`
(`text/html;profile=mcp-app`), and `tools/list` ends with `ample_ui_*` tools that
open it. `ample_ui_open` (apps, one `deployment_id`, or the progress of an
`execution_id` from `ample_deploy`/`ample_up`) is for the model; the others are
the panel's own (`_meta.ui.visibility: ["app"]`). They are read-only, answer
from the API without a worker VM, and need no `execution_key`. Agents can ignore
them; `ample_ui_open` also returns a plain-text summary.

Each tool in `tools/list` carries `securitySchemes` (top level and in `_meta`):
`ample_auth_signup` is `noauth`, everything else `oauth2`.

## Supply code, plan, and deploy

1. Call `ample_workspace_create` with a name and fresh `execution_key`.
2. Populate the returned workspace ID. With a shell and a local project folder,
   call `ample_upload_create` with `workspace` and `archive: true` and run the
   returned `shell` command from the folder: it sends a tar.gz of the folder to
   the one-time link (no credential needed), and Ample drops Git metadata, env
   and secret files, credentials and dependency directories on arrival,
   keeping the workspace's deploy ledger and, when the folder has none, the
   `ample.toml` that `ample_plan` wrote there. `ample_upload_get` with `wait` shows what arrived
   and what was left out. Without a shell, use `ample_workspace_write` (`id`,
   `path`, `text` or `data_base64`), `ample_workspace_upload` (`id`,
   `data_base64` containing a small tar.gz), or `ample_workspace_import` (`id`,
   public HTTPS `url`, optional `reference`).

   To upload and deploy in one step, give `ample_upload_create` `archive`,
   `name` (the app) and `deploy` instead of a workspace: the app keeps its own
   workspace between uploads, so an unchanged folder redeploys as a no-op, and
   `ample_upload_get` with `wait` returns the live URL. Env files never upload
   (Git usually ignores them, and Ample drops any that arrive): if the app
   needs values from a local `.env`, make the link without `deploy`, then call
   `ample_deploy` with the returned `workspace_id`, the same `name` and the
   values as `env`. A folder that does bring a root `.env` is not deployed on
   its own for the same reason.
3. Call `ample_plan` with `workspace_id`, `path: "."`, `write: true`, and a fresh
   `execution_key`. Resolve `needs_input` questions with `answers`, or edit
   `ample.toml` through the workspace file tools.
4. Call `ample_deploy` with the same `workspace_id` and a fresh execution key.
   Read its result: deployment is synchronous. An unchanged redeploy is a no-op.
   If the result says `status: running` (the work outlasted the call's wait of
   about 45 seconds), call `ample_execution_get` with its `execution_id` until
   the result arrives; never deploy again to check.
5. Use `ample_logs`, server/database/app tools, and workspace download as needed.
   Delete the workspace when finished; deployed apps are independent resources.
   Deleting an app also deletes the workspaces that deployed only that app and
   still hold what they deployed (`workspaces_deleted` in the result); one with
   changes that were never deployed stays until it expires (`workspaces_kept`,
   with the reason).

## Change a live app and send files

- `ample_workspace_create` with `from_app` (the app's name, URL or deployment
  ID) fills a new workspace with the source of the app's live release and a
  `.ample/config.toml` naming that app, so `ample_deploy` from it updates the
  same app. Apps whose source Ample Computer did not keep (deployed before source was
  stored) answer `app_source_unavailable` with what to do instead.
- `ample_workspace_write` takes `text`, `data_base64`, or `source_url` (a public
  HTTPS URL the API downloads, up to 16 MiB); `deploy: true` then deploys the
  workspace and returns the deploy's result. `ample_bucket_put` takes the same
  three sources (up to 95 MiB) and stores the object with the bucket's own key;
  `ample_bucket_upload_url` returns a presigned PUT for files of any size.
- When the person has the file and the model cannot pass it, `ample_upload_create`
  (`workspace` or `bucket`, `path`, optional `deploy`) returns an upload link
  valid for an hour: `page_url` is a page where the person drops the file, and
  `upload_url` (the same URL) takes `curl -T`. `ample_upload_get` with `wait`
  (up to 50 s) reports when it arrived and how its deploy went. One file per
  link; a failed file leaves the link usable.
- `ample_file_put` is answered on the API: it takes a file object
  (`download_url`, `file_id`, optional `mime_type`, `file_name`) and puts it into
  an `app` (redeploying it), a `workspace_id`, or a `bucket`. ChatGPT fills the
  object from the person's attachment (`_meta["openai/fileParams"]`); other
  hosts can pass any public HTTPS URL as `download_url`.

Workspace tools use their explicit `id` argument; omit the common `workspace_id`
context for these tools. Other calls use `workspace_id` as their current directory.
Local path arguments must be relative and remain inside it. `ample.toml` and
`.ample` deployment state survive between calls. A manifest can also provision a
managed database through the normal CLI planner/deployer path. Private Git
repositories can be supplied through uploads.

Workspaces allow 32 MiB compressed archives, 128 MiB expanded source, 16 MiB per
file, and 10,000 files, within the account's unchanged storage allowance. Archives
must contain regular files/directories; traversal, links, and special files are
rejected. Git uses public HTTPS/443 only, pinned public IPv4 DNS answers, no
credentials, no redirects, hooks, or submodule fetching. The resolved commit is
recorded. Inactive workspaces expire after seven days; account expiry/deletion
cleans them sooner, and so does deleting the last live app a workspace deployed
when it holds no undeployed changes.

## Where calls run

Commands that only call the API (`API_ONLY_TOOLS` in `commands/mcp.rs`: apps,
servers, databases, volumes, buckets, domains, operations, tokens, workspace file
operations, docs, recipes, and `logs` with an explicit deployment ID and without
`follow`) run without a worker VM when no argument names a local path and no
`workspace_id` context is given. The API host runs the same pinned CLI against
the same public API URL, in an empty directory, under a user of its own, with a
short-lived execution token. They usually answer in about a second. Planning,
deploying and anything that reads project files still run in an isolated worker
VM. Both paths keep the same `execution_key`, result, Tasks, cancellation,
credential-revocation and deadline behavior; a host run interrupted by an API
restart is reported as `execution_interrupted` and never replayed.

## Servers, commands and services

Agents that prefer to set things up themselves can work directly on a server:

1. `ample_server_create` with a systemd template (`ubuntu-24.04`, `node-22`,
   `python-3.12`, …; the minimal `empty` template cannot run services).
2. `ample_server_exec` to fetch and build, for example
   `git clone https://github.com/me/app /srv/app && cd /srv/app && npm ci && npm run build`.
   Commands run as root from `/` and may take up to `timeout_secs` (default 600,
   at most 3600).
   When a command outlives one request (about 50 s), the call answers
   `{"status":"running","poll":...}` and the CLI keeps polling for the result.
3. `ample_service_start` with `server`, `name`, `dir` (absolute, e.g. `/srv/app`),
   `command` (e.g. `npm start`; a web service listens on `$PORT` on 0.0.0.0),
   and `public` for a URL. The command becomes a supervised unit that restarts
   on failure and survives restarts; web services are health-checked
   before they go live. Workers use `kind: worker`. Re-running with the same
   arguments changes nothing.
4. After changing files, `ample_service_restart`; `ample_service_status` shows
   its state and restart count; `ample_logs` reads its output; `ample_service_delete`
   removes it and keeps the directory. A server holds up to three apps and services.

A service is an app deployed from a directory on its server: it appears in
`ample_app_list`, takes custom domains and secrets like any app, and is
delivered by the same deploy pipeline (`service_dir` on the deploy request).

## Results, retries, and cancellation

Each tool result has `structuredContent` containing `exit_code`, `result`, and
`stderr`. Exit 2 with `result.status == "needs_input"` is a normal planning
response. CLI parsing errors are explicitly `invalid_arguments` and `isError`.

Every remote tool call requires an `execution_key`, unique for the intended
action. Reuse the same key and arguments after a disconnect. It returns the
existing execution; a key reused with different arguments is rejected. Use a new
key after source/config edits. There are at most four active executions per
account, one mutating execution per workspace, a one-hour execution deadline, and
bounded output (4 MiB per stream and 3 MiB of serialized result). Completed
results are retained for seven days within a 128 MiB per-account result budget;
expired result keys remain tombstones and never repeat mutations. If saving the
workspace fails, the command's result is still returned; do not repeat a
mutation that succeeded. Results include an `execution_id`; REST
`GET /v1/executions/<id>` also retrieves them.

A `node_capacity_exhausted` refusal means no node could admit a VM at that
moment (a builder, a worker, or a cold boot), not that anything in the source is
wrong. It carries `error.retryable: true`, `error.details.retry_after_seconds`
and, on direct REST calls, a `Retry-After` header. Wait that long and reissue the
same command with a fresh execution key; do not inspect build logs first, since a
refused builder never produced any.

The endpoint speaks MCP `2026-07-28` statelessly (no session, `server/discover`
for version selection) and still accepts the `initialize` handshake of
`2025-11-25` and earlier.

Clients advertising `io.modelcontextprotocol/tasks` receive a task immediately
and retrieve its result through `tasks/get`. Executions never need input, so
`tasks/update` only acknowledges. Other clients wait synchronously for about
45 seconds; work that takes longer answers a non-error `status: running` with
the `execution_id` and a `next_action`, and `ample_execution_get` with that id
waits again and returns the result the first call would have given (or
`running` once more). The work is never repeated or cancelled by this.
Legacy progress notifications are sent when requested. Transport disconnects do
not cancel durable work. Use `tasks/cancel`, `ample_execution_cancel`, or
`POST /v1/executions/<id>/cancel` explicitly. Cancellation stops the worker;
cloud mutations already accepted by the API may have completed. Inspect their
operation/deployment result before retrying.

## A2A

The same tools are available to A2A 1.0 agents over JSON-RPC at
`https://api.ample.computer/a2a`; the agent card is at
`https://ample.computer/.well-known/agent-card.json` (also on the API host).
Each MCP tool is a skill with the same id and arguments. Send one data part,
authenticated with the same bearer token:

```sh
curl -s https://api.ample.computer/a2a -H 'A2A-Version: 1.0' \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{
        "messageId":"<fresh UUID v4>","role":"ROLE_USER",
        "parts":[{"data":{"skill":"ample_server_list","arguments":{}}}]}}}'
```

The `messageId` is the execution key: resend the same message after a
disconnect and you get the same task. `SendMessage` waits up to 45 seconds and
returns the task (`TASK_STATE_WORKING` if it is still running; add
`"configuration":{"returnImmediately":true}` to return at once). Poll
`GetTask` with the task `id`, stop it with `CancelTask`. The result is the
task's single data artifact, identical to the MCP `structuredContent.result`.
The `ample_auth_signup` skill needs no credentials; use a UUID v4 `messageId`.
Each message is one request: follow a running task with `GetTask`, and send
the next request as a new message. A message with no data part gets a text
reply explaining this format.

## Accounts and limits

Every call runs as the calling account, whether it arrives over the CLI, MCP or
REST: ownership, quotas and the account's spending cap apply the same way, and
builders and workers act for the account that started them.

Protocol references: [Rust SDK](https://docs.rs/rmcp/3.4.0/rmcp/),
[Tasks](https://modelcontextprotocol.io/extensions/tasks/overview),
[client credentials](https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials),
[A2A 1.0](https://a2a-protocol.org/v1.0.0/specification).

## Transactional app email

Active paid subscriptions can send 100 recipient attempts per account per UTC month
from each app’s ample.run domain. Use `ample email enable --app <id>`, inspect
`ample email status` (`can_send` false means `blocked_by` and `next_action` say
what the person must do, such as claim or upgrade; tell them before building),
then deploy (use `--force` for unchanged source) to bind
AMPLE_EMAIL_URL, AMPLE_EMAIL_TOKEN and AMPLE_EMAIL_FROM at runtime. Keep these
server-side. Templates are app-owned HTML and text; use `ample email preview`
before a requested live send. Free accounts can capture previews. Sending
addresses are not inboxes; configure reply-to for replies.

Use one stable idempotency_key per logical send. Accepted is not inbox delivery;
inspect unknown outcomes instead of retrying with a new key. Quota exhaustion
is a hard stop. Disable revokes the app token; rotation requires redeployment.
Read https://ample.computer/guides/send-app-email for the HTTP example and limits.
