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
swarmopsandso, 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 withE_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 acmeA 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#
-
A Claude Code process
Started with its own
SWARMOPS_AGENT, such asagent/worker-1. -
One
swarmops mcpserverClaude Code starts one server for a session. By default the session's subagents share it.
-
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_infoWhat the store is and holds: its id and name, the changeset
mainpoints at, and how many changesets and refs it has. -
resolve_revThe full id of the changeset a revision names, and whether the revision is a ref or a changeset.
-
read_fileOne file at a revision, without a checkout, as UTF-8 text or base64, from an offset or a range of lines.
-
read_treeOne directory at a revision, a page of entries at a time, each file with its size.
-
showOne changeset in full: its parents, author, message, changes and signatures.
-
logHistory along first parents, newest first, from
mainunlessrevsays otherwise. -
path_historyThe changesets that changed a file, or anything below a directory, newest first.
-
diffThe files that differ between two revisions, in path order. With
formatnames, each one's path and status; withstat, also its line counts; withunified, also its hunks. -
op_logThe store's operations, newest first: each write transaction, with the changesets it recorded and the refs it moved.
-
ref_listEvery ref, the changeset it points at, and how many moves it has had.
-
blob_hasOf the blob ids given, the ones the store holds blobs under.
-
verifyChecks the whole store's consistency; deep re-hashes every object too.
Six tools write#
-
commitRecords 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 namesparent. -
landMerges a change three-way onto
main, or anothertarget, and publishes the result as one changeset in one transaction. OnE_CONFLICT,details.reportlists each conflict, andresolutionssettle them. -
blob_putStores content as a blob, outside any commit, and gives its id: the way to commit a large file.
-
ref_setMoves a ref, or creates one, by compare-and-swap.
-
ref_deleteRemoves a ref by compare-and-swap.
-
undoMoves every ref an operation of
op_logmoved 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:
ref_listgives the full id thatmainholds.commitrecords the change, with that id asparentand withoutadvance. It moves no ref.landmerges the changeset three-way onto the head ofmain, whatevermaingained since, and movesmainto the result in one transaction, as one new changeset whose parent is that head. Give it anidempotency_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:
| Tool | Ask for at most | Read on with |
|---|---|---|
read_file | max_bytes of 32768; less for a binary file | next_offset, as offset |
read_tree, log, path_history | limit of 100 | cursor |
diff | limit of 100, and max_bytes of 32768 for unified | cursor |
show | max_changes of 100 | none |
op_log | limit of 20 | before |
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_REQUIREDThe server runs without an identity, and a write reached it. Start the Claude Code process with
SWARMOPS_AGENTset, or register the server with--as. -
E_INVALID_ARGon${SWARMOPS_AGENT}Claude Code passed an unset variable on as text. Keep the
:-default, and setSWARMOPS_AGENTbefore you start Claude Code. -
E_CAS_MISMATCH(exit 4)Another agent moved the ref first, and the ref's target is in
details.actual. Acommitwithadvancethat fails this way records nothing. Commit on the head you read, andlandit:landmerges three-way onto the ref's head however far the ref moved. Re-sending your whole-file puts on top ofdetails.actualwould revert the winner's edits to shared files. -
E_CONFLICT(exit 3)landcould not merge the change and recorded nothing.details.reportgives each conflicting file's kind, its hunks and the changeset and agent that last changed it on the target. Settle each path withresolutions, against the report'stheirsby 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_ARGon{"file": …}contentThe 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
--repoorSWARMOPS_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
commitor alandthat carries anidempotency_keyis 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 mcpbut 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 importandgit exportcarry 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.
landis 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.messageand of ahintis not part of the contract: thecode,exit,retryableand the keys ofdetailsare.
Go further#
- The README's MCP section has the full text.
- Known limitations lists what is still open, and which milestone takes each item.
- Parallel coding agents on one repository sets SwarmOps beside git worktrees.
- The home page lists the tools and the two ways to register the server.