@sammorrowdrums/mcpi-ext 0.2.0 → 0.2.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.
Files changed (208) hide show
  1. package/dist/code-mode/code-mode-integration.test.d.ts +2 -0
  2. package/dist/code-mode/code-mode-integration.test.d.ts.map +1 -0
  3. package/dist/code-mode/code-mode-integration.test.js +116 -0
  4. package/dist/code-mode/code-mode-integration.test.js.map +1 -0
  5. package/dist/code-mode/eligibility.d.ts +10 -0
  6. package/dist/code-mode/eligibility.d.ts.map +1 -0
  7. package/dist/code-mode/eligibility.js +14 -0
  8. package/dist/code-mode/eligibility.js.map +1 -0
  9. package/dist/code-mode/eligibility.test.d.ts +2 -0
  10. package/dist/code-mode/eligibility.test.d.ts.map +1 -0
  11. package/dist/code-mode/eligibility.test.js +53 -0
  12. package/dist/code-mode/eligibility.test.js.map +1 -0
  13. package/dist/code-mode/executor.d.ts +35 -0
  14. package/dist/code-mode/executor.d.ts.map +1 -0
  15. package/dist/code-mode/executor.js +120 -0
  16. package/dist/code-mode/executor.js.map +1 -0
  17. package/dist/code-mode/executor.test.d.ts +2 -0
  18. package/dist/code-mode/executor.test.d.ts.map +1 -0
  19. package/dist/code-mode/executor.test.js +130 -0
  20. package/dist/code-mode/executor.test.js.map +1 -0
  21. package/dist/code-mode/index.d.ts +65 -0
  22. package/dist/code-mode/index.d.ts.map +1 -0
  23. package/dist/code-mode/index.js +175 -0
  24. package/dist/code-mode/index.js.map +1 -0
  25. package/dist/code-mode/tools.d.ts +45 -0
  26. package/dist/code-mode/tools.d.ts.map +1 -0
  27. package/dist/code-mode/tools.js +70 -0
  28. package/dist/code-mode/tools.js.map +1 -0
  29. package/dist/code-mode/type-hints.d.ts +20 -0
  30. package/dist/code-mode/type-hints.d.ts.map +1 -0
  31. package/dist/code-mode/type-hints.js +189 -0
  32. package/dist/code-mode/type-hints.js.map +1 -0
  33. package/dist/code-mode/type-hints.test.d.ts +2 -0
  34. package/dist/code-mode/type-hints.test.d.ts.map +1 -0
  35. package/dist/code-mode/type-hints.test.js +157 -0
  36. package/dist/code-mode/type-hints.test.js.map +1 -0
  37. package/dist/docker-e2e.d.ts +55 -0
  38. package/dist/docker-e2e.d.ts.map +1 -0
  39. package/dist/docker-e2e.js +235 -0
  40. package/dist/docker-e2e.js.map +1 -0
  41. package/dist/docker-e2e.test.d.ts +2 -0
  42. package/dist/docker-e2e.test.d.ts.map +1 -0
  43. package/dist/docker-e2e.test.js +176 -0
  44. package/dist/docker-e2e.test.js.map +1 -0
  45. package/dist/index.d.ts +3 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +148 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/index.test.d.ts +2 -0
  50. package/dist/index.test.d.ts.map +1 -0
  51. package/dist/index.test.js +8 -0
  52. package/dist/index.test.js.map +1 -0
  53. package/dist/mcp/client-manager.d.ts +46 -0
  54. package/dist/mcp/client-manager.d.ts.map +1 -0
  55. package/dist/mcp/client-manager.js +148 -0
  56. package/dist/mcp/client-manager.js.map +1 -0
  57. package/dist/mcp/client-manager.test.d.ts +2 -0
  58. package/dist/mcp/client-manager.test.d.ts.map +1 -0
  59. package/dist/mcp/client-manager.test.js +204 -0
  60. package/dist/mcp/client-manager.test.js.map +1 -0
  61. package/dist/mcp/config-loader.d.ts +8 -0
  62. package/dist/mcp/config-loader.d.ts.map +1 -0
  63. package/dist/mcp/config-loader.js +43 -0
  64. package/dist/mcp/config-loader.js.map +1 -0
  65. package/dist/mcp/config-loader.test.d.ts +2 -0
  66. package/dist/mcp/config-loader.test.d.ts.map +1 -0
  67. package/dist/mcp/config-loader.test.js +93 -0
  68. package/dist/mcp/config-loader.test.js.map +1 -0
  69. package/dist/mcp/config.d.ts +46 -0
  70. package/dist/mcp/config.d.ts.map +1 -0
  71. package/dist/mcp/config.js +28 -0
  72. package/dist/mcp/config.js.map +1 -0
  73. package/{src/mcp/index.ts → dist/mcp/index.d.ts} +1 -0
  74. package/dist/mcp/index.d.ts.map +1 -0
  75. package/dist/mcp/index.js +4 -0
  76. package/dist/mcp/index.js.map +1 -0
  77. package/dist/skills/discover.d.ts +10 -0
  78. package/dist/skills/discover.d.ts.map +1 -0
  79. package/dist/skills/discover.js +60 -0
  80. package/dist/skills/discover.js.map +1 -0
  81. package/dist/skills/format.d.ts +10 -0
  82. package/dist/skills/format.d.ts.map +1 -0
  83. package/dist/skills/format.js +38 -0
  84. package/dist/skills/format.js.map +1 -0
  85. package/dist/skills/format.test.d.ts +2 -0
  86. package/dist/skills/format.test.d.ts.map +1 -0
  87. package/dist/skills/format.test.js +63 -0
  88. package/dist/skills/format.test.js.map +1 -0
  89. package/{src/skills/index.ts → dist/skills/index.d.ts} +2 -5
  90. package/dist/skills/index.d.ts.map +1 -0
  91. package/dist/skills/index.js +6 -0
  92. package/dist/skills/index.js.map +1 -0
  93. package/dist/skills/load-skill-tool.d.ts +40 -0
  94. package/dist/skills/load-skill-tool.d.ts.map +1 -0
  95. package/dist/skills/load-skill-tool.js +104 -0
  96. package/dist/skills/load-skill-tool.js.map +1 -0
  97. package/dist/skills/mcp-tool-proxy.d.ts +11 -0
  98. package/dist/skills/mcp-tool-proxy.d.ts.map +1 -0
  99. package/dist/skills/mcp-tool-proxy.js +116 -0
  100. package/dist/skills/mcp-tool-proxy.js.map +1 -0
  101. package/dist/skills/skill-integration.test.d.ts +2 -0
  102. package/dist/skills/skill-integration.test.d.ts.map +1 -0
  103. package/dist/skills/skill-integration.test.js +119 -0
  104. package/dist/skills/skill-integration.test.js.map +1 -0
  105. package/dist/skills/skill-registry.d.ts +39 -0
  106. package/dist/skills/skill-registry.d.ts.map +1 -0
  107. package/dist/skills/skill-registry.js +48 -0
  108. package/dist/skills/skill-registry.js.map +1 -0
  109. package/dist/skills/skill-registry.test.d.ts +2 -0
  110. package/dist/skills/skill-registry.test.d.ts.map +1 -0
  111. package/dist/skills/skill-registry.test.js +67 -0
  112. package/dist/skills/skill-registry.test.js.map +1 -0
  113. package/dist/test-servers/weather-server.d.ts +12 -0
  114. package/dist/test-servers/weather-server.d.ts.map +1 -0
  115. package/dist/test-servers/weather-server.js +139 -0
  116. package/dist/test-servers/weather-server.js.map +1 -0
  117. package/dist/test-servers/weather-stdio.d.ts +2 -0
  118. package/dist/test-servers/weather-stdio.d.ts.map +1 -0
  119. package/{src/test-servers/weather-stdio.ts → dist/test-servers/weather-stdio.js} +1 -1
  120. package/dist/test-servers/weather-stdio.js.map +1 -0
  121. package/dist/tool-cli/cli.d.ts +3 -0
  122. package/dist/tool-cli/cli.d.ts.map +1 -0
  123. package/dist/tool-cli/cli.js +179 -0
  124. package/dist/tool-cli/cli.js.map +1 -0
  125. package/dist/tool-cli/constants.d.ts +7 -0
  126. package/dist/tool-cli/constants.d.ts.map +1 -0
  127. package/dist/tool-cli/constants.js +15 -0
  128. package/dist/tool-cli/constants.js.map +1 -0
  129. package/dist/tool-cli/format.d.ts +8 -0
  130. package/dist/tool-cli/format.d.ts.map +1 -0
  131. package/{src/tool-cli/format.ts → dist/tool-cli/format.js} +5 -4
  132. package/dist/tool-cli/format.js.map +1 -0
  133. package/dist/tool-cli/format.test.d.ts +2 -0
  134. package/dist/tool-cli/format.test.d.ts.map +1 -0
  135. package/dist/tool-cli/format.test.js +30 -0
  136. package/dist/tool-cli/format.test.js.map +1 -0
  137. package/{src/tool-cli/index.ts → dist/tool-cli/index.d.ts} +1 -0
  138. package/dist/tool-cli/index.d.ts.map +1 -0
  139. package/dist/tool-cli/index.js +3 -0
  140. package/dist/tool-cli/index.js.map +1 -0
  141. package/dist/tool-cli/rpc-client.d.ts +6 -0
  142. package/dist/tool-cli/rpc-client.d.ts.map +1 -0
  143. package/dist/tool-cli/rpc-client.js +25 -0
  144. package/dist/tool-cli/rpc-client.js.map +1 -0
  145. package/dist/tool-cli/rpc-server.d.ts +48 -0
  146. package/dist/tool-cli/rpc-server.d.ts.map +1 -0
  147. package/dist/tool-cli/rpc-server.js +220 -0
  148. package/dist/tool-cli/rpc-server.js.map +1 -0
  149. package/dist/tool-cli/rpc-server.test.d.ts +2 -0
  150. package/dist/tool-cli/rpc-server.test.d.ts.map +1 -0
  151. package/dist/tool-cli/rpc-server.test.js +154 -0
  152. package/dist/tool-cli/rpc-server.test.js.map +1 -0
  153. package/package.json +5 -1
  154. package/.dockerignore +0 -6
  155. package/.github/dependabot.yml +0 -25
  156. package/.github/workflows/ci.yml +0 -50
  157. package/.hadolint.yaml +0 -6
  158. package/.prettierignore +0 -4
  159. package/.prettierrc +0 -7
  160. package/AGENTS.md +0 -118
  161. package/DECISIONS.md +0 -99
  162. package/Dockerfile +0 -39
  163. package/aube-lock.yaml +0 -4146
  164. package/docs/code-mode.md +0 -38
  165. package/docs/skills.md +0 -57
  166. package/docs/tool-cli.md +0 -54
  167. package/eslint.config.js +0 -27
  168. package/images/banner.webp +0 -0
  169. package/images/code-c-maude.webp +0 -0
  170. package/images/code-mode-histogram.png +0 -0
  171. package/images/nuclear-mcp-football.webp +0 -0
  172. package/images/skills-enabling-mcp-tools.png +0 -0
  173. package/images/the-skill-dealer.webp +0 -0
  174. package/images/tool-cli-grep.png +0 -0
  175. package/mise.toml +0 -53
  176. package/scripts/docker-entrypoint.sh +0 -29
  177. package/scripts/test-echo-server.mjs +0 -28
  178. package/scripts/test-mcp-integration.mjs +0 -59
  179. package/scripts/test-tool-cli-smoke.mjs +0 -93
  180. package/src/code-mode/code-mode-integration.test.ts +0 -135
  181. package/src/code-mode/eligibility.test.ts +0 -60
  182. package/src/code-mode/eligibility.ts +0 -16
  183. package/src/code-mode/executor.test.ts +0 -168
  184. package/src/code-mode/executor.ts +0 -163
  185. package/src/code-mode/index.ts +0 -208
  186. package/src/code-mode/tools.ts +0 -110
  187. package/src/code-mode/type-hints.test.ts +0 -187
  188. package/src/code-mode/type-hints.ts +0 -249
  189. package/src/docker-e2e.test.ts +0 -195
  190. package/src/docker-e2e.ts +0 -277
  191. package/src/index.test.ts +0 -8
  192. package/src/index.ts +0 -179
  193. package/src/mcp/client-manager.test.ts +0 -250
  194. package/src/mcp/client-manager.ts +0 -199
  195. package/src/mcp/config-loader.test.ts +0 -115
  196. package/src/mcp/config-loader.ts +0 -46
  197. package/src/mcp/config.ts +0 -40
  198. package/src/skills/discover.ts +0 -81
  199. package/src/skills/format.test.ts +0 -70
  200. package/src/skills/format.ts +0 -42
  201. package/src/skills/load-skill-tool.ts +0 -136
  202. package/src/skills/mcp-tool-proxy.ts +0 -143
  203. package/src/skills/skill-integration.test.ts +0 -139
  204. package/src/skills/skill-registry.test.ts +0 -83
  205. package/src/skills/skill-registry.ts +0 -69
  206. package/src/test-servers/weather-server.ts +0 -171
  207. package/src/tool-cli/format.test.ts +0 -33
  208. package/tsconfig.json +0 -21
package/docs/code-mode.md DELETED
@@ -1,38 +0,0 @@
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 DELETED
@@ -1,57 +0,0 @@
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 DELETED
@@ -1,54 +0,0 @@
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 &lt;server&gt; &lt;tool&gt; '{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).
package/eslint.config.js DELETED
@@ -1,27 +0,0 @@
1
- import eslint from "@eslint/js";
2
- import tseslint from "typescript-eslint";
3
- import eslintConfigPrettier from "eslint-config-prettier";
4
-
5
- export default tseslint.config(
6
- eslint.configs.recommended,
7
- ...tseslint.configs.strict,
8
- ...tseslint.configs.stylistic,
9
- eslintConfigPrettier,
10
- {
11
- languageOptions: {
12
- parserOptions: {
13
- projectService: true,
14
- tsconfigRootDir: import.meta.dirname,
15
- },
16
- },
17
- rules: {
18
- "@typescript-eslint/no-unused-vars": [
19
- "error",
20
- { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
21
- ],
22
- },
23
- },
24
- {
25
- ignores: ["dist/", "node_modules/", "*.js", "scripts/"],
26
- },
27
- );
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/mise.toml DELETED
@@ -1,53 +0,0 @@
1
- [tools]
2
- node = "22"
3
-
4
- [tasks.build]
5
- description = "Build TypeScript"
6
- run = "npx tsc"
7
-
8
- [tasks.dev]
9
- description = "Watch mode"
10
- run = "npx tsc --watch"
11
-
12
- [tasks.test]
13
- description = "Run tests"
14
- run = "npx vitest --run"
15
-
16
- [tasks.check]
17
- description = "Type-check without emitting"
18
- run = "npx tsc --noEmit"
19
-
20
- [tasks.lint]
21
- description = "Lint TypeScript files"
22
- run = "npx eslint ."
23
-
24
- [tasks.format]
25
- description = "Format code with Prettier"
26
- run = "npx prettier --write ."
27
-
28
- [tasks."format:check"]
29
- description = "Check code formatting"
30
- run = "npx prettier --check ."
31
-
32
- [tasks.start]
33
- description = "Run the built project"
34
- run = "node dist/index.js"
35
-
36
- [tasks.docker-build]
37
- description = "Build the Docker e2e image"
38
- run = "docker build -t mcpi-ext-e2e ."
39
-
40
- [tasks."test:integration"]
41
- description = "Run MCP integration smoke test"
42
- depends = ["build"]
43
- run = "node scripts/test-mcp-integration.mjs"
44
-
45
- [tasks."test:tool-cli"]
46
- description = "Run tool-cli smoke test (requires build)"
47
- depends = ["build"]
48
- run = "node scripts/test-tool-cli-smoke.mjs"
49
-
50
- [tasks."test:skill-integration"]
51
- description = "Run skill discovery integration test (requires build)"
52
- depends = ["build"]
53
- run = "npx vitest run src/skills/skill-integration.test.ts"
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- if [ -z "${GITHUB_TOKEN:-}" ]; then
5
- echo "ERROR: GITHUB_TOKEN environment variable is required" >&2
6
- exit 1
7
- fi
8
-
9
- if [ $# -eq 0 ]; then
10
- echo "ERROR: No task provided. Pass the task as arguments." >&2
11
- echo "Usage: docker run -e GITHUB_TOKEN=... IMAGE \"your task here\"" >&2
12
- exit 1
13
- fi
14
-
15
- TASK="$*"
16
-
17
- PI_ARGS=(
18
- -e /app/dist/index.js
19
- --provider github-copilot
20
- --mode json
21
- -p
22
- --no-session
23
- )
24
-
25
- if [ -n "${PI_MODEL:-}" ]; then
26
- PI_ARGS+=(--model "$PI_MODEL")
27
- fi
28
-
29
- exec npx pi "${PI_ARGS[@]}" "$TASK"
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Minimal MCP server for integration testing.
4
- * Exposes a single "echo" tool that returns its input.
5
- */
6
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
- import { z } from "zod";
9
-
10
- const server = new McpServer({
11
- name: "test-echo-server",
12
- version: "0.1.0",
13
- });
14
-
15
- server.registerTool(
16
- "echo",
17
- {
18
- description: "Echoes back the input message",
19
- inputSchema: { message: z.string() },
20
- annotations: { readOnlyHint: true },
21
- },
22
- async ({ message }) => ({
23
- content: [{ type: "text", text: message }],
24
- }),
25
- );
26
-
27
- const transport = new StdioServerTransport();
28
- await server.connect(transport);
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Integration smoke test: connect to the test-echo-server via stdio,
4
- * discover tools, call the echo tool, disconnect, exit 0.
5
- */
6
- import { McpClientManager } from "../dist/mcp/client-manager.js";
7
-
8
- const manager = new McpClientManager();
9
-
10
- try {
11
- await manager.connectAll(
12
- {
13
- mcpServers: {
14
- "test-echo": {
15
- type: "stdio",
16
- command: "node",
17
- args: ["scripts/test-echo-server.mjs"],
18
- },
19
- },
20
- },
21
- (msg) => console.log(msg),
22
- );
23
-
24
- const servers = manager.getConnectedServers();
25
- if (servers.length !== 1) {
26
- throw new Error(`Expected 1 server, got ${servers.length}`);
27
- }
28
-
29
- const tools = manager.getTools();
30
- console.log(`Discovered ${tools.length} tool(s): ${tools.map((t) => t.name).join(", ")}`);
31
-
32
- const echoTool = tools.find((t) => t.name === "echo");
33
- if (!echoTool) {
34
- throw new Error('Expected to find "echo" tool');
35
- }
36
-
37
- // Call the tool via the client
38
- const client = manager.getClient("test-echo");
39
- if (!client) throw new Error("No client for test-echo");
40
-
41
- const result = await client.callTool({
42
- name: "echo",
43
- arguments: { message: "hello from integration test" },
44
- });
45
-
46
- const text = result.content?.[0]?.text;
47
- if (text !== "hello from integration test") {
48
- throw new Error(`Unexpected echo result: ${JSON.stringify(result)}`);
49
- }
50
- console.log(`Echo tool returned: "${text}"`);
51
-
52
- await manager.disconnectAll();
53
- console.log("Integration test passed ✓");
54
- process.exit(0);
55
- } catch (err) {
56
- console.error("Integration test failed:", err);
57
- await manager.disconnectAll().catch(() => {});
58
- process.exit(1);
59
- }
@@ -1,93 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Smoke test: start RPC server with the weather test server,
4
- * exercise every tool-cli command via the CLI binary, then shut down.
5
- *
6
- * Tests the full wiring: ToolProvider → ToolCliServer → tool-cli CLI.
7
- */
8
- import { McpClientManager } from "../dist/mcp/client-manager.js";
9
- import { ToolCliServer } from "@sammorrowdrums/tool-cli/server";
10
- import { execFile } from "node:child_process";
11
- import { promisify } from "node:util";
12
- import { dirname, join } from "node:path";
13
- import { fileURLToPath } from "node:url";
14
-
15
- const execFileAsync = promisify(execFile);
16
-
17
- // Resolve the tool-cli binary from node_modules/.bin
18
- const __dirname = dirname(fileURLToPath(import.meta.url));
19
- const CLI = join(__dirname, "..", "node_modules", ".bin", "tool-cli");
20
-
21
- const manager = new McpClientManager();
22
- let server;
23
-
24
- async function run(...args) {
25
- const { stdout } = await execFileAsync("node", [CLI, ...args], {
26
- encoding: "utf-8",
27
- env: {
28
- ...process.env,
29
- TOOL_CLI_PORT: String(server.getPort()),
30
- TOOL_CLI_TOKEN: token,
31
- },
32
- });
33
- console.log(`$ tool-cli ${args.join(" ")}`);
34
- console.log(stdout);
35
- return stdout;
36
- }
37
-
38
- let token;
39
-
40
- try {
41
- await manager.connectOne("weather", {
42
- type: "stdio",
43
- command: "node",
44
- args: ["dist/test-servers/weather-stdio.js"],
45
- });
46
-
47
- // Bridge McpClientManager to ToolProvider
48
- const provider = {
49
- getServerNames: () => manager.getConnectedServers(),
50
- getTools: (s) => manager.getToolsForServer(s),
51
- async callTool(s, tool, args) {
52
- const client = manager.getClient(s);
53
- if (!client) throw new Error(`No client for "${s}"`);
54
- const result = await client.callTool({ name: tool, arguments: args });
55
- return {
56
- content: result.content,
57
- isError: result.isError === true ? true : undefined,
58
- structuredContent: result.structuredContent,
59
- };
60
- },
61
- };
62
-
63
- server = new ToolCliServer(provider);
64
- const startResult = await server.start(console.log);
65
- token = startResult.token;
66
-
67
- // --help: list servers
68
- const help = await run("--help");
69
- if (!help.includes("weather")) throw new Error("--help missing weather server");
70
-
71
- // <server>: list tools
72
- const tools = await run("weather");
73
- if (!tools.includes("check_weather_for_city"))
74
- throw new Error("tool list missing check_weather_for_city");
75
-
76
- // <server> <tool>: describe
77
- const desc = await run("weather", "check_weather_for_city");
78
- if (!desc.includes("city")) throw new Error("describe missing city param");
79
-
80
- // <server> <tool> <args>: call
81
- const result = await run("weather", "check_weather_for_city", '{"city":"London"}');
82
- if (!result.includes("London")) throw new Error("call result missing London");
83
-
84
- await server.stop();
85
- await manager.disconnectAll();
86
- console.log("Smoke test passed ✓");
87
- process.exit(0);
88
- } catch (err) {
89
- console.error("Smoke test failed:", err);
90
- await server?.stop().catch(() => {});
91
- await manager.disconnectAll().catch(() => {});
92
- process.exit(1);
93
- }
@@ -1,135 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import { McpClientManager } from "../mcp/client-manager.js";
3
- import { CodeModeManager } from "./index.js";
4
-
5
- /**
6
- * Integration test: connect to the test weather server, verify code mode
7
- * eligibility, type hint generation, and sandboxed execution with real
8
- * MCP tool calls returning structuredContent.
9
- */
10
- describe("code mode integration (weather server)", () => {
11
- const manager = new McpClientManager();
12
- const codeMode = new CodeModeManager();
13
-
14
- beforeAll(async () => {
15
- await manager.connectAll({
16
- mcpServers: {
17
- "test-weather": {
18
- type: "stdio",
19
- command: "node",
20
- args: ["dist/test-servers/weather-stdio.js"],
21
- },
22
- },
23
- });
24
-
25
- codeMode.initialize(manager);
26
- });
27
-
28
- afterAll(async () => {
29
- await manager.disconnectAll();
30
- });
31
-
32
- it("discovers eligible tools with readOnlyHint + outputSchema", () => {
33
- const eligible = codeMode.getEligibleTools();
34
- expect(eligible.length).toBeGreaterThanOrEqual(2);
35
- const names = eligible.map((t) => t.name).sort();
36
- expect(names).toContain("check_weather_for_city");
37
- expect(names).toContain("check_weekly_forecast_for_city");
38
- expect(names).toContain("echo");
39
- });
40
-
41
- it("generates type hints for eligible tools", () => {
42
- const hints = codeMode.getTypeHints();
43
- expect(hints).toContain("declare const codemode");
44
- expect(hints).toContain("check_weather_for_city");
45
- expect(hints).toContain("check_weekly_forecast_for_city");
46
- expect(hints).toContain("echo");
47
- expect(hints).toContain("listTools");
48
- });
49
-
50
- it("reports as active", () => {
51
- expect(codeMode.isActive).toBe(true);
52
- });
53
-
54
- it("generates system prompt section", () => {
55
- const section = codeMode.formatSystemPromptSection();
56
- expect(section).toContain("<code_mode>");
57
- expect(section).toContain("code_search");
58
- expect(section).toContain("code_execute");
59
- expect(section).toContain("declare const codemode");
60
- });
61
-
62
- it("executes code that calls a single tool", async () => {
63
- const result = await codeMode.executeCode(`
64
- const weather = await codemode.check_weather_for_city({ city: "Tokyo" });
65
- return weather;
66
- `);
67
-
68
- expect(result.error).toBeUndefined();
69
- expect(result.result).toHaveProperty("temperature", 26);
70
- expect(result.result).toHaveProperty("conditions", "Sunny");
71
- expect(result.result).toHaveProperty("humidity", 55);
72
- });
73
-
74
- it("executes code that chains multiple tool calls", async () => {
75
- const result = await codeMode.executeCode(`
76
- const cities = ["London", "Tokyo"];
77
- const results = [];
78
- for (const city of cities) {
79
- const w = await codemode.check_weather_for_city({ city });
80
- results.push({ city, temp: w.temperature });
81
- }
82
- return results;
83
- `);
84
-
85
- expect(result.error).toBeUndefined();
86
- expect(result.result).toEqual([
87
- { city: "London", temp: 14 },
88
- { city: "Tokyo", temp: 26 },
89
- ]);
90
- });
91
-
92
- it("executes code that uses listTools", async () => {
93
- const result = await codeMode.executeCode(`
94
- const tools = await codemode.listTools();
95
- return tools.sort();
96
- `);
97
-
98
- expect(result.error).toBeUndefined();
99
- const names = result.result as string[];
100
- expect(names).toContain("check_weather_for_city");
101
- expect(names).toContain("echo");
102
- });
103
-
104
- it("executes code that calls echo tool", async () => {
105
- const result = await codeMode.executeCode(`
106
- const r = await codemode.echo({ message: "hello code mode" });
107
- return r;
108
- `);
109
-
110
- expect(result.error).toBeUndefined();
111
- expect(result.result).toEqual({ echo: "hello code mode" });
112
- });
113
-
114
- it("handles tool errors gracefully", async () => {
115
- const result = await codeMode.executeCode(`
116
- const w = await codemode.check_weather_for_city({ city: "Atlantis" });
117
- return w;
118
- `);
119
-
120
- // Atlantis isn't in the data — server returns text-only content (no structuredContent)
121
- // The executor should still return something (parsed text or raw string)
122
- expect(result.error).toBeUndefined();
123
- expect(result.result).toBeDefined();
124
- });
125
-
126
- it("search mode works for tool discovery", async () => {
127
- const result = await codeMode.searchTools(`
128
- const tools = await codemode.listTools();
129
- return tools;
130
- `);
131
-
132
- expect(result.error).toBeUndefined();
133
- expect(Array.isArray(result.result)).toBe(true);
134
- });
135
- });
@@ -1,60 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { McpTool } from "../mcp/index.js";
3
- import { isEligibleForCodeMode } from "./eligibility.js";
4
-
5
- function makeTool(overrides: Partial<McpTool> = {}): McpTool {
6
- return {
7
- name: "test_tool",
8
- description: "A test tool",
9
- inputSchema: { type: "object", properties: {} },
10
- serverName: "test-server",
11
- ...overrides,
12
- };
13
- }
14
-
15
- describe("isEligibleForCodeMode", () => {
16
- it("returns true when readOnlyHint is true and outputSchema is defined", () => {
17
- const tool = makeTool({
18
- annotations: { readOnlyHint: true },
19
- outputSchema: { type: "object", properties: { result: { type: "string" } } },
20
- });
21
- expect(isEligibleForCodeMode(tool)).toBe(true);
22
- });
23
-
24
- it("returns false when readOnlyHint is false", () => {
25
- const tool = makeTool({
26
- annotations: { readOnlyHint: false },
27
- outputSchema: { type: "object", properties: {} },
28
- });
29
- expect(isEligibleForCodeMode(tool)).toBe(false);
30
- });
31
-
32
- it("returns false when readOnlyHint is missing", () => {
33
- const tool = makeTool({
34
- annotations: {},
35
- outputSchema: { type: "object", properties: {} },
36
- });
37
- expect(isEligibleForCodeMode(tool)).toBe(false);
38
- });
39
-
40
- it("returns false when annotations is undefined", () => {
41
- const tool = makeTool({
42
- annotations: undefined,
43
- outputSchema: { type: "object", properties: {} },
44
- });
45
- expect(isEligibleForCodeMode(tool)).toBe(false);
46
- });
47
-
48
- it("returns false when outputSchema is missing", () => {
49
- const tool = makeTool({
50
- annotations: { readOnlyHint: true },
51
- outputSchema: undefined,
52
- });
53
- expect(isEligibleForCodeMode(tool)).toBe(false);
54
- });
55
-
56
- it("returns false when both readOnlyHint and outputSchema are missing", () => {
57
- const tool = makeTool();
58
- expect(isEligibleForCodeMode(tool)).toBe(false);
59
- });
60
- });
@@ -1,16 +0,0 @@
1
- import type { McpClientManager, McpTool } from "../mcp/index.js";
2
-
3
- /**
4
- * A tool is eligible for code mode when:
5
- * 1. annotations.readOnlyHint === true — does not modify its environment
6
- * 2. outputSchema is defined — results are typed and parseable
7
- */
8
- export function isEligibleForCodeMode(tool: McpTool): boolean {
9
- const annotations = tool.annotations as { readOnlyHint?: boolean } | undefined;
10
- return annotations?.readOnlyHint === true && tool.outputSchema != null;
11
- }
12
-
13
- /** Get all tools eligible for code mode across all connected MCP servers. */
14
- export function getEligibleTools(mcpManager: McpClientManager): McpTool[] {
15
- return mcpManager.getTools().filter(isEligibleForCodeMode);
16
- }