@geohar/pi-mcp-combiner 0.13.4 → 0.14.1
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/README.md +227 -126
- package/dist/client/config-ladder.d.ts +62 -0
- package/dist/client/config-ladder.d.ts.map +1 -0
- package/dist/client/config-ladder.js +162 -0
- package/dist/client/config-ladder.js.map +1 -0
- package/dist/client/connection.d.ts +113 -0
- package/dist/client/connection.d.ts.map +1 -0
- package/dist/client/connection.js +332 -0
- package/dist/client/connection.js.map +1 -0
- package/dist/client/control.d.ts +17 -0
- package/dist/client/control.d.ts.map +1 -0
- package/dist/client/control.js +80 -0
- package/dist/client/control.js.map +1 -0
- package/dist/client/direct-tools.d.ts +41 -0
- package/dist/client/direct-tools.d.ts.map +1 -0
- package/dist/client/direct-tools.js +122 -0
- package/dist/client/direct-tools.js.map +1 -0
- package/dist/client/elicitation.d.ts +22 -0
- package/dist/client/elicitation.d.ts.map +1 -0
- package/dist/client/elicitation.js +93 -0
- package/dist/client/elicitation.js.map +1 -0
- package/dist/client/footer.d.ts +17 -0
- package/dist/client/footer.d.ts.map +1 -0
- package/dist/client/footer.js +99 -0
- package/dist/client/footer.js.map +1 -0
- package/dist/client/panel.d.ts +16 -0
- package/dist/client/panel.d.ts.map +1 -0
- package/dist/client/panel.js +426 -0
- package/dist/client/panel.js.map +1 -0
- package/dist/client/prompts.d.ts +30 -0
- package/dist/client/prompts.d.ts.map +1 -0
- package/dist/client/prompts.js +230 -0
- package/dist/client/prompts.js.map +1 -0
- package/dist/client/proxy-tool.d.ts +22 -0
- package/dist/client/proxy-tool.d.ts.map +1 -0
- package/dist/client/proxy-tool.js +225 -0
- package/dist/client/proxy-tool.js.map +1 -0
- package/dist/client/ranking.d.ts +21 -0
- package/dist/client/ranking.d.ts.map +1 -0
- package/dist/client/ranking.js +113 -0
- package/dist/client/ranking.js.map +1 -0
- package/dist/client/render.d.ts +28 -0
- package/dist/client/render.d.ts.map +1 -0
- package/dist/client/render.js +166 -0
- package/dist/client/render.js.map +1 -0
- package/dist/client/renderers.d.ts +31 -0
- package/dist/client/renderers.d.ts.map +1 -0
- package/dist/client/renderers.js +191 -0
- package/dist/client/renderers.js.map +1 -0
- package/dist/client/resources.d.ts +20 -0
- package/dist/client/resources.d.ts.map +1 -0
- package/dist/client/resources.js +119 -0
- package/dist/client/resources.js.map +1 -0
- package/dist/client/schema-signature.d.ts +3 -0
- package/dist/client/schema-signature.d.ts.map +1 -0
- package/dist/client/schema-signature.js +183 -0
- package/dist/client/schema-signature.js.map +1 -0
- package/dist/client/script.d.ts +10 -0
- package/dist/client/script.d.ts.map +1 -0
- package/dist/client/script.js +130 -0
- package/dist/client/script.js.map +1 -0
- package/dist/client/settings.d.ts +46 -0
- package/dist/client/settings.d.ts.map +1 -0
- package/dist/client/settings.js +81 -0
- package/dist/client/settings.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +333 -29
- package/dist/index.js.map +1 -1
- package/dist/pi.d.ts +82 -2
- package/dist/pi.d.ts.map +1 -1
- package/dist/pi.js +2 -1
- package/dist/pi.js.map +1 -1
- package/package.json +68 -57
- package/src/client/config-ladder.ts +221 -0
- package/src/client/connection.ts +373 -0
- package/src/client/control.ts +94 -0
- package/src/client/direct-tools.ts +146 -0
- package/src/client/elicitation.ts +115 -0
- package/src/client/footer.ts +108 -0
- package/src/client/panel.ts +478 -0
- package/src/client/prompts.ts +251 -0
- package/src/client/proxy-tool.ts +261 -0
- package/src/client/ranking.ts +130 -0
- package/src/client/render.ts +166 -0
- package/src/client/renderers.ts +211 -0
- package/src/client/resources.ts +137 -0
- package/src/client/schema-signature.ts +184 -0
- package/src/client/script.ts +150 -0
- package/src/client/settings.ts +109 -0
- package/src/index.ts +384 -32
- package/src/pi.ts +86 -6
package/README.md
CHANGED
|
@@ -1,161 +1,261 @@
|
|
|
1
1
|
# @geohar/pi-mcp-combiner
|
|
2
2
|
|
|
3
|
-
A [Pi](https://pi.dev) extension that
|
|
4
|
-
|
|
5
|
-
[`sharedserver`](https://github.com/georgeharker/sharedserver))
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
1. **Process** — on `session_start` it drives
|
|
42
24
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
89
|
+
## Configuration — three layers
|
|
99
90
|
|
|
100
|
-
|
|
91
|
+
### 1. Pi settings file — `$PI_CODING_AGENT_DIR/extensions/mcp-combiner.json`
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
pi -e ./plugins/pi/src/index.ts
|
|
116
|
+
The recognized `mcp-combiner` entry carries connection + per-project exposure:
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
157
|
-
|
|
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
|
-
|
|
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`)
|
|
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"}
|