@sammorrowdrums/mcpi-ext 0.2.0 → 1.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 +21 -0
- package/README.md +137 -47
- package/dist/code-mode/eligibility.d.ts +35 -0
- package/dist/code-mode/eligibility.js +64 -0
- package/dist/code-mode/executor.d.ts +53 -0
- package/dist/code-mode/executor.js +185 -0
- package/dist/code-mode/index.d.ts +107 -0
- package/dist/code-mode/index.js +267 -0
- package/dist/code-mode/isolated-vm.d.ts +65 -0
- package/dist/code-mode/isolated-vm.js +116 -0
- package/dist/code-mode/tools.d.ts +48 -0
- package/dist/code-mode/tools.js +73 -0
- package/dist/code-mode/type-hints.d.ts +20 -0
- package/dist/code-mode/type-hints.js +205 -0
- package/dist/docker-e2e.d.ts +54 -0
- package/dist/docker-e2e.js +234 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +227 -0
- package/dist/mcp/call-tool-result.d.ts +10 -0
- package/dist/mcp/call-tool-result.js +70 -0
- package/dist/mcp/client-factory.d.ts +59 -0
- package/dist/mcp/client-factory.js +76 -0
- package/dist/mcp/client-manager.d.ts +88 -0
- package/dist/mcp/client-manager.js +307 -0
- package/dist/mcp/config-loader.d.ts +7 -0
- package/dist/mcp/config-loader.js +42 -0
- package/dist/mcp/config.d.ts +56 -0
- package/dist/mcp/config.js +42 -0
- package/dist/mcp/gateway-defaults.d.ts +14 -0
- package/dist/mcp/gateway-defaults.js +21 -0
- package/dist/mcp/host-approval.d.ts +16 -0
- package/dist/mcp/host-approval.js +19 -0
- package/dist/mcp/host-elicitation.d.ts +19 -0
- package/dist/mcp/host-elicitation.js +94 -0
- package/dist/mcp/index.d.ts +8 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/policy.d.ts +314 -0
- package/dist/mcp/policy.js +848 -0
- package/dist/routing/facilities.d.ts +109 -0
- package/dist/routing/facilities.js +175 -0
- package/dist/routing/format.d.ts +24 -0
- package/dist/routing/format.js +64 -0
- package/dist/routing/index.d.ts +4 -0
- package/dist/routing/index.js +4 -0
- package/dist/routing/seam.d.ts +35 -0
- package/dist/routing/seam.js +28 -0
- package/dist/routing/tripwire.d.ts +42 -0
- package/dist/routing/tripwire.js +80 -0
- package/dist/skills/discover.d.ts +12 -0
- package/dist/skills/discover.js +85 -0
- package/dist/skills/format.d.ts +13 -0
- package/dist/skills/format.js +42 -0
- package/dist/skills/index.d.ts +6 -0
- package/dist/skills/index.js +6 -0
- package/dist/skills/load-skill-tool.d.ts +56 -0
- package/dist/skills/load-skill-tool.js +183 -0
- package/dist/skills/mcp-tool-proxy.d.ts +7 -0
- package/dist/skills/mcp-tool-proxy.js +41 -0
- package/dist/skills/sep2640/client.d.ts +91 -0
- package/dist/skills/sep2640/client.js +160 -0
- package/dist/skills/sep2640/discover.d.ts +33 -0
- package/dist/skills/sep2640/discover.js +82 -0
- package/dist/skills/sep2640/index.d.ts +13 -0
- package/dist/skills/sep2640/index.js +13 -0
- package/dist/skills/sep2640/integrity.d.ts +58 -0
- package/dist/skills/sep2640/integrity.js +185 -0
- package/dist/skills/sep2640/load.d.ts +61 -0
- package/dist/skills/sep2640/load.js +90 -0
- package/dist/skills/sep2640/protocol.d.ts +146 -0
- package/dist/skills/sep2640/protocol.js +214 -0
- package/dist/skills/sep2640/spec.d.ts +76 -0
- package/dist/skills/sep2640/spec.js +88 -0
- package/dist/skills/skill-registry.d.ts +85 -0
- package/dist/skills/skill-registry.js +85 -0
- package/dist/tool-cli/bridge.d.ts +14 -0
- package/dist/tool-cli/bridge.js +196 -0
- package/dist/tool-cli/format.d.ts +20 -0
- package/dist/tool-cli/format.js +73 -0
- package/dist/tool-cli/index.d.ts +7 -0
- package/dist/tool-cli/index.js +5 -0
- package/dist/tool-cli/provider.d.ts +21 -0
- package/dist/tool-cli/provider.js +114 -0
- package/dist/tool-cli/startup.d.ts +22 -0
- package/dist/tool-cli/startup.js +47 -0
- package/package.json +78 -26
- package/.dockerignore +0 -6
- package/.github/dependabot.yml +0 -25
- package/.github/workflows/ci.yml +0 -50
- package/.hadolint.yaml +0 -6
- package/.prettierignore +0 -4
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -118
- package/DECISIONS.md +0 -99
- package/Dockerfile +0 -39
- package/aube-lock.yaml +0 -4146
- package/docs/code-mode.md +0 -38
- package/docs/skills.md +0 -57
- package/docs/tool-cli.md +0 -54
- package/eslint.config.js +0 -27
- package/images/banner.webp +0 -0
- package/images/code-c-maude.webp +0 -0
- package/images/code-mode-histogram.png +0 -0
- package/images/nuclear-mcp-football.webp +0 -0
- package/images/skills-enabling-mcp-tools.png +0 -0
- package/images/the-skill-dealer.webp +0 -0
- package/images/tool-cli-grep.png +0 -0
- package/mise.toml +0 -53
- package/scripts/docker-entrypoint.sh +0 -29
- package/scripts/test-echo-server.mjs +0 -28
- package/scripts/test-mcp-integration.mjs +0 -59
- package/scripts/test-tool-cli-smoke.mjs +0 -93
- package/src/code-mode/code-mode-integration.test.ts +0 -135
- package/src/code-mode/eligibility.test.ts +0 -60
- package/src/code-mode/eligibility.ts +0 -16
- package/src/code-mode/executor.test.ts +0 -168
- package/src/code-mode/executor.ts +0 -163
- package/src/code-mode/index.ts +0 -208
- package/src/code-mode/tools.ts +0 -110
- package/src/code-mode/type-hints.test.ts +0 -187
- package/src/code-mode/type-hints.ts +0 -249
- package/src/docker-e2e.test.ts +0 -195
- package/src/docker-e2e.ts +0 -277
- package/src/index.test.ts +0 -8
- package/src/index.ts +0 -179
- package/src/mcp/client-manager.test.ts +0 -250
- package/src/mcp/client-manager.ts +0 -199
- package/src/mcp/config-loader.test.ts +0 -115
- package/src/mcp/config-loader.ts +0 -46
- package/src/mcp/config.ts +0 -40
- package/src/mcp/index.ts +0 -4
- package/src/skills/discover.ts +0 -81
- package/src/skills/format.test.ts +0 -70
- package/src/skills/format.ts +0 -42
- package/src/skills/index.ts +0 -9
- package/src/skills/load-skill-tool.ts +0 -136
- package/src/skills/mcp-tool-proxy.ts +0 -143
- package/src/skills/skill-integration.test.ts +0 -139
- package/src/skills/skill-registry.test.ts +0 -83
- package/src/skills/skill-registry.ts +0 -69
- package/src/test-servers/weather-server.ts +0 -171
- package/src/test-servers/weather-stdio.ts +0 -11
- package/src/tool-cli/format.test.ts +0 -33
- package/src/tool-cli/format.ts +0 -56
- package/src/tool-cli/index.ts +0 -3
- package/tsconfig.json +0 -21
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sam Morrow
|
|
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
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# mcpi-ext
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@sammorrowdrums/mcpi)
|
|
4
|
+
[](https://www.npmjs.com/package/@sammorrowdrums/mcpi-ext)
|
|
5
|
+
[](https://www.npmjs.com/package/@sammorrowdrums/tool-cli)
|
|
6
|
+
|
|
3
7
|
> **Experimental.** This extension implements progressive MCP tool discovery via skills for [mcpi](https://github.com/SamMorrowDrums/mcpi) (an experimental pi fork). See the [skills-as-groups proposal](https://github.com/modelcontextprotocol/experimental-ext-grouping/pull/13) for the proposed MCP spec addition, and the [progressive tool discovery docs](https://github.com/SamMorrowDrums/mcpi/blob/main/docs/progressive-tool-discovery.md) for implementation details.
|
|
4
8
|
|
|
5
9
|
```sh
|
|
@@ -12,7 +16,7 @@ See [Quick Start](#quick-start) for MCP server configuration.
|
|
|
12
16
|
|
|
13
17
|
---
|
|
14
18
|
|
|
15
|
-

|
|
19
|
+

|
|
16
20
|
|
|
17
21
|
> _They will tell you that MCP has a context problem. That the protocol gives too many tools, that the model drowns in schemas it doesn't need, that the cost of knowing everything is losing the ability to do anything well._
|
|
18
22
|
>
|
|
@@ -26,17 +30,17 @@ See [Quick Start](#quick-start) for MCP server configuration.
|
|
|
26
30
|
|
|
27
31
|
Building custom [MCP](https://modelcontextprotocol.io/) support as [mcpi](https://github.com/SamMorrowDrums/mcpi) extensions. This project implements **tiered progressive discovery** — three complementary strategies for exposing MCP tools to an AI agent, each paying only the context tokens it needs.
|
|
28
32
|
|
|
29
|
-
| Tier | Aspect | Mechanism
|
|
30
|
-
| ------------- | ------------------------ |
|
|
31
|
-
| 1 — Skills | **The Skill Dealer** | `skill://` resources gate tools via `allowed-tools`
|
|
32
|
-
| 2 — tool-cli | **The Nuclear Football** | CLI progressive discovery via shell
|
|
33
|
-
| 3 — Code Mode | **Codey C. Maude** |
|
|
33
|
+
| Tier | Aspect | Mechanism |
|
|
34
|
+
| ------------- | ------------------------ | --------------------------------------------------- |
|
|
35
|
+
| 1 — Skills | **The Skill Dealer** | `skill://` resources gate tools via `allowed-tools` |
|
|
36
|
+
| 2 — tool-cli | **The Nuclear Football** | CLI progressive discovery via shell |
|
|
37
|
+
| 3 — Code Mode | **Codey C. Maude** | Always-on sandboxed JS with read-only MCP dispatch |
|
|
34
38
|
|
|
35
39
|
---
|
|
36
40
|
|
|
37
41
|
## I. The Skill Dealer
|
|
38
42
|
|
|
39
|
-

|
|
43
|
+

|
|
40
44
|
|
|
41
45
|
> _The Skill Dealer does not give you what you ask for. The Skill Dealer gives you what you need — and nothing more._
|
|
42
46
|
|
|
@@ -44,76 +48,109 @@ MCP servers ship `skill://` resources — SKILL.md files declaring which tools a
|
|
|
44
48
|
|
|
45
49
|
When the model calls `load_skill`, the skill's instructions arrive and its tools are unblocked. The model discovers tools from the skill body and can call them immediately. The MCP server itself declares how its tools should be discovered.
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
Anthropic's [tool search](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) solves a similar problem from the model side -- deferring tool loading to avoid cache invalidation from large tool lists. But where tool search has the model _pull_ tools on demand, skill invocation _pushes_ them: when `load_skill` fires, the harness sends unsolicited tool definitions to the model API alongside the skill instructions. The model doesn't search for tools -- the right tools arrive because the skill declared them.
|
|
52
|
+
|
|
53
|
+
📖 [**How it works →**](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/docs/skills.md) — deferred gating, `defer_loading` provider support, `tool_call` hook enforcement.
|
|
48
54
|
|
|
49
55
|
> _"What you do not need to know," said the Skill Dealer, shuffling the deck, "you will not be burdened with knowing."_
|
|
50
56
|
|
|
51
|
-

|
|
57
|
+

|
|
52
58
|
|
|
53
59
|
---
|
|
54
60
|
|
|
55
61
|
## II. The Nuclear Football
|
|
56
62
|
|
|
57
|
-

|
|
63
|
+

|
|
58
64
|
|
|
59
65
|
> _The Football is not a weapon. The Football is the authority to use weapons. Whoever holds it can reach any server, call any tool, chain any result — but they must do so deliberately, one command at a time._
|
|
60
66
|
|
|
61
|
-
[`tool-cli`](https://github.com/SamMorrowDrums/tool-cli) is a thin CLI binary that speaks
|
|
67
|
+
[`tool-cli`](https://github.com/SamMorrowDrums/tool-cli) is a thin CLI binary that speaks authenticated bridge protocol v1 to the extension. The agent uses it like any shell command — composable with pipes, grep, jq, loops. Discovery is progressive: server list → tool list → schema → call. The same policy-backed bridge lists and reads ordinary MCP resources, including binary `--out` files, while keeping `skill://` and SEP-2640-declared skill resources isolated behind `load_skill`.
|
|
62
68
|
|
|
63
|
-
📖 [**How it works →**](docs/tool-cli.md) — architecture, progressive discovery, shell composability.
|
|
69
|
+
📖 [**How it works →**](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/docs/tool-cli.md) — architecture, progressive discovery, shell composability.
|
|
64
70
|
📦 [**Standalone package →**](https://github.com/SamMorrowDrums/tool-cli) — `ToolProvider` interface, server, and implementor guidance for other languages.
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
This is the dual-lock design: the agent holds the briefcase -- reach to every server, every tool, every chain of commands. But the harness holds the launch authority. The HTTP layer isn't a separate service with its own auth; it runs inside the extension process. Every call routes back through `McpPolicy`, the shared authorization boundary, giving full observability and a single HITL choke point. Bestow executive control to the agent, but keep the safety in the infrastructure.
|
|
73
|
+
|
|
74
|
+
> _They pass the Football from hand to hand. It is heavy with potential. Every tool on every server is one command away — but you must type the command yourself. And somewhere behind you, the harness is watching._
|
|
67
75
|
|
|
68
|
-

|
|
76
|
+

|
|
69
77
|
|
|
70
78
|
---
|
|
71
79
|
|
|
72
80
|
## III. Codey C. Maude
|
|
73
81
|
|
|
74
|
-

|
|
82
|
+

|
|
75
83
|
|
|
76
|
-
> _Codey does not ask permission. Codey does not need to. Everything Codey touches is read-only,
|
|
84
|
+
> _Codey does not ask permission. Codey does not need to. Everything Codey touches is explicitly read-only, and the sandbox cannot be escaped. Codey is safe by construction._
|
|
77
85
|
|
|
78
|
-
Code Mode
|
|
86
|
+
Code Mode is always available for arithmetic, parsing, and deterministic transforms. It catalogs every MCP tool, but only dispatches tools that are explicitly **read-only** and non-destructive. Declared output schemas produce precise hints; read-only tools without one get a client-internal permissive survival schema with visible provenance. The model's JavaScript runs inside a memory- and time-limited V8 isolate with no filesystem, network, or process access.
|
|
79
87
|
|
|
80
|
-
📖 [**How it works →**](docs/code-mode.md) — sandbox isolation,
|
|
88
|
+
📖 [**How it works →**](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/docs/code-mode.md) — sandbox isolation, catalog provenance, tool dispatch.
|
|
81
89
|
|
|
82
90
|
> _"I can see everything," Codey said, eyes reflecting infinite JSON. "I just can't touch it. That's the point. That's why they trust me."_
|
|
83
91
|
|
|
84
|
-

|
|
92
|
+

|
|
85
93
|
|
|
86
94
|
---
|
|
87
95
|
|
|
88
|
-
##
|
|
96
|
+
## Choosing an Execution Facility
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
Whenever the extension loads it emits a single `<execution_routing>` prompt section describing the
|
|
99
|
+
facilities available to the agent — **including when zero MCP servers are connected**. The section
|
|
100
|
+
sorts facilities by task shape, not by rank: none is a default, none outranks another, and there is
|
|
101
|
+
no sequence to try them in. Every facility states its own availability, so an unavailable one is
|
|
102
|
+
listed with the reason rather than silently omitted.
|
|
103
|
+
|
|
104
|
+
Three of the four facilities come from this extension; the fourth is the host's own shell, described
|
|
105
|
+
alongside them because most real tasks need it.
|
|
106
|
+
|
|
107
|
+
| Facility | Suits work that is… |
|
|
108
|
+
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
109
|
+
| [Skills](#i-the-skill-dealer) | a documented domain workflow — sequencing, conventions, and a curated tool set |
|
|
110
|
+
| [Code mode](#iii-codey-c-maude) | exact computation or control flow, sandboxed with no filesystem, network, or process access |
|
|
111
|
+
| [tool-cli](#ii-the-nuclear-football) | reaching a specific MCP tool, or discovering what exists — run through the host bash tool |
|
|
112
|
+
| bash + external programs | touching the real machine: files, git, build tools, data pipelines, artifacts that persist |
|
|
113
|
+
|
|
114
|
+
**Skills** — when a curated workflow exists for the domain task. "Triage these 20 issues" means
|
|
115
|
+
loading the triage skill, which supplies the right tools _plus_ the workflow instructions (dedup
|
|
116
|
+
checks, labeling conventions, close criteria). Re-deriving that from raw tool calls is wasteful and
|
|
117
|
+
error-prone. A skill enables the tools it declares only after the grant is approved.
|
|
99
118
|
|
|
100
|
-
**
|
|
119
|
+
**Code mode** — when you need real computation across many calls: pagination loops, aggregation,
|
|
120
|
+
joining results, math. 876 issues across 9 pages, counting labels per issue, summing into a
|
|
121
|
+
histogram — that's a loop with state, and one sandbox execution does it. Available even with zero
|
|
122
|
+
MCP servers connected, because pure computation needs no server.
|
|
101
123
|
|
|
102
|
-
**
|
|
124
|
+
**tool-cli** — one-shot or exploratory MCP calls, especially when piping through Unix tools.
|
|
125
|
+
`tool-cli github search_code '{"query":"auth"}' | jq '.items[].path'` — one call, pipe to jq, done.
|
|
126
|
+
Also the way to discover what's on a server you haven't used before. It is a program, not a tool:
|
|
127
|
+
the agent invokes the bash tool with a `tool-cli ...` command. It is advertised as available only
|
|
128
|
+
after bash is active and the local server completes an authenticated compatible v1 handshake;
|
|
129
|
+
inherited credentials are masked and usage docs remain withheld on startup, timeout, auth, or
|
|
130
|
+
major-version failure.
|
|
103
131
|
|
|
104
|
-
**
|
|
132
|
+
**bash + external programs** — the substrate the other three lack. It is the only facility that can
|
|
133
|
+
create, modify, or inspect files and artifacts, and the only one that runs the host's real programs.
|
|
105
134
|
|
|
106
|
-
###
|
|
135
|
+
### Facilities compose
|
|
136
|
+
|
|
137
|
+
tool-cli and bash compose especially closely: because tool-cli _is_ a program run with the bash
|
|
138
|
+
tool, fetching MCP data and then filtering, joining, or writing it to disk with ordinary programs is
|
|
139
|
+
a single bash command rather than two rival approaches.
|
|
107
140
|
|
|
108
141
|
> _"Triage the backlog of github/github-mcp-server: find stale bugs older than 90 days with no recent activity, summarize patterns, and close obvious duplicates."_
|
|
109
142
|
|
|
110
|
-
1. **
|
|
143
|
+
1. **Code mode** paginated all open bug issues, filtered by `updated < 90d ago`, grouped by label
|
|
144
|
+
and keyword to find clusters. Computation across many pages — this is what sandboxes are for.
|
|
111
145
|
|
|
112
|
-
2. **
|
|
146
|
+
2. **tool-cli** spot-checked suspect issues. `tool-cli github get_issue '{"number":42}'` piped
|
|
147
|
+
through `jq` to eyeball specific fields. Quick, ad-hoc, composable.
|
|
113
148
|
|
|
114
|
-
3. **
|
|
149
|
+
3. **A skill** (`triage-issues`) drove the actual closures — following the project's triage
|
|
150
|
+
workflow with correct labels, comment templates, and close reasons.
|
|
115
151
|
|
|
116
|
-
|
|
152
|
+
4. **bash** wrote the resulting summary to a file in the repo, because none of the other three can
|
|
153
|
+
touch the filesystem.
|
|
117
154
|
|
|
118
155
|
---
|
|
119
156
|
|
|
@@ -125,7 +162,7 @@ flowchart TD
|
|
|
125
162
|
T1["load_skill\n(Tier 1 — Skills)"]
|
|
126
163
|
T2["tool-cli\n(Tier 2 — Football)"]
|
|
127
164
|
T3["code_search / code_execute\n(Tier 3 — Code Mode)"]
|
|
128
|
-
MCM["McpClientManager\n(MCP
|
|
165
|
+
MCM["McpClientManager\n(split MCP v2 client — stdio & Streamable HTTP)"]
|
|
129
166
|
T1 --> MCM
|
|
130
167
|
T2 --> MCM
|
|
131
168
|
T3 --> MCM
|
|
@@ -139,10 +176,13 @@ The harness controls what the model sees. MCP servers just expose their tools an
|
|
|
139
176
|
|
|
140
177
|
### Every call flows through the harness
|
|
141
178
|
|
|
142
|
-
All three tiers route MCP tool calls back through the extension process
|
|
179
|
+
All three tiers route MCP tool calls back through the extension process, and every one of them crosses the same authorization boundary: `McpPolicy`. Even when the model writes sandboxed JavaScript (Code Mode) or shells out to `tool-cli`, the actual MCP call is authorized and dispatched by that one object. This means:
|
|
143
180
|
|
|
144
181
|
- **Every tool invocation appears in the agent log** — skills, tool-cli one-shots, and Code Mode sandbox calls alike. Full observability without instrumentation.
|
|
145
|
-
- **Human-in-the-loop
|
|
182
|
+
- **Human-in-the-loop happens at one point** — `McpPolicy` checks tool annotations (`readOnlyHint`, `destructiveHint`) and gates non-read-only calls through user confirmation, regardless of which tier initiated them. A tool unlocked by an approved skill grant is not re-prompted.
|
|
183
|
+
- **Undiscovered and gated tools never reach upstream** — the policy verifies the tool exists in the discovered set and is not skill-gated before contacting the server, so naming a hidden tool over the authenticated RPC socket fails at the boundary.
|
|
184
|
+
- **Resource operations use the same policy** — tool-cli can list templates and read ordinary text/binary resources, while every `skill://` URI and SEP-2640-declared resource remains isolated; skill reads are origin-bound, and a discovery pass cannot authorize a skill-load read.
|
|
185
|
+
- **Every decision is audited** — allowed and denied operations alike are recorded with their source (`proxy`, `code-mode`, `tool-cli`, `skill-discovery`, `skill-load`).
|
|
146
186
|
|
|
147
187
|
> _MCP doesn't have a context problem. It never did. It was just waiting for someone to imagine the right way to read the runes._
|
|
148
188
|
|
|
@@ -204,6 +244,26 @@ You can add more servers — both `stdio` (spawns a process) and `remote` (Strea
|
|
|
204
244
|
}
|
|
205
245
|
```
|
|
206
246
|
|
|
247
|
+
### Protocol compatibility and defaults
|
|
248
|
+
|
|
249
|
+
mcpi-ext uses `@modelcontextprotocol/client@2.0.0` in automatic version-negotiation
|
|
250
|
+
mode. It first probes the released `2026-07-28` protocol with `server/discover`, then
|
|
251
|
+
falls back to the legacy `initialize` handshake when a server does not support the
|
|
252
|
+
modern era. The connection log reports the negotiated era.
|
|
253
|
+
|
|
254
|
+
- Tool and skill-resource lists follow cursors automatically, with a 64-page safety
|
|
255
|
+
limit.
|
|
256
|
+
- Results without a server-provided `ttlMs` are immediately stale
|
|
257
|
+
(`defaultCacheTtlMs: 0`). Explicit server cache hints are still honored in the
|
|
258
|
+
SDK's in-memory cache; mcpi-ext does not configure a persistent or shared cache.
|
|
259
|
+
- Tool-list change handling is enabled. On modern servers the SDK may open a
|
|
260
|
+
`subscriptions/listen` stream when the capability is advertised; legacy servers
|
|
261
|
+
continue to use list-changed notifications. General subscription management,
|
|
262
|
+
durable subscription resume, and live skill-resource refresh are not exposed.
|
|
263
|
+
- Modern `input_required` flows support explicit form input, decline, and cancel in
|
|
264
|
+
interactive mcpi sessions. Headless and URL elicitation fail with an actionable
|
|
265
|
+
error rather than approving automatically.
|
|
266
|
+
|
|
207
267
|
### 3. Run
|
|
208
268
|
|
|
209
269
|
```sh
|
|
@@ -227,22 +287,52 @@ Then run with your local build:
|
|
|
227
287
|
mcpi --extension ./dist/index.js --mcp-config ~/.config/mcpi-ext/mcp.json
|
|
228
288
|
```
|
|
229
289
|
|
|
230
|
-
See [AGENTS.md](AGENTS.md) for full tooling docs, dev loop, and architecture details.
|
|
290
|
+
See [AGENTS.md](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/AGENTS.md) for full tooling docs, dev loop, and architecture details.
|
|
231
291
|
|
|
232
292
|
## Project Structure
|
|
233
293
|
|
|
234
294
|
```
|
|
235
295
|
src/
|
|
236
296
|
index.ts Extension entry point (lifecycle hooks, wiring)
|
|
237
|
-
mcp/ MCP client management (connections,
|
|
297
|
+
mcp/ MCP client management (connections, discovery) + McpPolicy
|
|
298
|
+
routing/ Execution-facility descriptors, prompt section, host seam
|
|
238
299
|
skills/ Skill registry, discovery, gating, tool proxies
|
|
239
|
-
tool-cli/ tool-cli RPC server,
|
|
240
|
-
code-mode/ V8 sandbox executor,
|
|
241
|
-
test-servers/ Test MCP servers (weather, echo)
|
|
242
|
-
docs/ Detailed mechanism documentation
|
|
300
|
+
tool-cli/ tool-cli RPC server, provider, bridge handshake, prompt
|
|
301
|
+
code-mode/ V8 sandbox executor, lazy isolated-vm adapter, type hints
|
|
302
|
+
test-servers/ Test MCP servers (weather, echo, skills fixtures)
|
|
303
|
+
docs/ Detailed mechanism documentation (incl. releasing.md)
|
|
243
304
|
images/ Banner, character art, and screenshots
|
|
305
|
+
scripts/ Integration, smoke, and release-check scripts
|
|
306
|
+
tsconfig.json Development build (compiles tests and fixture servers)
|
|
307
|
+
tsconfig.build.json Published build (no tests, fixtures, or source maps)
|
|
244
308
|
```
|
|
245
309
|
|
|
310
|
+
## Requirements
|
|
311
|
+
|
|
312
|
+
Node.js `>=22.13.0`. Node 22 and 24 are both covered by CI.
|
|
313
|
+
|
|
314
|
+
Code Mode needs the optional [`isolated-vm`](https://github.com/laverdet/isolated-vm)
|
|
315
|
+
native addon. It ships prebuilt binaries for Linux (x64, arm64), macOS
|
|
316
|
+
(Apple Silicon), and Windows (x64), so the usual install is a download rather than a
|
|
317
|
+
compile. Where no prebuild matches — Intel macOS, for instance — npm compiles it from
|
|
318
|
+
source and needs a C++ toolchain.
|
|
319
|
+
|
|
320
|
+
If the addon is unavailable for any reason, installation still succeeds and the
|
|
321
|
+
extension still loads. Code Mode reports itself unavailable with the specific cause,
|
|
322
|
+
and skills, tool-cli, and execution routing continue to work. Code Mode never falls
|
|
323
|
+
back to `node:vm`: that would silently downgrade an isolate boundary to same-process
|
|
324
|
+
execution and hand sandboxed code the host realm.
|
|
325
|
+
|
|
326
|
+
To skip the addon deliberately, install with `npm install --omit=optional`.
|
|
327
|
+
|
|
328
|
+
## Releasing
|
|
329
|
+
|
|
330
|
+
Published to npm by [`.github/workflows/publish.yml`](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/.github/workflows/publish.yml)
|
|
331
|
+
using npm trusted publishing — a GitHub Release triggers it, OIDC authenticates it,
|
|
332
|
+
and no `NPM_TOKEN` exists anywhere in this repository.
|
|
333
|
+
|
|
334
|
+
📖 [**Release process →**](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/docs/releasing.md) — trusted-publisher setup, cutting a release, and what the workflow refuses to do.
|
|
335
|
+
|
|
246
336
|
## License
|
|
247
337
|
|
|
248
|
-
|
|
338
|
+
[MIT](https://github.com/SamMorrowDrums/mcpi-ext/blob/main/LICENSE)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { McpClientManager, McpTool } from "../mcp/index.js";
|
|
2
|
+
export declare const SYNTHESIZED_OUTPUT_SCHEMA: NonNullable<McpTool["outputSchema"]>;
|
|
3
|
+
export type OutputSchemaProvenance = "declared" | "synthesized" | "unavailable";
|
|
4
|
+
export type CodeModeRefusalReason = "destructive_hint" | "read_only_hint_required";
|
|
5
|
+
/**
|
|
6
|
+
* Client-internal Code Mode metadata. The source MCP tool is retained separately
|
|
7
|
+
* so provenance never becomes part of an MCP request or response.
|
|
8
|
+
*/
|
|
9
|
+
export interface CodeModeTool {
|
|
10
|
+
readonly tool: McpTool;
|
|
11
|
+
readonly callable: boolean;
|
|
12
|
+
readonly refusalReasons: readonly CodeModeRefusalReason[];
|
|
13
|
+
readonly outputSchema?: NonNullable<McpTool["outputSchema"]>;
|
|
14
|
+
readonly outputSchemaProvenance: OutputSchemaProvenance;
|
|
15
|
+
}
|
|
16
|
+
export interface CodeModeDiagnostics {
|
|
17
|
+
readonly totalTools: number;
|
|
18
|
+
readonly callableTools: number;
|
|
19
|
+
readonly refusedTools: number;
|
|
20
|
+
readonly declaredOutputSchemas: number;
|
|
21
|
+
readonly synthesizedOutputSchemas: number;
|
|
22
|
+
readonly unavailableOutputSchemas: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Code Mode dispatch is allowed only when a tool explicitly declares itself
|
|
26
|
+
* read-only and does not also declare destructive behavior.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isEligibleForCodeMode(tool: McpTool): boolean;
|
|
29
|
+
/** Get all tools eligible for code mode across all connected MCP servers. */
|
|
30
|
+
export declare function getEligibleTools(mcpManager: McpClientManager): McpTool[];
|
|
31
|
+
/** Build the internal Code Mode catalog without mutating source MCP tool definitions. */
|
|
32
|
+
export declare function toCodeModeTool(tool: McpTool): CodeModeTool;
|
|
33
|
+
/** Get every discovered MCP tool with Code Mode permission and schema metadata. */
|
|
34
|
+
export declare function getCodeModeTools(mcpManager: McpClientManager): CodeModeTool[];
|
|
35
|
+
export declare function getCodeModeDiagnostics(tools: readonly CodeModeTool[]): CodeModeDiagnostics;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const SYNTHESIZED_OUTPUT_SCHEMA = Object.freeze({});
|
|
2
|
+
/**
|
|
3
|
+
* Code Mode dispatch is allowed only when a tool explicitly declares itself
|
|
4
|
+
* read-only and does not also declare destructive behavior.
|
|
5
|
+
*/
|
|
6
|
+
export function isEligibleForCodeMode(tool) {
|
|
7
|
+
return tool.annotations?.readOnlyHint === true && tool.annotations.destructiveHint !== true;
|
|
8
|
+
}
|
|
9
|
+
/** Get all tools eligible for code mode across all connected MCP servers. */
|
|
10
|
+
export function getEligibleTools(mcpManager) {
|
|
11
|
+
return mcpManager.getTools().filter(isEligibleForCodeMode);
|
|
12
|
+
}
|
|
13
|
+
/** Build the internal Code Mode catalog without mutating source MCP tool definitions. */
|
|
14
|
+
export function toCodeModeTool(tool) {
|
|
15
|
+
const refusalReasons = [];
|
|
16
|
+
if (tool.annotations?.readOnlyHint !== true) {
|
|
17
|
+
refusalReasons.push("read_only_hint_required");
|
|
18
|
+
}
|
|
19
|
+
if (tool.annotations?.destructiveHint === true) {
|
|
20
|
+
refusalReasons.push("destructive_hint");
|
|
21
|
+
}
|
|
22
|
+
const callable = refusalReasons.length === 0;
|
|
23
|
+
const declaredOutputSchema = tool.outputSchema !== undefined && tool.outputSchema !== null ? tool.outputSchema : undefined;
|
|
24
|
+
if (declaredOutputSchema) {
|
|
25
|
+
return {
|
|
26
|
+
tool,
|
|
27
|
+
callable,
|
|
28
|
+
refusalReasons,
|
|
29
|
+
outputSchema: declaredOutputSchema,
|
|
30
|
+
outputSchemaProvenance: "declared",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (callable) {
|
|
34
|
+
return {
|
|
35
|
+
tool,
|
|
36
|
+
callable,
|
|
37
|
+
refusalReasons,
|
|
38
|
+
outputSchema: SYNTHESIZED_OUTPUT_SCHEMA,
|
|
39
|
+
outputSchemaProvenance: "synthesized",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
tool,
|
|
44
|
+
callable,
|
|
45
|
+
refusalReasons,
|
|
46
|
+
outputSchemaProvenance: "unavailable",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Get every discovered MCP tool with Code Mode permission and schema metadata. */
|
|
50
|
+
export function getCodeModeTools(mcpManager) {
|
|
51
|
+
return mcpManager.getTools().map(toCodeModeTool);
|
|
52
|
+
}
|
|
53
|
+
export function getCodeModeDiagnostics(tools) {
|
|
54
|
+
const callableTools = tools.filter((tool) => tool.callable).length;
|
|
55
|
+
const countProvenance = (provenance) => tools.filter((tool) => tool.outputSchemaProvenance === provenance).length;
|
|
56
|
+
return {
|
|
57
|
+
totalTools: tools.length,
|
|
58
|
+
callableTools,
|
|
59
|
+
refusedTools: tools.length - callableTools,
|
|
60
|
+
declaredOutputSchemas: countProvenance("declared"),
|
|
61
|
+
synthesizedOutputSchemas: countProvenance("synthesized"),
|
|
62
|
+
unavailableOutputSchemas: countProvenance("unavailable"),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Result of code execution. */
|
|
2
|
+
export interface ExecuteResult {
|
|
3
|
+
result: unknown;
|
|
4
|
+
error?: string;
|
|
5
|
+
errorDetails?: CodeModeErrorDetails;
|
|
6
|
+
logs: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface CodeModeErrorDetails {
|
|
9
|
+
error: string;
|
|
10
|
+
message: string;
|
|
11
|
+
alternatives?: string[];
|
|
12
|
+
toolName?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class CodeModeDispatchError extends Error {
|
|
16
|
+
readonly details: CodeModeErrorDetails;
|
|
17
|
+
constructor(details: CodeModeErrorDetails);
|
|
18
|
+
}
|
|
19
|
+
/** A function the sandbox can call to invoke an MCP tool. */
|
|
20
|
+
export type ToolDispatchFn = (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
21
|
+
export interface ExecutorOptions {
|
|
22
|
+
/** Memory limit in MB for the V8 isolate. Default: 128. */
|
|
23
|
+
memoryLimit?: number;
|
|
24
|
+
/** Execution timeout in milliseconds. Default: 30000. */
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Error code surfaced when the V8 isolate backend is not installed. */
|
|
28
|
+
export declare const SANDBOX_UNAVAILABLE_ERROR = "sandbox_unavailable";
|
|
29
|
+
/**
|
|
30
|
+
* Execute model-generated JavaScript code in an isolated V8 sandbox.
|
|
31
|
+
*
|
|
32
|
+
* The code runs in a fresh `isolated-vm` isolate with:
|
|
33
|
+
* - Enforced memory limit (default 128MB)
|
|
34
|
+
* - Execution timeout (default 30s)
|
|
35
|
+
* - No access to Node.js APIs, filesystem, or network
|
|
36
|
+
* - Only access to provided tool dispatch functions via `codemode.*` proxy
|
|
37
|
+
*
|
|
38
|
+
* Tool calls are dispatched to the host via `Reference` callbacks —
|
|
39
|
+
* actual MCP tool execution happens outside the sandbox.
|
|
40
|
+
*
|
|
41
|
+
* The `isolated-vm` addon is optional and loaded lazily. If it is unavailable
|
|
42
|
+
* this returns a structured `sandbox_unavailable` error rather than falling
|
|
43
|
+
* back to Node's `vm` module: `node:vm` shares the host realm and heap, so
|
|
44
|
+
* using it here would silently void the isolation guarantee this API makes.
|
|
45
|
+
*/
|
|
46
|
+
export declare function executeInSandbox(code: string, toolNames: string[], dispatch: ToolDispatchFn, options?: ExecutorOptions): Promise<ExecuteResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Normalize model-generated code:
|
|
49
|
+
* - Strip markdown code fences
|
|
50
|
+
* - Handle arrow functions, function declarations, export default
|
|
51
|
+
* - Otherwise leave code as a statement block; callers must explicitly `return` a value
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeCode(code: string): string;
|