Install
$ agentstack add mcp-skymoore-codemcp Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Pipes remote content directly into a shell (remote code execution).
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ● Shell / process execution Used
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
codemcp — Meta-MCP "Code-Mode" Gateway
A single MCP server that connects to many upstream MCP servers and exposes one tool: execute_python. Agents write Python that calls every upstream tool as a typed function and transform/combine results in-process — instead of issuing many sequential MCP tool calls. SDK calls are dispatched concurrently: every call fires its request the moment it is made and blocks only when its value is actually read, so independent calls overlap automatically.
agent harness ──MCP──► codemcp gateway ──MCP clients──► upstream servers
(stdio/HTTP) (one tool: execute_python) (github, sentry, …)
│ (stdio / streamable-http)
├─ generates a typed Python SDK (1 fn per upstream tool)
├─ runs Python in a worker process
└─ SDK fns call back into the gateway → route to upstream
The agent's LLM sees only execute_python. Its description contains a short intro plus two lines per upstream tool: the full typed Python signature and a one-line summary. The SDK itself is preloaded into the Python runtime once — it is never concatenated into the per-call code string.
Why
A typical agent task ("find the open issue mentioning X, fetch its linked PR, and summarize the diff") becomes three or more round-trips through the model, each re-sending tool schemas and intermediate JSON. With codemcp the agent writes one Python snippet that calls the three tools and returns just the summary:
issue = github_search_issues(query="X", state="open")[0]
pr = github_get_pull_request(number=issue["linked_pr"])
result = {"title": pr["title"], "files_changed": len(pr["files"])}
One model turn, one tool call, only the final result returned.
Independent calls overlap automatically — both requests are on the wire before either result is read, no special syntax needed:
issues = github_search_issues(query="bug", state="open")
commits = github_list_commits(repo="codemcp", sha="main")
result = {"issue_count": issues["total_count"], "latest": commits[0]["sha"]}
Live measurement: 4 independent GitHub API calls overlap to ~1.48 s vs ~4.26 s when each result is read before the next call is made — a ~2.9× speedup on real network round-trips, with the agent writing ordinary Python.
Bench: token usage vs direct MCP
A repeatable experiment in [bench/](./bench) measures whether the design above actually saves model tokens. A standalone LangGraph agent answers three read-only questions over the GitHub user skymoore's data, under two arms that expose the identical GitHub MCP toolset (~41 tools) but differ only in how it reaches the model:
| arm | what the model sees | |---|---| | direct | all ~41 GitHub tools bound directly (large per-turn tool schema) | | codemcp | one execute_python tool whose description lists ~41 two-line signatures |
Both arms run the same model (claude-sonnet-4-6 via the OpenCode Zen Anthropic endpoint, temperature=0), the same upstream GitHub MCP server, and a fresh MCP client + (for codemcp) fresh gateway subprocess per run. Token counts are the provider's own usage figures (Anthropic usage block), summed per run. Each (task, arm) is repeated 3× for variance. Correctness is reviewed manually against ground_truth.json, computed by calling the GitHub tools directly (no LLM).
Results (18 runs, all answered correctly)
| task | direct input | codemcp input | Δinput | direct turns | codemcp turns | |---|---|---|---|---|---| | A — repo count (1 tool call) | 25,233 | 22,816 | −2,417 (−10%) | 2.0 | 2.0 | | B — most-starred repo's latest commit (2 calls) | 35,779 | 15,981 | −19,798 (−55%) | 3.0 | 3.0 | | C — most-issues repo + README check (2 calls) | 399,456 | 41,702 | −357,755 (−90%) | 3.0 | 3.7 |
Headline: on the multi-tool tasks codemcp cut input tokens 55–90%; on the 1-tool baseline it's roughly flat. cache_read was 0 across all runs (Zen returned no prompt-cache hits on these short sessions, so this is the no-caching case — a separate, larger-context run would be needed to measure caching behavior). Full per-run answers, per-turn usage, and the manual-review section are in bench/results/summary.md after a run.
> One toolset (GitHub) and three tasks — a real data point, not an exhaustive > benchmark. Re-run it and vary the tasks/toolset yourself.
Run it yourself
From the repo root:
cd bench
uv sync # install pinned deps into a local venv
uv run python ground_truth.py # compute ground_truth.json (no LLM)
uv run python runner.py --smoke # 6 runs: connectivity check
uv run python runner.py --reset --repeats 3 # full bench: 18 runs
uv run python analyze.py # -> results/summary.{md,csv}
cat results/summary.md
Prerequisites:
codemcpon yourPATH(the codemcp arm launches a fresh gateway per run).- A working Docker daemon (the GitHub MCP server runs in a container).
- An OpenCode Zen API key: sign in at https://opencode.ai/auth and run
opencode /connect once so it's stored in ~/.local/share/opencode/auth.json.
- A GitHub personal access token in
bench/.envasGITHUB_TOKEN(git-ignored;
bench/mcp.github.json uses {env:GITHUB_TOKEN}). One is already there if you cloned this setup; otherwise add your own with repo + read:org scopes.
See [bench/README.md](./bench/README.md) for the full methodology, fairness notes, and file layout.
Status
Working vertical slice over stdio and Streamable HTTP:
- Connects to upstream MCP servers (stdio + Streamable HTTP), discovers tools.
- Generates a typed Python SDK from each tool's JSON Schema.
- Exposes a single
execute_pythonMCP tool whose description carries the SDK. - Runs user Python in a persistent host CPython worker; SDK calls round-trip back
to the gateway over an authenticated WebSocket control channel and are routed to the right upstream server.
- Authenticates to OAuth-protected remote MCP servers via a full OAuth 2.1
browser flow (codemcp auth ). Tokens auto-refresh and persist across restarts.
Runs untrusted agents safely with CODEMCP_ISOLATION=DOCKER (the worker runs in a container; see [Docker isolation](#docker-isolation)). The Monty in-process sandbox and optional LLM tool summaries are still planned — see [TODO](#todo--planned-work).
Install
One-line install (prebuilt binary)
curl -fsSL https://raw.githubusercontent.com/skymoore/codemcp/main/install.sh | sh
This downloads a prebuilt binary for your OS/arch from GitHub Releases, verifies its SHA-256 checksum, and installs it to ~/.local/bin (or /usr/local/bin). Supported platforms: macOS (arm64, x8664) and Linux (arm64, x86_64).
Useful overrides:
# pin a version and/or choose the install dir
curl -fsSL https://raw.githubusercontent.com/skymoore/codemcp/main/install.sh \
| CODEMCP_VERSION=v0.1.0 CODEMCP_BIN_DIR="$HOME/bin" sh
| Variable | Purpose | | ----------------- | -------------------------------------------------- | | CODEMCP_VERSION | Release tag to install (default: latest) | | CODEMCP_BIN_DIR | Install directory | | CODEMCP_REPO | owner/repo to download from (default skymoore/codemcp) |
> opencode launches codemcp by bare name, so the install dir must be on your > PATH. The installer prints the exact line to add if it isn't.
Build from source
Requires a Rust toolchain.
make install # release build, install onto PATH (/usr/local/bin)
make install PREFIX=~/.local # install somewhere else
make uninstall # remove it
make help # list all targets
Or with cargo directly: cargo install --path ..
Feature flags:
| Feature | Default | What it adds | How to enable / disable | |---|---|---|---| | docker | on | Docker isolation backend (bollard) | Disable: --no-default-features | | tui | off | Interactive terminal UI (codemcp tui) | Enable: --features tui |
Quick start
Set up from an existing harness (opencode)
If you already have MCP servers configured in opencode, let codemcp adopt them:
codemcp setup opencode
This backs up ~/.config/opencode/opencode.json, moves its mcp section verbatim into codemcp's mcp.json, and rewrites opencode to launch a single codemcp server instead of all the individual ones. Restart opencode afterward. (codemcp must be on your PATH, since opencode launches it by bare name.) Only opencode is supported today; more harnesses can be added later.
Or configure manually
- Write a config at
~/.config/codemcp/mcp.json(XDG; override with
CODEMCP_CONFIG). The format is a subset of opencode's mcp object:
``json { "mcp": { "everything": { "type": "local", "command": ["npx", "-y", "@modelcontextprotocol/server-everything"] }, "sentry": { "type": "remote", "url": "https://mcp.sentry.dev/mcp", "headers": { "Authorization": "Bearer {env:SENTRY_TOKEN}" } }, "github": { "type": "remote", "url": "https://api.githubcopilot.com/mcp/", "tools": { "noisy_tool": { "enabled": false }, "another_tool": { "enabled": false } } }, "linear": { "type": "remote", "url": "https://mcp.linear.app/mcp", "oauth": { "scope": "read" } } } } ``
type: "local"→ stdio server launched viacommand(with optional
environment, cwd).
type: "remote"→ Streamable HTTP server aturl(with optionalheaders).- Any string value supports
{env:VAR}interpolation. "enabled": falseskips a server at boot."tools": { "": { "enabled": false } }hides individual tools by
default. Omitted tools default to enabled. See [Enabling/disabling tools](#enablingdisabling-tools-at-runtime) below.
"tools": { "": { "mutation": true|false } }overrides how a tool is
classified for write-safety. true forces it to require allow_mutations; false exempts it. Omitted → auto-classified from MCP annotations + a name-verb heuristic. See [Write safety](#write-safety-mutation-gating-dry-run-and-audit-trail).
"timeout":caps how long to wait for that upstream to spawn and
finish the MCP handshake (default 30s). An upstream that exceeds it is logged and skipped rather than blocking startup.
"oauth"controls OAuth 2.1 authentication for remote servers — see
[Authenticating remote MCP servers](#authenticating-remote-mcp-servers-oauth).
- Run the gateway:
```bash # stdio (default) — for an agent harness that launches codemcp as a subprocess codemcp
# Streamable HTTP CODEMCPTRANSPORT=http CODEMCPHTTP_BIND=127.0.0.1:3388 codemcp ```
- Point your MCP client at it. Inspect the generated SDK and tool description
without serving:
``bash CODEMCP_DUMP=1 codemcp ``
Enabling/disabling upstreams at runtime
mcp.json is the boot-time desired state. While the gateway is running you can connect or disconnect upstreams without restarting it using the admin subcommands, which talk to the running gateway over its Unix admin socket:
codemcp list # show every configured server + live status
codemcp enable github # connect 'github' now (runtime only)
codemcp disable github # disconnect 'github' now (runtime only)
NAME TYPE DEFAULT CONNECTED AUTH TOOLS
github remote yes yes authenticated 45
linear remote yes no needs_auth (Run: codemcp auth linear) 0
brave local no no n/a 0
DEFAULT= theenabledflag inmcp.json(what connects at boot).CONNECTED= whether it is connected in the running process right now.AUTH= OAuth authentication status for remote servers (see below).TOOLS= count of effective tools (connected and not individually disabled).
By default admin commands change only the live process and do not touch mcp.json. To also persist the change as the new boot default, pass --make-default (short: -d):
codemcp enable brave --make-default # connect now AND set enabled:true in mcp.json
codemcp disable github --make-default # disconnect now AND set enabled:false in mcp.json
When an upstream is enabled/disabled, codemcp regenerates the Python SDK, hot-reloads it into the running worker (no worker restart, no lost state), and sends a notifications/tools/list_changed to connected MCP clients. Clients that honor the notification re-read the updated execute_python description on their next tools/list; snapshot-once clients (which list tools only at startup) keep the old description until the next session.
> Note: --make-default rewrites mcp.json (preserving all values) and may > reorder keys alphabetically.
Enabling/disabling tools at runtime
You can enable or disable individual tools within a connected server without touching the server itself. Disabling a tool removes it from the SDK and the execute_python description — the LLM can no longer call it — while the server stays connected so its other tools keep working.
codemcp tools # list all tools across all servers
codemcp enable-tool github create_repository # expose one tool (session only)
codemcp disable-tool github delete_repository # hide one tool (session only)
codemcp disable-tool github delete_repository -d # hide AND persist to mcp.json
SERVER TOOL ON DEFAULT SESSION SUMMARY
github create_issue yes on - Create a GitHub issue
github delete_repository off off - Delete a repository
github list_commits yes on s List commits on a branch
ON= the effective state (what the model currently sees).DEFAULT= the persisted default frommcp.json(onif unset).SESSION= an in-memory override for this gateway run only (s= overridden,-= follows default).
Session vs. default:
| Flag | Effect | |---|---| | (no flag) | Change the live process only; reverts to config default on next restart. | | --make-default / -d | Also writes tools..enabled: true/false to mcp.json; persists across restarts. |
Enabling a tool on a disconnected server auto-connects it first. Disabling a tool never disconnects its server.
Authenticating remote MCP servers (OAuth)
Remote MCP servers that require OAuth 2.1 authentication are supported out of the box. codemcp auto-detects when a server requires auth — no config change needed unless the server needs a pre-registered client ID.
Auto-detection
For any remote server without an Authorization header and without oauth: false, codemcp automatically:
- Attempts a plain connection.
- If the server signals that auth is required, discovers its OAuth
authorization server metadata (RFC 8414 / RFC 9728).
- Reports the server as
needs_authincodemcp listwith a hint.
NAME TYPE DEFAULT CONNECTED AUTH TOOLS
linear remote yes no needs_auth (Run: codemcp auth linear) 0
Authenticating
codemcp auth linear # start OAuth flow → opens browser, waits for callback
codemcp auth # list all remote servers
…
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [skymoore](https://github.com/skymoore)
- **Source:** [skymoore/codemcp](https://github.com/skymoore/codemcp)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.