Git Rebase
БесплатноНе проверенMCP server that makes git rebase safe for agents by refusing unsafe operations, presenting conflicts as two intents, and verifying the branch's change against i
Описание
MCP server that makes git rebase safe for agents by refusing unsafe operations, presenting conflicts as two intents, and verifying the branch's change against its base.
README
An MCP server that makes driving a git rebase safe for an agent.
It exists because a rebase can corrupt history in ways git does not report. Three that happened, in one session, on one branch:
- Amending at a conflicted
editstop.editnormally stops withHEADon the commit just applied, but when it stops because of a conflictHEADis still the previous commit.git commit --amendthere silently folds two commits into one. Nothing in git's output distinguishes the two situations. - A hand-written todo list that dropped three commits. They vanished without a warning.
- Staging a file that still contained conflict markers. Two commits shipped
<<<<<<<into the tree.
Every one exited zero and reported success.
What it does about it
Refuses the unsafe operation rather than documenting it. rebase_amend is
not callable at a conflicted stop, and the refusal says why and what to do
instead:
Refusing to amend: the rebase is conflicted, and HEAD (2c806c04b 'base') is not a commit this step created. Stopped part-way through applying 87f3d0fd4 (third). That commit does not exist yet, so HEAD is still the one before it and amending would rewrite the wrong commit. Resolve the conflicted paths, then continue.
Shows a conflict as two intents, not as marker soup. Per contested region it reports what each side did to the common base:
──── branch so far ──── ──── replaying: "Use delta_count" ────
def counts(packages): def counts(packages):
- if packages: if packages:
- rows = [] rows = []
- for package in packages: for package in packages:
+ rows = [] - rows.append(delta(package))
+ for package in packages: + rows.append(delta_count(package))
return rows return rows
"The branch has not replayed the wrap yet" against "the fix swaps the call, and leaves the wrap alone". Composing those needs no reasoning about which of three interleaved blocks belongs to whom.
Each side also gets a sentence — "adds 1 line and reindents or moves 3 lines"
against "adds 1 line and removes 1 line" — because a block wrapped in an if
produces a diff the size of the block and a change of one line, and the diff
alone does not say which you are looking at.
Each region is headed by the definition it sits in — @@ -662,13 +662,12 @@ def counts_render(self, ctx): — worked out by git's own funcname driver for the
language, of which it ships twenty-five. Git applies one only where a repository
asked for it in .gitattributes, and most have not; its fallback then recognises
a definition at column 0 only, which in any language whose definitions nest names
the class every time and the method never. So this server picks the driver, and
picks nothing else: the patterns stay git's, and a language it has never heard of
is named as well as one it has.
Both sides get to state their intent. The replayed commit has its message; the branch so far is an accumulation with no message, so each region names the commits behind its lines — which is the nearest equivalent, and is left empty rather than guessed when the lines predate the rebase.
Offers the resolution rather than making it. resolve takes
take="both" | "branch" | "replaying" for the cases the two diffs make obvious,
so answering costs one call instead of sending a whole file back. auto_resolve
will compose conflicts where the two sides touched different lines and carry on
without stopping, but it is off by default: lines that do not overlap can
still contradict each other — one side adding a call, the other removing the
helper it needs — and a conflict resolved without being read has to be reviewed
afterwards anyway.
Records where the branch was, and checks the result against it. What must stay the same is the change the branch makes to its base -- not the resulting tree, which changes for good reason when the rebase also moves onto newer upstream work. A difference is a report of damage. This is what caught all three errors above.
It is not only for rebases
A rebase is not the only thing that leaves three stages in the index. A cherry-pick, a revert, a merge, a rebase you started by hand, a stash that popped into a conflict — git records all of them the same way, which is what the conflict view reads. So the tools work on any of them:
status: state=conflicted operation=cherry-pick
A cherry-pick of 47ba527eb (side change) left 1 path conflicted.
Nothing has been committed yet. Call conflicts to read them…
proceed: runs `git cherry-pick --continue`, because `git rebase --continue`
does not finish a cherry-pick
state stays conflicted whatever produced it, so one check answers the
question; operation says what to expect. This was a false negative until
recently: anything that was not a rebase read as "no rebase in progress", which
conflicts reported as "Nothing is conflicted." — of a repository with
unmerged paths sitting in the index.
A conflict nothing recorded — the popped stash — is reported as
operation="unknown", with its regions read exactly as any other. What it does
not get is a proceed or an abort, because there is no operation to finish
and no way to know what undoing it would discard.
The rebase-specific safety stays rebase-specific: the backup tag, the branch-change check and the amend refusal are all about rewriting history, which a cherry-pick is not doing.
Tools
| Tool | Works on | |
|---|---|---|
rebase_preflight |
What a rebase would do. Changes nothing. Names commits a todo would drop. | rebase |
rebase_start |
Tags the tip, moves aside colliding untracked files, begins. autosquash folds fixup! commits in. |
rebase |
status |
Typed state, what operation is in progress, and whether HEAD is the commit being replayed. |
any |
conflicts |
Each contested region as two diffs, headed by the definition it sits in, plus the incoming commit's message. context= for more surrounding lines, include_file_diffs= for everything the incoming side did to each file. |
any |
resolve |
Stages a resolution: take="both"/"branch"/"replaying", edited in place, or written inline. Refuses markers. |
any |
rebase_amend |
Amends — only where HEAD really is this step's commit. |
rebase |
proceed |
Carries on, by the operation's own --continue. Refuses while anything is unmerged. |
any |
skip |
Drops the commit being applied — for one already in the base. | rebase, cherry-pick, revert |
rebase_todo |
The steps left, and replaces them. Refuses to drop a commit. | rebase |
rebase_finish |
Checks the branch still makes the same change to its base, and names any commit that brought a conflict marker to a file. | rebase started here |
abort |
Abandons the operation and puts back what was moved aside. | rebase, cherry-pick, revert, merge |
The prefix carries the distinction: rebase_ is for the tools that only make
sense inside a rebase — a todo, an amend, a check against where the branch was —
and the bare names are for the ones that read or drive a conflict whatever
produced it. If a name has no prefix, it does not care how you got here.
rebase_start takes a check_command, run after every commit. It is the only
thing that catches a step which applies cleanly and still leaves the tree
broken -- a resolution that drops a line, say, so the file no longer parses.
Use it.
Install it
One stdio server, one command, no arguments and no environment. Put it on your
PATH:
uv tool install --from git+https://github.com/aaron-riact/git-rebase-mcp git-rebase-mcp
Then point your agent at git-rebase-mcp. Most harnesses take the same shape
and differ only in where the file lives and what the top-level key is called:
{
"mcpServers": {
"git-rebase": { "command": "git-rebase-mcp" }
}
}
| Harness | Where | Key |
|---|---|---|
| Claude Code | claude mcp add git-rebase --scope project -- git-rebase-mcp, or .mcp.json in the repo |
mcpServers |
| Cursor | .cursor/mcp.json, or ~/.cursor/mcp.json for every project |
mcpServers |
| Gemini CLI | .gemini/settings.json, or ~/.gemini/settings.json |
mcpServers |
| Codex CLI | codex mcp add git-rebase -- git-rebase-mcp, or ~/.codex/config.toml |
[mcp_servers.git-rebase] |
| VS Code (Copilot) | .vscode/mcp.json |
servers |
| Zed | ~/.config/zed/settings.json |
context_servers |
The two that are not JSON-with-mcpServers:
# ~/.codex/config.toml
[mcp_servers.git-rebase]
command = "git-rebase-mcp"
// .vscode/mcp.json — "servers", not "mcpServers"
{ "servers": { "git-rebase": { "type": "stdio", "command": "git-rebase-mcp" } } }
If the server fails to start in an editor launched from a desktop icon rather
than a shell, it is PATH: those processes do not read your shell profile, so
~/.local/bin is missing. Give the absolute path — which git-rebase-mcp — as
the command.
Every tool takes a repo argument, defaulting to the working directory, so one
installation serves every repository you work in.
For development, clone it and uv sync; uv tool install --from . git-rebase-mcp
installs the working tree instead of the published remote.
Prior art
The conflict view is DiffDiff's idea. It shows the same two diffs in vim, and its documented wishlist — commit messages as labels, conflict counts, resolve-with-ours/theirs — anticipates most of this tool surface. This server is that insight delivered to an agent instead of a buffer, wrapped in the rebase state machine.
The regions are DiffDiff's too, and that matters more than it looks. Git's merge has already decided which parts of a file could not be reconciled, and marked exactly those; DiffDiff diffs the three sides of one such block and never sees the rest of the file. Working the regions out independently -- diffing whole sides against the whole base and intersecting -- re-derives that decision badly: two independent diffs cannot know what a merge could reconcile, so a block one side has not reached yet gets fused with a one-line change beside it, and one side ends up with nothing to say. This server made that mistake first and measured it: 4556 characters for one conflict, of which one side was 104 lines of unchanged context. Asking git for the blocks instead brought the same conflict to 289.
Design notes
- docs/plan.md — the design, and what is deliberately left out.
- docs/decisions/0001-python-rather-than-rust.md — including the two things Rust would have done better, and how each is recovered here.
- docs/decisions/0002-git-s-funcname-drivers-rather-than-tree-sitter.md — why naming what a region sits inside did not need a parser, and why the language patterns are git's rather than this server's.
Rebase state is a closed union of four types rather than fields on one object,
so rebase_amend accepts one type instead of testing a set of conditions that
could drift apart from the states. patiencediff rather than difflib, because
the default matcher pairs up the wrong blocks in files that repeat — a test file
being the obvious case — and those hunk boundaries are the main output.
Status
The rebase tools are complete and tested, and have driven the same 21-commit
branch twice. They caught two defects nothing else would have — a syntax error
committed into 8 of 10 commits, and a fixup whose test depended on a commit
scheduled after it.
What they do not yet do is save much time: nine of eleven conflicts in the last run were mechanical shapes resolved by a hand-written script. Phase 2 is planned against that measurement rather than against a feature list, and says how to tell whether it worked.
Development
uv sync
uv run pytest # builds real repositories and runs real git against them
uv run pyright
Tests use scratch repositories rather than mocks. The point of the server is that it agrees with git, so mocking git would test nothing worth testing.
Установка Git Rebase
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/aaron-riact/git-rebase-mcpFAQ
Git Rebase MCP бесплатный?
Да, Git Rebase MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Git Rebase?
Нет, Git Rebase работает без API-ключей и переменных окружения.
Git Rebase — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Git Rebase в Claude Desktop, Claude Code или Cursor?
Открой Git Rebase на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Git Rebase with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
