@moor-sh/mcp
v0.28.0
Published
MCP server for moor - lets AI agents (Claude Code, Cursor, etc.) manage your moor projects via the moor HTTP API.
Readme
@moor-sh/mcp
MCP server for moor - lets AI agents (Claude Code, Cursor, etc.) manage your moor projects through standard MCP tools. Talks to moor's HTTP API; no repo clone needed.
Requires Bun on the machine running the MCP client. bunx fetches and runs @moor-sh/mcp directly as the client's MCP subprocess.
Setup
The easiest path is the moor mcp config subcommand from @moor-sh/cli:
bunx @moor-sh/cli mcp config --client claude # or claude-code, codexIt prints a config snippet you paste into your MCP client. Or configure manually:
Claude Code (~/.claude.json)
{
"mcpServers": {
"moor": {
"command": "bunx",
"args": ["@moor-sh/mcp"],
"env": {
"MOOR_URL": "http://127.0.0.1:8080",
"MOOR_API_KEY": "your-api-key"
}
}
}
}Codex (~/.codex/config.toml)
[mcp_servers.moor]
command = "bunx"
args = ["@moor-sh/mcp"]
[mcp_servers.moor.env]
MOOR_URL = "http://127.0.0.1:8080"
MOOR_API_KEY = "your-api-key"For a moor on the same machine as the client, change MOOR_URL to http://localhost:3000.
SSH tunnel for remote moor
By default moor's admin is bound to 127.0.0.1:3000 on the server. The MCP client connects to whatever MOOR_URL resolves to on the client machine, so for a remote moor, open a tunnel from your laptop:
ssh -fNL 8080:127.0.0.1:3000 your-serverMOOR_URL=http://127.0.0.1:8080 matches the laptop side of that tunnel. The tunnel must stay up while the MCP client is in use. For a tunnel that survives sleep and reboots, see the self-hosting guide.
API key
MOOR_API_KEY grants admin-equivalent control of the moor host. See the self-hosting guide for how to generate, verify, and rotate it.
Smoke test
Before relying on the integration:
MOOR_URL=http://127.0.0.1:8080 MOOR_API_KEY=your-api-key bunx @moor-sh/mcp < /dev/nullExit 0 with no output means the MCP connected, authenticated, and shut down cleanly when stdin closed. Any stderr line plus non-zero exit tells you what's wrong:
Cannot reach moor at ...- URL unreachable or tunnel is down.Authentication failed-MOOR_API_KEYdoesn't match the server.moor at ... returned 503- admin password not configured on the moor server.
Tools
This is the full reference. For the ~10 tools an agent actually needs for the core loop, see the recommended agent workflow.
The table below is generated from the tool registrations. Do not edit it by hand; run bun run docs in this package to regenerate.
The server registers 53 tools. Regenerate this section with bun run docs after changing any tool.
Projects
| Tool | Title | Description |
| --- | --- | --- |
| moor_status | List Projects | List all projects managed by Moor. status is moor's recorded state (only changes on explicit start/stop/build/cancel). live_status is Docker's view at last successful inspect; differences (e.g. recorded='running' live='error') mean moor missed an external change like a host docker stop, crash, or OOM kill. live_error non-null means the most recent inspect failed and the live_* values are the last successful snapshot, not necessarily current. |
| moor_project_get | Get Project | Returns the full record for a project (source, branch, dockerfile, domain, status, container id, restart policy). |
| moor_project_create | Create Project | Creates a new project. Provide exactly one of github_url or docker_image. Does not build or start; call moor_rebuild to bring it up, or use moor_deploy to create and start in one step. |
| moor_project_update | Update Project | Updates project metadata. Does NOT rebuild or restart the container. Domain or domain_port changes apply to Caddy immediately. Resource-limit changes (memory_limit_mb, cpus) take effect on the next container recreate (moor_rebuild / moor_restart / moor_deploy / moor_project run) — an already-running container keeps its existing limits. |
| moor_project_delete | Delete Project | Stops and removes the container, then deletes the project record. Requires confirm_name to match the resolved project name exactly. Irreversible. Named Docker volumes are preserved by default (data survives so a recreated project can remount them); pass purge_volumes: true to also delete the underlying Docker volumes — that deletion is also irreversible. |
| moor_deploy | Deploy Project | Create-or-update a project end to end: metadata, env vars (merged into existing), and an optional build/run. Default fails if the project already exists; pass update_existing: true to upsert. When run: true (default), waits for the full Docker build/pull and start, which can take minutes for large images. Errors are tagged by the failing step ([create], [update], [set_env], or [run]) and do not roll back earlier steps. |
Deployments & runs
| Tool | Title | Description |
| --- | --- | --- |
| moor_logs | Get Container Logs | Get recent logs from a project's container. Annotates output with state: ok (container running), exited (container is stopped but Docker still has logs), no_container (project never started), or missing (container_id is set but Docker doesn't have it). Throws only on docker_error (Docker daemon 5xx / unreachable) so an operator can distinguish infrastructure failure from app silence — pre-#74 the tool returned empty logs for all of these. |
| moor_rebuild | Rebuild Project | Rebuild a project from source (git pull + docker build) and restart the container. Returns the build output when it finishes. While a build is in flight, the most recent moor_runs entry has finished_at=null — call moor_run_get on its id to tail the live output. Use moor_rebuild for code, Dockerfile, or base-image changes. For env vars / resource limits / port / volume / restart-policy changes, or to recover a crashed container from the existing image, use moor_restart — it skips the build and is much faster. |
| moor_restart | Restart Project | Stop and recreate a project's container from its existing image. Does NOT pull from git or rebuild — uses the existing image_tag. Right tool for: applying changed env vars / resource limits / ports / volumes / restart policy, recovering a crashed container, or simply bouncing the process. Wrong tool for: code or Dockerfile changes (use moor_rebuild — those need a new image). |
| moor_runs | List Project Run History | Paginated list of cron runs and build runs for a project. Returns one compact line per run (id, type, status, exit code, duration, output byte counts, timestamps) — stdout/stderr bodies are NOT included to avoid blowing token budgets on large build outputs. Use moor_run_get(run_id) to fetch the stored output for a single run (cron rows store full output; build/manual rows store at most a 64 KiB tail with the original total bytes recorded separately). |
| moor_run_get | Get Run Detail | Fetch one cron or build run with its stdout and stderr. Output is tail-truncated (default 8 KiB per stream; max 65536) to keep responses under typical agent token limits. Use tail_bytes=0 for metadata-only. |
| moor_run_stop | Stop or Cancel a Run | Stops an active cron run or cancels an active build/pull run (from moor_rebuild / moor_deploy). Closing the connection to the Docker build/pull endpoint aborts the daemon-side job. Cancellation is only valid during the build/pull streaming phase — once the build finishes and container start has begun, the call returns not_cancellable. Returns one of: cancelled, cancelled_cron, not_cancellable, already_finished, not_active, not_found. These are all expected outcomes, not errors — the tool throws only on unexpected server failures. |
Exec & terminal
| Tool | Title | Description |
| --- | --- | --- |
| moor_exec | Execute Command | Run a shell command inside a project's running container. Bounded by a per-call timeout (default 10 min, max 1 h). For jobs that may exceed an hour, use moor_exec_async. |
| moor_exec_async | Start Async Exec | Run a long-lived command inside a project's container, returning immediately with a run_id. Use moor_exec_status to poll for output and exit code; moor_exec_stop to terminate. Bounded by an optional timeout_ms (default 86400000 = 24h; min 60000 = 1 min; max 86400000). The recorded output is tail-truncated to the last 64 KiB per stream; stdout_total_bytes and stderr_total_bytes report the full pre-truncation byte count. |
| moor_exec_status | Get Async Exec Status | Return the current state of an async exec run: state, exit code (when finished), running tail of stdout/stderr (default 8 KiB each inline; the API stores up to 64 KiB), total bytes seen, duration, and any error message. State is one of: running, exited, stopped, timed_out, error. Pass tail_bytes to control how many bytes of each stream are returned inline (0 to 65536; default 8192). The API's 64 KiB-per-stream storage cap is unchanged — tail_bytes only controls what the MCP tool returns to keep responses under typical agent token limits. |
| moor_exec_stop | Stop Async Exec | Terminate a running async exec by run_id. Walks the descendant process tree inside the container and sends SIGTERM then SIGKILL. Always transitions the run to a terminal state: state=stopped on clean termination (all descendants gone), state=error if any descendant survived OR if the kill handle was lost (moor restart, missing pidfile). Stop is NOT retry-safe — the kill script removes the pidfile after every attempt, and reparented survivors are unreachable from the original PID. |
Environment, cron, volumes & files
| Tool | Title | Description |
| --- | --- | --- |
| moor_env_list | List Environment Variables | List all environment variables set for a project. |
| moor_env_set | Set Environment Variables | Set environment variables for a project. Merges with existing vars. Automatically restarts the container if running. |
| moor_cron_create | Create Cron | Creates a cron schedule on a project. Schedule is a 5-field crontab string with numeric values only (no jan/sun/etc.). Day-of-week uses 0=Sunday through 6=Saturday; 7 is not accepted. timeout_ms defaults to 10 minutes and supports up to 7 days. |
| moor_cron_update | Update Cron | Updates a cron's fields by id, including timeout_ms. Schedule and timeout are validated if provided. |
| moor_cron_delete | Delete Cron | Deletes a cron by id. |
| moor_cron_run | Run Cron Now | Triggers a cron to run immediately. Requires the project's container to be running. |
| moor_env_delete | Delete Environment Variables | Removes one or more environment variables from a project. Restarts the container only if at least one key was actually deleted AND the project was running. |
| moor_volume_list | List Project Volumes | List the named Docker volumes attached to a project. Each entry includes the logical name (per-project handle), the in-container target path, and the actual Docker volume name (for docker volume ls / docker volume inspect outside moor). |
| moor_volume_add | Add Project Volume | Attach a named Docker volume to a project. The volume is created lazily by Docker on first container start; moor stores the mount config (logical name, in-container target, and the generated docker_name like moor--). Takes effect on container recreate (next moor_rebuild / moor_restart / moor_deploy / moor_project run) — already-running containers keep their existing mounts. |
| moor_volume_remove | Remove Project Volume Mount | Detach a named volume from a project's mount config. The underlying Docker volume (and its data) is intentionally preserved — to actually delete the data, use moor_project_delete with purge_volumes:true, or run docker volume rm <docker_name> manually. Takes effect on next container recreate. |
| moor_file_set | Set Project File | Declare a file to inject into a project's container. moor writes it via a tar archive PUT right before the container starts, on every recreate, honoring the octal mode (e.g. 0600 for a TLS key). Identified by path — setting the same path again updates its content/mode rather than duplicating. Provide exactly one of content (inline) or env_ref (the name of a project env var to source content from at create time, so a secret stays in the env store instead of plaintext here). Takes effect on next container recreate (moor_rebuild / moor_restart / moor_deploy / moor_project run). |
| moor_file_list | List Project Files | List the declarative files configured for a project. Each entry shows the in-container path, octal mode, and how content is sourced (inline or env). Raw inline content is never returned (it may be large, and env-sourced content lives in the env store). |
| moor_file_remove | Remove Project File | Remove a declared file from a project's injection set. The file stops being written on future container recreates; a copy already present in a running container is not deleted until the next recreate. Takes effect on next container recreate. |
Credentials & DNS
| Tool | Title | Description |
| --- | --- | --- |
| moor_dns_check | Check Domain DNS | Resolves a domain's A record and reports whether it matches the server's public IP. Useful before pointing a project's domain at the server. |
| moor_registry_credentials_list | List Registry Credentials | List all stored Docker registry credentials. Returns metadata only - the raw secret value is never returned by any read path. Each row carries secret: { configured: true, kind } where kind is derived from known token prefixes (github_classic_pat, github_fine_grained_pat) or 'unknown'. |
| moor_registry_credential_get | Get Registry Credential | Get a single stored registry credential by id. Returns metadata only - the raw secret is never returned. Use this before moor_registry_credential_delete to confirm the hostname you intend to delete. |
| moor_registry_credential_add | Add Registry Credential | Store a credential for a Docker registry. The pull path will attach X-Registry-Auth to /images/create whenever an image ref matches this hostname. Hostname must be the bare host as it appears in the image ref (e.g. ghcr.io, docker.io, localhost:5000) - no scheme, no path. Note: the secret value passes through the MCP client and tool-call transport, same security model as moor_env_set; rotate via moor_registry_credential_update if it has been exposed. |
| moor_registry_credential_update | Update Registry Credential | Rotate username and/or secret on an existing credential. Hostname is intentionally not patchable - changing the lookup key on an existing row would silently break the pull path. To change hostnames, delete and re-create. Requires at least one of username or secret. Note: the secret value passes through the MCP client on input - same security model as moor_env_set. |
| moor_registry_credential_delete | Delete Registry Credential | Delete a stored registry credential. Requires confirm_hostname to match the resolved row's hostname exactly - guards against deleting the wrong row from a stale id. After deletion, pulls for that registry fall back to anonymous. Irreversible. |
| moor_source_credentials_list | List Source Credentials | List all stored Git source credentials (HTTPS PATs). Returns metadata only - the raw secret value is never returned by any read path. Multiple credentials can share a hostname (e.g. two github.com rows for different orgs); use label to disambiguate. |
| moor_source_credential_get | Get Source Credential | Get a single source credential by id. Returns metadata only. Use this before moor_source_credential_delete to confirm the label you intend to delete. |
| moor_source_credential_add | Add Source Credential | Store a Git source credential (HTTPS PAT) for a private repo host. v1 supports HTTPS PATs only; SSH deploy keys may be added in a future version. Hostname must be the bare host as parsed from a Git URL (github.com, gitlab.com, etc.) - no scheme, no path. Multiple credentials can share a hostname; the (hostname, label) pair is unique. For GitHub: use a fine-grained PAT with Contents: read (username x-access-token), or a classic PAT with repo scope. Note: the secret value passes through the MCP client and tool-call transport on input, same security model as moor_env_set. |
| moor_source_credential_update | Update Source Credential | Rotate username, secret, label, or expires_at on an existing source credential. Hostname is intentionally not patchable - changing the lookup key on an existing row would silently break in-flight builds. To change hostname, delete and recreate. Requires at least one of username, secret, label, or expires_at. |
| moor_source_credential_delete | Delete Source Credential | Delete a stored source credential. Requires confirm_label to match the resolved row's label exactly - protects against deleting the wrong credential on a host that has several (e.g. two github.com rows). Refused with credential_in_use if any project still references this credential. Irreversible. |
| moor_source_credential_check | Check Source Credential Access | Run a real git ls-remote against the repo URL to verify access. Without source_credential_id, probes anonymously first; if private and exactly one credential matches the host, auto-selects it. With source_credential_id, tests that exact credential. With branch, tests the specific branch (branch_not_found is distinct from auth failure). Discovers default_branch via HEAD symref when no branch is provided. Side effect: updates last_checked_at and last_check_status on the credential row; flips state to failed on a credentialed rejection. |
Server & observability
| Tool | Title | Description |
| --- | --- | --- |
| moor_stats | Server Stats | Get server resource usage: load, memory, per-filesystem disk usage (the filesystems the moor container can see, plus any operator-configured monitored host disks via MOOR_MONITORED_DISKS), Docker disk by category (images/containers/volumes/build cache) with reclaimable bytes, and container counts. Note: cpu.percent is load-derived (load avg ÷ cores), not instantaneous CPU; use the load field for the same signal with explicit naming. |
| moor_drain_status | Drain Status | Read-only: current drain state (enabled, reason, expires_at, clear_after_version) plus counts of active work the operator should wait on before an update. active_work uses the same counter as moor_update_status so the two never disagree. |
| moor_drain_enable | Enable Drain Mode | Refuse new builds, deploys, execs, manual cron runs, and terminal upgrades with a 503 carrying { reason, expires_at, hint }. Existing in-flight work runs to completion — drain does NOT kill anything. Scheduled cron ticks during drain write a synthetic 'skipped due to drain' run row instead of executing. Read-only routes (status, logs, runs) keep working. Default TTL is 30 minutes; set ttl_minutes to override. clear_after_version is the updater's hook — when set, the drain auto-clears on boot if the running moor version matches. |
| moor_drain_disable | Disable Drain Mode | Explicit operator action to clear drain immediately. Does not kill or restart anything — just removes the gate so new builds/deploys/execs/cron triggers/terminal upgrades succeed again. |
| moor_db_backup | DB Backup (snapshot) | Take a SQLite snapshot of moor.db via VACUUM INTO. The file lands next to the main DB as moor.db.backup-. Retention is enforced after each snapshot (keeps the 7 most recent by default; older ones are pruned). After this returns, moor_update_status' db_backup.age_seconds will read close to 0. Use before a manual docker compose pull moor && up -d if you don't have MOOR_DB_BACKUP_INTERVAL_HOURS scheduled. |
| moor_project_stats | Project Container Stats (live) | Live container stats for one project: CPU percent, memory (excluding page cache, same accounting as docker stats), network and block I/O totals, PID count. Single Docker stats snapshot — CPU uses the cpu_stats/precpu_stats delta the daemon already includes. Stopped or never-started projects return running=false with zeroed counters (no 404). |
| moor_project_history | Project History (stored) | Stored resource history + lifecycle events for one project over a time window — answers 'what was going on with this project around this case?' (NOT live: use moor_project_stats for a current snapshot). Resource samples are taken ~every minute; CPU is averaged across each interval and network/block reported as rates, both computed from raw counters and reset-aware. Events come from the Docker event stream (start/die/oom/kill/restart) and moor's own state changes. Window defaults to the last hours (24); pass from_ms/to_ms (epoch ms) for an exact window. A gap warning means events may be incomplete in that window. |
Self-update
| Tool | Title | Description |
| --- | --- | --- |
| moor_update_status | Update status / preflight | Report moor's current version + image digest, the latest available digest on GHCR, active in-flight work counts, DB backup recency, and a safe_to_update boolean. update_available is null (not false) when either the local repo_digest or the registry digest is unknown — never lies by comparing across identifier spaces. unsafe_reasons is a human-readable array; render inline rather than re-deriving from booleans. Read-only diagnostic — does NOT perform any update. |
| moor_update_apply | Apply moor update (transient respawner) | Update moor in-place via a transient respawner container. Runs preflight, enables drain, takes a fresh DB backup, then launches a one-shot Compose-aware respawner that pulls + re-creates the moor service. The respawner writes a marker file when done; this tool returns the audit_id immediately so the caller can poll via moor_update_audit. Outcomes: success | failed (pull failed pre-replacement) | rolled_back (up/health failed, automatic rollback succeeded) | rollback_failed (rollback also failed — manual recovery needed) | crashed (no marker after 30-min grace). Bypass is per-blocker: pass {bypass:['active_work']} to interrupt in-flight builds/execs/crons via the existing shutdown coordinator; {bypass:['unknown_digest']} when the registry comparison was inconclusive. Backup is mandatory and not bypassable. |
| moor_update_audit | Update history (audit log) | Read-only: recent moor_update_apply attempts and their outcomes. Each row shows audit_id, state (success | failed | rolled_back | rollback_failed | in_progress | crashed), duration, digest deltas, backup path, and any error logs. error_log preserves the ORIGINAL apply failure (never overwritten by rollback step details); rollback_error is set only on rollback_failed. Default tail is 4 KiB per log field; pass tail_bytes=0 to omit log bodies entirely (keeps the metadata line and replaces the body with a sized marker), or up to 16384 to read more. |
Cleanup
| Tool | Title | Description |
| --- | --- | --- |
| moor_cleanup_plan | Cleanup Plan (dry-run) | Dry-run: list Docker resources that are safe to delete on this host. v1 covers build cache (host-wide prune) and dangling images (per-ID). Returns candidates with reclaimable bytes. Pass the same candidate list to moor_cleanup_execute to actually delete. No state is kept between plan and execute — execute re-validates eligibility against current Docker state. |
| moor_cleanup_execute | Cleanup Execute | Delete the candidates returned by moor_cleanup_plan. Server uses only the identifying fields (category + id where applicable) and re-validates eligibility against current Docker state immediately before each delete — Docker state can change between plan and execute. Reclaimable byte estimates from plan are ignored; the server reports the actual freed bytes. Every execute writes an audit row. |
Transport
Stdio only. The MCP client launches bunx @moor-sh/mcp as a subprocess and talks over stdin/stdout. HTTP transport is tracked but not yet shipped.
Links
- moor repo - main project
- Self-hosting guide - first boot, API keys, admin domain
@moor-sh/cli- command-line interface withmoor mcp confighelper
License
MIT.
