@sammorrowdrums/mcpi-ext 0.1.0 → 0.2.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/.github/workflows/ci.yml +1 -1
- package/README.md +109 -139
- package/docs/code-mode.md +38 -0
- package/docs/skills.md +57 -0
- package/docs/tool-cli.md +54 -0
- package/images/code-mode-histogram.png +0 -0
- package/images/skills-enabling-mcp-tools.png +0 -0
- package/images/tool-cli-grep.png +0 -0
- package/mise.toml +1 -13
- package/package.json +5 -7
- package/scripts/test-tool-cli-smoke.mjs +40 -9
- package/src/index.ts +24 -3
- package/src/tool-cli/index.ts +2 -2
- package/dist/code-mode/code-mode-integration.test.d.ts +0 -2
- package/dist/code-mode/code-mode-integration.test.d.ts.map +0 -1
- package/dist/code-mode/code-mode-integration.test.js +0 -116
- package/dist/code-mode/code-mode-integration.test.js.map +0 -1
- package/dist/code-mode/eligibility.d.ts +0 -10
- package/dist/code-mode/eligibility.d.ts.map +0 -1
- package/dist/code-mode/eligibility.js +0 -14
- package/dist/code-mode/eligibility.js.map +0 -1
- package/dist/code-mode/eligibility.test.d.ts +0 -2
- package/dist/code-mode/eligibility.test.d.ts.map +0 -1
- package/dist/code-mode/eligibility.test.js +0 -53
- package/dist/code-mode/eligibility.test.js.map +0 -1
- package/dist/code-mode/executor.d.ts +0 -35
- package/dist/code-mode/executor.d.ts.map +0 -1
- package/dist/code-mode/executor.js +0 -120
- package/dist/code-mode/executor.js.map +0 -1
- package/dist/code-mode/executor.test.d.ts +0 -2
- package/dist/code-mode/executor.test.d.ts.map +0 -1
- package/dist/code-mode/executor.test.js +0 -130
- package/dist/code-mode/executor.test.js.map +0 -1
- package/dist/code-mode/index.d.ts +0 -65
- package/dist/code-mode/index.d.ts.map +0 -1
- package/dist/code-mode/index.js +0 -175
- package/dist/code-mode/index.js.map +0 -1
- package/dist/code-mode/tools.d.ts +0 -45
- package/dist/code-mode/tools.d.ts.map +0 -1
- package/dist/code-mode/tools.js +0 -70
- package/dist/code-mode/tools.js.map +0 -1
- package/dist/code-mode/type-hints.d.ts +0 -20
- package/dist/code-mode/type-hints.d.ts.map +0 -1
- package/dist/code-mode/type-hints.js +0 -189
- package/dist/code-mode/type-hints.js.map +0 -1
- package/dist/code-mode/type-hints.test.d.ts +0 -2
- package/dist/code-mode/type-hints.test.d.ts.map +0 -1
- package/dist/code-mode/type-hints.test.js +0 -157
- package/dist/code-mode/type-hints.test.js.map +0 -1
- package/dist/docker-e2e.d.ts +0 -55
- package/dist/docker-e2e.d.ts.map +0 -1
- package/dist/docker-e2e.js +0 -235
- package/dist/docker-e2e.js.map +0 -1
- package/dist/docker-e2e.test.d.ts +0 -2
- package/dist/docker-e2e.test.d.ts.map +0 -1
- package/dist/docker-e2e.test.js +0 -176
- package/dist/docker-e2e.test.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -128
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/mcp/client-manager.d.ts +0 -46
- package/dist/mcp/client-manager.d.ts.map +0 -1
- package/dist/mcp/client-manager.js +0 -148
- package/dist/mcp/client-manager.js.map +0 -1
- package/dist/mcp/client-manager.test.d.ts +0 -2
- package/dist/mcp/client-manager.test.d.ts.map +0 -1
- package/dist/mcp/client-manager.test.js +0 -204
- package/dist/mcp/client-manager.test.js.map +0 -1
- package/dist/mcp/config-loader.d.ts +0 -8
- package/dist/mcp/config-loader.d.ts.map +0 -1
- package/dist/mcp/config-loader.js +0 -43
- package/dist/mcp/config-loader.js.map +0 -1
- package/dist/mcp/config-loader.test.d.ts +0 -2
- package/dist/mcp/config-loader.test.d.ts.map +0 -1
- package/dist/mcp/config-loader.test.js +0 -93
- package/dist/mcp/config-loader.test.js.map +0 -1
- package/dist/mcp/config.d.ts +0 -46
- package/dist/mcp/config.d.ts.map +0 -1
- package/dist/mcp/config.js +0 -28
- package/dist/mcp/config.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -5
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -4
- package/dist/mcp/index.js.map +0 -1
- package/dist/skills/discover.d.ts +0 -10
- package/dist/skills/discover.d.ts.map +0 -1
- package/dist/skills/discover.js +0 -60
- package/dist/skills/discover.js.map +0 -1
- package/dist/skills/format.d.ts +0 -10
- package/dist/skills/format.d.ts.map +0 -1
- package/dist/skills/format.js +0 -38
- package/dist/skills/format.js.map +0 -1
- package/dist/skills/format.test.d.ts +0 -2
- package/dist/skills/format.test.d.ts.map +0 -1
- package/dist/skills/format.test.js +0 -63
- package/dist/skills/format.test.js.map +0 -1
- package/dist/skills/index.d.ts +0 -6
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -6
- package/dist/skills/index.js.map +0 -1
- package/dist/skills/load-skill-tool.d.ts +0 -40
- package/dist/skills/load-skill-tool.d.ts.map +0 -1
- package/dist/skills/load-skill-tool.js +0 -104
- package/dist/skills/load-skill-tool.js.map +0 -1
- package/dist/skills/mcp-tool-proxy.d.ts +0 -11
- package/dist/skills/mcp-tool-proxy.d.ts.map +0 -1
- package/dist/skills/mcp-tool-proxy.js +0 -116
- package/dist/skills/mcp-tool-proxy.js.map +0 -1
- package/dist/skills/skill-integration.test.d.ts +0 -2
- package/dist/skills/skill-integration.test.d.ts.map +0 -1
- package/dist/skills/skill-integration.test.js +0 -119
- package/dist/skills/skill-integration.test.js.map +0 -1
- package/dist/skills/skill-registry.d.ts +0 -39
- package/dist/skills/skill-registry.d.ts.map +0 -1
- package/dist/skills/skill-registry.js +0 -48
- package/dist/skills/skill-registry.js.map +0 -1
- package/dist/skills/skill-registry.test.d.ts +0 -2
- package/dist/skills/skill-registry.test.d.ts.map +0 -1
- package/dist/skills/skill-registry.test.js +0 -67
- package/dist/skills/skill-registry.test.js.map +0 -1
- package/dist/test-servers/weather-server.d.ts +0 -12
- package/dist/test-servers/weather-server.d.ts.map +0 -1
- package/dist/test-servers/weather-server.js +0 -139
- package/dist/test-servers/weather-server.js.map +0 -1
- package/dist/test-servers/weather-stdio.d.ts +0 -2
- package/dist/test-servers/weather-stdio.d.ts.map +0 -1
- package/dist/test-servers/weather-stdio.js +0 -11
- package/dist/test-servers/weather-stdio.js.map +0 -1
- package/dist/tool-cli/cli.d.ts +0 -3
- package/dist/tool-cli/cli.d.ts.map +0 -1
- package/dist/tool-cli/cli.js +0 -179
- package/dist/tool-cli/cli.js.map +0 -1
- package/dist/tool-cli/constants.d.ts +0 -7
- package/dist/tool-cli/constants.d.ts.map +0 -1
- package/dist/tool-cli/constants.js +0 -15
- package/dist/tool-cli/constants.js.map +0 -1
- package/dist/tool-cli/format.d.ts +0 -8
- package/dist/tool-cli/format.d.ts.map +0 -1
- package/dist/tool-cli/format.js +0 -57
- package/dist/tool-cli/format.js.map +0 -1
- package/dist/tool-cli/format.test.d.ts +0 -2
- package/dist/tool-cli/format.test.d.ts.map +0 -1
- package/dist/tool-cli/format.test.js +0 -30
- package/dist/tool-cli/format.test.js.map +0 -1
- package/dist/tool-cli/index.d.ts +0 -4
- package/dist/tool-cli/index.d.ts.map +0 -1
- package/dist/tool-cli/index.js +0 -4
- package/dist/tool-cli/index.js.map +0 -1
- package/dist/tool-cli/rpc-client.d.ts +0 -6
- package/dist/tool-cli/rpc-client.d.ts.map +0 -1
- package/dist/tool-cli/rpc-client.js +0 -25
- package/dist/tool-cli/rpc-client.js.map +0 -1
- package/dist/tool-cli/rpc-server.d.ts +0 -48
- package/dist/tool-cli/rpc-server.d.ts.map +0 -1
- package/dist/tool-cli/rpc-server.js +0 -220
- package/dist/tool-cli/rpc-server.js.map +0 -1
- package/dist/tool-cli/rpc-server.test.d.ts +0 -2
- package/dist/tool-cli/rpc-server.test.d.ts.map +0 -1
- package/dist/tool-cli/rpc-server.test.js +0 -154
- package/dist/tool-cli/rpc-server.test.js.map +0 -1
- package/src/tool-cli/cli.ts +0 -232
- package/src/tool-cli/constants.ts +0 -15
- package/src/tool-cli/rpc-client.ts +0 -39
- package/src/tool-cli/rpc-server.test.ts +0 -193
- package/src/tool-cli/rpc-server.ts +0 -287
package/.github/workflows/ci.yml
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# mcpi-ext
|
|
2
2
|
|
|
3
|
-
> **Experimental.** This extension implements progressive MCP tool discovery via skills for [mcpi](https://github.com/SamMorrowDrums/mcpi) (an experimental pi fork).
|
|
3
|
+
> **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
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -g @sammorrowdrums/mcpi@latest @sammorrowdrums/mcpi-ext@latest @sammorrowdrums/tool-cli@latest
|
|
7
|
+
mcpi --extension $(npm root -g)/@sammorrowdrums/mcpi-ext/dist/index.js \
|
|
8
|
+
--mcp-config ~/.config/mcpi-ext/mcp.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See [Quick Start](#quick-start) for MCP server configuration.
|
|
12
|
+
|
|
13
|
+
---
|
|
4
14
|
|
|
5
15
|

|
|
6
16
|
|
|
@@ -30,143 +40,85 @@ Building custom [MCP](https://modelcontextprotocol.io/) support as [mcpi](https:
|
|
|
30
40
|
|
|
31
41
|
> _The Skill Dealer does not give you what you ask for. The Skill Dealer gives you what you need — and nothing more._
|
|
32
42
|
|
|
33
|
-
MCP servers
|
|
34
|
-
|
|
35
|
-
This approach is **cache-preserving**: the tools array and system prompt stay constant throughout the conversation, so prompt cache is never invalidated by skill activation.
|
|
43
|
+
MCP servers ship `skill://` resources — SKILL.md files declaring which tools a skill gates. The extension discovers skills on connection and registers their tools with `deferred: true`: present in the registry for dispatch but hidden from the model and the prompt. **Cache is preserved** — neither the tools array nor the system prompt ever changes.
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
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.
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
📖 [**How it works →**](docs/skills.md) — deferred gating, `defer_loading` provider support, `tool_call` hook enforcement.
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
> _"What you do not need to know," said the Skill Dealer, shuffling the deck, "you will not be burdened with knowing."_
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+

|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
---
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
## II. The Nuclear Football
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
2. The skill's `allowedTools` are added to the `enabledTools` set, unblocking the `tool_call` gate
|
|
51
|
-
3. The model can now call the tools — it discovers them from the skill body (which names them) and the provider's grammar
|
|
57
|
+

|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
sequenceDiagram
|
|
55
|
-
participant Model
|
|
56
|
-
participant load_skill
|
|
57
|
-
participant SkillRegistry
|
|
58
|
-
participant MCP Server
|
|
59
|
-
participant tool_call gate
|
|
60
|
-
|
|
61
|
-
Model->>load_skill: load_skill("github-pr")
|
|
62
|
-
load_skill->>SkillRegistry: Look up skill
|
|
63
|
-
SkillRegistry-->>load_skill: skill metadata + allowed-tools
|
|
64
|
-
load_skill->>MCP Server: Read skill://github-pr
|
|
65
|
-
MCP Server-->>load_skill: SKILL.md body
|
|
66
|
-
load_skill->>tool_call gate: Enable allowed-tools
|
|
67
|
-
load_skill-->>Model: Return workflow instructions
|
|
68
|
-
Note over Model: Skill body names the tools.<br/>Model calls them via tool_use.
|
|
69
|
-
Model->>tool_call gate: create_pull_request(...)
|
|
70
|
-
tool_call gate-->>Model: ✓ Allowed (skill loaded)
|
|
71
|
-
```
|
|
59
|
+
> _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._
|
|
72
60
|
|
|
73
|
-
|
|
61
|
+
[`tool-cli`](https://github.com/SamMorrowDrums/tool-cli) is a thin CLI binary that speaks JSON-RPC 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. Each step pays only the tokens it needs.
|
|
74
62
|
|
|
75
|
-
|
|
63
|
+
📖 [**How it works →**](docs/tool-cli.md) — architecture, progressive discovery, shell composability.
|
|
64
|
+
📦 [**Standalone package →**](https://github.com/SamMorrowDrums/tool-cli) — `ToolProvider` interface, server, and implementor guidance for other languages.
|
|
76
65
|
|
|
77
|
-
|
|
66
|
+
> _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._
|
|
78
67
|
|
|
79
|
-
|
|
68
|
+

|
|
80
69
|
|
|
81
70
|
---
|
|
82
71
|
|
|
83
|
-
##
|
|
84
|
-
|
|
85
|
-

|
|
86
|
-
|
|
87
|
-
> _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._
|
|
88
|
-
|
|
89
|
-
`tool-cli` is a thin CLI binary that speaks JSON-RPC 2.0 to the extension over HTTP. The agent uses it like any shell command — composable with pipes, grep, jq, loops, and all the bash idioms it already knows.
|
|
90
|
-
|
|
91
|
-
```mermaid
|
|
92
|
-
flowchart TD
|
|
93
|
-
A["Agent (mcpi)"] -->|shell exec| B["tool-cli <server> <tool> '{args}'"]
|
|
94
|
-
B -->|"HTTP JSON-RPC (localhost:7179)"| C["ToolCliRpcServer (in extension)"]
|
|
95
|
-
C -->|"MCP protocol (stdio/HTTP)"| D["MCP Server(s)"]
|
|
96
|
-
```
|
|
72
|
+
## III. Codey C. Maude
|
|
97
73
|
|
|
98
|
-
|
|
74
|
+

|
|
99
75
|
|
|
100
|
-
|
|
101
|
-
tool-cli --help # What servers exist?
|
|
102
|
-
tool-cli github # What tools does this server have?
|
|
103
|
-
tool-cli github search_code # What's the schema for this tool?
|
|
104
|
-
tool-cli github search_code '{"query":"auth"}' # Call it
|
|
105
|
-
```
|
|
76
|
+
> _Codey does not ask permission. Codey does not need to. Everything Codey touches is read-only, every result is typed, and the sandbox cannot be escaped. Codey is safe by construction._
|
|
106
77
|
|
|
107
|
-
|
|
78
|
+
Code Mode targets **read-only** tools with **structured output**. The model writes JavaScript that chains MCP tool calls inside a V8 isolate — memory-limited, time-limited, no filesystem or network access. Perfect for pagination loops, aggregation, and joins across many calls.
|
|
108
79
|
|
|
109
|
-
|
|
110
|
-
# Chain tool calls
|
|
111
|
-
tool-cli myserver list_items '{}' | jq -r '.[0].id' | \
|
|
112
|
-
xargs -I{} tool-cli myserver get_item '{"id":"{}"}'
|
|
113
|
-
|
|
114
|
-
# Process collections
|
|
115
|
-
for city in London Tokyo Paris; do
|
|
116
|
-
echo "=== $city ==="
|
|
117
|
-
tool-cli weather check_weather '{"city":"'"$city"'"}'
|
|
118
|
-
done
|
|
119
|
-
|
|
120
|
-
# Combine with the Unix toolbox
|
|
121
|
-
tool-cli myserver export_csv '{"table":"users"}' | sort -t, -k2 | head -20
|
|
122
|
-
```
|
|
80
|
+
📖 [**How it works →**](docs/code-mode.md) — sandbox isolation, eligibility, tool dispatch.
|
|
123
81
|
|
|
124
|
-
|
|
82
|
+
> _"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."_
|
|
125
83
|
|
|
126
|
-
|
|
84
|
+

|
|
127
85
|
|
|
128
86
|
---
|
|
129
87
|
|
|
130
|
-
##
|
|
88
|
+
## When to Use Each Tier
|
|
131
89
|
|
|
132
|
-
|
|
90
|
+
> _They asked the three: "Why are there three of you? Isn't one enough?"_
|
|
91
|
+
>
|
|
92
|
+
> _The Skill Dealer laid down a card. "When you know the ritual — the steps, the order, the tools that belong together — you come to me. I give you the ceremony whole."_
|
|
93
|
+
>
|
|
94
|
+
> _The Football's briefcase clicked open. "When you need one answer, quickly, and you know what you're looking for — you reach for me. I'm a shell command. I compose."_
|
|
95
|
+
>
|
|
96
|
+
> _Codey smiled, cross-legged in the isolate. "And when the answer is buried in nine pages of data, when you need loops and math and joins across a thousand records — you write the code, and I run it. Safely."_
|
|
97
|
+
>
|
|
98
|
+
> _"Three is not redundancy," said the Skill Dealer. "Three is completeness."_
|
|
133
99
|
|
|
134
|
-
|
|
100
|
+
**The Skill Dealer** — when there's a curated workflow for the domain task. "Triage these 20 issues" means loading the triage skill, which gives you the right tools _plus_ the workflow instructions (dedup checks, labeling conventions, close criteria). Re-deriving that from raw tool calls is wasteful and error-prone.
|
|
135
101
|
|
|
136
|
-
|
|
102
|
+
**The Nuclear Football** — one-shot or exploratory calls, especially when piping through Unix tools. `tool-cli github search_code '{"query":"auth"}' | jq '.items[].path'` — one call, pipe to jq, done. Also perfect for discovering what's on a server you haven't used before.
|
|
137
103
|
|
|
138
|
-
|
|
104
|
+
**Codey C. Maude** — when you need real computation across many calls: pagination loops, aggregation, joining results, math. 876 issues across 9 pages, counting labels per issue, summing into a histogram — that's a loop with state. Doing it via tool-cli would mean 9 separate calls plus shell-side aggregation. Fragile. Codey does it in one sandbox execution.
|
|
139
105
|
|
|
140
|
-
|
|
141
|
-
// Executed in a V8 isolate via isolated-vm
|
|
142
|
-
const issues = await codemode.list_issues({ repo: "owner/repo", state: "open" });
|
|
143
|
-
const critical = issues.filter((i) => i.labels.includes("critical"));
|
|
144
|
-
const details = await Promise.all(critical.map((i) => codemode.get_issue({ number: i.number })));
|
|
145
|
-
return details.map((d) => ({ title: d.title, assignee: d.assignee }));
|
|
146
|
-
```
|
|
106
|
+
### A single task using all three
|
|
147
107
|
|
|
148
|
-
|
|
108
|
+
> _"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."_
|
|
149
109
|
|
|
150
|
-
|
|
151
|
-
- **No access** to filesystem, network, or Node.js APIs
|
|
152
|
-
- Tool calls dispatch to the host via `Reference` callbacks — MCP execution happens outside the sandbox
|
|
153
|
-
- ~15ms overhead, negligible vs network I/O
|
|
110
|
+
1. **Codey** paginated all open bug issues, filtered by `updated < 90d ago`, grouped by label and keyword to find clusters. Computation across many pages — this is what sandboxes are for.
|
|
154
111
|
|
|
155
|
-
|
|
112
|
+
2. **The Football** spot-checked suspect issues. `tool-cli github get_issue '{"number":42}'` piped through `jq` to eyeball specific fields. Quick, ad-hoc, composable.
|
|
156
113
|
|
|
157
|
-
|
|
158
|
-
| -------------- | ---------------------------------------------------------------------------------- |
|
|
159
|
-
| `code_search` | Discover available tools — `codemode.listTools()`, `codemode.describeTools(names)` |
|
|
160
|
-
| `code_execute` | Chain tool calls — write JS that calls `codemode.toolName(args)` |
|
|
114
|
+
3. **The Skill Dealer** loaded `triage-issues` to actually close the duplicates — following the project's triage workflow with correct labels, comment templates, and close reasons. The ceremony, performed correctly.
|
|
161
115
|
|
|
162
|
-
>
|
|
116
|
+
> _The rule of thumb is simple: skill for workflows, tool-cli for one-shots, code_execute for computation. The three are not competing. They are collaborating._
|
|
163
117
|
|
|
164
118
|
---
|
|
165
119
|
|
|
166
120
|
## The Architecture
|
|
167
121
|
|
|
168
|
-
The three tiers are complementary. Skills give curated access with workflow knowledge. The Football gives interactive access with safety. Code Mode gives autonomous access to safe operations at scale.
|
|
169
|
-
|
|
170
122
|
```mermaid
|
|
171
123
|
flowchart TD
|
|
172
124
|
subgraph mcpi["mcpi (agent)"]
|
|
@@ -185,66 +137,62 @@ flowchart TD
|
|
|
185
137
|
|
|
186
138
|
The harness controls what the model sees. MCP servers just expose their tools and skills. The extension decides _when_ and _how_ to reveal them.
|
|
187
139
|
|
|
140
|
+
### Every call flows through the harness
|
|
141
|
+
|
|
142
|
+
All three tiers route MCP tool calls back through the extension process. This is a subtle but important property: even when the model writes sandboxed JavaScript (Code Mode) or shells out to `tool-cli`, the actual MCP call happens in the harness. This means:
|
|
143
|
+
|
|
144
|
+
- **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 can be added at one point** — the `McpClientManager` is the single choke point. Future work can check tool annotations (`readOnlyHint`, `destructiveHint`) and gate destructive calls through user confirmation, regardless of which tier initiated them.
|
|
146
|
+
|
|
188
147
|
> _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._
|
|
189
148
|
|
|
190
149
|
---
|
|
191
150
|
|
|
192
151
|
## Quick Start
|
|
193
152
|
|
|
194
|
-
### Install
|
|
195
|
-
|
|
196
|
-
```sh
|
|
197
|
-
npm install @sammorrowdrums/mcpi @sammorrowdrums/mcpi-ext
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Run mcpi with the extension:
|
|
153
|
+
### 1. Install
|
|
201
154
|
|
|
202
155
|
```sh
|
|
203
|
-
|
|
204
|
-
--mcp-config ~/.config/mcpi-ext/mcp.json
|
|
156
|
+
npm install -g @sammorrowdrums/mcpi@latest @sammorrowdrums/mcpi-ext@latest @sammorrowdrums/tool-cli@latest
|
|
205
157
|
```
|
|
206
158
|
|
|
207
|
-
###
|
|
159
|
+
### 2. Configure MCP servers
|
|
208
160
|
|
|
209
|
-
|
|
210
|
-
curl https://mise.run | sh # install mise
|
|
211
|
-
eval "$(~/.local/bin/mise activate bash)" # activate
|
|
212
|
-
mise install # install node
|
|
213
|
-
npm install # install dependencies
|
|
214
|
-
mise run build # build
|
|
215
|
-
mise run test # test
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
For local dev, override the `@sammorrowdrums/mcpi` dependency to point at your local mcpi clone:
|
|
161
|
+
Create `~/.config/mcpi-ext/mcp.json`:
|
|
219
162
|
|
|
220
163
|
```json
|
|
221
|
-
|
|
222
|
-
"
|
|
223
|
-
|
|
164
|
+
{
|
|
165
|
+
"mcpServers": {
|
|
166
|
+
"github": {
|
|
167
|
+
"type": "stdio",
|
|
168
|
+
"command": "docker",
|
|
169
|
+
"args": [
|
|
170
|
+
"run",
|
|
171
|
+
"--rm",
|
|
172
|
+
"-i",
|
|
173
|
+
"-e",
|
|
174
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
175
|
+
"ghcr.io/github/github-mcp-server:skill-discovery",
|
|
176
|
+
"stdio"
|
|
177
|
+
],
|
|
178
|
+
"env": {
|
|
179
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "xxx"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
224
183
|
}
|
|
225
184
|
```
|
|
226
185
|
|
|
227
|
-
|
|
186
|
+
Replace `xxx` with your [GitHub personal access token](https://github.com/settings/tokens). See [github/github-mcp-server](https://github.com/github/github-mcp-server) for the standard server.
|
|
228
187
|
|
|
229
|
-
|
|
230
|
-
mcpi --extension ./dist/index.js --mcp-config ~/.config/mcpi-ext/mcp.json
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### MCP server configuration
|
|
188
|
+
> **Note:** The `skill-discovery` tag includes experimental `skill://` resources that enable Tier 1 progressive discovery. The standard `ghcr.io/github/github-mcp-server` image works too — tool-cli (Tier 2) and Code Mode (Tier 3) function with any MCP server, but skill-gated tool activation requires `skill://` resources.
|
|
234
189
|
|
|
235
|
-
|
|
190
|
+
You can add more servers — both `stdio` (spawns a process) and `remote` (Streamable HTTP) are supported:
|
|
236
191
|
|
|
237
192
|
```json
|
|
238
193
|
{
|
|
239
194
|
"mcpServers": {
|
|
240
|
-
"github": {
|
|
241
|
-
"type": "stdio",
|
|
242
|
-
"command": "npx",
|
|
243
|
-
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
244
|
-
"env": {
|
|
245
|
-
"GITHUB_PERSONAL_ACCESS_TOKEN": "xxx"
|
|
246
|
-
}
|
|
247
|
-
},
|
|
195
|
+
"github": { "...": "..." },
|
|
248
196
|
"my-remote-server": {
|
|
249
197
|
"type": "remote",
|
|
250
198
|
"url": "https://my-mcp-server.example.com/mcp",
|
|
@@ -256,7 +204,28 @@ Create `~/.config/mcpi-ext/mcp.json` (or pass `--mcp-config /path/to/config.json
|
|
|
256
204
|
}
|
|
257
205
|
```
|
|
258
206
|
|
|
259
|
-
|
|
207
|
+
### 3. Run
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
mcpi --extension $(npm root -g)/@sammorrowdrums/mcpi-ext/dist/index.js \
|
|
211
|
+
--mcp-config ~/.config/mcpi-ext/mcp.json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Local development
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
git clone https://github.com/SamMorrowDrums/mcpi-ext.git
|
|
218
|
+
cd mcpi-ext
|
|
219
|
+
npm install
|
|
220
|
+
npm run build
|
|
221
|
+
npm test
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Then run with your local build:
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
mcpi --extension ./dist/index.js --mcp-config ~/.config/mcpi-ext/mcp.json
|
|
228
|
+
```
|
|
260
229
|
|
|
261
230
|
See [AGENTS.md](AGENTS.md) for full tooling docs, dev loop, and architecture details.
|
|
262
231
|
|
|
@@ -270,7 +239,8 @@ src/
|
|
|
270
239
|
tool-cli/ tool-cli RPC server, client, CLI binary, prompt
|
|
271
240
|
code-mode/ V8 sandbox executor, eligibility, type hints
|
|
272
241
|
test-servers/ Test MCP servers (weather, echo)
|
|
273
|
-
|
|
242
|
+
docs/ Detailed mechanism documentation
|
|
243
|
+
images/ Banner, character art, and screenshots
|
|
274
244
|
```
|
|
275
245
|
|
|
276
246
|
## License
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Tier 3 — Codey C. Maude (Code Mode)
|
|
2
|
+
|
|
3
|
+
Code Mode targets tools that are **read-only** (`annotations.readOnlyHint === true`) and return **structured output** (`outputSchema` defined). These two properties together make a tool safe for autonomous use — it can't modify anything, and its results are machine-parseable.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
The model writes JavaScript that chains MCP tool calls. The code runs in a V8 isolate via `isolated-vm`:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const issues = await codemode.list_issues({ repo: "owner/repo", state: "open" });
|
|
11
|
+
const critical = issues.filter((i) => i.labels.includes("critical"));
|
|
12
|
+
const details = await Promise.all(critical.map((i) => codemode.get_issue({ number: i.number })));
|
|
13
|
+
return details.map((d) => ({ title: d.title, assignee: d.assignee }));
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Sandbox isolation
|
|
17
|
+
|
|
18
|
+
- **128MB memory limit**, 30-second timeout
|
|
19
|
+
- **No access** to filesystem, network, or Node.js APIs
|
|
20
|
+
- Tool calls dispatch to the host via `Reference` callbacks — MCP execution happens outside the sandbox
|
|
21
|
+
- ~15ms overhead, negligible vs network I/O
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | Purpose |
|
|
26
|
+
| -------------- | ---------------------------------------------------------------------------------- |
|
|
27
|
+
| `code_search` | Discover available tools — `codemode.listTools()`, `codemode.describeTools(names)` |
|
|
28
|
+
| `code_execute` | Chain tool calls — write JS that calls `codemode.toolName(args)` |
|
|
29
|
+
|
|
30
|
+
## When to use
|
|
31
|
+
|
|
32
|
+
Code Mode shines when you need real computation across many calls: pagination loops, aggregation, joining results, math. For example:
|
|
33
|
+
|
|
34
|
+
- 876 issues across 9 pages, counting labels per issue, building a histogram
|
|
35
|
+
- For each open PR, fetch reviews and compute average time-to-first-review
|
|
36
|
+
- Paginate all items, filter, group, and summarize
|
|
37
|
+
|
|
38
|
+
See [DECISIONS.md #011–012](../DECISIONS.md) for implementation decisions.
|
package/docs/skills.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Tier 1 — The Skill Dealer
|
|
2
|
+
|
|
3
|
+
MCP servers can ship `skill://` resources: SKILL.md files with frontmatter declaring which tools a skill gates. On connection, the extension discovers all skills and registers their tools with `deferred: true`.
|
|
4
|
+
|
|
5
|
+
## How deferred tool gating works
|
|
6
|
+
|
|
7
|
+
Three mechanisms work together to keep tools hidden until the right moment — while preserving prompt cache:
|
|
8
|
+
|
|
9
|
+
### 1. `deferred: true`
|
|
10
|
+
|
|
11
|
+
MCP tool proxies are registered with this flag. mcpi keeps them in the tools array (so providers can include them in grammar/dispatch) but excludes them from the system prompt. The tools array stays static throughout the conversation — prompt cache is never invalidated.
|
|
12
|
+
|
|
13
|
+
### 2. Provider-native `defer_loading`
|
|
14
|
+
|
|
15
|
+
mcpi's providers map `deferred: true` to their native deferred loading mechanism:
|
|
16
|
+
|
|
17
|
+
- **Anthropic** — `defer_loading: true` hides the tool from the model's view while keeping it in the grammar.
|
|
18
|
+
- **OpenAI Responses** — `defer_loading: true` with auto-injected `{"type": "tool_search"}` enables server-side tool discovery.
|
|
19
|
+
|
|
20
|
+
Both tested with Claude Opus 4.7 and GPT-5.4. Since the tools array never changes, prompt cache is preserved on both providers.
|
|
21
|
+
|
|
22
|
+
### 3. `tool_call` hook gating
|
|
23
|
+
|
|
24
|
+
The extension registers a `tool_call` event handler that blocks premature calls to skill-gated tools. If the model tries to call a gated tool before loading its skill, the handler returns an error:
|
|
25
|
+
|
|
26
|
+
> _"Tool X requires loading a skill first. Call load_skill with: Y"_
|
|
27
|
+
|
|
28
|
+
This creates a natural feedback loop and serves as the enforcement layer across all providers — including those without native `defer_loading` support.
|
|
29
|
+
|
|
30
|
+
## Flow
|
|
31
|
+
|
|
32
|
+
```mermaid
|
|
33
|
+
sequenceDiagram
|
|
34
|
+
participant Model
|
|
35
|
+
participant load_skill
|
|
36
|
+
participant SkillRegistry
|
|
37
|
+
participant MCP Server
|
|
38
|
+
participant tool_call gate
|
|
39
|
+
|
|
40
|
+
Model->>load_skill: load_skill("github-pr")
|
|
41
|
+
load_skill->>SkillRegistry: Look up skill
|
|
42
|
+
SkillRegistry-->>load_skill: skill metadata + allowed-tools
|
|
43
|
+
load_skill->>MCP Server: Read skill://github-pr
|
|
44
|
+
MCP Server-->>load_skill: SKILL.md body
|
|
45
|
+
load_skill->>tool_call gate: Enable allowed-tools
|
|
46
|
+
load_skill-->>Model: Return workflow instructions
|
|
47
|
+
Note over Model: Skill body names the tools.<br/>Model calls them via tool_use.
|
|
48
|
+
Model->>tool_call gate: create_pull_request(...)
|
|
49
|
+
tool_call gate-->>Model: ✓ Allowed (skill loaded)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The MCP server itself declares how its tools should be discovered. The harness holds all the tools as deferred. The skill decides which ones the model can access. The model gets instructions in one atomic operation, paying only the tokens for the skills it actually loads — and the prompt cache stays intact.
|
|
53
|
+
|
|
54
|
+
## See also
|
|
55
|
+
|
|
56
|
+
- [skills-as-groups MCP spec proposal](https://github.com/modelcontextprotocol/experimental-ext-grouping/pull/13)
|
|
57
|
+
- [DECISIONS.md #008](../DECISIONS.md) — cache-safe progressive tool disclosure
|
package/docs/tool-cli.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Tier 2 — The Nuclear Football (tool-cli)
|
|
2
|
+
|
|
3
|
+
`tool-cli` is a thin CLI binary that speaks JSON-RPC 2.0 to the extension over HTTP. The agent uses it like any shell command — composable with pipes, grep, jq, loops.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TD
|
|
9
|
+
A["Agent (mcpi)"] -->|shell exec| B["tool-cli <server> <tool> '{args}'"]
|
|
10
|
+
B -->|"HTTP JSON-RPC (random port, token auth)"| C["ToolCliServer (from @sammorrowdrums/tool-cli)"]
|
|
11
|
+
C -->|"MCP protocol (stdio/HTTP)"| D["MCP Server(s)"]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The RPC server lives in the extension process, started on `session_start` and stopped on `session_shutdown`. The CLI binary uses `fetch` to call it.
|
|
15
|
+
|
|
16
|
+
## Progressive discovery
|
|
17
|
+
|
|
18
|
+
The agent pays only the tokens it needs:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
tool-cli --help # What servers exist?
|
|
22
|
+
tool-cli github # What tools does this server have?
|
|
23
|
+
tool-cli github search_code # What's the schema for this tool?
|
|
24
|
+
tool-cli github search_code '{"query":"auth"}' # Call it
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Shell composability
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
# Chain tool calls
|
|
31
|
+
tool-cli myserver list_items '{}' | jq -r '.[0].id' | \
|
|
32
|
+
xargs -I{} tool-cli myserver get_item '{"id":"{}"}'
|
|
33
|
+
|
|
34
|
+
# Process collections
|
|
35
|
+
for city in London Tokyo Paris; do
|
|
36
|
+
echo "=== $city ==="
|
|
37
|
+
tool-cli weather check_weather '{"city":"'"$city"'"}'
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# Combine with the Unix toolbox
|
|
41
|
+
tool-cli myserver export_csv '{"table":"users"}' | sort -t, -k2 | head -20
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Security
|
|
45
|
+
|
|
46
|
+
The server uses token-based auth and dynamic port allocation (provided by [`@sammorrowdrums/tool-cli`](https://github.com/SamMorrowDrums/tool-cli)):
|
|
47
|
+
|
|
48
|
+
1. `start()` binds to a random port and generates a 32-byte session token
|
|
49
|
+
2. Returns `{ port, token }` — the extension sets these as env vars via `pi.setEnv()`
|
|
50
|
+
3. Every request must include `Authorization: Bearer <token>` — rejected with 401 otherwise
|
|
51
|
+
|
|
52
|
+
This enables concurrent sessions and prevents random processes from calling MCP tools. The RPC server is the single choke point for all tool execution — the natural interception point for future human-in-the-loop confirmation on destructive operations.
|
|
53
|
+
|
|
54
|
+
See [tool-cli security docs](https://github.com/SamMorrowDrums/tool-cli#security) and [DECISIONS.md #010](../DECISIONS.md).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/mise.toml
CHANGED
|
@@ -3,19 +3,7 @@ node = "22"
|
|
|
3
3
|
|
|
4
4
|
[tasks.build]
|
|
5
5
|
description = "Build TypeScript"
|
|
6
|
-
run =
|
|
7
|
-
"npx tsc",
|
|
8
|
-
"chmod +x dist/tool-cli/cli.js",
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
[tasks."install:tool-cli"]
|
|
12
|
-
description = "Symlink tool-cli binary for local use"
|
|
13
|
-
depends = ["build"]
|
|
14
|
-
run = [
|
|
15
|
-
"ln -sf ../../dist/tool-cli/cli.js node_modules/.bin/tool-cli",
|
|
16
|
-
"mkdir -p ~/.local/bin",
|
|
17
|
-
"ln -sf $PWD/dist/tool-cli/cli.js ~/.local/bin/tool-cli",
|
|
18
|
-
]
|
|
6
|
+
run = "npx tsc"
|
|
19
7
|
|
|
20
8
|
[tasks.dev]
|
|
21
9
|
description = "Watch mode"
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sammorrowdrums/mcpi-ext",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
6
|
+
"@sammorrowdrums/tool-cli": "^0.3.0",
|
|
6
7
|
"isolated-vm": "^6.1.2",
|
|
7
8
|
"typebox": "^1.1.31",
|
|
8
9
|
"zod": "^4.3.6"
|
|
9
10
|
},
|
|
10
11
|
"peerDependencies": {
|
|
11
|
-
"@sammorrowdrums/mcpi": ">=0.70.
|
|
12
|
+
"@sammorrowdrums/mcpi": ">=0.70.9"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"@sammorrowdrums/mcpi": "file:../../../repos/pi-mono/packages/coding-agent",
|
|
15
15
|
"@eslint/js": "^10.0.1",
|
|
16
|
+
"@sammorrowdrums/mcpi": "^0.70.9",
|
|
16
17
|
"@types/node": "^25.6.0",
|
|
17
18
|
"eslint": "^10.2.1",
|
|
18
19
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -39,8 +40,5 @@
|
|
|
39
40
|
"license": "MIT",
|
|
40
41
|
"main": "./dist/index.js",
|
|
41
42
|
"type": "module",
|
|
42
|
-
"types": "./dist/index.d.ts"
|
|
43
|
-
"bin": {
|
|
44
|
-
"tool-cli": "./dist/tool-cli/cli.js"
|
|
45
|
-
}
|
|
43
|
+
"types": "./dist/index.d.ts"
|
|
46
44
|
}
|