@geohar/pi-mcp-combiner
Pi extension: run the mcp-combiner MCP aggregator via sharedserver and inject its tool-discovery directive. Pairs with pi-mcp-adapter, which makes the combiner's tools reachable from Pi.
Package details
Install @geohar/pi-mcp-combiner from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@geohar/pi-mcp-combiner- Package
@geohar/pi-mcp-combiner- Version
0.13.3- Published
- Aug 21, 2026
- Downloads
- 863/mo · 863/wk
- Author
- georgeharker
- License
- MIT
- Types
- extension
- Size
- 118.2 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@geohar/pi-mcp-combiner
A Pi extension that makes the mcp-combiner MCP aggregator
available to Pi: it starts the combiner (supervised by
sharedserver) and appends the
combiner's tool-discovery directive to the system prompt.
It is the Pi counterpart of the
Claude Code
and
OpenCode
plugins, and shares the same combiner and the same sharedserver instance — so Pi,
Claude Code, OpenCode, and Neovim can all talk to one refcounted combiner process.
How it fits together
Pi has no MCP of its own. Two pieces give it the combiner:
pi-mcp-adapter— the Pi package that actually speaks MCP. It reads its ownmcp.jsonand connects to the combiner over HTTP. This extension does not replace it — you install both.- This extension — the process + instructions half, mirroring the sibling plugins:
- Run the combiner — on
session_startit drivessharedserver use <name> --pid <pi-pid> --grace-period <g> \ -- <combiner> --mcp --config <servers.json> --port <port>sharedserverrefcounts by PID with a grace period, so the combiner is shared across clients and outlives any single one. It releases the refcount onsession_shutdown— but only whenreason === "quit", since reload/resume/fork keep the same Pi process and a freshsession_startre-attaches. - Inject instructions — on
before_agent_startit appends the combiner's<server>_-prefix / "discover before assuming" directive to the system prompt. The combiner also serves the same text as its MCPinstructions, whichpi-mcp-adaptersurfaces on connect, so this is a belt to those braces.
- Run the combiner — on
sharedserver itself is fetched automatically if it is not already installed (a pinned
release via the cargo-dist installer — no Rust toolchain needed), using the exact same
resolver as the other two plugins.
Requirements
pi-mcp-adapterinstalled in Pi:pi install npm:pi-mcp-adapter.mcp-combineravailable as a command (uv tool install mcp-combiner), or justuvon PATH — a pinned release is fetched from PyPI on demand. Requires combiner ≥ 0.8.0 (the--mcpserve flag; version-gated automatically).- A combiner
servers.json(see auto-probe locations below).
Install
1. Point pi-mcp-adapter at the combiner
Drop the combiner entry into one of pi-mcp-adapter's mcp.json locations — e.g.
project-local .pi/mcp.json, or global ~/.config/mcp/mcp.json:
{
"mcpServers": {
"mcp-combiner": {
"url": "http://127.0.0.1:9741/mcp",
"auth": "bearer",
"bearerTokenEnv": "MCP_COMBINER_AUTH_TOKEN"
}
}
}
Or let the extension write it for you — /mcp-combiner install-config merges
exactly this entry into ~/.config/mcp/mcp.json (or a path you pass), preserving
any other servers and any existing url you set. (It only writes when you ask —
the extension never edits the adapter's config on its own.)
"auth": "bearer" with "bearerTokenEnv" is the correct single pairing — it does
two jobs at once:
- Sends the token.
pi-mcp-adapteronly attachesAuthorization: Bearer …whenauth === "bearer"(server-manager.ts);bearerTokenEnvnames the env var it reads at connect. So if you lock the combiner down with an inbound bearer (MCP_COMBINER_AUTH_TOKEN, see the combiner README), the header is presented — nothing is written to disk. Note:bearerTokenEnvalone, or withauth: false, does not send the header — the adapter gates it onauth === "bearer". - Suppresses OAuth.
auth: "bearer"also makes the adapter'ssupportsOAuth()false, so a wrong/missing-token 401 surfaces as an honest error instead of the spuriousFailed to start OAuth … DCR rejected (HTTP 404)probe.
Harmless when the combiner is open: the env var is unset, so no header is sent, the endpoint returns 200, and OAuth still never fires. So this one entry is correct whether or not inbound auth is enabled.
(See mcp.json.example.) To give this Pi instance its own chat
identity toward the combiner — parking its isolated upstream sessions separately — add a
path token: "url": "http://127.0.0.1:9741/mcp/pi-<yourname>". The combiner gives URL
tokens first priority.
If you edit mcp.json while Pi is running, run /reload (or mcp({ connect: "mcp-combiner" })) so the adapter re-reads it.
2. Install this extension
Any of Pi's extension-loading mechanisms — all support a local directory:
# a) drop-in package dir (uses "main": dist/index.js — run `npm run build` first)
npm --prefix plugins/pi run build
ln -s "$PWD/plugins/pi" ~/.pi/agent/extensions/mcp-combiner
# b) one-off, build-free live dev — Pi loads the TS source directly
pi -e ./plugins/pi/src/index.ts
# c) settings.json — list a local path under "extensions"
# { "extensions": ["/abs/path/to/mcp-companion/plugins/pi/src/index.ts"] }
# d) published package — list under "packages" in settings.json
# { "packages": ["@geohar/pi-mcp-combiner@latest"] }
Configuration
Everything is via the PI_MCP_COMBINER_* environment namespace, which mirrors the Claude
plugin's CLAUDE_MCP_COMBINER_* and the OpenCode plugin's OPENCODE_MCP_COMBINER_* — so
running several clients means one namespace per client.
| Variable | Default | Effect |
|---|---|---|
PI_MCP_COMBINER_PORT |
9741 |
HTTP port the combiner serves on. |
PI_MCP_COMBINER_HOST |
127.0.0.1 |
HTTP host the combiner binds. |
PI_MCP_COMBINER_CONFIG |
(auto-probed) | Path to the combiner's servers.json. |
PI_MCP_COMBINER_COMMAND / _ARGS |
(auto-resolved) | Override the combiner invocation. |
PI_MCP_COMBINER_CHECKOUT |
— | Checkout for uv run --project <checkout> python -m mcp_combiner. |
PI_MCP_COMBINER_NAME |
mcp-combiner |
sharedserver instance name. |
PI_MCP_COMBINER_GRACE |
30m |
sharedserver grace period. |
PI_MCP_COMBINER_LOG |
~/.local/state/mcp-combiner/mcp-combiner.log |
Capture the combiner's stdout/stderr; "none" disables. |
PI_MCP_COMBINER_PYLOG |
~/.local/state/mcp-combiner/mcp-combiner-py.log |
The combiner's own --log-file; "none" disables. |
PI_MCP_COMBINER_LOG_LEVEL |
info |
The combiner's --log-level. |
PI_MCP_COMBINER_MANAGE |
true |
false → don't launch (assume the combiner runs elsewhere); instructions only. |
PI_MCP_COMBINER_INSTRUCTIONS |
true |
false → don't append the directive to the system prompt. |
PI_MCP_COMBINER_NOTIFY |
true |
false → don't surface attach/health messages via the Pi UI. |
SHAREDSERVER_BIN |
(auto-resolved) | Path to the sharedserver binary. |
SHAREDSERVER_LOCKDIR |
— | sharedserver lock directory. |
servers.json auto-probe
$PI_MCP_COMBINER_CONFIG → ~/.cache/secrets/<user>.mcpservers.json →
~/.config/mcp-combiner/servers.json → ~/.config/mcp/servers.json.
Combiner command resolution
$PI_MCP_COMBINER_COMMAND (+$PI_MCP_COMBINER_ARGS) → mcp-combiner on PATH (only
when ≥ 0.8.0) → uv run --project <checkout> python -m mcp_combiner → a pinned release
via uvx. If the mcp-combiner on PATH is older than 0.8.0, the extension warns and
falls back to a pinned uvx release rather than silently using a stale binary.
Host-owned mode
If $MCP_COMPANION_COMBINER_URL is set, an editor/host (e.g. Neovim) already owns
and refcounts the combiner. The extension then only injects instructions and never
starts or stops the process — the same early-exit as the sibling plugins. Equivalent to
PI_MCP_COMBINER_MANAGE=false.
Development
npm install
npm run typecheck
npm run build # emits dist/ (not committed; built on publish)
The src/sharedserver-resolve.ts file is vendored byte-identical from
georgeharker/sharedserver (via
scripts/sync-vendored.sh), shared with the OpenCode plugin so both answer "which
sharedserver, and why" identically. Edit it upstream; re-sync here.
License
MIT © George Harker