Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

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

GitHubEmbed

Описание

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 edit stop. edit normally stops with HEAD on the commit just applied, but when it stops because of a conflict HEAD is still the previous commit. git commit --amend there 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

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.

from github.com/aaron-riact/git-rebase-mcp

Установка Git Rebase

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/aaron-riact/git-rebase-mcp

FAQ

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

Compare Git Rebase with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development