@geohar/pi-mcp-combiner 0.13.4 → 0.14.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.
Files changed (95) hide show
  1. package/README.md +227 -126
  2. package/dist/client/config-ladder.d.ts +62 -0
  3. package/dist/client/config-ladder.d.ts.map +1 -0
  4. package/dist/client/config-ladder.js +162 -0
  5. package/dist/client/config-ladder.js.map +1 -0
  6. package/dist/client/connection.d.ts +113 -0
  7. package/dist/client/connection.d.ts.map +1 -0
  8. package/dist/client/connection.js +332 -0
  9. package/dist/client/connection.js.map +1 -0
  10. package/dist/client/control.d.ts +17 -0
  11. package/dist/client/control.d.ts.map +1 -0
  12. package/dist/client/control.js +80 -0
  13. package/dist/client/control.js.map +1 -0
  14. package/dist/client/direct-tools.d.ts +41 -0
  15. package/dist/client/direct-tools.d.ts.map +1 -0
  16. package/dist/client/direct-tools.js +122 -0
  17. package/dist/client/direct-tools.js.map +1 -0
  18. package/dist/client/elicitation.d.ts +22 -0
  19. package/dist/client/elicitation.d.ts.map +1 -0
  20. package/dist/client/elicitation.js +93 -0
  21. package/dist/client/elicitation.js.map +1 -0
  22. package/dist/client/footer.d.ts +17 -0
  23. package/dist/client/footer.d.ts.map +1 -0
  24. package/dist/client/footer.js +99 -0
  25. package/dist/client/footer.js.map +1 -0
  26. package/dist/client/panel.d.ts +16 -0
  27. package/dist/client/panel.d.ts.map +1 -0
  28. package/dist/client/panel.js +426 -0
  29. package/dist/client/panel.js.map +1 -0
  30. package/dist/client/prompts.d.ts +30 -0
  31. package/dist/client/prompts.d.ts.map +1 -0
  32. package/dist/client/prompts.js +230 -0
  33. package/dist/client/prompts.js.map +1 -0
  34. package/dist/client/proxy-tool.d.ts +22 -0
  35. package/dist/client/proxy-tool.d.ts.map +1 -0
  36. package/dist/client/proxy-tool.js +225 -0
  37. package/dist/client/proxy-tool.js.map +1 -0
  38. package/dist/client/ranking.d.ts +21 -0
  39. package/dist/client/ranking.d.ts.map +1 -0
  40. package/dist/client/ranking.js +113 -0
  41. package/dist/client/ranking.js.map +1 -0
  42. package/dist/client/render.d.ts +28 -0
  43. package/dist/client/render.d.ts.map +1 -0
  44. package/dist/client/render.js +166 -0
  45. package/dist/client/render.js.map +1 -0
  46. package/dist/client/renderers.d.ts +31 -0
  47. package/dist/client/renderers.d.ts.map +1 -0
  48. package/dist/client/renderers.js +191 -0
  49. package/dist/client/renderers.js.map +1 -0
  50. package/dist/client/resources.d.ts +20 -0
  51. package/dist/client/resources.d.ts.map +1 -0
  52. package/dist/client/resources.js +119 -0
  53. package/dist/client/resources.js.map +1 -0
  54. package/dist/client/schema-signature.d.ts +3 -0
  55. package/dist/client/schema-signature.d.ts.map +1 -0
  56. package/dist/client/schema-signature.js +183 -0
  57. package/dist/client/schema-signature.js.map +1 -0
  58. package/dist/client/script.d.ts +10 -0
  59. package/dist/client/script.d.ts.map +1 -0
  60. package/dist/client/script.js +130 -0
  61. package/dist/client/script.js.map +1 -0
  62. package/dist/client/settings.d.ts +46 -0
  63. package/dist/client/settings.d.ts.map +1 -0
  64. package/dist/client/settings.js +81 -0
  65. package/dist/client/settings.js.map +1 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +333 -29
  68. package/dist/index.js.map +1 -1
  69. package/dist/pi.d.ts +82 -2
  70. package/dist/pi.d.ts.map +1 -1
  71. package/dist/pi.js +2 -1
  72. package/dist/pi.js.map +1 -1
  73. package/dist/sharedserver-resolve.d.ts.map +1 -1
  74. package/dist/sharedserver-resolve.js +2 -1
  75. package/dist/sharedserver-resolve.js.map +1 -1
  76. package/package.json +68 -57
  77. package/src/client/config-ladder.ts +221 -0
  78. package/src/client/connection.ts +373 -0
  79. package/src/client/control.ts +94 -0
  80. package/src/client/direct-tools.ts +146 -0
  81. package/src/client/elicitation.ts +115 -0
  82. package/src/client/footer.ts +108 -0
  83. package/src/client/panel.ts +478 -0
  84. package/src/client/prompts.ts +251 -0
  85. package/src/client/proxy-tool.ts +261 -0
  86. package/src/client/ranking.ts +130 -0
  87. package/src/client/render.ts +166 -0
  88. package/src/client/renderers.ts +211 -0
  89. package/src/client/resources.ts +137 -0
  90. package/src/client/schema-signature.ts +184 -0
  91. package/src/client/script.ts +150 -0
  92. package/src/client/settings.ts +109 -0
  93. package/src/index.ts +384 -32
  94. package/src/pi.ts +86 -6
  95. package/src/sharedserver-resolve.ts +10 -3
package/README.md CHANGED
@@ -1,161 +1,261 @@
1
1
  # @geohar/pi-mcp-combiner
2
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.
3
+ A [Pi](https://pi.dev) extension that gives Pi the **`mcp-combiner`** MCP aggregator —
4
+ end to end. It starts the combiner (supervised by
5
+ [`sharedserver`](https://github.com/georgeharker/sharedserver)), **speaks MCP to it
6
+ directly** (a built-in client half — no second package required), and surfaces every
7
+ server, tool, resource, and prompt as first-class Pi UX.
8
+
9
+ Use it **standalone** (recommended) or **alongside
10
+ [`pi-mcp-adapter`](https://pi.dev/packages/pi-mcp-adapter)** — see
11
+ [Two ways to run it](#two-ways-to-run-it).
7
12
 
8
13
  It is the Pi counterpart of the
9
14
  [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)
15
+ and [OpenCode](https://github.com/georgeharker/mcp-companion/tree/main/plugins/opencode)
12
16
  plugins, and shares the same combiner and the same `sharedserver` instance — so Pi,
13
17
  Claude Code, OpenCode, and Neovim can all talk to **one** refcounted combiner process.
14
18
 
15
19
  ## How it fits together
16
20
 
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.
21
+ Three halves, all in this one package:
40
22
 
41
- ## Requirements
23
+ 1. **Process** — on `session_start` it drives
42
24
 
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).
25
+ ```sh
26
+ sharedserver use <name> --pid <pi-pid> --grace-period <g> \
27
+ -- <combiner> --mcp --config <servers.json> --port <port>
28
+ ```
48
29
 
49
- ## Install
30
+ `sharedserver` refcounts by PID with a grace period, so the combiner is shared
31
+ across clients and outlives any single one. The refcount releases on
32
+ `session_shutdown` only when `reason === "quit"` — reload/resume/fork keep the same
33
+ Pi process and a fresh `session_start` re-attaches.
50
34
 
51
- ### 1. Point `pi-mcp-adapter` at the combiner
35
+ 2. **Client** — a thin, combiner-specific MCP client (streamable HTTP, per-Pi-session
36
+ identity, an elicitation bridge) that registers the agent-facing surface: the
37
+ `mcp()` proxy tool, a scripting tool, `read_*` resource tools, prompt slash
38
+ commands, a status footer, and an interactive panel. Everything the hundreds of
39
+ tool definitions would cost in context is replaced by two or three tool
40
+ definitions and on-demand discovery.
41
+ 3. **Instructions** — on `before_agent_start` it appends the combiner's
42
+ `<server>_`-prefix / "discover before assuming" directive to the system prompt.
52
43
 
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`:
44
+ `sharedserver` itself is fetched automatically if not installed (pinned release via
45
+ the cargo-dist installer), and the combiner is resolved from PATH / a checkout /
46
+ a pinned PyPI release — same resolvers as the sibling plugins.
55
47
 
56
- ```json
57
- {
58
- "mcpServers": {
59
- "mcp-combiner": {
60
- "url": "http://127.0.0.1:9741/mcp",
61
- "auth": "bearer",
62
- "bearerTokenEnv": "MCP_COMBINER_AUTH_TOKEN"
63
- }
64
- }
65
- }
48
+ ## Two ways to run it
49
+
50
+ ### A. Standalone (recommended)
51
+
52
+ Install only this extension. Nothing else needed — the client half connects to the
53
+ combiner and registers everything:
54
+
55
+ ```sh
56
+ pi install /path/to/mcp-companion/plugins/pi # local checkout
57
+ # or the published package under settings.json "packages"
66
58
  ```
67
59
 
68
- Or let the extension write it for you — **`/mcp-combiner install-config`** merges
69
- exactly this entry into `~/.config/mcp/mcp.json` (or a path you pass), preserving
70
- any other servers and any existing `url` you set. (It only writes when you ask —
71
- the extension never edits the adapter's config on its own.)
60
+ On startup you get: the `mcp` tool (search → describe → call), `mcpScript`,
61
+ `read_<resource>` tools, `/<server>__<prompt>` slash commands, a footer line
62
+ (`14 servers enabled (13 ready) · 671 tools`), and `/mcp-combiner panel`.
63
+
64
+ ### B. Alongside pi-mcp-adapter
72
65
 
73
- `"auth": "bearer"` with `"bearerTokenEnv"` is the correct single pairing — it does
74
- two jobs at once:
66
+ Already running pi-mcp-adapter (for other servers, or while evaluating)? The client
67
+ half is gated by a **tri-state**:
75
68
 
76
- - **Sends the token.** `pi-mcp-adapter` only attaches `Authorization: Bearer …`
77
- when `auth === "bearer"` (`server-manager.ts`); `bearerTokenEnv` names the env
78
- var it reads at connect. So if you lock the combiner down with an inbound bearer
79
- (`MCP_COMBINER_AUTH_TOKEN`, see the combiner README), the header is presented —
80
- nothing is written to disk. **Note:** `bearerTokenEnv` alone, or with `auth:
81
- false`, does **not** send the header — the adapter gates it on `auth === "bearer"`.
82
- - **Suppresses OAuth.** `auth: "bearer"` also makes the adapter's `supportsOAuth()`
83
- false, so a wrong/missing-token 401 surfaces as an honest error instead of the
84
- spurious `Failed to start OAuth … DCR rejected (HTTP 404)` probe.
69
+ | Setting | Behaviour |
70
+ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | `"adapter": "auto"` _(default)_ | client **off** when pi-mcp-adapter is detected installed, **on** otherwise — existing adapter setups upgrade with zero behaviour change |
72
+ | `"adapter": true` | client on, adapter stays for other servers. Rename ours (`"toolName": "combiner"`) so both tools coexist, and remove the combiner entry from the shared `mcp.json` so the adapter doesn't double-connect |
73
+ | `"adapter": false` | legacy mode — process + instructions only; the adapter owns all MCP |
85
74
 
86
- Harmless when the combiner is **open**: the env var is unset, so no header is sent,
87
- the endpoint returns 200, and OAuth still never fires. So this one entry is correct
88
- whether or not inbound auth is enabled.
75
+ Env override: `PI_MCP_COMBINER_ADAPTER=off|on|auto`. `/mcp-combiner status` reports
76
+ which mode is active and why.
89
77
 
90
- (See [`mcp.json.example`](./mcp.json.example).) To give this Pi instance its own chat
91
- identity toward the combiner — parking its isolated upstream sessions separately — add a
92
- path token: `"url": "http://127.0.0.1:9741/mcp/pi-<yourname>"`. The combiner gives URL
93
- tokens first priority.
78
+ The legacy **`/mcp-combiner install-config`** verb (writes the combiner entry into the
79
+ shared `mcp.json` for the adapter to read) still exists for mode B.
94
80
 
95
- If you edit `mcp.json` while Pi is running, run `/reload` (or `mcp({ connect:
96
- "mcp-combiner" })`) so the adapter re-reads it.
81
+ ## Requirements
82
+
83
+ - **`mcp-combiner`** available as a command (`uv tool install mcp-combiner`), or just
84
+ **`uv`** on PATH — a pinned release is fetched from PyPI on demand. Requires
85
+ combiner ≥ 0.8.0 (version-gated automatically).
86
+ - A combiner **`servers.json`** (auto-probe locations below).
87
+ - pi-mcp-adapter **not** required.
97
88
 
98
- ### 2. Install this extension
89
+ ## Configuration — three layers
99
90
 
100
- Any of Pi's extension-loading mechanisms — all support a **local directory**:
91
+ ### 1. Pi settings file — `$PI_CODING_AGENT_DIR/extensions/mcp-combiner.json`
101
92
 
102
- ```sh
103
- # a) drop-in package dir (uses "main": dist/index.js — run `npm run build` first)
104
- npm --prefix plugins/pi run build
105
- ln -s "$PWD/plugins/pi" ~/.pi/agent/extensions/mcp-combiner
93
+ Pi-side knobs (see [`settings.example.json`](./settings.example.json)):
94
+
95
+ | Key | Default | Effect |
96
+ | ----------------- | -------- | --------------------------------------------------------------------------------------------------------- |
97
+ | `toolName` | `"mcp"` | Name of the proxy tool. Rename (e.g. `"combiner"`) to coexist with pi-mcp-adapter's own `mcp`. |
98
+ | `adapter` | `"auto"` | Client-half gate — see above. |
99
+ | `lazy` | `"lazy"` | `"eager"` connects at session start; `"lazy"` on first use. Prompts/resources/directTools imply eager. |
100
+ | `exposeResources` | `true` | Register `read_<resource>` tools. |
101
+ | `prompts` | `true` | Register prompt slash commands. |
102
+ | `scriptMode` | `true` | Register the `<toolName>Script` batching tool. |
103
+ | `uiAutoOpen` | `true` | Auto-open interactive widget URLs in the browser (Stage 2 holds + resource reads). |
104
+ | `mcpFooterStatus` | `"full"` | Footer text: `"full"` = `N servers enabled (M ready) · T tools`, `"compact"` = `MCP M/N`, `"off"` = none. |
105
+ | `mcpFooterKey` | `"mcp"` | The `ctx.ui.setStatus` key the footer publishes under (the slot oh-my-posh-style footers aggregate). |
106
+ | `url` | — | Explicit combiner URL. Env wins. |
107
+ | `notify` | `true` | Surface lifecycle messages via the Pi UI. |
108
+
109
+ ### 2. Shared MCP config ladder — read-only
110
+
111
+ The extension **reads** the standard MCP files (same ladder and precedence as
112
+ pi-mcp-adapter, later wins): `~/.config/mcp/mcp.json` → `~/.agents/mcp.json` →
113
+ `~/.agents/mcp/mcp.json` → `<agent dir>/mcp.json` → `.mcp.json` → `.pi/mcp.json`
114
+ (project). It **never writes them**.
106
115
 
107
- # b) one-off, build-free live dev — Pi loads the TS source directly
108
- pi -e ./plugins/pi/src/index.ts
116
+ The recognized `mcp-combiner` entry carries connection + per-project exposure:
109
117
 
110
- # c) settings.json — list a local path under "extensions"
111
- # { "extensions": ["/abs/path/to/mcp-companion/plugins/pi/src/index.ts"] }
118
+ ```jsonc
119
+ {
120
+ "mcpServers": {
121
+ "mcp-combiner": {
122
+ "url": "http://127.0.0.1:9741/mcp",
123
+ "auth": "bearer",
124
+ "bearerTokenEnv": "MCP_COMBINER_AUTH_TOKEN",
125
+ "combiner": {
126
+ // extension-specific, ignored by other readers
127
+ "servers": { "allow": ["github", "svg-mcp"] }, // or "deny": [...]
128
+ "exposeResources": true,
129
+ "prompts": true,
130
+ "directTools": ["combiner__status", "github_search_*"], // or "search"
131
+ },
132
+ },
133
+ },
134
+ }
135
+ ```
112
136
 
113
- # d) published package — list under "packages" in settings.json
114
- # { "packages": ["@geohar/pi-mcp-combiner@latest"] }
137
+ - **`servers.allow/deny`** — per-project exposure, enforced _at the combiner_ for this
138
+ chat's token (sees through scripting too) and mirrored client-side.
139
+ - **`directTools`** — promote named tools (globs) to first-class Pi tools at session
140
+ start, or `"search"` to promote tools the first time `mcp({search})` matches them.
141
+ A >50-entry allowlist warns; `true` is deliberately not offered (context cost).
142
+ - Project layers are read against the **session cwd** — worktree subagents and
143
+ project switches get their own `.pi/mcp.json`. Commit the `combiner` block if you
144
+ want worktree agents to honour it.
145
+ - URL precedence: `MCP_COMPANION_COMBINER_URL` (host-owned) → `PI_MCP_COMBINER_URL` →
146
+ settings `url` → ladder entry `url` → `host:port/mcp`.
147
+
148
+ ### 3. Environment — `PI_MCP_COMBINER_*`
149
+
150
+ | Variable | Default | Effect |
151
+ | ----------------------------------------------- | ----------------- | ------------------------------------------------------------------ |
152
+ | `PI_MCP_COMBINER_ADAPTER` | _(settings)_ | `off` / `on` / `auto` — client-half gate. |
153
+ | `PI_MCP_COMBINER_TOOL_NAME` | _(settings)_ | Proxy tool name override. |
154
+ | `PI_MCP_COMBINER_URL` | — | Explicit combiner URL. |
155
+ | `PI_MCP_COMBINER_PORT` | `9741` | HTTP port the combiner serves on. |
156
+ | `PI_MCP_COMBINER_HOST` | `127.0.0.1` | HTTP host the combiner binds. |
157
+ | `PI_MCP_COMBINER_CONFIG` | _(auto-probed)_ | Path to the combiner's `servers.json`. |
158
+ | `PI_MCP_COMBINER_COMMAND` / `_ARGS` | _(auto-resolved)_ | Override the combiner invocation. |
159
+ | `PI_MCP_COMBINER_CHECKOUT` | — | Checkout for `uv run --project <checkout> python -m mcp_combiner`. |
160
+ | `PI_MCP_COMBINER_NAME` | `mcp-combiner` | `sharedserver` instance name. |
161
+ | `PI_MCP_COMBINER_GRACE` | `30m` | `sharedserver` grace period. |
162
+ | `PI_MCP_COMBINER_LOG` / `_PYLOG` / `_LOG_LEVEL` | _(state dir)_ | Combiner logging; `"none"` disables. |
163
+ | `PI_MCP_COMBINER_MANAGE` | `true` | `false` → don't launch (combiner runs elsewhere). |
164
+ | `PI_MCP_COMBINER_INSTRUCTIONS` | `true` | `false` → skip the system-prompt directive. |
165
+ | `PI_MCP_COMBINER_NOTIFY` | `true` | `false` → don't surface messages via the Pi UI. |
166
+ | `SHAREDSERVER_BIN` / `SHAREDSERVER_LOCKDIR` | _(auto)_ | `sharedserver` binary / lock dir. |
167
+
168
+ `servers.json` auto-probe: `$PI_MCP_COMBINER_CONFIG` →
169
+ `~/.cache/secrets/<user>.mcpservers.json` → `~/.config/mcp-combiner/servers.json` →
170
+ `~/.config/mcp/servers.json`.
171
+
172
+ ## The UX
173
+
174
+ **The proxy tool** (`mcp`, or your `toolName`) — one tool instead of hundreds:
175
+
176
+ ```ts
177
+ mcp({search: "github search code"}) → ranked hits + describe-next hint
178
+ mcp({describe: "github_search_code"}) → full schema (TS-shaped) + description
179
+ mcp({tool: "github_search_code", args: {...}}) → the call
180
+ mcp({}) → status
115
181
  ```
116
182
 
117
- ## Configuration
118
-
119
- Everything is via the `PI_MCP_COMBINER_*` environment namespace, which mirrors the Claude
120
- plugin's `CLAUDE_MCP_COMBINER_*` and the OpenCode plugin's `OPENCODE_MCP_COMBINER_*` — so
121
- running several clients means one namespace per client.
122
-
123
- | Variable | Default | Effect |
124
- |----------|---------|--------|
125
- | `PI_MCP_COMBINER_PORT` | `9741` | HTTP port the combiner serves on. |
126
- | `PI_MCP_COMBINER_HOST` | `127.0.0.1` | HTTP host the combiner binds. |
127
- | `PI_MCP_COMBINER_CONFIG` | *(auto-probed)* | Path to the combiner's `servers.json`. |
128
- | `PI_MCP_COMBINER_COMMAND` / `_ARGS` | *(auto-resolved)* | Override the combiner invocation. |
129
- | `PI_MCP_COMBINER_CHECKOUT` | — | Checkout for `uv run --project <checkout> python -m mcp_combiner`. |
130
- | `PI_MCP_COMBINER_NAME` | `mcp-combiner` | `sharedserver` instance name. |
131
- | `PI_MCP_COMBINER_GRACE` | `30m` | `sharedserver` grace period. |
132
- | `PI_MCP_COMBINER_LOG` | `~/.local/state/mcp-combiner/mcp-combiner.log` | Capture the combiner's stdout/stderr; `"none"` disables. |
133
- | `PI_MCP_COMBINER_PYLOG` | `~/.local/state/mcp-combiner/mcp-combiner-py.log` | The combiner's own `--log-file`; `"none"` disables. |
134
- | `PI_MCP_COMBINER_LOG_LEVEL` | `info` | The combiner's `--log-level`. |
135
- | `PI_MCP_COMBINER_MANAGE` | `true` | `false` → don't launch (assume the combiner runs elsewhere); instructions only. |
136
- | `PI_MCP_COMBINER_INSTRUCTIONS` | `true` | `false` → don't append the directive to the system prompt. |
137
- | `PI_MCP_COMBINER_NOTIFY` | `true` | `false` → don't surface attach/health messages via the Pi UI. |
138
- | `SHAREDSERVER_BIN` | *(auto-resolved)* | Path to the `sharedserver` binary. |
139
- | `SHAREDSERVER_LOCKDIR` | — | `sharedserver` lock directory. |
140
-
141
- ### `servers.json` auto-probe
142
-
143
- `$PI_MCP_COMBINER_CONFIG` → `~/.cache/secrets/<user>.mcpservers.json` →
144
- `~/.config/mcp-combiner/servers.json` → `~/.config/mcp/servers.json`.
145
-
146
- ### Combiner command resolution
147
-
148
- `$PI_MCP_COMBINER_COMMAND` (+`$PI_MCP_COMBINER_ARGS`) → `mcp-combiner` on `PATH` (only
149
- when ≥ 0.8.0) → `uv run --project <checkout> python -m mcp_combiner` → a pinned release
150
- via `uvx`. If the `mcp-combiner` on PATH is older than 0.8.0, the extension warns and
151
- falls back to a pinned `uvx` release rather than silently using a stale binary.
183
+ In `"directTools": "search"` mode, search matches are promoted to first-class tools
184
+ and announced in the result.
185
+
186
+ **`mcpScript`** — batch calls with trusted JavaScript:
187
+ `{code: "const r = await tools.search('q'); emit(r); return await tools.call('t', {})"}`.
188
+
189
+ **`read_<resource>` tools** — one zero-parameter tool per MCP resource; interactive
190
+ `mcp-app` resources are flagged and their `read_*` results append the combiner UI-host URL (`/ui/<token>/?resource=…`), auto-opened in the browser — the interactive widget runs combiner-side.
191
+
192
+ **Interactive widgets (mcp-app)** — when a widget-bound tool result arrives (e.g.
193
+ `todoist_find-tasks-by-date`), the combiner **holds the call in flight**: the
194
+ extension auto-opens the widget in your browser, the tool's data streams to it
195
+ over SSE, and you interact while the call waits. Hit **Done** in the widget and
196
+ the call resolves with a summary of what you did. Every widget action runs
197
+ through the combiner's permission pipeline, and the full interaction stays
198
+ retrievable: ask for `combiner__ui_messages` afterwards (the hold budget is 50s
199
+ by default — `MCP_COMBINER_UI_HOLD_TIMEOUT` combiner-side; the widget URL stays
200
+ valid after a timeout, you just lose the fold-into-result for that call).
201
+ Widgets that self-fetch (svg-mcp's preview) work the same way.
202
+
203
+ **Prompt slash commands** — `mcp__<server>__<name>` (e.g. `mcp__todoist__productivity_analysis`),
204
+ positional + `name=value` args with bash quoting.
205
+
206
+ **Footer** — `14 servers enabled (13 ready) · 671 tools` under the `mcp` status key;
207
+ refreshed on connect/changes and every 30s; honest degradation when unreachable.
208
+
209
+ **`/mcp-combiner` command**:
210
+
211
+ | Verb | Effect |
212
+ | --------------------------------------------- | ------------------------------------------------------------ |
213
+ | _(none)_ / `status` | Connection state, per-server glyph table, session view |
214
+ | `panel` | Interactive panel (below) |
215
+ | `enable` / `disable` / `restart-server <srv>` | Drive the combiner's meta-tools |
216
+ | `system-prompt` | Show the injected directive |
217
+ | `install-config [path]` | Legacy: write the shared-`mcp.json` entry for pi-mcp-adapter |
218
+
219
+ **The panel** — `/mcp-combiner panel`: connection + port + session token, exposure
220
+ filter, fuzzy search (`/`), per-server rows with state glyphs (`● ○ ⊘ ✗ ◌`) and the
221
+ tools/resources/prompts counts trio, expandable tool lists with token estimates,
222
+ `[session off]` labels for project-filtered servers, the combiner's own `⬢` meta-tools
223
+ group. Keys: `↑↓/jk` move · `enter` expand/copy · `e` enable/disable · `c` copy ·
224
+ `/` filter · `r` refresh · `q` close.
225
+
226
+ ## Chat identity
227
+
228
+ Each Pi session mints its own grouping token (`pi-<sessionId>`) into the combiner URL
229
+ path, so per-chat isolation (`isolate: true` servers), parked upstream sessions, and
230
+ restart handover all key on the chat — subagents automatically get their own tokens
231
+ (each child session binds fresh). A resumed chat continues its identity; a fork
232
+ deliberately starts fresh. An explicit token in the configured URL always wins.
233
+
234
+ ## Permissions
235
+
236
+ Tool-call policy is enforced **at the combiner** (`permissions` in `servers.json`:
237
+ deny/elicit/allow per server, with interactive elicitation bridged to Pi's UI —
238
+ subagents decline securely by default). If you also run
239
+ [`pi-permission-system`](https://github.com/gotgenes/pi-packages), keep `toolName:
240
+ "mcp"` for its `mcp`-surface rules (tool-glob patterns like `github_*` match out of
241
+ the box).
242
+
243
+ ## Acknowledgments
244
+
245
+ The client half began as a substantial reduction of
246
+ [**pi-mcp-adapter**](https://github.com/nicobailon/pi-mcp-adapter) (MIT, © Nico Bailon) —
247
+ the proxy-tool calling convention, search semantics, result-guard behavior, prompt
248
+ command format, resource naming, the schema-signature renderer, and the HTML/JS
249
+ widget contract derive from it, and the conformance cases in `test/` are ported from
250
+ its suite. Everything OAuth, multi-server, and transport-related is deliberately
251
+ _not_ here — the combiner owns that. This package would be a much worse tool
252
+ without Nico's design work; go star it.
152
253
 
153
254
  ## Host-owned mode
154
255
 
155
256
  If **`$MCP_COMPANION_COMBINER_URL`** is set, an editor/host (e.g. Neovim) already owns
156
- and refcounts the combiner. The extension then **only injects instructions** and never
157
- starts or stops the process — the same early-exit as the sibling plugins. Equivalent to
158
- `PI_MCP_COMBINER_MANAGE=false`.
257
+ and refcounts the combiner — this extension never launches it (the client still
258
+ connects). Equivalent to `PI_MCP_COMBINER_MANAGE=false` for the process half.
159
259
 
160
260
  ## Development
161
261
 
@@ -163,12 +263,13 @@ starts or stops the process — the same early-exit as the sibling plugins. Equi
163
263
  npm install
164
264
  npm run typecheck
165
265
  npm run build # emits dist/ (not committed; built on publish)
266
+ npm run smoke # live suite against the running combiner on :9741
166
267
  ```
167
268
 
168
- The `src/sharedserver-resolve.ts` file is **vendored byte-identical** from
269
+ Design notes: [`docs/adapter-design.md`](./docs/adapter-design.md). The
270
+ `src/sharedserver-resolve.ts` file is **vendored byte-identical** from
169
271
  [`georgeharker/sharedserver`](https://github.com/georgeharker/sharedserver) (via
170
- `scripts/sync-vendored.sh`), shared with the OpenCode plugin so both answer "which
171
- sharedserver, and why" identically. Edit it upstream; re-sync here.
272
+ `scripts/sync-vendored.sh`). Edit upstream; re-sync here.
172
273
 
173
274
  ## License
174
275
 
@@ -0,0 +1,62 @@
1
+ import type { DirectToolsSpec } from "./direct-tools.js";
2
+ export type ServerFilter = {
3
+ allow?: string[];
4
+ deny?: string[];
5
+ };
6
+ export type CombinerBlock = {
7
+ servers?: ServerFilter;
8
+ exposeResources?: boolean;
9
+ prompts?: boolean;
10
+ /** Direct tool promotion: glob allowlist over combined tool names (e.g.
11
+ * ["combiner__status", "github_search_*"]), or "search" to register tools as
12
+ * first-class Pi tools the first time mcp({search}) matches them. */
13
+ directTools?: string[] | "search";
14
+ };
15
+ export type CombinerEntry = {
16
+ url?: string;
17
+ bearerTokenEnv?: string;
18
+ combiner?: CombinerBlock;
19
+ };
20
+ export type LadderResult = {
21
+ /** Merged combiner entry (later files win). Empty object when absent everywhere. */
22
+ entry: CombinerEntry;
23
+ /** Non-combiner server names seen in the ladder — reported (not connected) in v1. */
24
+ otherServers: string[];
25
+ /** Files that existed and were read, in ladder order. */
26
+ sources: string[];
27
+ };
28
+ /** The ladder, lowest precedence first. Mirrors pi-mcp-adapter's documented order. */
29
+ export declare function ladderPaths(cwd: string): string[];
30
+ /** Recognize "our" entry: explicit name, explicit marker, or url origin match. */
31
+ export declare function isCombinerEntry(name: string, entry: Record<string, unknown>, combinerOrigin: string | undefined): boolean;
32
+ /** Walk the ladder (cwd-relative project files included) and merge. */
33
+ export declare function readLadder(cwd: string, combinerOrigin: string | undefined): LadderResult;
34
+ export type SessionConfig = {
35
+ /** Token-stripped base URL incl. /mcp. */
36
+ baseUrl: string;
37
+ bearerTokenEnv: string | undefined;
38
+ /** Explicit token from the configured URL path (user override), if any. */
39
+ urlToken: string | undefined;
40
+ serverFilter: ServerFilter | undefined;
41
+ directSpec: DirectToolsSpec | undefined;
42
+ entry: CombinerEntry;
43
+ otherServers: string[];
44
+ sources: string[];
45
+ };
46
+ export type ResolveSessionConfigOptions = {
47
+ /** Session working directory (project layers read from here). */
48
+ cwd: string;
49
+ /** Explicit env URL (host-owned or PI_MCP_COMBINER_URL), highest precedence. */
50
+ envUrl: string | undefined;
51
+ /** Settings-file URL, next precedence. */
52
+ settingsUrl: string | undefined;
53
+ /** Serve-side default (host:port/mcp) for origin matching and the fallback. */
54
+ defaultUrl: string;
55
+ };
56
+ /** Extract an explicit /mcp/<token> path token from a URL, if present. */
57
+ export declare function urlTokenOf(url: string): string | undefined;
58
+ /** Normalize a URL back to bare /mcp (strip any token path). */
59
+ export declare function stripUrlToken(url: string): string;
60
+ /** Resolve the full per-session config. Pure — no env or settings reads inside. */
61
+ export declare function resolveSessionConfig(opts: ResolveSessionConfigOptions): SessionConfig;
62
+ //# sourceMappingURL=config-ladder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-ladder.d.ts","sourceRoot":"","sources":["../../src/client/config-ladder.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhE,MAAM,MAAM,aAAa,GAAG;IACxB,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;0EAEsE;IACtE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,aAAa,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,oFAAoF;IACpF,KAAK,EAAE,aAAa,CAAA;IACpB,qFAAqF;IACrF,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,yDAAyD;IACzD,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CASjD;AAmBD,kFAAkF;AAClF,wBAAgB,eAAe,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,GACnC,OAAO,CAMT;AAqDD,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAUxF;AASD,MAAM,MAAM,aAAa,GAAG;IACxB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,YAAY,GAAG,SAAS,CAAA;IACtC,UAAU,EAAE,eAAe,GAAG,SAAS,CAAA;IACvC,KAAK,EAAE,aAAa,CAAA;IACpB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACtC,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,gFAAgF;IAChF,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,0CAA0C;IAC1C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAUD,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,2BAA2B,GAAG,aAAa,CAarF"}