@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/src/docker-e2e.ts DELETED
@@ -1,277 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import type {
3
- AgentToolResult,
4
- AgentToolUpdateCallback,
5
- ExtensionContext,
6
- } from "@sammorrowdrums/mcpi";
7
- import { Type, type Static } from "typebox";
8
-
9
- const DOCKER_IMAGE = "mcpi-ext-e2e";
10
-
11
- const DockerE2EParams = Type.Object({
12
- task: Type.String({ description: "The task/prompt to run inside the sandbox" }),
13
- token: Type.String({
14
- description: "GitHub token for Copilot model access",
15
- }),
16
- model: Type.Optional(
17
- Type.String({
18
- description: "Model to use (e.g. claude-sonnet-4-5). Defaults to the provider default.",
19
- }),
20
- ),
21
- timeout: Type.Optional(
22
- Type.Number({
23
- description: "Timeout in seconds. Defaults to 300 (5 minutes).",
24
- }),
25
- ),
26
- });
27
-
28
- type DockerE2EInput = Static<typeof DockerE2EParams>;
29
-
30
- interface RunLogEntry {
31
- type: string;
32
- timestamp: number;
33
- raw: string;
34
- }
35
-
36
- export interface DockerE2EDetails {
37
- exitCode: number | null;
38
- log: RunLogEntry[];
39
- duration: number;
40
- model?: string;
41
- usage?: {
42
- input: number;
43
- output: number;
44
- cacheRead: number;
45
- cacheWrite: number;
46
- cost: number;
47
- turns: number;
48
- };
49
- }
50
-
51
- export function parseJsonlLine(line: string): RunLogEntry | null {
52
- const trimmed = line.trim();
53
- if (!trimmed) return null;
54
- try {
55
- const parsed = JSON.parse(trimmed);
56
- return { type: parsed.type ?? "unknown", timestamp: Date.now(), raw: trimmed };
57
- } catch {
58
- return { type: "raw", timestamp: Date.now(), raw: trimmed };
59
- }
60
- }
61
-
62
- export function extractAssistantText(log: RunLogEntry[]): string {
63
- const parts: string[] = [];
64
- for (const entry of log) {
65
- if (entry.type !== "message_end") continue;
66
- try {
67
- const event = JSON.parse(entry.raw);
68
- const msg = event.message;
69
- if (msg?.role !== "assistant") continue;
70
- for (const part of msg.content ?? []) {
71
- if (part.type === "text") parts.push(part.text);
72
- }
73
- } catch {
74
- // skip malformed
75
- }
76
- }
77
- return parts.join("\n");
78
- }
79
-
80
- export function extractUsage(log: RunLogEntry[]) {
81
- const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
82
- for (const entry of log) {
83
- if (entry.type !== "message_end") continue;
84
- try {
85
- const event = JSON.parse(entry.raw);
86
- const msg = event.message;
87
- if (msg?.role !== "assistant") continue;
88
- usage.turns++;
89
- const u = msg.usage;
90
- if (u) {
91
- usage.input += u.input ?? 0;
92
- usage.output += u.output ?? 0;
93
- usage.cacheRead += u.cacheRead ?? 0;
94
- usage.cacheWrite += u.cacheWrite ?? 0;
95
- usage.cost += u.cost?.total ?? 0;
96
- }
97
- } catch {
98
- // skip
99
- }
100
- }
101
- return usage;
102
- }
103
-
104
- export function extractModel(log: RunLogEntry[]): string | undefined {
105
- for (const entry of log) {
106
- if (entry.type !== "message_end") continue;
107
- try {
108
- const event = JSON.parse(entry.raw);
109
- if (event.message?.model) return event.message.model as string;
110
- } catch {
111
- // skip
112
- }
113
- }
114
- return undefined;
115
- }
116
-
117
- export function buildDockerArgs(image: string, params: DockerE2EInput): string[] {
118
- const args = ["run", "--rm", "-e", `GITHUB_TOKEN=${params.token}`];
119
-
120
- if (params.model) {
121
- args.push("-e", `PI_MODEL=${params.model}`);
122
- }
123
-
124
- args.push(image, params.task);
125
- return args;
126
- }
127
-
128
- export const dockerE2ETool = {
129
- name: "docker_e2e",
130
- label: "Docker E2E",
131
- description:
132
- "Run a task end-to-end inside an isolated Docker container with pi and this project's extensions. Returns the full run log for evaluation.",
133
- promptSnippet: "Run an isolated e2e task in Docker. Requires a GitHub token and task prompt.",
134
- parameters: DockerE2EParams,
135
-
136
- async execute(
137
- _toolCallId: string,
138
- params: DockerE2EInput,
139
- signal: AbortSignal | undefined,
140
- onUpdate: AgentToolUpdateCallback<DockerE2EDetails> | undefined,
141
- _ctx: ExtensionContext,
142
- ): Promise<AgentToolResult<DockerE2EDetails>> {
143
- const startTime = Date.now();
144
- const timeoutMs = (params.timeout ?? 300) * 1000;
145
- const log: RunLogEntry[] = [];
146
- let lastAssistantText = "";
147
-
148
- const dockerArgs = buildDockerArgs(DOCKER_IMAGE, params);
149
-
150
- const exitCode = await new Promise<number | null>((resolve) => {
151
- const proc = spawn("docker", dockerArgs, {
152
- stdio: ["ignore", "pipe", "pipe"],
153
- });
154
-
155
- let buffer = "";
156
- let timedOut = false;
157
- let timeoutHandle: NodeJS.Timeout | undefined;
158
-
159
- if (timeoutMs > 0) {
160
- timeoutHandle = setTimeout(() => {
161
- timedOut = true;
162
- proc.kill("SIGTERM");
163
- setTimeout(() => {
164
- if (!proc.killed) proc.kill("SIGKILL");
165
- }, 5000);
166
- }, timeoutMs);
167
- }
168
-
169
- const processLine = (line: string) => {
170
- const entry = parseJsonlLine(line);
171
- if (!entry) return;
172
- log.push(entry);
173
-
174
- if (entry.type === "message_end") {
175
- const text = extractAssistantText(log);
176
- if (text) lastAssistantText = text;
177
- }
178
-
179
- onUpdate?.({
180
- content: [
181
- {
182
- type: "text",
183
- text: lastAssistantText || `(running… ${log.length} events)`,
184
- },
185
- ],
186
- details: {
187
- exitCode: null,
188
- log,
189
- duration: Date.now() - startTime,
190
- model: extractModel(log),
191
- usage: extractUsage(log),
192
- },
193
- });
194
- };
195
-
196
- proc.stdout.on("data", (data: Buffer) => {
197
- buffer += data.toString();
198
- const lines = buffer.split("\n");
199
- buffer = lines.pop() ?? "";
200
- for (const line of lines) processLine(line);
201
- });
202
-
203
- proc.stderr.on("data", (data: Buffer) => {
204
- const text = data.toString().trim();
205
- if (text) {
206
- log.push({ type: "stderr", timestamp: Date.now(), raw: text });
207
- }
208
- });
209
-
210
- proc.on("close", (code) => {
211
- if (timeoutHandle) clearTimeout(timeoutHandle);
212
- if (buffer.trim()) processLine(buffer);
213
- if (timedOut) {
214
- log.push({
215
- type: "timeout",
216
- timestamp: Date.now(),
217
- raw: `Timed out after ${params.timeout ?? 300}s`,
218
- });
219
- }
220
- resolve(timedOut ? null : (code ?? 1));
221
- });
222
-
223
- proc.on("error", (err) => {
224
- if (timeoutHandle) clearTimeout(timeoutHandle);
225
- log.push({
226
- type: "error",
227
- timestamp: Date.now(),
228
- raw: err.message,
229
- });
230
- resolve(null);
231
- });
232
-
233
- if (signal) {
234
- const onAbort = () => {
235
- proc.kill("SIGTERM");
236
- setTimeout(() => {
237
- if (!proc.killed) proc.kill("SIGKILL");
238
- }, 5000);
239
- };
240
- if (signal.aborted) onAbort();
241
- else signal.addEventListener("abort", onAbort, { once: true });
242
- }
243
- });
244
-
245
- const duration = Date.now() - startTime;
246
- const assistantText = extractAssistantText(log);
247
- const usage = extractUsage(log);
248
- const model = extractModel(log);
249
- const fullLog = log.map((e) => e.raw).join("\n");
250
-
251
- const summary = [
252
- `## Docker E2E Run Complete`,
253
- ``,
254
- `**Exit code:** ${exitCode ?? "killed"}`,
255
- `**Duration:** ${(duration / 1000).toFixed(1)}s`,
256
- model ? `**Model:** ${model}` : null,
257
- usage.turns > 0
258
- ? `**Usage:** ${usage.turns} turns, ↑${usage.input} ↓${usage.output} tokens, $${usage.cost.toFixed(4)}`
259
- : null,
260
- ``,
261
- `### Output`,
262
- assistantText || "(no assistant output)",
263
- ``,
264
- `### Full Log (${log.length} events)`,
265
- "```",
266
- fullLog,
267
- "```",
268
- ]
269
- .filter((l) => l !== null)
270
- .join("\n");
271
-
272
- return {
273
- content: [{ type: "text", text: summary }],
274
- details: { exitCode, log, duration, model, usage },
275
- };
276
- },
277
- };
package/src/index.test.ts DELETED
@@ -1,8 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
-
3
- describe("mcpi-ext", () => {
4
- it("exports a default function", async () => {
5
- const mod = await import("./index.js");
6
- expect(typeof mod.default).toBe("function");
7
- });
8
- });
package/src/index.ts DELETED
@@ -1,179 +0,0 @@
1
- import type {
2
- BeforeAgentStartEvent,
3
- ExtensionAPI,
4
- ExtensionContext,
5
- SessionStartEvent,
6
- ToolCallEvent,
7
- } from "@sammorrowdrums/mcpi";
8
- import { CodeModeManager } from "./code-mode/index.js";
9
- import { dockerE2ETool } from "./docker-e2e.js";
10
- import { McpClientManager, loadMcpConfig } from "./mcp/index.js";
11
- import {
12
- SkillRegistry,
13
- createLoadSkillTool,
14
- discoverSkillsFromServer,
15
- formatMcpSkillsForPrompt,
16
- registerMcpToolProxies,
17
- } from "./skills/index.js";
18
- import { ToolCliServer, formatToolCliForPrompt } from "./tool-cli/index.js";
19
- import type { ToolProvider } from "./tool-cli/index.js";
20
-
21
- export default function (pi: ExtensionAPI) {
22
- pi.registerTool(dockerE2ETool);
23
-
24
- pi.registerFlag("mcp-config", {
25
- description: "Path to MCP server configuration JSON file",
26
- type: "string",
27
- });
28
-
29
- const mcpManager = new McpClientManager();
30
- const skillRegistry = new SkillRegistry();
31
- const codeModeManager = new CodeModeManager();
32
- const enabledTools = new Set<string>();
33
- const gatedToolNames = new Set<string>();
34
-
35
- // Bridge McpClientManager to the ToolProvider interface
36
- const toolProvider: ToolProvider = {
37
- getServerNames: () => mcpManager.getConnectedServers(),
38
- getTools: (server) => mcpManager.getToolsForServer(server),
39
- async callTool(server, tool, args) {
40
- const client = mcpManager.getClient(server);
41
- if (!client) throw new Error(`No client for server "${server}"`);
42
- const result = await client.callTool({ name: tool, arguments: args });
43
- return {
44
- content: result.content as unknown[],
45
- isError: result.isError === true ? true : undefined,
46
- structuredContent: result.structuredContent as Record<string, unknown> | undefined,
47
- };
48
- },
49
- };
50
- const rpcServer = new ToolCliServer(toolProvider);
51
-
52
- // Register the load_skill tool so the model can activate MCP skills
53
- pi.registerTool(createLoadSkillTool({ registry: skillRegistry, mcpManager, enabledTools }));
54
-
55
- pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
56
- if (ctx.hasUI) {
57
- ctx.ui.notify("mcpi-ext loaded", "info");
58
- }
59
-
60
- const configPath = pi.getFlag("mcp-config") as string | undefined;
61
- const log = (msg: string) => {
62
- if (ctx.hasUI) {
63
- ctx.ui.notify(msg, "info");
64
- } else {
65
- console.error(msg);
66
- }
67
- };
68
- try {
69
- const config = await loadMcpConfig(configPath);
70
- const serverCount = Object.keys(config.mcpServers).length;
71
- if (serverCount > 0) {
72
- await mcpManager.connectAll(config, log);
73
- const tools = mcpManager.getTools();
74
- log(
75
- `MCP: ${mcpManager.getConnectedServers().length} server(s), ${tools.length} tool(s) discovered`,
76
- );
77
-
78
- // Pre-register all MCP tools as deferred Pi tool proxies
79
- // (in tools array for dispatch but excluded from system prompt)
80
- const allToolNames = tools.map((t) => t.name);
81
- registerMcpToolProxies(allToolNames, mcpManager, pi);
82
-
83
- // Discover skills from all connected servers
84
- for (const serverName of mcpManager.getConnectedServers()) {
85
- const client = mcpManager.getClient(serverName);
86
- if (!client) continue;
87
- try {
88
- const skills = await discoverSkillsFromServer(client, serverName, log);
89
- skillRegistry.registerAll(skills);
90
- } catch (err) {
91
- log(
92
- `[skills] Failed to discover skills from "${serverName}": ${(err as Error).message}`,
93
- );
94
- }
95
- }
96
-
97
- if (skillRegistry.size > 0) {
98
- for (const skill of skillRegistry.getAll()) {
99
- for (const t of skill.allowedTools) {
100
- gatedToolNames.add(t);
101
- }
102
- }
103
- log(
104
- `MCP: ${skillRegistry.size} skill(s) discovered, ${gatedToolNames.size} tool(s) deferred`,
105
- );
106
- }
107
-
108
- // Start the tool-cli RPC server for progressive tool discovery
109
- try {
110
- const { port, token } = await rpcServer.start(log);
111
- pi.setEnv("TOOL_CLI_PORT", String(port));
112
- pi.setEnv("TOOL_CLI_TOKEN", token);
113
- } catch (err) {
114
- log(`[tool-cli] Failed to start RPC server: ${(err as Error).message}`);
115
- }
116
-
117
- // Initialize code mode (Tier 3) for read-only tools with structured output
118
- codeModeManager.initialize(mcpManager);
119
- if (codeModeManager.isActive) {
120
- const { codeSearch, codeExecute } = codeModeManager.createTools();
121
- pi.registerTool(codeSearch);
122
- pi.registerTool(codeExecute);
123
- log(
124
- `MCP: Code mode active (${codeModeManager.getEligibleTools().length} eligible tool(s))`,
125
- );
126
- }
127
- }
128
- } catch (err) {
129
- const msg = `MCP config error: ${(err as Error).message}`;
130
- if (ctx.hasUI) {
131
- ctx.ui.notify(msg, "warning");
132
- } else {
133
- console.error(msg);
134
- }
135
- }
136
- });
137
-
138
- // Inject MCP skills, tool-cli advice, and code mode type hints into the system prompt
139
- pi.on("before_agent_start", async (event: BeforeAgentStartEvent) => {
140
- let extra = "";
141
-
142
- const skills = skillRegistry.getAll();
143
- if (skills.length > 0) {
144
- extra += formatMcpSkillsForPrompt(skills);
145
- }
146
-
147
- const serverCount = mcpManager.getConnectedServers().length;
148
- extra += formatToolCliForPrompt(serverCount);
149
-
150
- if (codeModeManager.isActive) {
151
- extra += codeModeManager.formatSystemPromptSection();
152
- }
153
-
154
- if (extra.length === 0) return;
155
- return { systemPrompt: event.systemPrompt + extra };
156
- });
157
-
158
- // Block deferred MCP tools until their skill is loaded
159
- pi.on("tool_call", async (event: ToolCallEvent) => {
160
- const name = "toolName" in event ? event.toolName : undefined;
161
- if (!name || !gatedToolNames.has(name) || enabledTools.has(name)) return;
162
- const relevantSkills = skillRegistry
163
- .getAll()
164
- .filter((s) => s.allowedTools.includes(name))
165
- .map((s) => s.name);
166
- return {
167
- block: true,
168
- reason: `Tool "${name}" requires loading a skill first. Call load_skill with one of: ${relevantSkills.join(", ")}`,
169
- };
170
- });
171
-
172
- pi.on("session_shutdown", async () => {
173
- pi.unsetEnv("TOOL_CLI_PORT");
174
- pi.unsetEnv("TOOL_CLI_TOKEN");
175
- await rpcServer.stop();
176
- await mcpManager.disconnectAll();
177
- skillRegistry.clear();
178
- });
179
- }