git-mcp-broker
Lets a sandboxed coding agent (e.g. a Canonical Workshop container) commit and push with the host's git identity, signing key, hooks, and credentials — without any of that ever entering the sandbox.
Two pieces:
git-mcp-host— a single-file MCP server (runs viauv run --script, no separate install needed) exposing a small, fail-closed allowlist of git operations over MCP streamable HTTP. Structurally absent: force push, delete, reset, clean, rebase, amend,--no-verify, arbitrary git args. Meant to run on the host, targeting one project directory, reachable from inside a sandbox over a tunnel (e.g. a Workshop tunnel interface).git-broker— a stdlib-only Python CLI that managesgit-mcp-host's lifecycle (start/stop/status as asystemd-run --userunit, one per project) and can talk to a running instance over its MCP endpoint directly (raw tool calls, plus averifycheck), without needing node or an agent in the loop.
Install
ln -sf "$PWD/git-mcp-host" ~/.local/bin/git-mcp-host
ln -sf "$PWD/git-broker" ~/.local/bin/git-broker
(git-broker finds git-mcp-host next to itself, so both need to stay in
the same directory — the symlinks above just need to point at this repo.)
Usage
git-broker start [project-dir] [--name NAME] [--port PORT] # starts git-mcp-host as a systemd unit (defaults to cwd)
git-broker stop [name] # defaults to cwd's basename
git-broker status [name] # omit name to list all known brokers
git-broker call <method> [json-params] [--name NAME] # raw MCP call, e.g. tools/list
git-broker verify [--name NAME] # git_status + 3 negative checks
Each project gets its own unit (git-mcp-<name>) and port, tracked under
~/.local/state/git-mcp-broker/<name>.json. --port defaults to 3015 or the
next free port after it. --name (on every subcommand) defaults to the
current directory's basename, so running commands from inside the project
directory generally doesn't need it.
Wiring the broker into an actual Workshop (tunnel interface, MCP
registration inside the sandbox) is a separate, Workshop-specific step —
see the workshop-git-broker Claude
Code skill.
Negative checks verify runs
git_pushwithbranch="--force"→ rejectedgit_addwith a path that escapes the project (../x) → rejectedgit_fetchfrom an unknown remote → rejected
If any of these succeed instead of erroring, the allowlist is broken, not the tunnel.