The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the MCP Broker listing page.
mcp-broker is a local Model Context Protocol process broker for MCP clients.
Think PgBouncer for MCP: one stable local endpoint in front of many upstream MCP servers. The broker owns upstream startup, reuse, cleanup, profile exposure, status, and safe tool routing.
The core idea is simple: do not make every agent session load every upstream tool definition before the user asks a task.
AI coding sessions with many MCP servers tend to accumulate the same problems:
mcp-broker puts a small broker facade in front of those upstreams. It is not a hosted workflow builder; it is local infrastructure for keeping MCP clients small, predictable, and under one config contract.
The client sees a small set of broker tools:
The upstream MCPs still exist. They are discovered and called through the broker when a task needs them.
On 2026-05-24, the measured Codex setup went from many raw MCP and hosted app tool definitions to one broker facade plus a pruned codex_apps cache.
| Surface | Before | After | Reduction |
|---|---|---|---|
| Direct Codex MCP server entries | 11 | 1 | 90.91% |
| MCP tool definitions | 414 | 4 | 99.03% |
Hosted codex_apps tool definitions | 195 | 39 | 80.00% |
| Combined always-loaded tool definitions | 609 | 43 | 92.94% |
| Combined serialized tool payload bytes | 1,026,171 | 185,877 | 81.89% |
Combined o200k_base tool tokens | 276,989 | 45,281 | 83.65% |
The 92.94% number is a tool-definition count reduction. The 83.65% number is a token reduction for canonical serialized tool payloads measured with tiktoken o200k_base.
See docs/context-reduction-measurement.md for evidence and caveats.
$HOME/mcp/mcp-broker, outside the repo.Core differentiators:
Use mcp-broker if you:
This repo is not an enterprise MCP control plane. It is local desktop infrastructure for developer-agent workflows.
Implemented:
config/broker.private.yaml, created from config/broker.example.yaml.config/broker.schema.json.runtime.root..gemini/config/mcp_config.json, including its MCP
allowed-server policy.Wiring status:
/mcp acceptance..gemini/config/mcp_config.json.Public release status:
make release-version-check; publication proof is tracked in docs/distribution.md.mcpb/manifest.json for local directory review.See ROADMAP.md for public-facing release work.
mcp-broker has three runtime layers:
| Layer | Responsibility |
|---|---|
| Client shim | Presents one stdio MCP server entry to each MCP client and forwards JSON-RPC over the broker socket. |
| Broker daemon | Owns profile gates, namespace routing, upstream lifecycle, status, logging, and cleanup. |
| Upstream MCP servers | Run as configured stdio, HTTP, streamable HTTP, or SSE connectors with shared or per-session process policy. |
The config file is the contract. Profiles decide exposure, upstreams define transport and lifecycle behavior, and smoke probes define safe read calls for validation.
| Approach | Best fit | Tradeoff |
|---|---|---|
| Raw MCP client config | Small setups with a few tools. | Every session loads the full tool list and each client repeats config. |
| Simple MCP proxy | Forwarding one server to one client. | Does not own upstream lifecycle, profile budgets, or cross-client cleanup. |
| Hosted app connectors | SaaS tools managed by the client provider. | Local MCP state and cross-client parity remain outside user control. |
mcp-broker | Local developers with many upstream MCPs across MCP clients. | Adds a local daemon and config contract that must be installed and monitored. |
The quickstart flow should look like this:
In an MCP client, /mcp should show one mcp-broker entry. Use broker_status
to inspect profile-visible upstream state.
Prerequisites:
launchctl for LaunchAgent use.python3.make.npx for npm-based upstream MCP servers.Package installs:
Homebrew installs the same console scripts as the Python package. Package installs do not write MCP client config; client wiring stays an explicit Makefile action.
Docker is for container-friendly configs:
Local stdio clients and MCPB-style installs use the package-owned lifecycle:
Create the local venv, install dependencies, and verify runtime layout:
Create private config from the public template:
config-init creates the destination directory when needed and copies the public
template as the starting point. It does not import local MCP inventory, user
paths, or secrets.
Edit config/broker.private.yaml for local upstreams. Keep secret values out of config. Use environment variable names or files under:
Run the quality gate:
Validate the configured YAML contract:
Start the broker:
Check status:
For the full install flow, see docs/install.md. For a clone-to-running adoption flow, see docs/adoption-guide.md#clone-to-running-path. For shared-runtime boundaries, see docs/shared-runtime-guardrails.md. P3.8 E2E proof covers tenant isolation, authz denial, quota denial, session affinity, audit events, rollback, degraded mode, local-only routing, and shared-eligible routing. Hosted execution remains unsupported in the public local broker.
Default runtime root:
Runtime files are not repo files. Upstream OAuth state, browser state, secret files, sockets, logs, rendered client configs, backups, and daemon state belong under the runtime root.
Back up a client config:
Dry-run render:
Apply after reviewing the rendered file under $HOME/mcp/mcp-broker/renders/:
Rollback:
Use CLIENT=claude or CLIENT=agy after that profile smoke passes and that
client is intended to use the broker. For new JSON-based MCP clients, generate a
starter block:
See docs/add-profile.md for the full new-profile flow.
The compact facade keeps chat-facing profiles small:
| Tool | Purpose |
|---|---|
broker_search_tools | Search configured upstream tools by query. Results carry name, description, upstream, purpose, tags, and mutating flag; the heavy inputSchema is omitted and fetched on demand from broker_describe_tool. |
broker_describe_tool | Return schema and metadata for one upstream tool. |
broker_call_tool | Call one upstream tool through broker routing. Accepts an optional projection ({"paths": [...], "max_array_items": N}) that trims the response server-side before it reaches the client. |
broker_status | Show profile-visible upstream state, passive auth probes, and last errors without starting tools. |
broker_close_session | Release caller-owned per-session upstream processes without stopping shared upstreams or another client session. |
Codex /mcp shows the single mcp-broker entry by design. Per-upstream visibility, status, and socket path come from broker_status.
Profiles decide which upstreams a client can see and call.
Supported concepts:
max_tools protects clients from huge tool lists.compact_tools_enabled exposes broker facade tools instead of raw upstream tools.broker_tool_name_style adapts broker facade names for clients that cannot surface dotted tool names.mcp_allowed_servers renders client settings for MCP clients that require an explicit server allowlist.allow_mutating_upstreams is required before a mutating upstream can be exposed.shared mode reuses one upstream process where shared account state is acceptable.per_session mode isolates upstream state per client session.per_call mode starts a fresh stdio process for each tool call or tool-list operation, then stops that process before returning.disabled mode keeps compatibility records without exposing the upstream.Status reports active_call_count for in-flight per_call operations, including
tool discovery. It returns to zero after success, failure, or timeout cleanup.
This count is separate from session_count and is not a count of saved threads.
Reading status does not start an upstream; profile-hidden upstreams stay hidden.
Protected surfaces such as OAuth, browser state, filesystem roots, and databases require explicit config and validation. Public examples stay disabled or placeholder-based.
See docs/security-review.md and docs/upstream-compatibility-matrix.md. For a deeper safety checklist, see docs/safety.md.
The public template is config/broker.example.yaml. The matching JSON Schema is config/broker.schema.json.
Supported top-level sections:
The loader rejects unknown keys. Runtime placeholders such as {runtime.root}, {runtime.state_dir}, and {runtime.secrets_dir} can be used in upstream command, args, working directory, and env file paths.
make config-validate checks the selected CONFIG_PATH against the public JSON Schema first, then runs the runtime loader so semantic rules are enforced from the same code path the broker uses.
Each enabled upstream exposed to a profile should define a safe smoke probe:
make profile-validation PROFILE=<profile> validates every enabled upstream visible to that profile through broker_status, broker_search_tools, broker_describe_tool, and the configured safe broker_call_tool.
Repo-owned tests validate broker behavior through the local client shim. The last Codex-specific check has to run inside an active Codex session because that is where the deferred MCP wrapper tools exist.
Generate the current acceptance steps from YAML:
The target reads the configured smoke probes and prints the exact
mcp__mcp_broker__ wrapper calls for search, describe, and safe call. It does
not invoke Codex, does not call an external LLM session, and is not part of
make quality-gate.
See docs/codex-deferred-tool-acceptance.md.
Render without writing:
Apply and load:
Unload or remove:
Linux user-service install uses the same runtime root and config path contract:
For package installs, set MCP_BROKER_DAEMON_COMMAND to the installed daemon
path before applying the service.
Windows startup uses PowerShell Scheduled Task commands with the same runtime root and config path contract:
Remove it with:
Run all test tiers:
Run the public quality gate:
The coverage gate uses line and branch coverage for Python source.
Run the release gate when preparing a tag:
release-gate runs package, smoke, and mutation checks. Mutation receives a
release-scoped child count derived from LOCAL_CPU_BUDGET and
RELEASE_GATE_JOBS, so it does not take the full CPU budget while other
release children run. Mutation runs public unit and journey tests last and writes
var/quality/mutation_stats.json with total counts, score, and ranked
blocked_by_file entries. On macOS, the release gate runs mutation inside a
Linux container to avoid local mutmut fork failures. E2E tests remain in make quality-gate.
Run smoke and runtime cleanup checks:
Release or client config apply should wait for:
make quality-gatemake config-validatemake broker-smokemake release-smokemake release-gate before taggingmake doctor with no stale broker-owned resourcesSee docs/release-checklist.md.
These targets use this repo plus declared Python and Node prerequisites:
For source contributions, install gitleaks on PATH and run make hooks-install
in each checkout. The tracked pre-commit hook requires a redacted staged secret
scan, then runs commit-tier affected tests through Make. Missing scanner tools,
empty staged scope, and scan or test failures block the commit. GITLEAKS and
PYTHON select the scanner and maintainer interpreter. Installation uses Git's
worktree-specific config and refuses unknown existing hooks or common
core.worktree/bare settings that need migration. No hosted workflow is started.
make quality-gate is repo-local. It does not call personal scripts outside this repo.
make codex-deferred-acceptance is maintainer-only. It does not invoke Codex
or an external LLM session. It reads the same YAML smoke probes and prints the
exact mcp__mcp_broker__ deferred wrapper calls to run inside an active Codex
session. See docs/codex-deferred-tool-acceptance.md.
Generated reports stay under var/, especially var/coverage/, var/test-logs/, and var/quality/.
$HOME/mcp/mcp-broker.config/broker.private.yaml, which is ignored by git.