Give each coding agent only the part of GitHub it is responsible for.
A local MCP gateway that binds one agent connection to one repository,
one branch, and one enforced responsibility.
Early alpha · local-first · open source
Prompts tell agents to stay in their lane. GitBlinder removes the other lanes.
GitBlinder keeps your GitHub token outside the agent and exposes a separate, enforced MCP perimeter for each responsibility. An agent connected to one perimeter receives only its allowed tools and path-filtered content. The repository and branch are fixed by the connection itself, not chosen by the agent or supplied in tool arguments.
Out-of-scope reads look absent. Out-of-scope writes never reach GitHub. Branch discovery and branch switching are kept outside the agent surface.
I started GitBlinder after launching a crowd of coding agents on a project built with spec-driven development (SDD).
At first, parallelism felt like a superpower. Then the repository and the agents' contexts grew:
- agents became increasingly amnesiac as their context filled up;
- they stepped on each other and overwrote work owned by another role;
- implementation agents changed tests, or made them green without a real implementation behind them;
- every new agent received far more repository context and GitHub authority than its task required.
The problem was not only prompting. Every agent could still see and touch everything. Their responsibilities were conventions, not boundaries.
GitBlinder applies the same idea as the single-responsibility principle to agent access: define one responsibility, carve out the smallest useful part of the repository, and bind one agent connection to it.
The agent becomes a narrow specialist. It cannot wander into another domain because that domain does not exist in its GitHub view.
GitBlinder is an experiment in structurally focused agents. Enforced, disjoint perimeters should make it possible to:
| Goal | Mechanism |
|---|---|
| Preserve focus | Fix the repository and branch at connection time, then hide irrelevant files and tools |
| Prevent collisions | Give concurrent agents non-overlapping write perimeters |
| Protect test integrity | Let an implementer read tests without being able to rewrite them |
| Build specialization | Keep one agent inside one stable domain of the codebase |
| Reduce latency and cost | Return less repository data and spend fewer tokens rediscovering context |
These are hypotheses to measure, not benchmark claims. The companion
gitblinder-sdd-lab
provides a small, reproducible workload for comparing prompt-only roles with
enforced ones.
Imagine one repository with a specification, its tests, and its implementation:
specs/** ──► test/** ──► src/**
▲ ▲ ▲
reviewer test author implementer
read-only writes tests writes source
The corresponding perimeters can be declared in YAML:
perimeters:
spec-reviewer:
repo: Math1987/gitblinder-sdd-lab
tools: [get_tree, get_file, get_files, list_commits, get_commit]
read:
- "specs/**"
write: []
test-author:
repo: Math1987/gitblinder-sdd-lab
tools: [get_tree, get_file, get_files, push_files, list_commits, get_commit]
read:
- "specs/**"
- "test/**"
- "package.json"
write:
- "test/**"
implementer:
repo: Math1987/gitblinder-sdd-lab
tools: [get_tree, get_file, get_files, push_files, list_commits, get_commit]
read:
- "specs/**"
- "test/**"
- "src/**"
- "package.json"
write:
- "src/**"At startup, each perimeter is bound to the repository's configured default
branch (main when omitted). This creates three distinct MCP views:
http://127.0.0.1:7400/mcp/spec-reviewer
http://127.0.0.1:7400/mcp/test-author
http://127.0.0.1:7400/mcp/implementer
The test author cannot inspect the implementation. The implementer can read
the fixed tests but cannot weaken them. With non-overlapping write globs, the
two writing agents cannot overwrite one another. If an agent needs a feature
branch, the operator allowlists it and the orchestrator connects the agent to
/mcp/<perimeter>/<branch>.
GitHub token ─► GitBlinder
├─ /mcp/spec-reviewer
│ └─ repo fixed · main fixed · specs/** read-only
├─ /mcp/test-author/test-contract
│ └─ repo fixed · test-contract fixed · test/** writable
└─ /mcp/implementer/feature-parser
└─ repo fixed · feature-parser fixed · src/** writable
│
└─► GitHub
One local GitBlinder process owns the credential. Each named MCP endpoint is bound to one perimeter and revalidates every call before it reaches GitHub.
A perimeter controls:
- repository — the agent cannot redirect a call to another repository;
- bound branch —
/mcp/<id>fixes the repository default branch, while/mcp/<id>/<branch>fixes an operator-allowlisted branch; - tools — tools outside its allowlist do not appear in
tools/list; - read paths — trees, files, and results are filtered through
readglobs; - write paths — every path in a mutation must match
write, or the entire mutation is rejected; - commit history — commit lists use prefixes derived from
read, and commit details expose only readable files plus an opaque omitted count.
read and write are independent. Reading a file does not imply permission to
change it, and writing a path does not automatically reveal its current
contents.
An out-of-scope file or branch looks like it does not exist. This avoids turning the gateway into an oracle for hidden repository structure.
An out-of-scope write returns a clear error, so the agent does not continue under the false belief that a change succeeded.
push_files creates an atomic multi-file commit. If one requested path falls
outside the write perimeter, nothing is forwarded to GitHub.
An agent does not select or switch its repository, perimeter, or branch during
a tool call. Its MCP URL establishes the binding, and GitBlinder removes the
bound owner, repo, ref, and branch fields from the relevant agent tool
schemas.
New perimeters allow only the repository's default branch. The operator may
discover GitHub branches and add an existing branch to a perimeter. The agent
never receives list_branches or compare, even if either tool is mistakenly
included in the perimeter's tool grant.
list_commits is restricted to static path prefixes derived from read globs.
get_commit filters its changed files through the full read perimeter. A mixed
commit reports omitted_count without naming hidden paths; a purely
out-of-scope commit looks absent.
GitBlinder does not infer architecture or automatically resolve overlapping responsibilities. To isolate concurrent agents, define deliberate, non-overlapping write globs that follow the boundaries of your system.
- Node.js 20 or newer
- A GitHub personal access token
Use a dedicated, least-privilege token. The token remains the maximum authority of the GitBlinder process; perimeters can only narrow it.
Install the compiled CLI globally:
npm install --global git-blinder
git-blinder --versionOr try it without a global install:
npx --yes git-blinder@latest --helpnpm install
npm run build
npm linkStart the local gateway. The token stays in the operator process and is never passed to an agent:
export GITHUB_TOKEN=github_pat_xxxxx
git-blinder serveAuthenticated as your-github-user
git-blinder is running on http://127.0.0.1:7400
In another terminal, inspect what the token can access and create a perimeter:
# Operator commands authenticate with the same token as the local gateway.
export GITHUB_TOKEN=github_pat_xxxxx
git-blinder repos list
git-blinder branches list --repo YOUR_GITHUB_USER/YOUR_REPO
git-blinder perimeter create implementer \
--repo YOUR_GITHUB_USER/YOUR_REPO \
--read 'specs/**' \
--write 'src/**' \
--tools get_tree,get_file,get_files,push_files,list_commits,get_commit
git-blinder perimeter show implementer
git-blinder perimeter branch list implementerA newly created perimeter permits only the repository's default branch. To use another branch, create it on GitHub first, then explicitly add it:
git-blinder perimeter branch add implementer feature-parserThe orchestrator chooses the branch by choosing the MCP URL:
| MCP URL | Bound branch |
|---|---|
/mcp/implementer |
Repository default branch |
/mcp/implementer/feature-parser |
Allowlisted feature-parser branch |
Connect an agent to a scoped endpoint, never /mcp/root:
claude mcp add git-blinder-implementer \
--transport streamable-http \
http://127.0.0.1:7400/mcp/implementer/feature-parserCursor configuration:
{
"mcpServers": {
"git-blinder-implementer": {
"url": "http://127.0.0.1:7400/mcp/implementer/feature-parser"
}
}
}Ops note: unfiltered tools live at
/mcp/root(not for agents)./mcpis health-only./api/*requiresAuthorization: Bearerwith the same token used forserve. Agents connect only to/mcp/<id>or/mcp/<id>/<branch>.
The operator can inspect the same filtered history without giving the agent a branch-discovery tool:
git-blinder perimeter commits list implementer --branch feature-parser
git-blinder perimeter commits show implementer COMMIT_SHA --branch feature-parser
git-blinder perimeter list
git-blinder perimeter delete implementerTo preload stable perimeters at startup, copy and edit the YAML example:
cp config.example.yaml config.yaml
GITHUB_TOKEN=github_pat_xxxxx git-blinder serve --config config.yamlRuntime changes currently live in memory. Put long-lived perimeter definitions in YAML until registry persistence is implemented.
/mcp/root contains the complete operator/debug catalogue. A perimeter
endpoint intersects its optional tool grant with the safe agent catalogue.
POST /mcp does not expose tools (405).
| Tool | Ops /mcp/root |
Perimeter endpoint |
|---|---|---|
get_tree |
yes | yes, path-filtered |
get_file |
yes | yes, path-filtered |
get_files |
yes | yes, path-filtered |
push_files |
yes | yes, atomic and write-filtered |
list_commits |
yes | yes, branch-bound and path-filtered |
get_commit |
yes | yes, readable files only |
list_branches |
yes | no |
compare |
yes | no |
If tools is omitted from a perimeter, all safe perimeter tools are available.
If it is present, only the granted safe tools appear in tools/list.
GitBlinder is an early alpha under active development. The current implementation includes:
- local GitHub token validation and a streamable HTTP MCP server;
- ops unfiltered MCP at
/mcp/root; health-only/mcp; Bearer on/api/*; - YAML-preloaded and runtime-managed in-memory perimeters;
- default-branch
/mcp/<id>bindings and allowlisted/mcp/<id>/<branch>bindings; - forced repository and branch arguments, optional tool subsets, and
path-scoped
read/write; - strict path normalization and all-or-nothing writes via
push_files; - operator CLI for repos/branches discovery and perimeter branch allowlists;
- filtered commit history (
list_commitsvia derived GitHubpath=prefixes,get_commitwith filtered files andomitted_count); - agent perimeter surface without
list_branches/compare.
Current limitations worth knowing:
- runtime perimeter and branch-allowlist changes are not persisted;
- one process owns one GitHub token, and each perimeter targets one repository;
list_commitsreturns an empty list when no static prefix can be derived from a read glob such as**or*.md;/mcp/rootremains available for operator/debug use (not for agents);/api/*is protected by the ops Bearer token, but perimeter MCP URLs are still capability-by-path (guessable ids) in this version.
See SPEC.md for the model, CLI-SPEC.md for the
CLI, and features/cli/ for executable contracts.
GitBlinder is a GitHub capability gateway, not an operating-system sandbox. Its guarantees assume that an agent:
- reaches GitHub only through its scoped
/mcp/<id>[/<branch>]endpoint; - cannot read the GitHub token from the environment or process;
- does not also receive an unrestricted local clone, shell, or alternative GitHub credential that bypasses the gateway.
Bind the server to localhost, use a least-privilege upstream token, and give each agent only its named MCP endpoint.
# Run from source
npx tsx src/cli/index.ts serve --config config.yaml
# Unit, integration, and E2E tests
npm test
# All supported behavioral contracts
npm run test:features
# Two remaining Git-write harness scenarios
npm run test:features:wip
# One feature file
npm run test:features -- features/cli/03_perimeter_mcp.featureThe test suite uses a deterministic mock GitHub upstream; no real repository is
needed for perimeter and filtering tests. CLI and agent-perception contracts
are wired and executable. Two scenarios remain explicitly tagged @wip while
the mock Git write flow is completed: a successful bound-branch write and the
cross-perimeter check after a concurrent write.
SPEC.md— product and security modelCLI-SPEC.md— incremental CLI contractCONTRIBUTING.md— development workflow and contribution areasconfig.example.yaml— example perimeter configurationfeatures/— Gherkin behavior contractsgitblinder-sdd-lab— reproducible SDD workload
MIT

