Skip to content
Conduit

mcp server

ghostfleet

PabloG55/ghostfleetcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "ghostfleet": {
      "command": "npx",
      "args": [
        "-y",
        "ghostfleet-cli"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 21, 2026

Run a fleet of Claude Code agents in parallel, from one terminal. Each agent gets its own git worktree — cut, branched, dependency-linked and booted in one keystroke — and you get one screen that shows what every one of them is doing.

A ghost fleet is a fleet of autonomous, unmanned vessels under one command — agents working with nobody in the seat, and one control plane steering them.

Why another one of these

Orchestrating agents is easy to demo and hard to trust. The parts that took real debugging — and that most wrappers get wrong:

problemwhat ghostfleet does
"is it working?" — the transcript's mtime says idle mid-generation, and busy when a background write landsreads the live pane, the same signal you read
a worker needs you, you handle it, the card stays red forevera need-you older than the session's own activity is treated as spent
every project has a session called master, so their statuses collidestatus is scoped by the fleet's socket, not by name
a dispatched prompt silently lands in the input box without submittingdispatch waits for the paste, submits, then verifies a turn actually started
one account: 5 agents drain the budget 5× faster and all stall togethera non-Claude governor meters usage and parks workers at the ceiling, resuming on reset (it can't be the lead — the lead stalls too)
one laptop: 5 agents running tests can fill the process table and wedge the machinethe same governor watches the kernel's own memory-pressure level and the process table, and parks workers before the OOM killer does
you already run agents by hand in a dozen panesfleet-adopt finds those conversations and rebuilds them as one fleet

A worktree you can actually work in

git worktree add gives you a directory. An agent needs more than that before it can do anything, and the gap is where the fiddly parts live — so fleet-spawn closes it:

why it matters
node_modules symlinked from the main checkoutthe worker can run lint, typecheck and tests on its first turn instead of waiting out an install
a dev-stack slot allocatedtwo workers don't both try to bind port 3000 and one of them silently lose
the task recorded in a manifestfleet-worktrees shows what each tree is for, which is how a lead rebuilds its map after a restart instead of guessing
reuse before createit refuses to cut a new tree while a free one is sitting there, and lists them — so the disk doesn't fill with abandoned checkouts
a session started in it, on the agent you pickeda worktree with nobody in it isn't a worker

The branch is cut from your local ref, not the remote tip, so a worker never misses work you committed but haven't pushed. And fleet-spawn refuses to run from inside a worktree: a session already in one is a leaf, and spawning there would put a second tree beside the one you're standing in rather than starting a worker.

Recycling an existing tree onto fresh work is one command — fleet-spawn <name> --reuse <worktree> --branch <new> --from <base> cleans it and checks out the new branch — which is the usual case once a fleet has been running for a while. What gets recycled is the folder, never the session: a worker that finished a task has that task all through its context, so it is retired with fleet-stop --reclaim and the next task starts a new conversation. fleet-send refuses to hand a finished, shipped worker a new brief, and says to spawn instead.

Documentation

The README is the pitch, the install and the shape of the thing. Everything you look up while using it lives in docs/, one file per question, because three reference sections had grown to 78% of this page and a README is not where you go to check a keystroke.

docs/SHORTCUTS.mdEvery key, and what answers it. Start at §0 — the handful that covers almost everything — then the exhaustive sections: inside a session, the Projects screen, the grid, the stack, the fleet-* commands, and the behaviors that are not guessable from the UI
docs/ORCHESTRATION.mdA lead session driving workers. Dispatching briefs to siblings, watching them, unblocking them, reusing worktrees before making more, and metering cost with the governor
docs/OPERATIONS.mdThe special cases. Adopting Claude sessions you started by hand, the phone client, notifications, work-vs-personal profiles, staying awake, where a worktree goes, the two ways to register a project, which fleet a worker lands on, and updating Claude Code under a live fleet
docs/mobile.md · web/README.mdThe phone client — the design argument, and the client itself
docs/jarvis.mdJarvis, one session above every fleet — experimental, off by default; fleet-experimental enable jarvis turns it on. ghostfleet jarvis, the digest it reads instead of polling, the confirm-list the tools enforce, conversation mode on the phone (transcribed on the Mac), and its fresh start once the conversation grows long
docs/stack-view.mdWhy the stack is nested attaches and not join-pane, and what was measured to find out
docs/multi-agent-sessions.mdRunning codex, opencode, agy and cursor beside claude — the measured capability matrix (hooks, MCP, skill, resume) and what picking a non-default agent costs
docs/attachments.mdSending a photo from the phone: what was measured per agent, why the bytes are converted on the Mac rather than the phone, and the two recommendations the build overturned
docs/agent-council.mdWould a second agent checking the first one's work reduce iteration? Measured against 3,494 real turns and this repo's own history: misread intent is the smallest correction category, three quarters of the rest are found by a human looking at a screen, and a browser-based verifier would have passed the bug it was built for
docs/ROADMAP.md · docs/IDEAS.mdWhat is next, and what is only an idea
CHANGELOG.mdWhat changed between releases, and whether it is a reason to upgrade
CLAUDE.mdFor working on ghostfleet: how to deploy a change, what the tests cover, and the failure modes that have bitten more than once

Prerequisites

requirementwhy
gitworktrees are the isolation model — a worker is a checkout on its own branch. fleet-spawn refuses to run without it
claude (Claude Code)what a session runs by default, and what the pane detectors are written against — missing? the installer offers Claude Code's native installer (into ~/.local/bin, no sudo)
node (v18+ to run, v20.19+ to build from a clone)the grid is a zero-npm-dependency Node TUI, and v18 runs it. The phone client is built from web/src with vite, which needs 20.19 — that applies only to a clone, because the published package ships web/ already built. Debian and Ubuntu package 18.x, so a clone there needs a newer node first; the installer checks the version up front and says which
tmuxthe hidden substrate that keeps sessions alive in the background — missing? the installer offers to install it for you (see below). With no terminal attached it has nobody to ask, so a piped or CI install prints the command instead — pass --yes there and it installs without prompting
jqthe installer wires the hooks and MCP entries with it, and the status hook parses its payload with it. macOS 26 already ships it (/usr/bin/jq); anywhere it is missing the installer offers to install it
macOS, Linux, or Windows via WSL2sessions are tmux servers, and tmux is POSIX-only — see the native-Windows note below
codex / opencode / agy / cursor-agent (optional)alternative agents, chosen per worktree on the w form. Their pane signals are detected separately — see docs/multi-agent-sessions.md
$EDITOR (optional, default nvim .)what Ctrl-n's editor tab opens. Any editor works — override with CLAUDE_FLEET_EDITOR. With neither set and no Neovim new enough for LazyVim (0.11.2+), the installer offers the official Neovim release (under ~/.local) and, only where there is no ~/.config/nvim yet, the LazyVim starter
tailscale (optional)only for the phone client, and only off-LAN: it is how fleet-serve is reachable without exposing a port — see docs/mobile.md
zellij (optional)not required, but the included layout gives you one pane that frees Ctrl-s/arrows from its own bindings
terminal-notifier + AeroSpace (optional, macOS)for clickable notifications that jump straight to the fleet — see Notifications

(Native Windows isn't supported and won't be: sessions are tmux servers, and tmux is POSIX-only. Under WSL2 it's just Linux and works the same — docs/windows.md is the whole path, from a bare Windows machine to an open fleet.)

Install

npx ghostfleet-cli          # installs, no clone needed
ghostfleet demo             # see it working, on three throwaway projects

On a fresh Linux box there is no npx to run that with. A stock Ubuntu 24.04 image ships none of node, npm, git, tmux, jq or curl — measured, not assumed — so the line above is command not found and nothing tells you which of the six is the one you need. Install them first, and the rest of this page applies unchanged:

sudo apt-get update && sudo apt-get install -y nodejs npm git tmux jq curl

(That nodejs is 18.x, which runs the fleet perfectly well. It cannot build the phone client, which matters only if you clone — see the node row above.)

ghostfleet demo is the fastest way to find out whether you want this. It creates three scratch git repos under ~/gf-demo, registers them in a separate demo profile, and opens the real control plane on them — the same screens the GIFs above were recorded against. It is additive and it never repairs: anything already there is reused and said so, and if it finds something it cannot safely reuse it stops and tells you what it found rather than guessing. Your own profiles are untouched; rm -rf ~/gf-demo ~/.config/ghostfleet/projects.demo ~/.claude-demo removes every trace.

Every screen works before you log anything in — the Projects picker, a project's session grid, the new-worktree form, the stack. What needs a login is an agent taking a turn: Claude Code keeps credentials per config dir, so the demo profile has its own, and a session you open will sit at its login prompt until you do this once:

CLAUDE_CONFIG_DIR=~/.claude-demo claude     # then /login

An empty pane before that is the login prompt, not a broken fleet.

Ready to point it at your own work? ghostfleet opens your projects — and with none registered yet it walks you through picking a folder, naming it, and starting the first session, rather than showing you an empty screen.

And put it on your phone:

fleet-phone                 # what is left to do, and the one command that does it

The same fleet as an installable app — every session as a chat, the real pane one tap away, and a notification when a worker is blocked, so a permission prompt gets answered from your pocket. fleet-phone reports which of the three steps you have done (configure, enrol a passkey, run the daemon) and prints the next one; it never runs any of them for you, because each opens a port, writes a config or spends a passkey. It reaches the phone over Tailscale — fleet-serve refuses a wildcard, a LAN address or a public one before the socket opens, because this endpoint runs commands. See docs/mobile.md for the design and the threat model.

Optional, and local only: the Mac's voice. The phone can read replies aloud with Kokoro running on your machine — better English, real Spanish, chosen sentence by sentence — instead of the phone's own voice. It is ~350 MB, so ./install.sh asks and the default is No (--yes does not answer it). Add it any time:

fleet-jarvis voice --kokoro --install   # verified download + a pinned Python 3.12 venv; re-run to repair

Without it nothing is missing: the phone reads with its own voice. fleet-phone and fleet-jarvis status say whether it is installed, not installed or broken. docs/mobile.md has the details.

Two things that cost people time, so they are in that command's output too: the passkey is enforced server-side, so until a phone is enrolled the app sits on its lock screen and the API answers 401 — and an installed iOS PWA resumed from the app switcher does not pick up a new client version. The shell is served cache-first, so reopening is a resume, not a navigation; swipe the app away and relaunch.

Prefer to clone the repo (e.g. to develop against it)?

git clone https://github.com/PabloG55/ghostfleet.git
cd ghostfleet
./install.sh                # add --verbose to watch every step

Both run the same install.sh — npx ghostfleet-cli just fetches the package and runs it for you, so nothing is left checked out afterwards. The command it installs is ghostfleet, not ghostfleet-cli: the npm package name and the binary name are separate, and only the package name had to change.

The package is ghostfleet-cli, and ghostfleet on npm is someone else's. ghostfleet@0.0.2 was published by an unrelated project ten days after this one took the name, pitched as "fleets of disposable AI agents in your own cloud" — close enough that npx ghostfleet looks right and installs a stranger's package. Nothing here can be done about that, so the suffix is load-bearing: type -cli.

Cloning is also what you want if you intend to edit ghostfleet itself: cf-sync syncs the runtime from a real repo, and an npx cache is not one.

If you already have a clone, be careful running the npx installer over it. install.sh records where to sync FROM in <runtime>/.source, and cf-sync with no argument reads it — so an installer run from an npx cache used to repoint that at the cache, after which every cf-sync in your clone copied the cache into the live runtime. Nothing errors and the sync still prints synced runtime; your edits just quietly stop arriving. The installer now keeps a recorded clone when it is running from a cache (it says so: pointer stays on your clone: …), so this only bites an older install. To fix one, do either:

cd /path/to/ghostfleet && ./install.sh    # re-install from the clone — it re-records it
cf-sync /path/to/ghostfleet               # or just repoint it once; later `cf-sync` remembers

Check it any time with cat ~/.local/libexec/ghostfleet/.source. An install run from a clone always repoints — that includes a re-install from the same clone, a moved clone, and a second clone — because the guard only fires when the copy being installed from could not serve as a sync source at all.

Missing tmux? The installer detects it, works out the right package manager for your OS (Homebrew, apt, dnf, yum, pacman, zypper, or apk), and asks before running anything — it never installs (or sudos) without you confirming. No package manager recognized, or you say no? It just tells you the command to run yourself.

Installing with no terminal (CI, a Dockerfile, curl | bash)? There is nobody to ask, so the default is to install nothing and print the command — and tmux is not optional here: a fleet session is a tmux server, so an install that skipped it leaves a grid that cannot start anything. Consent up front instead:

npx ghostfleet-cli --yes          # or: ./install.sh --yes, or CLAUDE_FLEET_YES=1

That installs the missing dependencies without prompting, using the same package manager it would have offered — sudo included on Linux, which is why it is opt-in and never the default. With --yes, an install that ends without tmux exits non-zero instead of looking successful. Without it, nothing is installed unless you say yes at the terminal.

With npx, the flag goes after the package name. --yes (and -y) is npx's own flag too, so npx --yes ghostfleet-cli is consumed by npm and this installer is invoked with no arguments at all — it then refuses exactly as if you had never passed it (measured on npm 11.18; it notices and says where the flag belongs, but the install still skips tmux). In a Dockerfile or a CI config, the env var is the form nothing can misparse:

ENV CLAUDE_FLEET_YES=1
RUN npx ghostfleet-cli

The installer stages the runtime — it copies bin/, hooks/, mcp/, skill/, and layouts/ out of the repo into ~/.local/libexec/ghostfleet (override with CLAUDE_FLEET_HOME) — then symlinks the commands (ghostfleet, claude-here, cf-sync, and the fleet-* helpers) into ~/.local/bin pointing at the staged copy; wires the status + notification hooks into every Claude config dir it finds (~/.claude, ~/.claude-*, backing each up), plus a PreToolUse guard that stops Claude Code's built-in EnterWorktree from walking a fleet session off its own checkout (it is appended to PreToolUse, so hooks you already have there survive); registers the fleet MCP server into each config dir's .claude.json via claude mcp add -s user (Claude Code reads MCP from .claude.json/.mcp.json, not settings.json) and once, globally, for codex, opencode, agy and cursor-agent, which keep one config each and have no per-profile equivalent (agy and cursor also get the event bridge and the skill, under ~/.gemini/config/ and ~/.cursor/; an edited ~/.cursor file keeps its original beside it as <file>.pre-ghostfleet); installs the ghostfleet-orchestrate skill; and links the zellij layout. Re-run any time; it's idempotent.

macOS guards ~/Documents, ~/Desktop, and ~/Downloads with TCC. An app that hasn't been granted Documents folder / Full Disk Access — notably ClaudeCode.app — gets Operation not permitted when it tries to execute anything stored there. So if you cloned this repo under ~/Documents, running the fleet CLI, the event hook, or the MCP server directly from the repo breaks the instant such an app hosts your session (symlinks don't help — exec follows them back into the protected folder). ~/.local is not TCC-protected, so the installer runs everything from the staged copy there and the repo stays purely for development.

After you edit the repo, run cf-sync to push those edits into the live runtime (it copies the runtime dirs from the recorded source repo into ~/.local/libexec/ghostfleet; the PATH symlinks, hook, and MCP already point there, so no re-link is needed). The alternative — granting ClaudeCode.app Full Disk Access — also works but can reset on app/OS updates; staging survives updates.

The screens

One terminal window (or one zellij pane) — ghostfleet is the whole control plane:

flowchart LR
    Projects["Projects\npick a project · + add project"] -- "⏎ enter" --> Master["Master Claude\nthe lead — spawns &\ncoordinates workers"]
    Master -- "Ctrl-s" --> Grid["The grid\napi · api-1 · api-2 …"]

` (backtick) always steps back exactly one level, from anywhere — session → grid → master → Projects — no matter which shortcut you took down.

  • Projects — pick a project and ⏎ drops you straight into its Master Claude. Each project has its own hidden tmux server (cf-<project>) holding its sessions.
  • Master Claude — the lead session that spawns worktrees and coordinates workers.
  • The grid — a card per Claude session (status · branch · last message), plus a card for every worktree that has no live session yet. Every session keeps running in the background, so agents work in parallel while you jump between them.
  • The stack (t from the grid) — several of those sessions on screen at the same time, in split panes, including sessions from different projects.
zellij --layout fleet attach -c fleet    # one zellij session runs everything
# or just run `ghostfleet` in any pane

What makes it different: it runs beside your setup instead of taking it over. Sessions live on per-project tmux servers ghostfleet manages for you, so you never lose a session by closing a window — and if you use zellij, it stays a single pane instead of commandeering your multiplexer (Claude Squad, ccmanager) or pushing you to a web dashboard (Omnara).

How it works

  • One tmux server per project (tmux -L cf-<project>) is the hidden substrate. It keeps each Claude session alive in the background and handles attach / detach / resize — the battle-tested part. You never interact with tmux directly.
  • ghostfleet is a tiny loop: it runs the grid, and when you pick a card it hands off to tmux attach. Detach and the loop redraws the grid. Node never owns PTYs.
  • fleet-grid.mjs is a flicker-free Node TUI (zero npm deps). Each card joins three sources: the tmux session list, the per-session status file that the Claude hooks write to ~/.claude/fleet/, and the last assistant line from the transcript in ~/.claude/projects/. Worktrees with no live session are read straight from git worktree list. Card order is its own fourth source (<sock>.order, rewritten by ⇧hjkl) — and the single one, since ghostfleet counts Ctrl-f <p> <s> through fleet-grid.mjs --order rather than re-deriving it, and fleet-cycle reads the copy mirrored onto the tmux server as @cf_order.
  • claude-here is what each session runs, so sessions resume by checkout. If a conversation was registered as a Claude Code background agent (e.g. it was backgrounded, or created by a bg workflow), a plain --resume is refused with "currently running as a background agent" — so claude-here detects that (via claude agents --json) and resumes with --fork-session, branching an interactive copy with full history. The fork becomes the newest conversation, so the next open resumes it cleanly.

Status per card: ● NEEDS YOU (permission/question) · ◆ working · ✓ ready · · idle · · FREE (worktree, no session).

Config

ghostfleet sets these per project; each spawned session inherits them (used by the grid, hooks, and fleet-* tools):

Env varMeaning
CLAUDE_FLEET_SCOPEThe project name (shown in the header; scopes checkout discovery).
CLAUDE_FLEET_ROOTThe project's root folder (where its checkouts/worktrees live).
CLAUDE_FLEET_SOCKThe project's tmux socket, cf-<project>.
CLAUDE_FLEET_SOCK_FORCEPins fleet-spawn to that socket, beating $TMUX (which is inherited by anything a fleet session launches). Same as fleet-spawn -s. See Putting a worker on a specific fleet.
CLAUDE_CONFIG_DIRThe account/config dir for the project's profile.
CLAUDE_FLEET_DIRPer-session status files ($CLAUDE_CONFIG_DIR/fleet).
CLAUDE_FLEET_YOLO0 to require permission prompts in sessions (default: bypass).
CLAUDE_FLEET_AWAKEdisplay to keep the screen on as well as the machine; off to inhibit nothing. Default on. See Staying awake.
CLAUDE_FLEET_NOTIFY_LEAD1 to push worker done/need-you events to the lead instead of it polling. See docs/SHORTCUTS.md for the per-project/per-session settings-page toggle and the full precedence rules.

ghostfleet <project> --plain prints a one-shot, non-interactive table for that project (scripts).


Updating

ghostfleet update                    # releases: follows the branch your clone is on
ghostfleet update --branch staging   # follow development instead (--branch main to go back)
ghostfleet update --yes              # ...and restart every session without asking

One command, because the code is not the only thing that is old. In order, it:

  1. brings the code up — in a clone, fetches and fast-forwards the tracked branch (main for releases, staging for development); from npm, runs npx ghostfleet-cli@latest. It never merges and never stashes: uncommitted edits or local commits that origin lacks are yours, so it stops and says which it found.
  2. runs the install, so new commands, hooks and the MCP registration land too.
  3. restarts the phone daemon — a launchd job whose label ends in ghostfleet.fleet-serve is kickstarted. Run by hand instead? Restart it by hand.
  4. offers fleet-restart --all — every session's MCP server is the one it started with, for as long as it lives. This relaunches every session on the machine onto its own conversation, so it asks (or takes --yes).
  5. tells you to relaunch the phone app — swipe it away and open it again. Reopening it from the app switcher is a resume, and a resume keeps the client it already had.

A clone from before the repository was recreated cannot be updated. Its history shares no commit with origin's, so there is nothing to fast-forward from — ghostfleet update says so and prints the git clone to run, and changes nothing. Keep the old clone until anything only it holds has been copied out.

An npm install is told when a newer version is out: the Projects screen prints one line. The check reads a cache (~/.config/ghostfleet/npm-latest, refreshed in the background at most daily), so startup never waits on the network.

Uninstall

Claude — in each config dir (~/.claude, ~/.claude-*): remove the fleet hooks blocks and the ghostfleet entry under mcpServers from settings.json (or restore a settings.json.bak.*), delete the ghostfleet key from .claude.json's mcpServers (claude mcp remove -s user ghostfleet does it), and delete skills/ghostfleet-orchestrate.

codex, opencode, agy and cursor — these are registered ONCE, globally, not per profile (see the comment above register_codex_mcp in install.sh for why that is correct rather than a shortcut):

codex mcp remove ghostfleet                                    # ~/.codex/config.toml
tmp=$(mktemp); jq 'del(.mcp.ghostfleet)' ~/.config/opencode/opencode.jsonc > "$tmp" \
  && mv "$tmp" ~/.config/opencode/opencode.jsonc              # opencode's MCP entry
rm -f ~/.config/opencode/plugin/ghostfleet-event.js            # opencode's event bridge
for f in hooks mcp_config; do tmp=$(mktemp); jq 'del(.ghostfleet, .mcpServers.ghostfleet)' \
  ~/.gemini/config/$f.json > "$tmp" && mv "$tmp" ~/.gemini/config/$f.json; done  # agy's bridge + MCP
rm -f ~/.gemini/config/skills/ghostfleet-orchestrate             # agy's skill
tmp=$(mktemp); jq '.hooks |= with_entries(.value |= map(select((.command // "") | contains("cursor-fleet-event.sh") | not)))' \
  ~/.cursor/hooks.json > "$tmp" && mv "$tmp" ~/.cursor/hooks.json   # cursor's bridge
tmp=$(mktemp); jq 'del(.mcpServers.ghostfleet)' ~/.cursor/mcp.json > "$tmp" && mv "$tmp" ~/.cursor/mcp.json  # cursor's MCP
rm -f ~/.cursor/skills/ghostfleet-orchestrate                    # cursor's skill

Then delete the symlinks in ~/.local/bin, rm -rf ~/.local/libexec/ghostfleet, and tmux -L cf-<project> kill-server for any live fleets.

Leave any of the four registrations behind and it points at a runtime you have deleted — each one names ~/.local/libexec/ghostfleet/mcp/fleet-mcp.mjs, and an agent that starts one after the runtime is gone fails per tool call rather than at startup.

Support, and what to expect

This is one person's daily driver that happens to be public. Bugs are very welcome — especially on Linux and WSL2, which I do not run every day and where most of the surprises have been environmental rather than logical. There is no SLA, and features are often declined, which is not a verdict on the idea: every option here has to keep working across four agents, three platforms and several tmux versions.

The issue form asks for your OS and tmux -V before it asks what went wrong, and that ordering is deliberate — see CONTRIBUTING.md for why, and for the one rule worth knowing before sending a patch: a test that cannot fail proves nothing.

License

MIT © 2026 Pablo Garces

↑↓ navigate openesc close