@geohar/pi-mcp-combiner 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George Harker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @geohar/pi-mcp-combiner
2
+
3
+ A [Pi](https://pi.dev) extension that makes the **`mcp-combiner`** MCP aggregator
4
+ available to Pi: it starts the combiner (supervised by
5
+ [`sharedserver`](https://github.com/georgeharker/sharedserver)) and appends the
6
+ combiner's tool-discovery directive to the system prompt.
7
+
8
+ It is the Pi counterpart of the
9
+ [Claude Code](https://github.com/georgeharker/mcp-companion/tree/main/plugins/claude)
10
+ and
11
+ [OpenCode](https://github.com/georgeharker/mcp-companion/tree/main/plugins/opencode)
12
+ plugins, and shares the same combiner and the same `sharedserver` instance — so Pi,
13
+ Claude Code, OpenCode, and Neovim can all talk to **one** refcounted combiner process.
14
+
15
+ ## How it fits together
16
+
17
+ Pi has no MCP of its own. Two pieces give it the combiner:
18
+
19
+ 1. **[`pi-mcp-adapter`](https://pi.dev/packages/pi-mcp-adapter)** — the Pi package that
20
+ actually speaks MCP. It reads its own `mcp.json` and connects to the combiner over
21
+ HTTP. **This extension does not replace it — you install both.**
22
+ 2. **This extension** — the process + instructions half, mirroring the sibling plugins:
23
+ - **Run the combiner** — on `session_start` it drives
24
+ ```
25
+ sharedserver use <name> --pid <pi-pid> --grace-period <g> \
26
+ -- <combiner> --mcp --config <servers.json> --port <port>
27
+ ```
28
+ `sharedserver` refcounts by PID with a grace period, so the combiner is shared
29
+ across clients and outlives any single one. It releases the refcount on
30
+ `session_shutdown` — but only when `reason === "quit"`, since reload/resume/fork
31
+ keep the same Pi process and a fresh `session_start` re-attaches.
32
+ - **Inject instructions** — on `before_agent_start` it appends the combiner's
33
+ `<server>_`-prefix / "discover before assuming" directive to the system prompt. The
34
+ combiner also serves the same text as its MCP `instructions`, which `pi-mcp-adapter`
35
+ surfaces on connect, so this is a belt to those braces.
36
+
37
+ `sharedserver` itself is fetched automatically if it is not already installed (a pinned
38
+ release via the cargo-dist installer — no Rust toolchain needed), using the exact same
39
+ resolver as the other two plugins.
40
+
41
+ ## Requirements
42
+
43
+ - **`pi-mcp-adapter`** installed in Pi: `pi install npm:pi-mcp-adapter`.
44
+ - **`mcp-combiner`** available as a command (`uv tool install mcp-combiner`), or just
45
+ **`uv`** on PATH — a pinned release is fetched from PyPI on demand. Requires combiner
46
+ ≥ 0.8.0 (the `--mcp` serve flag; version-gated automatically).
47
+ - A combiner **`servers.json`** (see auto-probe locations below).
48
+
49
+ ## Install
50
+
51
+ ### 1. Point `pi-mcp-adapter` at the combiner
52
+
53
+ Drop the combiner entry into one of `pi-mcp-adapter`'s `mcp.json` locations — e.g.
54
+ project-local `.pi/mcp.json`, or global `~/.config/mcp/mcp.json`:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "mcp-combiner": {
60
+ "url": "http://127.0.0.1:9741/mcp"
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ (See [`mcp.json.example`](./mcp.json.example).) To give this Pi instance its own chat
67
+ identity toward the combiner — parking its isolated upstream sessions separately — add a
68
+ path token: `"url": "http://127.0.0.1:9741/mcp/pi-<yourname>"`. The combiner gives URL
69
+ tokens first priority.
70
+
71
+ If you edit `mcp.json` while Pi is running, run `/reload` (or `mcp({ connect:
72
+ "mcp-combiner" })`) so the adapter re-reads it.
73
+
74
+ ### 2. Install this extension
75
+
76
+ Any of Pi's extension-loading mechanisms — all support a **local directory**:
77
+
78
+ ```sh
79
+ # a) drop-in package dir (uses "main": dist/index.js — run `npm run build` first)
80
+ npm --prefix plugins/pi run build
81
+ ln -s "$PWD/plugins/pi" ~/.pi/agent/extensions/mcp-combiner
82
+
83
+ # b) one-off, build-free live dev — Pi loads the TS source directly
84
+ pi -e ./plugins/pi/src/index.ts
85
+
86
+ # c) settings.json — list a local path under "extensions"
87
+ # { "extensions": ["/abs/path/to/mcp-companion/plugins/pi/src/index.ts"] }
88
+
89
+ # d) published package — list under "packages" in settings.json
90
+ # { "packages": ["@geohar/pi-mcp-combiner@latest"] }
91
+ ```
92
+
93
+ ## Configuration
94
+
95
+ Everything is via the `PI_MCP_COMBINER_*` environment namespace, which mirrors the Claude
96
+ plugin's `CLAUDE_MCP_COMBINER_*` and the OpenCode plugin's `OPENCODE_MCP_COMBINER_*` — so
97
+ running several clients means one namespace per client.
98
+
99
+ | Variable | Default | Effect |
100
+ |----------|---------|--------|
101
+ | `PI_MCP_COMBINER_PORT` | `9741` | HTTP port the combiner serves on. |
102
+ | `PI_MCP_COMBINER_HOST` | `127.0.0.1` | HTTP host the combiner binds. |
103
+ | `PI_MCP_COMBINER_CONFIG` | *(auto-probed)* | Path to the combiner's `servers.json`. |
104
+ | `PI_MCP_COMBINER_COMMAND` / `_ARGS` | *(auto-resolved)* | Override the combiner invocation. |
105
+ | `PI_MCP_COMBINER_CHECKOUT` | — | Checkout for `uv run --project <checkout> python -m mcp_combiner`. |
106
+ | `PI_MCP_COMBINER_NAME` | `mcp-combiner` | `sharedserver` instance name. |
107
+ | `PI_MCP_COMBINER_GRACE` | `30m` | `sharedserver` grace period. |
108
+ | `PI_MCP_COMBINER_LOG` | `~/.local/state/mcp-combiner/mcp-combiner.log` | Capture the combiner's stdout/stderr; `"none"` disables. |
109
+ | `PI_MCP_COMBINER_PYLOG` | `~/.local/state/mcp-combiner/mcp-combiner-py.log` | The combiner's own `--log-file`; `"none"` disables. |
110
+ | `PI_MCP_COMBINER_LOG_LEVEL` | `info` | The combiner's `--log-level`. |
111
+ | `PI_MCP_COMBINER_MANAGE` | `true` | `false` → don't launch (assume the combiner runs elsewhere); instructions only. |
112
+ | `PI_MCP_COMBINER_INSTRUCTIONS` | `true` | `false` → don't append the directive to the system prompt. |
113
+ | `PI_MCP_COMBINER_NOTIFY` | `true` | `false` → don't surface attach/health messages via the Pi UI. |
114
+ | `SHAREDSERVER_BIN` | *(auto-resolved)* | Path to the `sharedserver` binary. |
115
+ | `SHAREDSERVER_LOCKDIR` | — | `sharedserver` lock directory. |
116
+
117
+ ### `servers.json` auto-probe
118
+
119
+ `$PI_MCP_COMBINER_CONFIG` → `~/.cache/secrets/<user>.mcpservers.json` →
120
+ `~/.config/mcp-combiner/servers.json` → `~/.config/mcp/servers.json`.
121
+
122
+ ### Combiner command resolution
123
+
124
+ `$PI_MCP_COMBINER_COMMAND` (+`$PI_MCP_COMBINER_ARGS`) → `mcp-combiner` on `PATH` (only
125
+ when ≥ 0.8.0) → `uv run --project <checkout> python -m mcp_combiner` → a pinned release
126
+ via `uvx`. If the `mcp-combiner` on PATH is older than 0.8.0, the extension warns and
127
+ falls back to a pinned `uvx` release rather than silently using a stale binary.
128
+
129
+ ## Host-owned mode
130
+
131
+ If **`$MCP_COMPANION_COMBINER_URL`** is set, an editor/host (e.g. Neovim) already owns
132
+ and refcounts the combiner. The extension then **only injects instructions** and never
133
+ starts or stops the process — the same early-exit as the sibling plugins. Equivalent to
134
+ `PI_MCP_COMBINER_MANAGE=false`.
135
+
136
+ ## Development
137
+
138
+ ```sh
139
+ npm install
140
+ npm run typecheck
141
+ npm run build # emits dist/ (not committed; built on publish)
142
+ ```
143
+
144
+ The `src/sharedserver-resolve.ts` file is **vendored byte-identical** from
145
+ [`georgeharker/sharedserver`](https://github.com/georgeharker/sharedserver) (via
146
+ `scripts/sync-vendored.sh`), shared with the OpenCode plugin so both answer "which
147
+ sharedserver, and why" identically. Edit it upstream; re-sync here.
148
+
149
+ ## License
150
+
151
+ MIT © George Harker
@@ -0,0 +1,3 @@
1
+ import type { ExtensionAPI } from "./pi.js";
2
+ export default function mcpCombiner(pi: ExtensionAPI): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAER,YAAY,EAIf,MAAM,SAAS,CAAA;AA6NhB,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+I1D"}
package/dist/index.js ADDED
@@ -0,0 +1,419 @@
1
+ // Pi extension: run the `mcp-combiner` MCP aggregator via the `sharedserver` CLI so it
2
+ // is available to Pi through `pi-mcp-adapter`.
3
+ //
4
+ // Pi has no MCP of its own; `pi-mcp-adapter` (a separate Pi package) is what actually
5
+ // talks MCP, reading its own `mcp.json`. This extension is the OTHER half — the exact
6
+ // counterpart of the process side of the Claude Code and OpenCode plugins:
7
+ //
8
+ // 1. Process — drive `sharedserver use … -- <combiner> --mcp --config … --port …` so
9
+ // the combiner is running and refcounted, SHARED with any other client (Claude
10
+ // Code, OpenCode, Neovim) that uses the same sharedserver name. Launched in
11
+ // `session_start` (Pi forbids background startup from the factory) and released in
12
+ // `session_shutdown` — but only on `reason === "quit"`, since reload/new/resume/
13
+ // fork keep the same Pi process alive and a fresh `session_start` re-attaches.
14
+ // 2. Instructions — append the combiner's tool-discovery directive to the system
15
+ // prompt via `before_agent_start` (analogue of the CC plugin's SessionStart
16
+ // additionalContext and the OpenCode plugin's system.transform hook). The combiner
17
+ // also serves the same text as its MCP `instructions`, which pi-mcp-adapter
18
+ // surfaces on connect — this is the guaranteed, client-native belt to that braces.
19
+ //
20
+ // Registration itself (pointing pi-mcp-adapter at the combiner) is a single `mcp.json`
21
+ // entry — see mcp.json.example and the README. That is static, CC-style, by design;
22
+ // this extension deliberately does not write another extension's config.
23
+ //
24
+ // The sharedserver resolution/fetch and the combiner command ladder are ported
25
+ // faithfully from plugins/opencode/src/index.ts — same floor, same warnings, same
26
+ // degrade-rather-than-die behaviour, so a user running several clients gets one answer
27
+ // to "which combiner / which sharedserver am I on, and why".
28
+ import { spawnSync } from "node:child_process";
29
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
30
+ import { homedir } from "node:os";
31
+ import { dirname, join } from "node:path";
32
+ import { fileURLToPath } from "node:url";
33
+ import { resolveSharedserver } from "./sharedserver-resolve.js";
34
+ const DEFAULT_PORT = 9741;
35
+ const DEFAULT_NAME = "mcp-combiner";
36
+ const DEFAULT_GRACE = "30m";
37
+ // The `--mcp` serve flag arrived in combiner 0.8.0; older versions serve with a bare
38
+ // `--config`. That boundary is also the floor for trusting a PATH install — below it we
39
+ // fetch a known-good release via uvx rather than limp along. NOT the lockstep release
40
+ // version; it moves only when this extension depends on newer combiner behaviour.
41
+ const MIN_COMBINER_VERSION = [0, 8, 0];
42
+ // Floor-only against sharedserver's latest release: this repo consumes sharedserver
43
+ // rather than shipping it. Kept equal to the OpenCode plugin's value (they resolve the
44
+ // same binary the same way).
45
+ const SHAREDSERVER_MIN_VERSION = "0.6.7";
46
+ // ── the tool-discovery directive ───────────────────────────────────
47
+ // Appended to the system prompt so the agent knows combined tools arrive under a
48
+ // `<server>_` prefix (and, through pi-mcp-adapter, are reached via its `mcp()` proxy or
49
+ // promoted `directTools`), and looks before deciding a capability is absent. Canonical
50
+ // source: CLAUDE.md.example at the repo root; a release-time `prepack` copies it to this
51
+ // package's root as instructions.txt (see package.json). A dev/unbuilt run without the
52
+ // copy falls back to empty and simply injects nothing.
53
+ const COMBINER_DIRECTIVE = (() => {
54
+ try {
55
+ const here = dirname(fileURLToPath(import.meta.url));
56
+ // dist/index.js and the packed instructions.txt both sit one level up from here
57
+ // when built (dist/), and the source layout mirrors it (src/ → package root).
58
+ return readFileSync(join(here, "..", "instructions.txt"), "utf8");
59
+ }
60
+ catch {
61
+ return "";
62
+ }
63
+ })();
64
+ // First line of the directive — used to detect an already-appended prompt so repeated
65
+ // `before_agent_start` turns do not stack duplicate copies.
66
+ const DIRECTIVE_MARKER = COMBINER_DIRECTIVE.split("\n", 1)[0] ?? "";
67
+ // ── env configuration ──────────────────────────────────────────────
68
+ // The PI_MCP_COMBINER_* namespace mirrors the Claude plugin's CLAUDE_MCP_COMBINER_* and
69
+ // the OpenCode plugin's OPENCODE_MCP_COMBINER_*, so a user running several clients keeps
70
+ // one namespace per client rather than options in one place and env vars in another.
71
+ function env(name) {
72
+ const v = process.env[name];
73
+ return v !== undefined && v !== "" ? v : undefined;
74
+ }
75
+ function splitArgs(value) {
76
+ if (!value)
77
+ return [];
78
+ return value.split(/\s+/).filter((s) => s.length > 0);
79
+ }
80
+ function parseVersion(text) {
81
+ const m = text.match(/(\d+)\.(\d+)\.(\d+)/);
82
+ if (!m)
83
+ return undefined;
84
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
85
+ }
86
+ function gte(a, b) {
87
+ for (let i = 0; i < 3; i++) {
88
+ if (a[i] !== b[i])
89
+ return a[i] > b[i];
90
+ }
91
+ return true;
92
+ }
93
+ /** Probe a command once for BOTH facts: is it runnable, and which version. Presence is
94
+ * "spawn did not fail", NOT "exited 0" — a pre-0.8.0 combiner exits non-zero on
95
+ * `--version` (the flag did not exist), which is exactly the stale install the floor
96
+ * exists to catch. Probes with ONLY the structural args; the user's extras are never
97
+ * folded in, so one arg the CLI rejects cannot make a healthy install look absent. */
98
+ function probe(cmd) {
99
+ const r = spawnSync(cmd.cmd, [...cmd.args, "--version"], { env: process.env });
100
+ if (r.error)
101
+ return { present: false };
102
+ if (r.status !== 0)
103
+ return { present: true };
104
+ return { present: true, version: parseVersion(`${r.stdout?.toString() ?? ""}${r.stderr?.toString() ?? ""}`) };
105
+ }
106
+ function probeUsable(cmd) {
107
+ const { version } = probe(cmd);
108
+ if (!version || !gte(version, MIN_COMBINER_VERSION))
109
+ return undefined;
110
+ return { ...cmd, version };
111
+ }
112
+ /** This extension's own version, kept equal to the published PyPI mcp-combiner by
113
+ * lockstep releases (scripts/bump-version.sh). Used as the uvx pin — read from the
114
+ * manifest, never duplicated as a constant. */
115
+ const PLUGIN_VERSION = (() => {
116
+ try {
117
+ const here = dirname(fileURLToPath(import.meta.url));
118
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
119
+ return pkg.version;
120
+ }
121
+ catch {
122
+ return undefined;
123
+ }
124
+ })();
125
+ /** Resolve how to invoke the combiner, mirroring the sibling plugins' priority:
126
+ * explicit env command → `mcp-combiner` on PATH (only when new enough) →
127
+ * `uv run --project <checkout> python -m mcp_combiner` → a pinned release via uvx. The
128
+ * uvx tail is what makes a bare install work with nothing installed by hand. */
129
+ function resolveCombiner(log) {
130
+ const extra = splitArgs(env("PI_MCP_COMBINER_ARGS"));
131
+ const command = env("PI_MCP_COMBINER_COMMAND");
132
+ if (command) {
133
+ return { cmd: command, args: [], extra };
134
+ }
135
+ const onPath = { cmd: "mcp-combiner", args: [] };
136
+ const pathProbe = probe(onPath);
137
+ if (pathProbe.present) {
138
+ if (pathProbe.version && gte(pathProbe.version, MIN_COMBINER_VERSION)) {
139
+ return { ...onPath, extra, version: pathProbe.version };
140
+ }
141
+ log("warn", `mcp-combiner on PATH reports ${pathProbe.version?.join(".") ?? "a pre-0.8.0 version"}, older than ` +
142
+ `${MIN_COMBINER_VERSION.join(".")} — ignoring it and fetching a pinned release instead. ` +
143
+ "Upgrade with: uv tool install --upgrade mcp-combiner");
144
+ }
145
+ const checkout = env("PI_MCP_COMBINER_CHECKOUT");
146
+ if (checkout && existsSync(checkout) && probe({ cmd: "uv", args: [] }).present) {
147
+ return { cmd: "uv", args: ["run", "--project", checkout, "python", "-m", "mcp_combiner"], extra };
148
+ }
149
+ // Out-of-the-box path: no install required, fetch from PyPI. Pinned to this
150
+ // extension's version so the pair moves in lockstep; if that exact release is
151
+ // missing, fall back to latest rather than failing. The probe both validates the pin
152
+ // and warms uv's cache, so the real spawn is a cache hit.
153
+ if (probe({ cmd: "uvx", args: [] }).present) {
154
+ if (PLUGIN_VERSION) {
155
+ const pinned = probeUsable({ cmd: "uvx", args: [`mcp-combiner@${PLUGIN_VERSION}`] });
156
+ if (pinned)
157
+ return { ...pinned, extra };
158
+ log("warn", `pinned mcp-combiner@${PLUGIN_VERSION} unavailable or too old; trying latest from PyPI`);
159
+ }
160
+ const latest = probeUsable({ cmd: "uvx", args: ["mcp-combiner"] });
161
+ if (latest)
162
+ return { ...latest, extra };
163
+ }
164
+ return undefined;
165
+ }
166
+ /** True if the resolved combiner supports (needs) the `--mcp` serve flag. Unknown
167
+ * version → assume NO: a version we cannot read is a pre-0.8.0 build serving on a bare
168
+ * `--config`, and a bare `--config` still serves on newer releases (with a deprecation
169
+ * warning), so guessing low degrades gracefully whereas guessing high does not. */
170
+ function combinerNeedsMcpFlag(cmd) {
171
+ const ver = cmd.version ?? probe({ cmd: cmd.cmd, args: cmd.args }).version;
172
+ return ver ? gte(ver, MIN_COMBINER_VERSION) : false;
173
+ }
174
+ // ── servers.json resolution (mirrors the sibling plugins) ──
175
+ function resolveConfig(log) {
176
+ const explicit = env("PI_MCP_COMBINER_CONFIG");
177
+ if (explicit)
178
+ return explicit;
179
+ const user = process.env.USER ?? process.env.LOGNAME ?? "";
180
+ const candidates = [
181
+ join(homedir(), ".cache", "secrets", `${user}.mcpservers.json`),
182
+ join(homedir(), ".config", "mcp-combiner", "servers.json"),
183
+ join(homedir(), ".config", "mcp", "servers.json"),
184
+ ];
185
+ const found = candidates.find(existsSync);
186
+ if (!found) {
187
+ log("error", "no combiner servers.json found; set $PI_MCP_COMBINER_CONFIG (probed " +
188
+ "~/.cache/secrets/<user>.mcpservers.json, ~/.config/mcp-combiner/servers.json, " +
189
+ "~/.config/mcp/servers.json)");
190
+ }
191
+ return found;
192
+ }
193
+ let attachment = null;
194
+ let cleanupInstalled = false;
195
+ function installProcessCleanup() {
196
+ if (cleanupInstalled)
197
+ return;
198
+ cleanupInstalled = true;
199
+ // Belt to the session_shutdown("quit") braces: if Pi is killed hard enough that
200
+ // session_shutdown never fires, still release the refcount. Idempotent via `detach`.
201
+ process.on("exit", () => detach());
202
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
203
+ process.on(sig, () => {
204
+ detach();
205
+ process.kill(process.pid, sig);
206
+ });
207
+ }
208
+ }
209
+ function detach() {
210
+ if (!attachment)
211
+ return;
212
+ const { binary, name } = attachment;
213
+ attachment = null;
214
+ spawnSync(binary, ["unuse", name, "--pid", String(process.pid)], { stdio: "ignore", env: process.env });
215
+ }
216
+ // ── the extension ──────────────────────────────────────────────────
217
+ export default function mcpCombiner(pi) {
218
+ const notify = env("PI_MCP_COMBINER_NOTIFY") !== "false";
219
+ const wantInstructions = env("PI_MCP_COMBINER_INSTRUCTIONS") !== "false";
220
+ const manage = env("PI_MCP_COMBINER_MANAGE") !== "false";
221
+ // Host-owned mode: a set $MCP_COMPANION_COMBINER_URL means an editor/host (e.g.
222
+ // Neovim) already owns and refcounts the combiner. Then we NEVER launch — only the
223
+ // instructions half runs. The host never sets a port var, mirroring the sibling
224
+ // plugins' distinction.
225
+ const hostOwned = env("MCP_COMPANION_COMBINER_URL") !== undefined && env("PI_MCP_COMBINER_PORT") === undefined;
226
+ // ── instructions: appended every turn (analogue of the sibling plugins) ──
227
+ pi.on("before_agent_start", (event) => {
228
+ if (!wantInstructions || !COMBINER_DIRECTIVE)
229
+ return;
230
+ // Do not stack duplicates across turns: the chained prompt carries forward.
231
+ if (DIRECTIVE_MARKER && event.systemPrompt.includes(DIRECTIVE_MARKER))
232
+ return;
233
+ return { systemPrompt: `${event.systemPrompt}\n\n${COMBINER_DIRECTIVE}` };
234
+ });
235
+ // ── /mcp-combiner command: inspect the extension (verb: system-prompt) ──
236
+ pi.registerCommand("mcp-combiner", {
237
+ description: "mcp-combiner extension — verb: system-prompt (show the injected directive)",
238
+ getArgumentCompletions: (prefix) => completeVerbs(prefix),
239
+ handler: (args, ctx) => {
240
+ const verb = args.trim();
241
+ if (verb === "" || verb === "system-prompt") {
242
+ showDirective(ctx, "mcp-combiner", COMBINER_DIRECTIVE, wantInstructions);
243
+ return;
244
+ }
245
+ ctx.ui?.notify?.(`mcp-combiner: unknown verb "${verb}". Try: system-prompt`, "warn");
246
+ },
247
+ });
248
+ if (hostOwned || !manage) {
249
+ // Registration is static (mcp.json) and the combiner is someone else's to run:
250
+ // nothing to launch. The instructions handler above still applies.
251
+ return;
252
+ }
253
+ // ── process: launch on session_start, release on session_shutdown("quit") ──
254
+ // Pi forbids starting background resources from the factory, so all of this is
255
+ // deferred to session_start. session_start fires again on reload/new/resume/fork
256
+ // within the same process; the `attachment` guard makes re-entry a no-op attach.
257
+ pi.on("session_start", (_event, ctx) => {
258
+ if (attachment)
259
+ return; // already attached in this process
260
+ const log = makeLog(ctx, notify);
261
+ const binary = resolveSharedserver({
262
+ label: "mcp-combiner",
263
+ minVersion: SHAREDSERVER_MIN_VERSION,
264
+ installerUrl: "https://github.com/georgeharker/sharedserver/releases/latest/download/sharedserver-installer.sh",
265
+ }, env("SHAREDSERVER_BIN"), process.env, log);
266
+ if (!binary) {
267
+ log("error", "sharedserver binary not found; set $SHAREDSERVER_BIN, or PI_MCP_COMBINER_MANAGE=false");
268
+ return;
269
+ }
270
+ const combiner = resolveCombiner(log);
271
+ if (!combiner) {
272
+ log("error", "mcp-combiner could not be found or fetched; install uv and it is fetched from PyPI on demand, " +
273
+ "or install mcp-combiner, or set $PI_MCP_COMBINER_COMMAND / $PI_MCP_COMBINER_CHECKOUT");
274
+ return;
275
+ }
276
+ const cfgPath = resolveConfig(log);
277
+ if (!cfgPath)
278
+ return; // resolveConfig already logged the specifics
279
+ const name = env("PI_MCP_COMBINER_NAME") ?? DEFAULT_NAME;
280
+ const port = resolvePort(log);
281
+ const grace = env("PI_MCP_COMBINER_GRACE") ?? DEFAULT_GRACE;
282
+ // Assemble: <combiner> [--mcp] --config <cfg> --port <port> [--host <host>]
283
+ const serve = [];
284
+ const modern = combinerNeedsMcpFlag(combiner);
285
+ if (modern)
286
+ serve.push("--mcp");
287
+ serve.push("--config", cfgPath, "--port", String(port));
288
+ const host = env("PI_MCP_COMBINER_HOST");
289
+ if (host)
290
+ serve.push("--host", host);
291
+ // Logging parity with the sibling plugins' two-file scheme: sharedserver captures
292
+ // raw stdout/stderr (--log-file on `use`), and the combiner writes its own
293
+ // --log-file. Both default under $XDG_STATE_HOME/mcp-combiner; "none" disables.
294
+ // Combiner-side flags ride the same version gate as --mcp.
295
+ const logDir = join(process.env.XDG_STATE_HOME || join(homedir(), ".local", "state"), "mcp-combiner");
296
+ const pyLogFile = env("PI_MCP_COMBINER_PYLOG") ?? join(logDir, "mcp-combiner-py.log");
297
+ if (modern) {
298
+ if (pyLogFile !== "none")
299
+ serve.push("--log-file", pyLogFile);
300
+ serve.push("--log-level", env("PI_MCP_COMBINER_LOG_LEVEL") ?? "info");
301
+ }
302
+ const wrappedArgs = [...combiner.args, ...(combiner.extra ?? []), ...serve];
303
+ const useArgs = [
304
+ "use",
305
+ name,
306
+ "--pid",
307
+ String(process.pid),
308
+ "--grace-period",
309
+ grace,
310
+ "--metadata",
311
+ `pi-${process.pid}`,
312
+ ];
313
+ const logFile = env("PI_MCP_COMBINER_LOG") ?? join(logDir, "mcp-combiner.log");
314
+ if (logFile !== "none") {
315
+ try {
316
+ mkdirSync(dirname(logFile), { recursive: true });
317
+ }
318
+ catch {
319
+ // best-effort — a failed mkdir just means sharedserver may drop the capture
320
+ }
321
+ useArgs.push("--log-file", logFile);
322
+ }
323
+ useArgs.push("--", combiner.cmd, ...wrappedArgs);
324
+ installProcessCleanup();
325
+ const result = spawnSync(binary, useArgs, { stdio: "pipe", env: process.env });
326
+ if (result.error) {
327
+ log("error", `${name}: failed to spawn sharedserver (${result.error.message})`);
328
+ return;
329
+ }
330
+ if (result.status !== 0) {
331
+ const stderr = result.stderr?.toString().trim();
332
+ log("error", `${name}: sharedserver use exited ${result.status}${stderr ? ` (${stderr})` : ""}`);
333
+ return;
334
+ }
335
+ attachment = { binary, name };
336
+ log("info", `combiner "${name}" attached on port ${port} (${combiner.cmd} ${wrappedArgs.join(" ")})`);
337
+ });
338
+ pi.on("session_shutdown", (event) => {
339
+ // Only "quit" means the Pi process is actually leaving. reload/new/resume/fork
340
+ // keep the process alive and a fresh session_start re-attaches — releasing the
341
+ // refcount on those would needlessly drop (and re-take) the shared combiner.
342
+ if (event.reason === "quit")
343
+ detach();
344
+ });
345
+ }
346
+ // ── helpers ────────────────────────────────────────────────────────
347
+ // The verbs the extension's slash command understands. `system-prompt` shows the
348
+ // directive this extension injects — the show-command pattern from pi-custom-system-prompt,
349
+ // since `before_agent_start` injections are per-turn and never appear in Pi's own
350
+ // `/system-prompt` (which reports the base prompt only).
351
+ const COMMAND_VERBS = ["system-prompt"];
352
+ function completeVerbs(prefix) {
353
+ const p = prefix.trim();
354
+ const matches = COMMAND_VERBS.filter((v) => v.startsWith(p));
355
+ return matches.length ? matches.map((v) => ({ value: v, label: v })) : null;
356
+ }
357
+ const SHOW_LIMIT = 1600;
358
+ function showDirective(ctx, label, directive, enabled) {
359
+ if (!directive) {
360
+ ctx.ui?.notify?.(`${label}: no directive bundled (instructions.txt missing)`, "warn");
361
+ return;
362
+ }
363
+ const head = enabled
364
+ ? `${label} directive — injected into the system prompt on every turn (before_agent_start):`
365
+ : `${label} directive — injection is DISABLED this session; it would be:`;
366
+ const body = directive.length > SHOW_LIMIT
367
+ ? `${directive.slice(0, SHOW_LIMIT)}\n\n… (${directive.length} chars total)`
368
+ : directive;
369
+ ctx.ui?.notify?.(`${head}\n\n${body}`, "info");
370
+ }
371
+ function makeLog(ctx, notify) {
372
+ return (level, message) => {
373
+ const line = `mcp-combiner: ${message}`;
374
+ // Pi has no structured plugin log sink like OpenCode's client.app.log; surface
375
+ // through the UI when there is one (and the user has not opted out), else stderr.
376
+ if (notify && ctx.hasUI && ctx.ui?.notify) {
377
+ ctx.ui.notify(line, level === "error" ? "error" : level === "warn" ? "warn" : "info");
378
+ }
379
+ else if (level === "error" || level === "warn") {
380
+ process.stderr.write(`${line}\n`);
381
+ }
382
+ };
383
+ }
384
+ /** The port to serve on, reconciling PI_MCP_COMBINER_PORT with any explicit
385
+ * $MCP_COMPANION_COMBINER_URL (which is what pi-mcp-adapter registers, so serving
386
+ * anywhere else would be unreachable — the URL wins, same rule as the sibling plugins). */
387
+ function resolvePort(log) {
388
+ const raw = env("PI_MCP_COMBINER_PORT");
389
+ let port = DEFAULT_PORT;
390
+ if (raw !== undefined) {
391
+ const n = Number(raw);
392
+ if (Number.isInteger(n) && n > 0)
393
+ port = n;
394
+ else
395
+ log("warn", `PI_MCP_COMBINER_PORT=${raw} is not a positive integer; using ${DEFAULT_PORT}`);
396
+ }
397
+ const url = env("MCP_COMPANION_COMBINER_URL");
398
+ if (url && raw !== undefined) {
399
+ let urlPort = Number.NaN;
400
+ try {
401
+ urlPort = Number(new URL(url).port);
402
+ }
403
+ catch {
404
+ // malformed URL — fall through to the no-usable-port warning below
405
+ }
406
+ if (!Number.isInteger(urlPort) || urlPort <= 0) {
407
+ log("warn", `MCP_COMPANION_COMBINER_URL=${url} has no explicit port; serving on ${port}. ` +
408
+ `Use an explicit port in the URL (e.g. http://127.0.0.1:${port}/mcp).`);
409
+ }
410
+ else if (urlPort !== port) {
411
+ log("warn", `PI_MCP_COMBINER_PORT=${port} disagrees with MCP_COMPANION_COMBINER_URL=${url}; the URL is what ` +
412
+ `pi-mcp-adapter registers, so serving on ${port} would be unreachable — using ${urlPort}. ` +
413
+ "Set them to the same port.");
414
+ port = urlPort;
415
+ }
416
+ }
417
+ return port;
418
+ }
419
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}