We replaced git with MCP
Most of the code in our projects is now written by coding agents, often several at once. Under all of that sits a tool designed in 2005 for a developer who edits by hand, decides when something is worth a commit, explains it afterwards, and works in one checkout at a time.
Agents don’t work that way. They make dozens of edits per request, often on several requests at once, and step on each other in a shared working tree. Their commits record the diff but not the request behind it. The most useful fact about a change, what the human asked for, never reaches git at all.
So we tried something few people would risk on real projects: we replaced git outright. mcpc is a version control system built for agents and served to them over MCP. The name is the change itself: s/git/mcp/, plus a c for control. Somewhere along the way it stopped feeling like version control and started feeling like grey matter: one memory that every agent on a project thinks with.
A nod to git
Some of us are old enough to remember what came before it.
- SCCS (Bell Labs, 1972) and RCS (1982) versioned one file at a time, and you locked a file to edit it.
- CVS (1986) put a client-server workflow on top of RCS. A commit still wasn’t atomic, so an interrupted commit could leave half a change in the repository. Renaming a file meant losing its history, and nobody branched without dread.
- Subversion (2000) fixed atomic commits and was a real step forward. Branches were cheap copies, but merging them back was painful enough that most teams avoided it.
Then, in April 2005, the Linux kernel lost free use of BitKeeper, the proprietary system it had been using. Linus Torvalds wrote a replacement himself. Within days git was tracking its own source code, and within a couple of months it was managing the kernel. Junio Hamano took over as maintainer that summer and has led it ever since.
Any version control system that can run the Linux kernel has earned its place. Every release takes changes from thousands of developers, funnelled through a web of maintainers who each keep their own tree, and git has carried more than a million of the kernel’s commits for twenty years without the project outgrowing it. GitHub then built a social network on top of it, and git became the way the world shares code. Very few tools have passed a test that hard, and git still passes it every day.
So this isn’t an attempt to retire git. Git is superb at the job it was built for: people exchanging finished changes. We wanted to find out what the source control layer could look like if it were reimagined for agentic workloads, where changes come from agents working side by side many times a minute and the most important record is the request behind each one. Replacing git on our own projects was the most honest way to test that. mcpc imports a git branch’s history, and exports back to git with one commit per request and the prompt as its message, so the two can sit side by side.
Why MCP
The interesting question isn’t what version control looks like without git. It’s what becomes possible when source control is a set of tools the agent calls itself.
MCP, the Model Context Protocol, is how an agent’s host offers tools to a model. Each tool has a name, a description the model reads, and a JSON schema for its arguments. Anthropic published it in late 2024, and in December 2025 it moved to the Agentic AI Foundation under the Linux Foundation, which Anthropic, OpenAI and Block co-founded. The major frontier labs all support it in their own agents: Claude Code and claude.ai, OpenAI’s Codex CLI and ChatGPT, Google’s Gemini CLI. So do editors such as VS Code, Cursor and Cline. The host translates MCP tools into whatever function-calling format its model uses, so open-weight models with tool calling, such as gpt-oss, Gemma and Qwen, can use the same tools through any MCP-capable host.
That gives source control something it never had: one interface that a reasoning model from any vendor can discover and use without a wrapper. An agent asks mcpc for history symbol=parse_config or searches for "where do we rate-limit logins", and gets back structured answers that include the prompt behind each change. It never has to guess the format of command-line output. The tool descriptions are documentation the model reads at the moment it needs them. We have run Claude, OpenAI’s gpt-oss and Google’s Gemma in one mcpc project, the last two locally through Ollama, each in its own workspace and coordinating only through mcpc.
The interface is mostly uniform, not entirely. MCP standardizes the tools; what each host does around them differs. mcpc also relies on hooks, which fire on every prompt and tool call to snapshot edits and deliver what other agents have done, and every vendor’s hooks have their own shape. mcpc ships adapters for Claude Code, Codex CLI, Gemini CLI and Cline that translate them into one form. A host without hooks still gets the tools. It loses automatic snapshots, and it has to ask for news instead of being told.
You also choose where the MCP server runs and under what security context. In mcpc it’s a short-lived process on the developer’s machine, started by the agent’s host and running as that user. It reads only the workspace in front of it and reaches the mcpc server with that user’s token, which can be narrowed to one project or one workspace. It never holds a database password, and nothing reaches a model provider except the tool results the agent asked for. The same tools could run in a sandbox, a CI job or a server of your own, under whatever identity you give them. Source control has always been a security boundary, and MCP lets it stay one while agents use it.
Once source control is something agents talk to, it can answer questions that used to need a person’s memory. Which request produced this line? Can I undo one request and keep another that came later? Is anyone else changing this function right now? The rest of this essay is about what we built to answer them.
What mcpc does instead
mcpc is a single Rust binary of about 32,000 lines. On each developer’s machine it runs as Claude Code hooks and an MCP server, and those talk to an mcpc server that keeps the history in Postgres. A workspace holds nothing but a small .mcpc/ marker and a stat cache.
Every edit is a snapshot, and every snapshot has an intent
There is no commit command. After each tool call that can change files (Edit, Write, even Bash), a hook scans the workspace, using the stat cache so unchanged files aren’t re-hashed. If anything changed, it records a snapshot: a content-addressed tree (blake3, zstd, deduplicated per project).
When you submit a prompt, a hook first snapshots anything you changed since the agent last ran, attributed to user. It then records the prompt as an intent, and every snapshot until the next prompt points to it:
“Why does this line exist?” now has a literal answer: the prompt. And because intents are units, so is undo. mcpc revert i41 means “as if that request never happened”: mcpc rebuilds the tree from just before the intent, replays every other change since (later work wins any overlap), and three-way merges the result into the current state. No operation destroys anything; restore, revert, merge and switch each snapshot the working state first.
History at the level of symbols
Each snapshot also records which symbols it touched, extracted with tree-sitter for 15 languages, plus a reader for SQL. mcpc history symbol=exchange_code lists every snapshot that touched a function, with its prompt, and mcpc diff semantic=true reports + fn exchange_code, ~ fn login_page instead of hunks. “Which prompts shaped this function?” is a better question than git blame.
Workspaces, and agents that talk about the code
fork feat-sso creates a branch and a workspace directory in one step, usually one per subagent; merge brings it back with a three-way merge. Comments anchor to a snapshot (s312), an intent (i41), a line range or a symbol (src/auth/sso.rs#exchange_code). Add to=<workspace> and a comment becomes a message.
Agents don’t poll for any of this. On every hook call, mcpc checks what other agents have done since this session last looked: changes to files it touched or is watching, messages addressed to it, merges into its branch. It adds a short summary to the agent’s context. Behind that, every event is a numbered row and each session keeps a cursor, so the server answers each hook with whatever is new since that session’s last call. When agent B changes a function agent A has been editing, A hears about it on its next tool call, not after a failed merge.
Rules that only block what you just broke
.mcpc/rules.toml, versioned with the code, with sections scoped to directories, defines standards:
[[rule]]
id = "no-console"
paths = '\.(ts|tsx|js)$'
forbid = 'console\.log\('
[[rule]]
id = "errors"
paths = '^src/.*\.rs$'
agent = "Library code must propagate errors with context instead of unwrap()/expect()."
Fast checks run before the edit, against the content it would produce, so the write is refused with a reason before anything touches disk. Slow checks, such as rustfmt --check, run afterwards. A plain-English rule is judged by a model on the server, and a failing verdict comes back as a comment on the offending lines. The key choice is a ratchet: an edit is blocked only for violations it introduces, so you can adopt a rule on a legacy codebase without the agent getting stuck on 400 old violations.
Notes and search
Project knowledge lives in mcpc as scoped, versioned notes instead of one monolithic CLAUDE.md. The note about migrations arrives the first time an agent opens src/db.rs, not in every session about CSS. And because everything sits in one database, search covers paths, every stored version of every file, prompts, comments and notes across all projects, by keyword and by meaning (pgvector, with an embedding model on your own network, so code never leaves it). A file can be found by the prompt it was written for, even when none of your words appear in it.
Two things you can’t do with git
Both transcripts are real mcpc output, lightly trimmed. We scripted the sessions by feeding mcpc the hook events Claude Code sends, against a throwaway database.
1. Undo one request while another agent keeps its work
Two sessions share a workspace and edit app.py at the same time. Alice is adding rate limiting; Bob is adding a health endpoint and request logging. Their edits interleave, and nobody commits:
Then the user changes their mind: “Take the rate limiting back out, the load balancer does it now.”
$ mcpc revert i5
reverted i5 as s11: M app.py
$ mcpc diff from=@~1 semantic=true
M app.py
M handle (function_definition, line 1)
D allow (function_definition, line 5)
$ mcpc history symbol=handle
s11 [revert] M app.py#handle "revert i5"
s10 [hook:Edit] M app.py#handle i6 "Add a /health endpoint and request logging"
s9 [hook:Edit] M app.py#handle i5 "Add rate limiting to the request handler: …"
s6 [init] A app.py#handle "initial snapshot"
The rate limiter is gone. Bob’s endpoint is still there, and so is the logging line he added inside the same handle function Alice edited. In git this would be one working tree, or one commit holding both agents’ changes. Adjacent edits still produce conflict markers, as in git; the difference is that the thing you want to undo exists as a unit at all.
2. An agent hears about a change before it merges
Alice is working in main and has edited src/config.py. A subagent, Bob, forks a workspace to refactor config loading, changes the signature of parse_config, and leaves a message on that function:
$ mcpc fork refactor
workspace refactor on branch refactor at s14: …/.mcpc/ws/refactor
$ mcpc comment to=main target='src/config.py#parse_config' body="parse_config now takes …"
posted c1 from refactor to main on src/config.py#parse_config
Alice doesn’t poll. On her next edit in main, the hook adds this to her context:
mcpc activity from other agents relevant to you:
[e15] main forked branch refactor at s14 into workspace refactor
[e16] s15 on refactor/refactor [hook:Edit]: M src/config.py — i9 "Refactor config loading to accept a dict as well as a path"
[e17] c1 from refactor to main on src/config.py#parse_config: parse_config now takes
(source, *, strict=False); strict will raise on unknown keys next. Callers that pass a path still work.
Before anything has merged, Alice knows that a file she depends on changed on another branch, why (Bob’s prompt), and what its author wants her to know. She was told because she had touched the file; an agent can also watch a file, a symbol or a directory. In git you only see another branch when you fetch it, and agents rarely do that mid-task.
Before and after
Here is what a team running coding agents on git actually operates.
Every machine holds the whole history, and every copy is kept in step with every remote it talks to: the hosted origin, a fork, a mirror, an LFS server for anything large. Agents on one laptop share a single .git through worktrees. The conversation about the code lives in pull requests and issues, stored in the host’s own database behind its own API. The request that caused a change lives in a chat log, if anywhere. Coordinating several agents means stitching those systems together by hand.
mcpc collapses that into one server and two backends. Clients are one-shot processes: a hook, an MCP call or a CLI command scans the working directory, sends one HTTPS request, applies the answer and exits. Nothing stays resident, and no client logs in to the database. The server holds no state of its own, so it can run as several replicas. Everything that needs transactions lives in Postgres: history, intents, notes, comments and messages, the event log each session reads from, the job queue, tokens, roles and a hash-chained audit trail. Bytes too big for rows, meaning large files and live streams cut into content-defined chunks, go to an object store: a directory, or an S3-compatible bucket. That is the same split Git LFS and Perforce make. Embeddings and the model that judges agent-written rules are optional services the server calls. mcpc never proxies an agent’s own model.
That is the whole backend: a Postgres with pgvector and somewhere to put chunks. There is no message broker and no cache cluster. The job queue is a table read with FOR UPDATE SKIP LOCKED, and NOTIFY only wakes the server’s own workers. Agents get their updates in the answer to the next thing they do, which is the moment they can act on them.
Client-server, again
Version control has been client-server before, mostly as a commercial product. CVS put RCS behind a network server, and Subversion (2000) added atomic commits. The commercial systems went further. Perforce (1995) tracked every workspace on the server, down to which revision of each file it held, and let you lock a binary file so nobody else could edit it; game studios still run on it for that reason. ClearCase (1992, later Rational and then IBM) served files through a virtual file system, so a “dynamic view” showed whatever the server said your branch contained. Microsoft shipped Visual SourceSafe and then Team Foundation Version Control. StarTeam, AccuRev, PVCS and Plastic SCM filled out the market.
Teams left those systems for good reasons. Subversion made branches cheap, but until 2008 it didn’t record what had already been merged, so teams avoided merging. ClearCase needed dedicated administrators. Locks serialized work. Every log and diff crossed the network, nobody could commit while the server was down, and the licences cost real money. Distributed systems answered all of it at once: BitKeeper first, a commercial product the Linux kernel used for free from 2002, then git and Mercurial, both started in April 2005 when that arrangement ended. Mercurial’s MQ extension, a stack of patches you could pop, reorder and reapply, is the closest older idea to undoing one intent.
Then the biggest codebases quietly went back. Google moved off Perforce onto Piper, its own server, and many of its engineers work in CitC, workspaces that live in the cloud and appear on their machines through a virtual file system. Meta built Sapling, a Mercurial descendant with a server (Mononoke) and a virtual file system (EdenFS). Microsoft moved Windows to git and then wrote GVFS, a virtual file system that fetched files on demand, so a clone didn’t have to hold all of Windows’ history. At that scale, a server that knows what every workspace holds isn’t overhead. It’s the product.
mcpc makes the same bet for a different reason. Git was built so every clone is a complete repository, so you can commit on a plane and sync later. An AI coding agent can’t work on a plane anyway, because its model is a cloud API. What agents need instead is a shared, live picture of what everyone else is doing, and only a server that sees every workspace can provide it. That is what lets mcpc tell an agent, before its next edit, that another agent just changed the same function. One source of truth also means a snapshot and its branch-head update happen together or not at all, and search covers everything without anyone fetching first.
mcpc leaves out what made the old systems painful. Branches are cheap and merges are three-way, both taken from git. There are no locks: conflicts are announced, not prevented. The server is a stateless program in front of an ordinary Postgres, not a proprietary database, and clients are one-shot processes that hold no connection. And all of it is open source.
The cost is real, but smaller than it first looks. When the server is unreachable, through a network blip or a restart, hooks write each snapshot to a local spool and replay it inside the next call that gets through. If the branch moved meanwhile, that work lands on its own branch and is merged, so nothing is lost. What you can’t do without the server is see anyone else’s work or search shared history, and for a contributor who lives offline, git is still the better tool. A single Postgres primary is also a ceiling, but every row belongs to exactly one project, which makes the project a natural shard key. That isn’t built, because one server has been plenty.
How it scales
Two questions decide whether a version control system survives real work: what happens as more people and agents share it, and what happens as the project grows. mcpc answers them differently from git because the expensive parts live in different places.
More agents, more people
One developer and a handful of agents. The smallest setup is Postgres and an mcpc server in one Docker Compose file, listening on localhost. Each subagent forks its own workspace, hooks snapshot every edit, and agents message each other through comments. Nothing else is needed.
A team. The same server moves next to a shared Postgres and gets TLS and real identities. People log in through their identity provider. Tokens can be narrowed to one project or one workspace, for an agent or a service, and CI exchanges its workload identity for a short-lived token instead of keeping a secret in the pipeline. Roles (reader, writer, reviewer, admin) are per project, .mcpc/access.toml restricts paths, section by directory, and every grant, login and refusal lands in a hash-chained audit log. An agent writes only through a workspace it owns or was granted, so one person’s agent can’t quietly edit another’s branch.
The harder problem at team size is attention, not permissions. Twenty agents produce a lot of events, and every line delivered costs context in someone’s model. mcpc filters before it delivers. A session hears about changes to files it touched or watches, messages addressed to it and merges into its branch, in priority order and under a fixed budget per hook: about 2 KB after a tool call, 8 KB when a prompt starts and 1 KB before an edit. The rest waits behind “N more; run inbox”. Before an edit, the hook asks the server whether anyone just changed the files it’s about to touch, and gives up after 300 ms rather than slow the agent down.
Standards scale the same way. Rules a pattern can check run on every write. Rules only a model can judge, like “library code must propagate errors with context”, go to a reviewer on the server. It reads batches of recent changes against them and, depending on the rule, comments on the offending lines, requests changes or reverts the snapshot. The reviewer acts under its own identity, so its actions appear in the audit log like anyone else’s.
An organization. Groups come from Okta or LDAP, so access follows the org chart. The server keeps no state of its own, so several replicas can sit behind a TCP load balancer. Each replica wakes its own workers from Postgres NOTIFY, and the job queue is a table read with FOR UPDATE SKIP LOCKED, so replicas share work without a message broker. We have run one replica, not several. Beyond that, the ceiling is the single Postgres primary. Every row belongs to exactly one project, which makes the project a natural shard key, but sharding isn’t built.
Bigger projects
The hot path stays small. Git’s cost grows with the repository, because every clone carries the whole history. An mcpc client carries none. A hook compares the working directory against a local stat cache, hashes only files whose size or modification time changed, and sends just those changes in one request. Content the server already has is never sent again, since everything is addressed by its blake3 hash and deduplicated per project. On our hardware, through the server, a hook after a one-file edit takes about 60 ms at the median, a hook where nothing changed about 35 ms, and the check before an edit about 7 ms.
History stays cheap to ask about. log, history and search are database queries against indexes, not walks over pack files, and they run where the data is. Symbol history comes from tree-sitter for 15 languages (Rust, Python, TypeScript, JavaScript, Go, Java, C, C++, C#, Ruby, PHP, Kotlin, Swift, Bash and Perl) plus a hand-written reader for SQL, for files up to 1 MiB by default. Semantic search uses pgvector and an embedding model the server calls; ours runs on a GPU on the local network.
Large files don’t live in rows. A file of 8 MiB or more is cut into content-defined chunks of 1 to 16 MiB, stored in a directory or any S3-compatible bucket. When a large file changes, the client sends its new manifest, the server answers with the chunks it’s missing, and only those are uploaded. Live streams, such as meeting recordings, use the same chunks with a growing tail.
What that looks like in practice. Our production server holds six projects, among them a billing system and a podcast pipeline whose agents render video. One agent command in the podcast project produced more than 800 files, mostly video, and they became a single snapshot linked to the prompt that asked for them. The database is 3.5 GB; the 17 GB of video lives in chunks. In git that history would need LFS, or every clone would carry it.
Grey matter
The longer we used mcpc, the less it felt like version control and the more it felt like memory. Versioning is one thing it does. What it really holds is a project’s grey matter: every change and the request behind it, the notes agents leave for each other, the decisions people made along the way, even recordings of the meetings where those decisions were argued. Any agent that speaks MCP can read it, and whatever one agent learns is there for the next one.
That memory doesn’t care which model is doing the thinking. A coding agent from one vendor writes the code; later, a chat model from another vendor can ask what changed in billing yesterday and why, and get back the prompts that caused it. Two founders can keep their own tools, one on Claude and one on ChatGPT, and still work from one memory. When one founder’s agent changes a function the other depends on, the other’s agent hears about it on its next edit, and a question asked in a chat window on a phone becomes a message waiting for the next coding session. The coding side is what we have used every day for the work described here. The chat side runs through a remote MCP endpoint with OAuth sign-in, which we have built and are putting in front of our first outside collaborator now.
Shared memory needs boundaries. A project shared with someone outside your own work gets its own mcpc server and its own database, so what that collaborator can reach is exactly that project, and from where they stand nothing else exists.
The questions we’d like help with
- Is the prompt the right unit of history? Long multi-turn sessions blur intents together, and “revert the third thing I asked for” only works if requests line up with changes.
- What should humans review? If agents write most of the code, the line diff may be the wrong artifact. Is “this intent, these symbols, these rule verdicts” a better one?
- Does agent-to-agent messaging belong in version control? We put it there because the code is what agents talk about. It could be a separate layer.
- Did we keep the right parts of git? Content addressing, three-way merges and branches carried over. Offline clones and the staging area didn’t.
- Is this version control at all? We started out replacing git and ended up with something closer to a shared memory for agents. Maybe that is the more useful way to think about it.
Try it
mcpc is open source. The public repository is being prepared for release, with a licence, a security policy and a contribution guide. Until it is published, ask us for early access.
cargo install --path . --locked # from a checkout
docker compose -f deploy/docker-compose.yml \
--env-file ~/.config/mcpc/server.env up -d # Postgres + mcpc server on 127.0.0.1:7080
mcpc install --global --server http://127.0.0.1:7080 --token <bootstrap token>
cd your-project && mcpc init # put a project under mcpc
Then just work. Ask your agent “what changed today and why?”, or revert the intent you regret. If you try it on something real, we’d like to hear what broke.