mcpgate is a companion runtime for mcp2cli-style, CLI-first MCP use. It keeps persistent stdio MCP sessions behind a small local daemon, avoiding the roughly 1.7 seconds of cold per-process startup paid for each discovery, help, or call; repeated warm operations typically take about 2–10 ms. It also adds the guardrails missing when agents invoke MCP through a shell: read-only mutation-name rejection, optional exact tool allowlists, and metadata-only audit trails. In a Codex CLI benchmark with a 150-tool catalog, CLI-first discovery used 31% fewer tokens than naive MCP catalog injection while retaining 100% task success. mcpgate is not an MCP replacement and is not an mcp2cli clone.
Python 3.11 or newer and uv are required:
uv tool install .For development from a checkout:
uv sync
uv run mcpgate --helpCreate ~/.config/mcpgate/targets.toml:
[targets.local_docs]
command = "uv"
args = ["run", "--directory", "/absolute/path/to/server", "python", "-m", "docs_server"]
read_only = true
allow_tools = ["search_documents", "read_document", "server_info"]
idle_timeout_minutes = 15The command must start an MCP server using stdio. Then use the compact workflow:
mcpgate list local_docs
mcpgate describe local_docs search_documents
mcpgate call local_docs search_documents --args '{"query":"safety"}'
mcpgate audit --lines 10list, describe, and call start the daemon automatically. Explicit lifecycle
commands are also available:
mcpgate daemon start
mcpgate daemon status
mcpgate daemon stopSet MCPGATE_CONFIG, MCPGATE_STATE_DIR, or MCPGATE_SOCKET to override the
config file, state directory, or Unix socket. MCPGATE_BRIDGE_DIR enables the
filesystem bridge described below.
Some agent shells cannot reach the Unix socket. In measured Codex CLI
workspace-write sandboxing, filesystem access is the only usable host IPC:
| Channel from the sandbox | Measured result |
|---|---|
| Nested stdio MCP server spawn | Blocked |
| Unix-domain socket connect | Blocked with PermissionError: Operation not permitted |
Internet socket creation (AF_INET socket()) |
Blocked with the same error |
| Files in the shared workspace | Read/write allowed |
The last row is what the file bridge uses. The host daemon still owns the stdio
MCP processes and also keeps serving its Unix socket. A sandbox client publishes
a complete JSON request by atomic rename under requests/; the daemon polls it,
runs it through the same session, policy, and audit path as a socket request,
then atomically publishes the response under responses/.
Start the daemon in a normal host shell. Put the bridge inside the workspace shared with the agent:
export MCPGATE_CONFIG=/absolute/path/to/targets.toml
export MCPGATE_STATE_DIR=/absolute/path/to/mcpgate-state
export MCPGATE_SOCKET=/absolute/path/to/mcpgate-state/daemon.sock
export MCPGATE_BRIDGE_DIR=/absolute/path/to/agent-workspace/.mcpgate-bridge
mcpgate daemon start
mcpgate daemon statusThen, in a Codex or Claude Code sandboxed shell, only the bridge variable and a visible mcpgate binary path are needed. The client must not be given the target configuration, state directory, or socket:
export MCPGATE_BRIDGE_DIR=/workspace/.mcpgate-bridge
/absolute/path/to/mcpgate list local_docs
/absolute/path/to/mcpgate describe local_docs search_documents
/absolute/path/to/mcpgate call local_docs search_documents \
--args '{"query":"safety"}'If no bridge daemon is reachable and the sandbox has no config, the client
instructs you to start it on the host. Stop it from either environment with
mcpgate daemon stop; daemon status lists the active unix and
file_bridge transports.
Bridge requests temporarily contain the raw call arguments because they are the
IPC message, so use a dedicated bridge directory inside a workspace trusted by
the same user. Its requests, responses, and dead subdirectories are mode
0700, and a normal client deletes request and response artifacts after
completion. The bridge is not an authorization boundary and does not expand
mcpgate's single-user scope. An agent that forges a request file gains no
operation beyond what the CLI already exposes: every request still reaches the
daemon's existing target session, pre-call policy enforcement, safe error
mapping, and metadata-only audit writer. Raw arguments and results are never
written to the audit log.
read_only defaults to false, including under an optional [defaults] table.
When it is true, mcpgate rejects tokenized mutation-shaped tool names such as
create_issue, run_query, or delete-record. Names such as server_info are
not false positives. If allow_tools is present, every call must also match that
exact allowlist, regardless of read_only. Rejections happen in the daemon
before the MCP request is sent and return an explicit policy error.
This is a fail-closed name and allowlist gate, not semantic verification of server behavior. A deceptively named tool or a compromised server can still mutate data. For meaningful read-only safety, use a narrow allowlist, trusted local commands, and read-only credentials or OS permissions at the server.
The append-only audit log is
~/.local/state/mcpgate/audit.jsonl, or
$MCPGATE_STATE_DIR/audit.jsonl. It records exactly one entry for each
list, describe, and call: timestamp, operation, target, tool name,
duration, serialized result byte count and SHA-256 digest, status, and error
class. It never records raw arguments, results, environment variables, secrets,
or config contents.
mcpgate skill local_docs > SKILL.mdThe generated short skill teaches agents to discover with list, inspect only
the selected tool with describe, and then invoke it with call.
- mcpgate is not an MCP replacement or an mcp2cli clone.
- v0.1 supports local stdio MCP servers only—no HTTP or SSE.
- The file bridge is client-to-daemon IPC; MCP targets remain stdio-only.
- v0.1 is a single-user local daemon with no authentication.
- It does not adapt OpenAPI or GraphQL services.
Tests are local and offline:
uv sync
uv run pytest -qSee AGENTS.md for invariants and docs/test-plan.md for the full quality gate.