A lightweight, project-agnostic MCP server that gives Claude Code agents shared
context across multiple repos on the same machine. Each repo owns its own
.context/ folder — the server is a stateless I/O tool with zero project knowledge.
Three install paths depending on your use case:
Download the latest .mcpb from the
GitHub Releases page,
or build one from source:
npm install
npm run release:mcpb
# Produces: context-bridge-mcp.mcpbThen drag the .mcpb file into Claude Desktop (or any host that implements the
MCPB spec). The host will prompt
for the Ecosystem Root (defaults to ~/.context-bridge) and wire everything
up. No manual config.
npm install
npm run buildRegister once at user scope so it works in every repo automatically:
claude mcp add --scope user --transport stdio context-bridge \
-- node /absolute/path/to/context-bridge-mcp/dist/index.jsOptional: override the contracts directory for a shared location:
claude mcp add --scope user --transport stdio context-bridge \
-- node /absolute/path/to/context-bridge-mcp/dist/index.js \
--env CONTRACTS_ROOT=/absolute/path/to/shared-contractsOr use claude.json.example as a template for per-repo configuration.
Context Bridge is not published to npm — install it directly from this GitHub repo:
npm install github:Bantarus/context-bridge-mcp
# Or pin to a specific release tag:
npm install github:Bantarus/context-bridge-mcp#v1.0.0npm runs the prepare script after install, which builds dist/ for you.
See Embedding in a host application below for the spawn pattern.
The server reads and writes .context/ folders relative to the working directory
of the process that invokes it (typically the repo root where Claude Code is running).
Each repo is self-describing via a manifest.json in its .context/ folder.
The server trusts that manifest — it does not enforce any schema or naming.
Cross-repo access is explicit via bridge_get_from, which takes a path to
another repo and reads its .context/ folder.
| Variable | Default | Description |
|---|---|---|
CONTEXT_ROOT |
$CWD/.context |
Path to the context directory |
CONTRACTS_ROOT |
$CONTEXT_ROOT/contracts |
Path to contracts (can be shared across repos) |
ECOSYSTEM_ROOT |
~/.context-bridge |
Path to the shared ecosystem registry |
| Tool | Description |
|---|---|
bridge_manifest |
Full registry — call first every session |
bridge_get |
Fetch one context file by domain/component |
bridge_update |
Write a context file after implementing |
bridge_list |
Discover existing files, optionally filtered by domain |
bridge_get_from |
Fetch a context file from another repo by path |
bridge_register |
Register a repo in the shared ecosystem |
bridge_discover |
List or inspect repos in the ecosystem |
bridge_get_contract |
Fetch a contract — searches local then ecosystem |
bridge_update_contract |
Write a contract file |
bridge_list_contracts |
List all contracts |
bridge_changes |
Show changes from other repos since last check |
bridge_sync_skills |
Install/update companion skills into current repo |
bridge_manifest_update |
Deep-merge a patch into manifest.json |
Each repo that uses the bridge creates this structure:
your-repo/
.context/
manifest.json <- registry of domains and components
contracts/ <- API contracts (inter-repo boundaries)
users.md
billing.md
api/ <- example domain
routes.md
middleware.md
schemas/ <- example domain
user.md
session.md
events/ <- example domain
user-events.md
- Copy
CONTEXT.md.templateto<your-repo>/.context/CONTEXT.mdand fill it in - Create your first context file and manifest:
mkdir -p .context/api
echo '{"version":"1.0","domains":{"api":["routes"]}}' > .context/manifest.json- Install the companion skills into the repo:
bridge_sync_skills()
This copies context-reader, context-feeder, and context-bridge skills
into .claude/skills/ so Claude Code knows how to use the bridge automatically.
- Start using the bridge tools in Claude Code — call
bridge_manifest()first
When Claude Code works in one repo, it has no idea what exists in related repos. If your frontend calls an API, Claude Code in the frontend repo doesn't know the endpoint signatures, event shapes, or data models from the backend. Loading the entire backend codebase into context is wasteful and noisy.
The bridge solves this by giving each repo a small .context/ folder that
describes its architecture in plain markdown. Claude Code reads only the context
files relevant to the current task — not the full codebase of every repo.
1. Set up each repo once
Create a .context/ folder with a manifest and context files that describe
your repo's architecture. You don't need to document everything — start with
the parts that other repos interact with.
my-api/
.context/
manifest.json
routes/
users.md ← describes the /users endpoints
billing.md ← describes the /billing endpoints
schemas/
user.md ← describes the User data model
contracts/
users.md ← the agreed API contract other repos depend on
my-frontend/
.context/
manifest.json
pages/
dashboard.md ← describes what data the dashboard needs
settings.md
contracts/
users.md ← same contract, from the consumer's perspective
The manifest is a simple registry:
{
"version": "1.0",
"domains": {
"routes": ["users", "billing"],
"schemas": ["user"],
}
}2. Register each repo in the ecosystem
Each repo declares its existence once so other repos can discover it automatically:
bridge_register({
name: "my-api",
path: "/absolute/path/to/my-api",
exposes: ["routes", "schemas", "contracts"],
stack: "Node.js / Express"
})
bridge_register({
name: "my-frontend",
path: "/absolute/path/to/my-frontend",
exposes: ["contracts"],
stack: "React / TypeScript"
})
This writes to a shared ecosystem.json at ~/.context-bridge/. All repos
on the machine can see each other without hardcoded paths.
3. Claude Code reads context at the start of a session
When you start working, Claude Code orients itself, checks for changes, then fetches only the context relevant to the task:
bridge_manifest() ← what domains does this repo have?
bridge_discover() ← what other repos exist in the ecosystem?
bridge_changes() ← what changed in other repos since last session?
bridge_get("routes", "users") ← fetch the context I need
bridge_get_contract("users") ← resolved automatically from ecosystem
bridge_changes is filtered by watches in your manifest. If you declare
watches, you only see changes from the repos and domains you care about:
{
"watches": {
"my-api": ["contracts", "schemas"],
"shared-lib": ["events"]
}
}Each watch token matches in two ways:
- Category keyword —
"contracts"matches all contract changes,"context"matches all context changes,"manifests"matches manifest changes. Both singular ("contract") and plural ("contracts") forms work, case-insensitive. - Specific domain name —
"schemas"matches changes whose domain isschemas(typically context files under.context/schemas/);"users"matches theuserscontract or any domain literally namedusers.
The example above subscribes to all of my-api's contracts plus changes
in its schemas domain, and to anything in shared-lib's events domain.
If no watches are declared, bridge_changes shows all contract changes from
other repos as a safe default.
bridge_get_contract is ecosystem-aware: it searches the current repo first,
then all ecosystem repos that expose contracts. No need to know which repo
owns a contract.
4. Claude Code reads from other repos when needed
For internal context (not contracts), Claude Code can read another repo directly via path or by discovering it first:
bridge_discover("my-api") ← get path and details
bridge_get_from("/path/to/my-api", "routes", "users") ← read internal context
This is read-only — Claude Code never writes to another repo's context.
5. Claude Code writes back after implementing
After making changes, Claude Code updates the context files so they stay in sync with the actual code. This is the most important step — stale context is worse than no context.
bridge_update("routes", "users", "# Users Routes\n\n## Purpose\n...")
bridge_manifest_update({ "patch": { "domains": { "routes": ["users", "billing", "auth"] } } })
-
Context files (
.context/<domain>/<component>.md) describe internal architecture. They help Claude Code understand your repo. Other repos can read them viabridge_get_from, but they're not designed as a stable interface. -
Contracts (
.context/contracts/<domain>.md) define the agreed boundary between repos — endpoints, event shapes, shared types. They are the only thing another repo should rely on. When a contract changes, both sides need to update.
| Situation | Use |
|---|---|
| Need to know another repo's API shape | bridge_get_contract (read the contract) |
| Debugging a mismatch between repos | bridge_get_from (peek at their internals) |
| Implementing against a stable interface | bridge_get_contract |
| Understanding how another repo works internally | bridge_get_from |
Every contract must have a ## Version section:
# Contract: users
## Version
2.1
## Changelog
- 2026-04-21: Added rate limit header
- 2026-04-15: Initial contractWhen a repo reads a contract via bridge_get_contract, the bridge automatically
pins the consumed version in the ecosystem. On the next session start,
bridge_changes compares the pinned version against the current version and
warns about drift:
Version drift detected (1):
⚠ contract "users": you consumed v2.0 from my-api on 2026-04-15, current is v2.1
This means the agent knows exactly what changed and can re-fetch the contract to understand the delta before writing any code against a stale interface.
- Start small. You don't need to document every file. Begin with the components that cross repo boundaries, then expand as needed.
- Contracts are the source of truth. If a contract and a context file disagree, the contract wins.
- Keep context files short. A few paragraphs per component is ideal. If a file is getting long, split it into multiple components.
- Automate the write-back. Use the
context-feederskill to automatically update context files after implementing. Context drift is the main failure mode of the bridge pattern.
The bridge works between repos in the same environment (WSL-to-WSL or Windows-to-Windows) with no extra setup. Cross-environment usage (WSL repo talking to a Windows repo or vice versa) requires using cross-filesystem mount paths when registering.
If the MCP server runs in WSL, register Windows projects via /mnt/c/:
bridge_register({
name: "my-windows-project",
path: "/mnt/c/Users/you/projects/my-app",
exposes: ["contracts", "api"],
stack: "..."
})
If the MCP server runs on Windows, register WSL projects via the UNC path:
bridge_register({
name: "my-wsl-project",
path: "\\\\wsl$\\Ubuntu\\home\\you\\DEV\\my-app",
exposes: ["contracts"],
stack: "..."
})
Caveats:
/mnt/c/access from WSL has a performance overhead (filesystem bridge)- File watching does not work across the boundary
- Two separate Claude Code instances (one in WSL, one in Windows) need two
MCP server processes, but can share the same
ecosystem.jsonby settingECOSYSTEM_ROOTto a path both environments can access
Recommendation: keep all repos in the same environment (ideally WSL). Use cross-mount paths only when you have no choice.
Context Bridge MCP can be embedded as a child process inside any host that speaks MCP — Electron operator gateways, IDE plugins, custom orchestrators. This is the same pattern Claude Code, Cursor, Continue, and Zed use for native MCPs.
Either depend on it via npm:
npm install context-bridge-mcpOr bundle the compiled dist/ folder directly with your app's resources.
The cleanest path is to let the MCP SDK manage the child process via
StdioClientTransport:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { app } from "electron";
import { resolve } from "node:path";
// Resolve to the bundled or installed binary
const binary = resolve(
app.getAppPath(),
"node_modules/context-bridge-mcp/dist/index.js"
);
const transport = new StdioClientTransport({
command: process.execPath, // Electron's bundled Node
args: [binary],
env: {
...process.env,
// Persistent state under the host's user data dir
ECOSYSTEM_ROOT: resolve(app.getPath("userData"), "context-bridge"),
// Override per active workspace if needed
CONTEXT_ROOT: resolve(activeRepoPath, ".context"),
},
cwd: activeRepoPath, // .context/ is read from cwd by default
});
const client = new Client({ name: "operator-gateway", version: "1.0.0" }, {});
await client.connect(transport);
// Now call tools
const manifest = await client.callTool({ name: "bridge_manifest", arguments: {} });If you need to manage the process yourself (custom restart logic, crash supervision, log capture):
import { spawn } from "node:child_process";
const proc = spawn(process.execPath, [binary], {
stdio: ["pipe", "pipe", "pipe"],
cwd: activeRepoPath,
env: {
...process.env,
ECOSYSTEM_ROOT: resolve(app.getPath("userData"), "context-bridge"),
},
});
proc.stderr.on("data", (chunk) => {
// Server logs (boot info, warnings) go to stderr
console.log("[context-bridge]", chunk.toString());
});
// Wire proc.stdin / proc.stdout to your MCP client transport
// Handle proc.on("exit", ...) for restart logic
// Call proc.kill() in app.on("before-quit", ...)| Var | Purpose | Recommended value |
|---|---|---|
ECOSYSTEM_ROOT |
Where ecosystem.json and changelog.jsonl live |
app.getPath("userData") + "/context-bridge" (or shared across all your app instances) |
CONTEXT_ROOT |
Override the .context/ location |
Usually unset — process.cwd() + "/.context" is correct when you set cwd |
CONTRACTS_ROOT |
Override contracts location | Set if you want a shared contracts folder across multiple repos |
The bridge resolves .context/ from process.cwd(). To switch active
workspaces in your host app, you have two options:
- Restart the child process with a new
cwd— clean state, simple, takes ~50ms. Recommended for most operator gateways. - Set
CONTEXT_ROOTper-call via env — not currently supported, would require an architectural change to make the tools accept a workspace argument.
- Use
process.execPathrather than"node"so you get the Electron-bundled Node runtime (no system Node dependency for end users) - On Windows,
node_modules/.bin/context-bridge-mcpresolves to a.cmdshim — prefer the direct path todist/index.jsfor spawning - The shebang line works on POSIX systems if you mark the file executable
(npm install does this automatically via the
binfield)
Context Bridge ships an MCPB manifest
at the repo root (manifest.json) so it can be packaged as a single .mcpb file
for one-click install in Claude Desktop and other MCPB-aware hosts.
# Validate the manifest
npm run validate:mcpb
# Build a quick bundle (uses your current node_modules — may include devDeps)
npm run pack:mcpb
# Build a release bundle (production-only deps, ~2.5 MB)
npm run release:mcpbThe release script reinstalls deps as production-only, builds, packs, then
restores your dev environment. Output: context-bridge-mcp.mcpb at the repo root.
Note: The MCPB manifest.json at the repo root is unrelated to the bridge's
own .context/manifest.json per-repo registry — they describe different things
in different scopes.
Individual developers should use the .mcpb bundle above. The Docker path is
for organizations that have standardized on Docker MCP Gateway, want to
distribute through a private OCI registry, or need supply-chain governance
(signing, SBOMs, central allowlists). The repo includes three reference files
under docker/ as starting points for your platform team to adapt —
context-bridge does not publish or maintain its own Docker images.
| File | Purpose |
|---|---|
| docker/Dockerfile.example | Multi-stage build, ~80 MB alpine image, dist/ + companion skills |
| docker/catalog-entry.example.yaml | Minimal server.yaml for docker mcp catalog server add or profile server add |
| docker/.dockerignore.example | Copy to repo root before building to shrink build context |
cp docker/.dockerignore.example .dockerignore
docker build -f docker/Dockerfile.example \
-t registry.internal.corp/mcp/context-bridge:1.0.0 .
docker push registry.internal.corp/mcp/context-bridge:1.0.0Context Bridge is a filesystem orchestrator: it must see the active repo
and the shared ecosystem directory. The MCP client speaks stdio over
docker run -i:
docker run -i --rm \
-v "$PWD:/workspace" \
-v "$HOME/.context-bridge:/ecosystem" \
-e ECOSYSTEM_ROOT=/ecosystem \
-w /workspace \
registry.internal.corp/mcp/context-bridge:1.0.0| Flag | Why |
|---|---|
-i |
stdio transport — keep stdin open |
-v "$PWD:/workspace" |
the bridge reads .context/ from the active repo |
-v "$HOME/.context-bridge:/ecosystem" |
shared ecosystem registry must persist across runs |
-e ECOSYSTEM_ROOT=/ecosystem |
tells the bridge where the ecosystem lives inside the container |
-w /workspace |
sets process.cwd() so the default CONTEXT_ROOT resolves correctly |
Caveat: when registering with claude mcp add, the bind-mount path is
fixed at registration time. If you cd into a different repo, the container
will still see whichever directory you registered. Solutions:
- Use the MCPB bundle instead (recommended for the common per-repo workflow)
- Register the server per-repo with a wrapper script that resolves
$PWD - Use Docker MCP Gateway (next section) which manages per-session mounts
Add the server to a private custom catalog and distribute it to your team:
# Platform team — build the catalog once
docker mcp catalog create registry.internal.corp/mcp/team-catalog:latest \
--title "Internal MCP Tools" \
--server file://./docker/catalog-entry.example.yaml \
--server docker://registry.internal.corp/mcp/internal-api:latest
docker mcp catalog push registry.internal.corp/mcp/team-catalog:latest
# Individual developers
docker mcp catalog pull registry.internal.corp/mcp/team-catalog:latest
docker mcp gateway run --catalog registry.internal.corp/mcp/team-catalog:latestThe volumes / env / workingDir schema at the per-server level varies
across Docker Desktop versions — the catalog-entry.example.yaml
ships with commented hints. Validate against your toolkit's current reference
before rolling out; if in doubt, fall back to the raw docker run invocation
above.
- Active-repo discovery. A container has no view of the user's full
filesystem. The MCPB path remains the right answer for the everyday
"I just
cd'd into a different repo" workflow. - Cross-environment paths. WSL/Windows path translation still applies
(see the WSL section above); bind mounts in WSL pointing at
/mnt/c/...carry the same performance cost as the non-Docker path.
The suite is split into four layers, in increasing fidelity:
| Layer | Path | Speed | Runs in CI? |
|---|---|---|---|
| Unit | test/unit/ | <100ms | ✅ |
| Integration | test/integration/ | ~400ms (spawns the compiled bridge per file, drives it via real MCP Client over stdio) | ✅ |
| Scenarios | test/scenarios/ | ~300ms (multi-repo journeys: greenfield onboarding, contract drift + recovery, watches filter, resolution precedence) | ✅ |
| Headless E2E | test/e2e-headless/ | ~10s each (spawns real claude -p and asserts the bridge was driven correctly) |
❌ local-only |
npm test # unit + integration + scenarios (everything CI runs)
npm run test:watch # vitest watch mode
npm run test:coverage # with v8 coverage report
RUN_E2E_HEADLESS=1 npm run test:e2e # local-only — see belowThe headless tests spawn claude -p and drive the bridge as a real agent would. They need a Claude account (subscription OAuth via ~/.claude/.credentials.json, or a long-lived token from claude setup-token exported as CLAUDE_CODE_OAUTH_TOKEN, or ANTHROPIC_API_KEY). The default CI workflows in this repo deliberately do not wire up any of those — running the suite on every PR would cost real money and the value-vs-cost tradeoff isn't compelling for a small project. Developers should run them locally before tagging releases.
If you want to enable them in CI:
claude setup-tokenlocally — get a long-lived OAuth token tied to your subscription.- Add it as a GitHub Actions secret named
CLAUDE_CODE_OAUTH_TOKEN. - Add a
RUN_E2E_HEADLESS: "1"env andCLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}to a CI step that runsnpm run test:e2e.
# Fast iteration (no build step)
npm run dev
# Production
npm run build && npm start