SwarmOps
View as Markdown

Use SwarmOps from Claude Code over MCP#

SwarmOps is a git replacement for coding-agent swarms. swarmops mcp serves a SwarmOps store to Claude Code as MCP tools. An agent reads files, history and diffs from the store, commits to it, lands its change with a three-way merge, and undoes an operation.

This guide registers the server, gives each agent process an identity of its own, and sets out the two rules an agent needs: full ids for compare-and-swap, and small results.

Pre-1.0 · Linux today; macOS and Windows next · MIT licence

Before you start#

You need SwarmOps, a store and Claude Code, on one host.

  • SwarmOps. The Quick start installs swarmops and so, the same program under a shorter name.
  • A store: one directory, on a native Linux filesystem. A path under /mnt/, where WSL mounts Windows drives, is refused with E_UNSUPPORTED_FS.
  • Claude Code on the same host. SwarmOps has no network code: every operation runs against the host's own store, and the client starts the server as its own user.

Make a store

export SWARMOPS_REPO=~/.local/state/swarmops/repos/acme   # or --repo <store> on each command
so init "$SWARMOPS_REPO" --name acme

A new store has no main yet. The Quick start makes the first commit with the CLI, which creates it: through MCP, a commit with advance needs the full id of a parent.

Register the server with Claude Code#

For one agent, register the server with claude mcp add, naming the store and the agent. Use the store's absolute path.

Claude Code

claude mcp add swarmops -- so mcp --repo <store> --as <agent>

For a project that several agents open, give the project a .mcp.json instead:

.mcp.json

{
  "mcpServers": {
    "swarmops": {
      "command": "swarmops",
      "args": ["mcp", "--repo", "/home/me/.local/state/swarmops/repos/acme"],
      "env": {"SWARMOPS_AGENT": "${SWARMOPS_AGENT:-}"}
    }
  }
}

A project's .mcp.json is shared by every agent that opens the project, so it takes each agent's identity from that agent's own SWARMOPS_AGENT, which Claude Code expands into the server's environment, rather than giving all of them one --as.

It runs swarmops, not so: on some machine the short name may be missing (--bin swarmops installs the long one alone) or belong to another program.

The empty default (:-) is deliberate. Without it, Claude Code passes an unset ${SWARMOPS_AGENT} on as that very text, which the server refuses as an invalid agent name (E_INVALID_ARG), and claude mcp list warns too. With it, an unset variable is an empty value, which is no identity, so an agent without one can read, and gets E_IDENTITY_REQUIRED when it writes.

Run claude mcp list to see whether Claude Code connected, then ask Claude to call repo_info. In an interactive session, Claude Code asks you to approve the servers a project's .mcp.json names before it uses them; its MCP documentation covers scopes and approval.

One identity per agent#

  1. A Claude Code process

    Started with its own SWARMOPS_AGENT, such as agent/worker-1.

  2. One swarmops mcp server

    Claude Code starts one server for a session. By default the session's subagents share it.

  3. One store

    Each call opens the store, so an agent sees what the others commit meanwhile.

A tool that changes the store needs an identity, <family>/<instance>, from --as or else SWARMOPS_AGENT, with SWARMOPS_RUN_ID naming the agent's run if it has one. Without one it fails with E_IDENTITY_REQUIRED (exit 5). Reads need none.

The store and the identity are the server's, never a call's. No call can name another identity: every commit and ref move is recorded for the agent the server runs as. That includes the session's subagents, which share the server by default.

An identity, for one shell

export SWARMOPS_AGENT=agent/worker-1                 # or --as <family>/<instance>

An agent that needs an identity of its own runs as its own Claude Code process, with its own SWARMOPS_AGENT. A subagent's definition can also declare a server inline, which Claude Code connects when the subagent starts and disconnects when it finishes, while a reference to a server by name shares the session's connection; Claude Code's subagent documentation covers both.

SwarmOps records an identity and does not verify it: every envelope's meta.auth says "trust". It treats every agent that runs against a store as a trusted local user acting for the same owner, and the store directory's permissions are the access boundary.

The 18 MCP tools#

A changeset is what a commit records, and a ref, such as main, is a name that points at one. swarmops mcp lists one tool for each store operation but derive, which swarmops derive runs beside the store as maintenance.

A tool's arguments are the operation's request. Its result is the envelope the CLI prints for that request, as structuredContent and as text, and a failed operation sets isError. A tool's annotations say whether it changes the store and how.

Twelve tools read#

  • repo_info

    What the store is and holds: its id and name, the changeset main points at, and how many changesets and refs it has.

  • resolve_rev

    The full id of the changeset a revision names, and whether the revision is a ref or a changeset.

  • read_file

    One file at a revision, without a checkout, as UTF-8 text or base64, from an offset or a range of lines.

  • read_tree

    One directory at a revision, a page of entries at a time, each file with its size.

  • show

    One changeset in full: its parents, author, message, changes and signatures.

  • log

    History along first parents, newest first, from main unless rev says otherwise.

  • path_history

    The changesets that changed a file, or anything below a directory, newest first.

  • diff

    The files that differ between two revisions, in path order. With format names, each one's path and status; with stat, also its line counts; with unified, also its hunks.

  • op_log

    The store's operations, newest first: each write transaction, with the changesets it recorded and the refs it moved.

  • ref_list

    Every ref, the changeset it points at, and how many moves it has had.

  • blob_has

    Of the blob ids given, the ones the store holds blobs under.

  • verify

    Checks the whole store's consistency; deep re-hashes every object too.

Six tools write#

  • commit

    Records a changeset from an explicit change list. With advance, it also moves that ref to the changeset in the same transaction, only if the ref still names parent.

  • land

    Merges a change three-way onto main, or another target, and publishes the result as one changeset in one transaction. On E_CONFLICT, details.report lists each conflict, and resolutions settle them.

  • blob_put

    Stores content as a blob, outside any commit, and gives its id: the way to commit a large file.

  • ref_set

    Moves a ref, or creates one, by compare-and-swap.

  • ref_delete

    Removes a ref by compare-and-swap.

  • undo

    Moves every ref an operation of op_log moved back to what it named before, all of them or none. Changesets are never removed.

init, whoami and schema live in the CLI, and so do git import and git export: an agent runs them as commands, not as tools. The server speaks MCP 2026-07-28, and 2025-11-25 and 2025-06-18 to clients that open with initialize.

Publish a change#

The server's instructions tell the model to publish a changeset by landing it. Three calls do that:

  1. ref_list gives the full id that main holds.
  2. commit records the change, with that id as parent and without advance. It moves no ref.
  3. land merges the changeset three-way onto the head of main, whatever main gained since, and moves main to the result in one transaction, as one new changeset whose parent is that head. Give it an idempotency_key, such as a new uuid v7.

A land that loses a race to another publish merges again and retries by itself, and the same land run again with its key replays it. A change that conflicts with what main gained fails with E_CONFLICT and the conflict report in details.report, and records nothing. While main has not moved, a commit with advance and parent publishes in one step.

Two rules for agents#

Two rules matter more to an agent than to a person at the CLI.

Give compare-and-swap a full changeset id#

ref_set and ref_delete take expected_old as the full changeset id, 64 hex digits, that the ref held when the agent read it, from ref_list, show, log or a commit's result. A commit with advance takes its parent by the same rule, and so does a land for the changeset its resolutions were decided against.

A ref name or a relative revision such as main~1 is read from the store when the call runs instead of pinning what the agent saw, so the compare-and-swap protects nothing: the ref's own name always matches, and main~1 on main never does. An id prefix stops resolving (E_AMBIGUOUS_ID) once another changeset shares it. The server refuses all three with E_INVALID_ARG.

Ask for little#

Clients limit long tool results. Claude Code saves a successful one over 25,000 tokens by default, or over 50,000 characters whatever its token count, to a file and gives the agent its path instead; it cuts only the text of an error. Each result holds its envelope twice, so an agent should ask for little:

ToolAsk for at mostRead on with
read_filemax_bytes of 32768; less for a binary filenext_offset, as offset
read_tree, log, path_historylimit of 100cursor
difflimit of 100, and max_bytes of 32768 for unifiedcursor
showmax_changes of 100none
op_loglimit of 20before

The tools' descriptions say so, and the operations' own defaults stay as they are. read_file alone raises Claude Code's limit for itself: its entry in tools/list carries _meta: {"anthropic/maxResultSizeChars": 200000}, so Claude Code keeps a read_file result of up to 200,000 characters in the conversation instead of saving it to a file.

When a call fails#

A failed call returns the envelope with a stable E_* code, whether running it again can succeed (retryable), machine-readable details and a hint, often the next command to run. A hint is often a swarmops command, and the tool of the same name runs it: ref list is ref_list, cat is read_file, ls is read_tree and info is repo_info.

A write without an identity

{
  "ok": false,
  "op": "commit",
  "api": 1,
  "error": {
    "code": "E_IDENTITY_REQUIRED",
    "message": "this operation changes the store, so it needs the caller's identity",
    "retryable": false,
    "exit": 5,
    "details": {},
    "hint": "pass --as <family>/<instance> or set SWARMOPS_AGENT, e.g. --as agent/worker-1"
  },
  "meta": {"repo": "…", "elapsed_ms": 0, "resolved": {}, "auth": "trust"}
}
  • E_IDENTITY_REQUIRED

    The server runs without an identity, and a write reached it. Start the Claude Code process with SWARMOPS_AGENT set, or register the server with --as.

  • E_INVALID_ARG on ${SWARMOPS_AGENT}

    Claude Code passed an unset variable on as text. Keep the :- default, and set SWARMOPS_AGENT before you start Claude Code.

  • E_CAS_MISMATCH (exit 4)

    Another agent moved the ref first, and the ref's target is in details.actual. A commit with advance that fails this way records nothing. Commit on the head you read, and land it: land merges three-way onto the ref's head however far the ref moved. Re-sending your whole-file puts on top of details.actual would revert the winner's edits to shared files.

  • E_CONFLICT (exit 3)

    land could not merge the change and recorded nothing. details.report gives each conflicting file's kind, its hunks and the changeset and agent that last changed it on the target. Settle each path with resolutions, against the report's theirs by its full id, and land again; a path the target changed since comes back stale.

  • E_BUSY, E_IO_TRANSIENT (exit 4)

    Retryable: the call can succeed if it runs again.

  • E_INVALID_ARG on {"file": …} content

    The server reads the file itself, as the user it runs as, so it refuses a relative path, its own stdin or stdout, and anything that is not a regular file. Give an absolute path.

  • A result saved to a file

    Claude Code limited a long result. Ask for little, as the second rule says.

  • E_NOT_A_REPO (exit 6)

    There is no store at --repo or SWARMOPS_REPO. The server opens the store anew for each call, and serves on.

  • The server exits

    The store runs in the server's process, so the server ends with it on any of the store's four documented aborts. The client sees the server exit, and has to start it again. A commit or a land that carries an idempotency_key is safe to run again: the same call replays instead of recording a second changeset. A commit's key is a uuid v7, new for each commit.

  • The server never starts

    A command line that parses as swarmops mcp but is refused starts no server, and its envelope goes to stderr, not to stdout, which belongs to the protocol.

What to know first#

  • Work happens through the tools. An agent reads a file at a revision and writes whole-file changes in a commit. Workspaces, which write files to disk, are planned (M5), with an adapter for Claude Code's worktree hooks.
  • Git stays outside the tools. No tool pushes to or pulls from a git remote. Git does that, and git import and git export carry history between git and the store.
  • One host. There is no network code, and SwarmOps is built and tested on x86_64 Linux today; macOS and Windows are next. Between hosts, work moves through a git remote, one writing host at a time.
  • Landing is not yet measured under a swarm's load. land is built, and it merges again and retries by itself when the ref moves.
  • Pre-1.0. A minor version, 0.1 to 0.2, may break a public surface other than the hash contract. The MCP tools' names, input schemas and results are such a surface.
  • What a client may rely on. A new tool, optional argument or error code is not a breaking change, so a client should allow for them. The wording of error.message and of a hint is not part of the contract: the code, exit, retryable and the keys of details are.

Go further#

Edit on GitHub

On this page