@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.
- package/dist/code-mode/code-mode-integration.test.d.ts +2 -0
- package/dist/code-mode/code-mode-integration.test.d.ts.map +1 -0
- package/dist/code-mode/code-mode-integration.test.js +116 -0
- package/dist/code-mode/code-mode-integration.test.js.map +1 -0
- package/dist/code-mode/eligibility.d.ts +10 -0
- package/dist/code-mode/eligibility.d.ts.map +1 -0
- package/dist/code-mode/eligibility.js +14 -0
- package/dist/code-mode/eligibility.js.map +1 -0
- package/dist/code-mode/eligibility.test.d.ts +2 -0
- package/dist/code-mode/eligibility.test.d.ts.map +1 -0
- package/dist/code-mode/eligibility.test.js +53 -0
- package/dist/code-mode/eligibility.test.js.map +1 -0
- package/dist/code-mode/executor.d.ts +35 -0
- package/dist/code-mode/executor.d.ts.map +1 -0
- package/dist/code-mode/executor.js +120 -0
- package/dist/code-mode/executor.js.map +1 -0
- package/dist/code-mode/executor.test.d.ts +2 -0
- package/dist/code-mode/executor.test.d.ts.map +1 -0
- package/dist/code-mode/executor.test.js +130 -0
- package/dist/code-mode/executor.test.js.map +1 -0
- package/dist/code-mode/index.d.ts +65 -0
- package/dist/code-mode/index.d.ts.map +1 -0
- package/dist/code-mode/index.js +175 -0
- package/dist/code-mode/index.js.map +1 -0
- package/dist/code-mode/tools.d.ts +45 -0
- package/dist/code-mode/tools.d.ts.map +1 -0
- package/dist/code-mode/tools.js +70 -0
- package/dist/code-mode/tools.js.map +1 -0
- package/dist/code-mode/type-hints.d.ts +20 -0
- package/dist/code-mode/type-hints.d.ts.map +1 -0
- package/dist/code-mode/type-hints.js +189 -0
- package/dist/code-mode/type-hints.js.map +1 -0
- package/dist/code-mode/type-hints.test.d.ts +2 -0
- package/dist/code-mode/type-hints.test.d.ts.map +1 -0
- package/dist/code-mode/type-hints.test.js +157 -0
- package/dist/code-mode/type-hints.test.js.map +1 -0
- package/dist/docker-e2e.d.ts +55 -0
- package/dist/docker-e2e.d.ts.map +1 -0
- package/dist/docker-e2e.js +235 -0
- package/dist/docker-e2e.js.map +1 -0
- package/dist/docker-e2e.test.d.ts +2 -0
- package/dist/docker-e2e.test.d.ts.map +1 -0
- package/dist/docker-e2e.test.js +176 -0
- package/dist/docker-e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +8 -0
- package/dist/index.test.js.map +1 -0
- package/dist/mcp/client-manager.d.ts +46 -0
- package/dist/mcp/client-manager.d.ts.map +1 -0
- package/dist/mcp/client-manager.js +148 -0
- package/dist/mcp/client-manager.js.map +1 -0
- package/dist/mcp/client-manager.test.d.ts +2 -0
- package/dist/mcp/client-manager.test.d.ts.map +1 -0
- package/dist/mcp/client-manager.test.js +204 -0
- package/dist/mcp/client-manager.test.js.map +1 -0
- package/dist/mcp/config-loader.d.ts +8 -0
- package/dist/mcp/config-loader.d.ts.map +1 -0
- package/dist/mcp/config-loader.js +43 -0
- package/dist/mcp/config-loader.js.map +1 -0
- package/dist/mcp/config-loader.test.d.ts +2 -0
- package/dist/mcp/config-loader.test.d.ts.map +1 -0
- package/dist/mcp/config-loader.test.js +93 -0
- package/dist/mcp/config-loader.test.js.map +1 -0
- package/dist/mcp/config.d.ts +46 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +28 -0
- package/dist/mcp/config.js.map +1 -0
- package/{src/mcp/index.ts → dist/mcp/index.d.ts} +1 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/skills/discover.d.ts +10 -0
- package/dist/skills/discover.d.ts.map +1 -0
- package/dist/skills/discover.js +60 -0
- package/dist/skills/discover.js.map +1 -0
- package/dist/skills/format.d.ts +10 -0
- package/dist/skills/format.d.ts.map +1 -0
- package/dist/skills/format.js +38 -0
- package/dist/skills/format.js.map +1 -0
- package/dist/skills/format.test.d.ts +2 -0
- package/dist/skills/format.test.d.ts.map +1 -0
- package/dist/skills/format.test.js +63 -0
- package/dist/skills/format.test.js.map +1 -0
- package/{src/skills/index.ts → dist/skills/index.d.ts} +2 -5
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +6 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/load-skill-tool.d.ts +40 -0
- package/dist/skills/load-skill-tool.d.ts.map +1 -0
- package/dist/skills/load-skill-tool.js +104 -0
- package/dist/skills/load-skill-tool.js.map +1 -0
- package/dist/skills/mcp-tool-proxy.d.ts +11 -0
- package/dist/skills/mcp-tool-proxy.d.ts.map +1 -0
- package/dist/skills/mcp-tool-proxy.js +116 -0
- package/dist/skills/mcp-tool-proxy.js.map +1 -0
- package/dist/skills/skill-integration.test.d.ts +2 -0
- package/dist/skills/skill-integration.test.d.ts.map +1 -0
- package/dist/skills/skill-integration.test.js +119 -0
- package/dist/skills/skill-integration.test.js.map +1 -0
- package/dist/skills/skill-registry.d.ts +39 -0
- package/dist/skills/skill-registry.d.ts.map +1 -0
- package/dist/skills/skill-registry.js +48 -0
- package/dist/skills/skill-registry.js.map +1 -0
- package/dist/skills/skill-registry.test.d.ts +2 -0
- package/dist/skills/skill-registry.test.d.ts.map +1 -0
- package/dist/skills/skill-registry.test.js +67 -0
- package/dist/skills/skill-registry.test.js.map +1 -0
- package/dist/test-servers/weather-server.d.ts +12 -0
- package/dist/test-servers/weather-server.d.ts.map +1 -0
- package/dist/test-servers/weather-server.js +139 -0
- package/dist/test-servers/weather-server.js.map +1 -0
- package/dist/test-servers/weather-stdio.d.ts +2 -0
- package/dist/test-servers/weather-stdio.d.ts.map +1 -0
- package/{src/test-servers/weather-stdio.ts → dist/test-servers/weather-stdio.js} +1 -1
- package/dist/test-servers/weather-stdio.js.map +1 -0
- package/dist/tool-cli/cli.d.ts +3 -0
- package/dist/tool-cli/cli.d.ts.map +1 -0
- package/dist/tool-cli/cli.js +179 -0
- package/dist/tool-cli/cli.js.map +1 -0
- package/dist/tool-cli/constants.d.ts +7 -0
- package/dist/tool-cli/constants.d.ts.map +1 -0
- package/dist/tool-cli/constants.js +15 -0
- package/dist/tool-cli/constants.js.map +1 -0
- package/dist/tool-cli/format.d.ts +8 -0
- package/dist/tool-cli/format.d.ts.map +1 -0
- package/{src/tool-cli/format.ts → dist/tool-cli/format.js} +5 -4
- package/dist/tool-cli/format.js.map +1 -0
- package/dist/tool-cli/format.test.d.ts +2 -0
- package/dist/tool-cli/format.test.d.ts.map +1 -0
- package/dist/tool-cli/format.test.js +30 -0
- package/dist/tool-cli/format.test.js.map +1 -0
- package/{src/tool-cli/index.ts → dist/tool-cli/index.d.ts} +1 -0
- package/dist/tool-cli/index.d.ts.map +1 -0
- package/dist/tool-cli/index.js +3 -0
- package/dist/tool-cli/index.js.map +1 -0
- package/dist/tool-cli/rpc-client.d.ts +6 -0
- package/dist/tool-cli/rpc-client.d.ts.map +1 -0
- package/dist/tool-cli/rpc-client.js +25 -0
- package/dist/tool-cli/rpc-client.js.map +1 -0
- package/dist/tool-cli/rpc-server.d.ts +48 -0
- package/dist/tool-cli/rpc-server.d.ts.map +1 -0
- package/dist/tool-cli/rpc-server.js +220 -0
- package/dist/tool-cli/rpc-server.js.map +1 -0
- package/dist/tool-cli/rpc-server.test.d.ts +2 -0
- package/dist/tool-cli/rpc-server.test.d.ts.map +1 -0
- package/dist/tool-cli/rpc-server.test.js +154 -0
- package/dist/tool-cli/rpc-server.test.js.map +1 -0
- package/package.json +5 -1
- package/.dockerignore +0 -6
- package/.github/dependabot.yml +0 -25
- package/.github/workflows/ci.yml +0 -50
- package/.hadolint.yaml +0 -6
- package/.prettierignore +0 -4
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -118
- package/DECISIONS.md +0 -99
- package/Dockerfile +0 -39
- package/aube-lock.yaml +0 -4146
- package/docs/code-mode.md +0 -38
- package/docs/skills.md +0 -57
- package/docs/tool-cli.md +0 -54
- package/eslint.config.js +0 -27
- package/images/banner.webp +0 -0
- package/images/code-c-maude.webp +0 -0
- package/images/code-mode-histogram.png +0 -0
- package/images/nuclear-mcp-football.webp +0 -0
- package/images/skills-enabling-mcp-tools.png +0 -0
- package/images/the-skill-dealer.webp +0 -0
- package/images/tool-cli-grep.png +0 -0
- package/mise.toml +0 -53
- package/scripts/docker-entrypoint.sh +0 -29
- package/scripts/test-echo-server.mjs +0 -28
- package/scripts/test-mcp-integration.mjs +0 -59
- package/scripts/test-tool-cli-smoke.mjs +0 -93
- package/src/code-mode/code-mode-integration.test.ts +0 -135
- package/src/code-mode/eligibility.test.ts +0 -60
- package/src/code-mode/eligibility.ts +0 -16
- package/src/code-mode/executor.test.ts +0 -168
- package/src/code-mode/executor.ts +0 -163
- package/src/code-mode/index.ts +0 -208
- package/src/code-mode/tools.ts +0 -110
- package/src/code-mode/type-hints.test.ts +0 -187
- package/src/code-mode/type-hints.ts +0 -249
- package/src/docker-e2e.test.ts +0 -195
- package/src/docker-e2e.ts +0 -277
- package/src/index.test.ts +0 -8
- package/src/index.ts +0 -179
- package/src/mcp/client-manager.test.ts +0 -250
- package/src/mcp/client-manager.ts +0 -199
- package/src/mcp/config-loader.test.ts +0 -115
- package/src/mcp/config-loader.ts +0 -46
- package/src/mcp/config.ts +0 -40
- package/src/skills/discover.ts +0 -81
- package/src/skills/format.test.ts +0 -70
- package/src/skills/format.ts +0 -42
- package/src/skills/load-skill-tool.ts +0 -136
- package/src/skills/mcp-tool-proxy.ts +0 -143
- package/src/skills/skill-integration.test.ts +0 -139
- package/src/skills/skill-registry.test.ts +0 -83
- package/src/skills/skill-registry.ts +0 -69
- package/src/test-servers/weather-server.ts +0 -171
- package/src/tool-cli/format.test.ts +0 -33
- 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
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
|
-
}
|