mini-coder 0.5.14 → 0.6.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/README.md +25 -109
- package/bin/mc.ts +8 -11
- package/bun.lock +79 -269
- package/package.json +17 -22
- package/src/agent.ts +237 -1403
- package/src/args.ts +289 -0
- package/src/headless.ts +43 -358
- package/src/index.ts +29 -1016
- package/src/oauth.ts +117 -0
- package/src/prompt.ts +227 -284
- package/src/session.ts +55 -1306
- package/src/shared.ts +117 -38
- package/src/tool-bash.ts +110 -0
- package/src/tool-edit.ts +133 -0
- package/src/tool-task.ts +114 -0
- package/src/tui-components.ts +150 -0
- package/src/tui-conversation.ts +262 -0
- package/src/tui-editor.ts +29 -0
- package/src/tui-overlay.ts +403 -0
- package/src/tui.ts +236 -0
- package/src/types.ts +160 -0
- package/tsconfig.json +17 -0
- package/BENCHMARK.md +0 -107
- package/LICENSE +0 -9
- package/PROGRESS.md +0 -5
- package/assets/icon-1-minimal.svg +0 -31
- package/assets/icon-2-dark-terminal.svg +0 -48
- package/assets/icon-3-gradient-modern.svg +0 -45
- package/assets/icon-4-filled-bold.svg +0 -54
- package/assets/icon-5-community-badge.svg +0 -63
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/assets/preview-0-5-0.png +0 -0
- package/assets/preview.gif +0 -0
- package/benchmark-baseline.sh +0 -15
- package/benchmark-loop.sh +0 -19
- package/skills-lock.json +0 -15
- package/src/assistant-output.ts +0 -73
- package/src/cli.ts +0 -134
- package/src/delegation.ts +0 -238
- package/src/errors.ts +0 -15
- package/src/git.ts +0 -247
- package/src/input.ts +0 -168
- package/src/mcp.ts +0 -609
- package/src/paths.ts +0 -37
- package/src/session-message.ts +0 -385
- package/src/settings.ts +0 -449
- package/src/skills.ts +0 -271
- package/src/submit.ts +0 -376
- package/src/text.ts +0 -71
- package/src/theme.ts +0 -330
- package/src/tool-common.ts +0 -93
- package/src/tool-delegate.ts +0 -125
- package/src/tool-grep.ts +0 -606
- package/src/tool-read.ts +0 -313
- package/src/tool-shell.ts +0 -1051
- package/src/tools.ts +0 -1179
- package/src/ui/agent.ts +0 -320
- package/src/ui/commands.test.ts +0 -957
- package/src/ui/commands.ts +0 -848
- package/src/ui/conversation.test.ts +0 -585
- package/src/ui/conversation.ts +0 -1836
- package/src/ui/help.ts +0 -158
- package/src/ui/input.test.ts +0 -64
- package/src/ui/input.ts +0 -138
- package/src/ui/overlay.ts +0 -59
- package/src/ui/runtime.ts +0 -69
- package/src/ui/status.ts +0 -220
- package/src/ui.ts +0 -1190
- package/src/version.ts +0 -48
package/src/mcp.ts
DELETED
|
@@ -1,609 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model Context Protocol client discovery and tool integration.
|
|
3
|
-
*
|
|
4
|
-
* mini-coder tracks configured Streamable HTTP MCP servers, connects enabled
|
|
5
|
-
* ones when needed, imports their tools, and exposes those tools through the
|
|
6
|
-
* normal pi-ai tool interface.
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Static, Tool, ToolCall, TSchema } from "@mariozechner/pi-ai";
|
|
12
|
-
import { Type, validateToolArguments } from "@mariozechner/pi-ai";
|
|
13
|
-
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
14
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
15
|
-
import type { ToolHandler, ToolUpdateCallback } from "./agent.ts";
|
|
16
|
-
import { getErrorMessage } from "./errors.ts";
|
|
17
|
-
import type { McpSettings } from "./settings.ts";
|
|
18
|
-
import { type ToolExecResult, textResult } from "./tool-common.ts";
|
|
19
|
-
|
|
20
|
-
const MCP_DISCOVERY_TIMEOUT_MS = 3_000;
|
|
21
|
-
|
|
22
|
-
interface McpListedTool {
|
|
23
|
-
/** JSON Schema describing the tool input object. */
|
|
24
|
-
inputSchema: {
|
|
25
|
-
properties?: Record<string, object>;
|
|
26
|
-
required?: string[];
|
|
27
|
-
type: "object";
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
};
|
|
30
|
-
/** Human-readable tool description when provided by the server. */
|
|
31
|
-
description?: string;
|
|
32
|
-
/** Server-defined MCP tool name. */
|
|
33
|
-
name: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type McpCallToolResult =
|
|
37
|
-
| {
|
|
38
|
-
/** Tool content blocks returned by the MCP server. */
|
|
39
|
-
content: McpToolResultContent[];
|
|
40
|
-
/** Optional structured data returned alongside content. */
|
|
41
|
-
structuredContent?: unknown;
|
|
42
|
-
/** Whether the server marked this result as an error. */
|
|
43
|
-
isError?: boolean;
|
|
44
|
-
}
|
|
45
|
-
| {
|
|
46
|
-
/** Compatibility payload from older MCP tool result formats. */
|
|
47
|
-
toolResult: unknown;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type McpToolResultContent =
|
|
51
|
-
| {
|
|
52
|
-
type: "text";
|
|
53
|
-
text: string;
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
type: "image";
|
|
57
|
-
data: string;
|
|
58
|
-
mimeType: string;
|
|
59
|
-
}
|
|
60
|
-
| {
|
|
61
|
-
type: "audio";
|
|
62
|
-
data: string;
|
|
63
|
-
mimeType: string;
|
|
64
|
-
}
|
|
65
|
-
| {
|
|
66
|
-
type: "resource";
|
|
67
|
-
resource:
|
|
68
|
-
| {
|
|
69
|
-
uri: string;
|
|
70
|
-
text: string;
|
|
71
|
-
mimeType?: string;
|
|
72
|
-
}
|
|
73
|
-
| {
|
|
74
|
-
uri: string;
|
|
75
|
-
blob: string;
|
|
76
|
-
mimeType?: string;
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
| {
|
|
80
|
-
type: "resource_link";
|
|
81
|
-
uri: string;
|
|
82
|
-
name: string;
|
|
83
|
-
description?: string;
|
|
84
|
-
mimeType?: string;
|
|
85
|
-
size?: number;
|
|
86
|
-
title?: string;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
type McpProgress = {
|
|
90
|
-
/** Current completed work units reported by the server. */
|
|
91
|
-
progress: number;
|
|
92
|
-
/** Total work units when the server reports them. */
|
|
93
|
-
total?: number;
|
|
94
|
-
/** Optional human-readable progress message. */
|
|
95
|
-
message?: string;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
interface McpTransportLike {
|
|
99
|
-
close(): Promise<void>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
interface McpClientLike {
|
|
103
|
-
connect(
|
|
104
|
-
transport: McpTransportLike,
|
|
105
|
-
options?: { timeout?: number },
|
|
106
|
-
): Promise<void>;
|
|
107
|
-
listTools(
|
|
108
|
-
params?: { cursor?: string },
|
|
109
|
-
options?: { timeout?: number },
|
|
110
|
-
): Promise<{
|
|
111
|
-
tools: McpListedTool[];
|
|
112
|
-
nextCursor?: string;
|
|
113
|
-
}>;
|
|
114
|
-
callTool(
|
|
115
|
-
params: {
|
|
116
|
-
name: string;
|
|
117
|
-
arguments?: Record<string, unknown>;
|
|
118
|
-
},
|
|
119
|
-
resultSchema?: undefined,
|
|
120
|
-
options?: {
|
|
121
|
-
signal?: AbortSignal;
|
|
122
|
-
onprogress?: (progress: McpProgress) => void;
|
|
123
|
-
},
|
|
124
|
-
): Promise<McpCallToolResult>;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
interface McpRuntime {
|
|
128
|
-
createClient(): McpClientLike;
|
|
129
|
-
createTransport(url: URL): McpTransportLike;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const defaultMcpRuntime: McpRuntime = {
|
|
133
|
-
createClient() {
|
|
134
|
-
return new Client(
|
|
135
|
-
{
|
|
136
|
-
name: "mini-coder",
|
|
137
|
-
version: "0.0.0",
|
|
138
|
-
},
|
|
139
|
-
{ capabilities: {} },
|
|
140
|
-
);
|
|
141
|
-
},
|
|
142
|
-
createTransport(url) {
|
|
143
|
-
return new StreamableHTTPClientTransport(url);
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
async function noopClose(): Promise<void> {}
|
|
148
|
-
|
|
149
|
-
/** A configured MCP server and its current runtime connection state. */
|
|
150
|
-
export interface McpServerState {
|
|
151
|
-
/** User-configured server identifier from `settings.json`. */
|
|
152
|
-
name: string;
|
|
153
|
-
/** Absolute Streamable HTTP endpoint URL. */
|
|
154
|
-
url: string;
|
|
155
|
-
/** Whether this server should be available to future turns. */
|
|
156
|
-
enabled: boolean;
|
|
157
|
-
/** Whether mini-coder currently has an active connection to this server. */
|
|
158
|
-
connected: boolean;
|
|
159
|
-
/** pi-ai tool definitions derived from the server's tool list. */
|
|
160
|
-
tools: Tool[];
|
|
161
|
-
/** Tool name → handler map for calling the remote MCP tools. */
|
|
162
|
-
toolHandlers: Map<string, ToolHandler>;
|
|
163
|
-
/** Close the underlying MCP transport when connected. */
|
|
164
|
-
close(): Promise<void>;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
interface McpConnectionState {
|
|
168
|
-
/** Non-fatal warnings encountered while importing tools. */
|
|
169
|
-
warnings: string[];
|
|
170
|
-
/** pi-ai tool definitions derived from the server's tool list. */
|
|
171
|
-
tools: Tool[];
|
|
172
|
-
/** Tool name → handler map for calling the remote MCP tools. */
|
|
173
|
-
toolHandlers: Map<string, ToolHandler>;
|
|
174
|
-
/** Close the underlying MCP transport. */
|
|
175
|
-
close(): Promise<void>;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
interface McpDiscoveryResult {
|
|
179
|
-
/** MCP servers kept in runtime state after startup discovery. */
|
|
180
|
-
servers: McpServerState[];
|
|
181
|
-
/** Non-fatal startup warnings for skipped or unreachable servers. */
|
|
182
|
-
warnings: string[];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function createDisconnectedMcpServer(
|
|
186
|
-
entry: NonNullable<McpSettings["servers"]>[number],
|
|
187
|
-
): McpServerState {
|
|
188
|
-
return {
|
|
189
|
-
name: entry.name,
|
|
190
|
-
url: entry.url,
|
|
191
|
-
enabled: entry.enabled,
|
|
192
|
-
connected: false,
|
|
193
|
-
tools: [],
|
|
194
|
-
toolHandlers: new Map(),
|
|
195
|
-
close: noopClose,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function resetMcpServerConnection(server: McpServerState): void {
|
|
200
|
-
server.connected = false;
|
|
201
|
-
server.tools = [];
|
|
202
|
-
server.toolHandlers = new Map();
|
|
203
|
-
server.close = noopClose;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function parseMcpServerUrl(rawUrl: string): URL | null {
|
|
207
|
-
try {
|
|
208
|
-
return new URL(rawUrl);
|
|
209
|
-
} catch {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Connect a configured MCP server and populate its imported tools.
|
|
216
|
-
*
|
|
217
|
-
* On failure the server is left disconnected and the returned warnings describe
|
|
218
|
-
* why the connection was skipped.
|
|
219
|
-
*
|
|
220
|
-
* @param server - Runtime MCP server state to connect.
|
|
221
|
-
* @param runtime - Internal runtime injection for tests.
|
|
222
|
-
* @returns Non-fatal warnings produced while connecting or importing tools.
|
|
223
|
-
*/
|
|
224
|
-
export async function connectMcpServer(
|
|
225
|
-
server: McpServerState,
|
|
226
|
-
runtime: McpRuntime = defaultMcpRuntime,
|
|
227
|
-
): Promise<string[]> {
|
|
228
|
-
if (server.connected) {
|
|
229
|
-
return [];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const connection = await createMcpServerConnection(
|
|
233
|
-
server.name,
|
|
234
|
-
server.url,
|
|
235
|
-
runtime,
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
if (!("tools" in connection)) {
|
|
239
|
-
resetMcpServerConnection(server);
|
|
240
|
-
return [connection.warning];
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
server.connected = true;
|
|
244
|
-
server.tools = connection.tools;
|
|
245
|
-
server.toolHandlers = connection.toolHandlers;
|
|
246
|
-
server.close = connection.close;
|
|
247
|
-
return connection.warnings;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Disconnect an MCP server and clear its imported tool state.
|
|
252
|
-
*
|
|
253
|
-
* @param server - Runtime MCP server state to disconnect.
|
|
254
|
-
*/
|
|
255
|
-
export async function disconnectMcpServer(
|
|
256
|
-
server: McpServerState,
|
|
257
|
-
): Promise<void> {
|
|
258
|
-
if (!server.connected) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const close = server.close;
|
|
263
|
-
resetMcpServerConnection(server);
|
|
264
|
-
await close();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Connect enabled MCP servers at startup while keeping disabled ones in state.
|
|
269
|
-
*
|
|
270
|
-
* Invalid or unreachable servers do not fail startup. Enabled servers that
|
|
271
|
-
* cannot connect are skipped with warnings. Disabled servers stay disconnected
|
|
272
|
-
* until the user turns them back on.
|
|
273
|
-
*
|
|
274
|
-
* @param settings - Optional MCP settings from `settings.json`.
|
|
275
|
-
* @param runtime - Internal runtime injection for tests.
|
|
276
|
-
* @returns MCP server state plus any non-fatal startup warnings.
|
|
277
|
-
*/
|
|
278
|
-
export async function discoverMcpServers(
|
|
279
|
-
settings: McpSettings | undefined,
|
|
280
|
-
runtime: McpRuntime = defaultMcpRuntime,
|
|
281
|
-
): Promise<McpDiscoveryResult> {
|
|
282
|
-
const servers: McpServerState[] = [];
|
|
283
|
-
const warnings: string[] = [];
|
|
284
|
-
|
|
285
|
-
for (const entry of settings?.servers ?? []) {
|
|
286
|
-
if (!parseMcpServerUrl(entry.url)) {
|
|
287
|
-
warnings.push(`MCP server "${entry.name}": invalid URL (${entry.url})`);
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const server = createDisconnectedMcpServer(entry);
|
|
292
|
-
if (!entry.enabled) {
|
|
293
|
-
servers.push(server);
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const connectWarnings = await connectMcpServer(server, runtime);
|
|
298
|
-
warnings.push(...connectWarnings);
|
|
299
|
-
if (!server.connected) {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
servers.push(server);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { servers, warnings };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function createMcpServerConnection(
|
|
310
|
-
serverName: string,
|
|
311
|
-
serverUrl: string,
|
|
312
|
-
runtime: McpRuntime,
|
|
313
|
-
): Promise<McpConnectionState | { warning: string }> {
|
|
314
|
-
const url = parseMcpServerUrl(serverUrl);
|
|
315
|
-
if (!url) {
|
|
316
|
-
return {
|
|
317
|
-
warning: `MCP server "${serverName}": invalid URL (${serverUrl})`,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const transport = runtime.createTransport(url);
|
|
322
|
-
const client = runtime.createClient();
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
await client.connect(transport, { timeout: MCP_DISCOVERY_TIMEOUT_MS });
|
|
326
|
-
const listedTools = await listAllTools(client);
|
|
327
|
-
|
|
328
|
-
if (listedTools.length === 0) {
|
|
329
|
-
await closeQuietly(transport);
|
|
330
|
-
return {
|
|
331
|
-
warning: `MCP server "${serverName}" exposes no tools and was skipped.`,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const warnings: string[] = [];
|
|
336
|
-
const tools: Tool[] = [];
|
|
337
|
-
const toolHandlers = new Map<string, ToolHandler>();
|
|
338
|
-
|
|
339
|
-
for (const listedTool of listedTools) {
|
|
340
|
-
const { tool, handler } = createMcpTool(serverName, listedTool, client);
|
|
341
|
-
if (toolHandlers.has(tool.name)) {
|
|
342
|
-
warnings.push(
|
|
343
|
-
`MCP server "${serverName}": duplicate tool name "${listedTool.name}" was skipped.`,
|
|
344
|
-
);
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
tools.push(tool);
|
|
348
|
-
toolHandlers.set(tool.name, handler);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (tools.length === 0) {
|
|
352
|
-
await closeQuietly(transport);
|
|
353
|
-
return {
|
|
354
|
-
warning: `MCP server "${serverName}" exposes no usable tools and was skipped.`,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
warnings,
|
|
360
|
-
tools,
|
|
361
|
-
toolHandlers,
|
|
362
|
-
close: () => transport.close(),
|
|
363
|
-
};
|
|
364
|
-
} catch (error) {
|
|
365
|
-
await closeQuietly(transport);
|
|
366
|
-
return {
|
|
367
|
-
warning: `MCP server "${serverName}": ${getErrorMessage(error)} (${serverUrl})`,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function listAllTools(client: McpClientLike): Promise<McpListedTool[]> {
|
|
373
|
-
const tools: McpListedTool[] = [];
|
|
374
|
-
let cursor: string | undefined;
|
|
375
|
-
|
|
376
|
-
do {
|
|
377
|
-
const page = await client.listTools(cursor ? { cursor } : undefined, {
|
|
378
|
-
timeout: MCP_DISCOVERY_TIMEOUT_MS,
|
|
379
|
-
});
|
|
380
|
-
tools.push(...page.tools);
|
|
381
|
-
cursor = page.nextCursor;
|
|
382
|
-
} while (cursor);
|
|
383
|
-
|
|
384
|
-
return tools;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function createMcpTool(
|
|
388
|
-
serverName: string,
|
|
389
|
-
listedTool: McpListedTool,
|
|
390
|
-
client: McpClientLike,
|
|
391
|
-
): {
|
|
392
|
-
tool: Tool;
|
|
393
|
-
handler: ToolHandler;
|
|
394
|
-
} {
|
|
395
|
-
const toolName = `${serverName}__${listedTool.name}`;
|
|
396
|
-
const tool: Tool = {
|
|
397
|
-
name: toolName,
|
|
398
|
-
description: listedTool.description
|
|
399
|
-
? `[MCP ${serverName}] ${listedTool.description}`
|
|
400
|
-
: `[MCP ${serverName}] ${listedTool.name}`,
|
|
401
|
-
parameters: Type.Unsafe<Record<string, unknown>>(listedTool.inputSchema),
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
const handler: ToolHandler = async (args, _cwd, signal, onUpdate) => {
|
|
405
|
-
const validatedArgs = validateMcpToolArgs(tool, args) as Record<
|
|
406
|
-
string,
|
|
407
|
-
unknown
|
|
408
|
-
>;
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
const result = await client.callTool(
|
|
412
|
-
{
|
|
413
|
-
name: listedTool.name,
|
|
414
|
-
arguments: validatedArgs,
|
|
415
|
-
},
|
|
416
|
-
undefined,
|
|
417
|
-
{
|
|
418
|
-
...(signal ? { signal } : {}),
|
|
419
|
-
...(onUpdate
|
|
420
|
-
? {
|
|
421
|
-
onprogress: (progress: McpProgress) => {
|
|
422
|
-
emitMcpProgressUpdate(
|
|
423
|
-
serverName,
|
|
424
|
-
listedTool.name,
|
|
425
|
-
progress,
|
|
426
|
-
onUpdate,
|
|
427
|
-
);
|
|
428
|
-
},
|
|
429
|
-
}
|
|
430
|
-
: {}),
|
|
431
|
-
},
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
return convertMcpToolResult(result);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
return textResult(
|
|
437
|
-
`MCP server "${serverName}" tool "${listedTool.name}" failed: ${getErrorMessage(error)}`,
|
|
438
|
-
true,
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
return { tool, handler };
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function emitMcpProgressUpdate(
|
|
447
|
-
serverName: string,
|
|
448
|
-
toolName: string,
|
|
449
|
-
progress: McpProgress,
|
|
450
|
-
onUpdate: ToolUpdateCallback,
|
|
451
|
-
): void {
|
|
452
|
-
const status =
|
|
453
|
-
progress.total != null
|
|
454
|
-
? `${progress.progress}/${progress.total}`
|
|
455
|
-
: `${progress.progress}`;
|
|
456
|
-
const suffix = progress.message ? ` — ${progress.message}` : "";
|
|
457
|
-
onUpdate(
|
|
458
|
-
textResult(
|
|
459
|
-
`MCP ${serverName}/${toolName} progress: ${status}${suffix}`,
|
|
460
|
-
false,
|
|
461
|
-
),
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function convertMcpToolResult(result: McpCallToolResult): ToolExecResult {
|
|
466
|
-
if ("toolResult" in result) {
|
|
467
|
-
return textResult(formatUnknownValue(result.toolResult), false);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const content: ToolExecResult["content"] = [];
|
|
471
|
-
|
|
472
|
-
for (const item of result.content) {
|
|
473
|
-
switch (item.type) {
|
|
474
|
-
case "text":
|
|
475
|
-
content.push({ type: "text", text: item.text });
|
|
476
|
-
break;
|
|
477
|
-
case "image":
|
|
478
|
-
content.push({
|
|
479
|
-
type: "image",
|
|
480
|
-
data: item.data,
|
|
481
|
-
mimeType: item.mimeType,
|
|
482
|
-
});
|
|
483
|
-
break;
|
|
484
|
-
case "audio":
|
|
485
|
-
content.push({
|
|
486
|
-
type: "text",
|
|
487
|
-
text: `Audio output (${item.mimeType}) is not renderable in mini-coder tool results.`,
|
|
488
|
-
});
|
|
489
|
-
break;
|
|
490
|
-
case "resource":
|
|
491
|
-
content.push({
|
|
492
|
-
type: "text",
|
|
493
|
-
text: formatResourceContent(item.resource),
|
|
494
|
-
});
|
|
495
|
-
break;
|
|
496
|
-
case "resource_link":
|
|
497
|
-
content.push({
|
|
498
|
-
type: "text",
|
|
499
|
-
text: formatResourceLinkContent(item),
|
|
500
|
-
});
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (content.length === 0) {
|
|
506
|
-
if (result.structuredContent !== undefined) {
|
|
507
|
-
content.push({
|
|
508
|
-
type: "text",
|
|
509
|
-
text: formatUnknownValue(result.structuredContent),
|
|
510
|
-
});
|
|
511
|
-
} else {
|
|
512
|
-
content.push({
|
|
513
|
-
type: "text",
|
|
514
|
-
text: "MCP tool returned no content.",
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return {
|
|
520
|
-
content,
|
|
521
|
-
details:
|
|
522
|
-
result.structuredContent === undefined
|
|
523
|
-
? undefined
|
|
524
|
-
: { structuredContent: result.structuredContent },
|
|
525
|
-
isError: result.isError ?? false,
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function formatResourceContent(
|
|
530
|
-
resource:
|
|
531
|
-
| {
|
|
532
|
-
uri: string;
|
|
533
|
-
text: string;
|
|
534
|
-
mimeType?: string;
|
|
535
|
-
}
|
|
536
|
-
| {
|
|
537
|
-
uri: string;
|
|
538
|
-
blob: string;
|
|
539
|
-
mimeType?: string;
|
|
540
|
-
},
|
|
541
|
-
): string {
|
|
542
|
-
if ("text" in resource) {
|
|
543
|
-
return resource.mimeType
|
|
544
|
-
? `Resource ${resource.uri} (${resource.mimeType})\n\n${resource.text}`
|
|
545
|
-
: `Resource ${resource.uri}\n\n${resource.text}`;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return resource.mimeType
|
|
549
|
-
? `Resource blob ${resource.uri} (${resource.mimeType})`
|
|
550
|
-
: `Resource blob ${resource.uri}`;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function formatResourceLinkContent(item: {
|
|
554
|
-
uri: string;
|
|
555
|
-
name: string;
|
|
556
|
-
description?: string;
|
|
557
|
-
mimeType?: string;
|
|
558
|
-
size?: number;
|
|
559
|
-
title?: string;
|
|
560
|
-
}): string {
|
|
561
|
-
const lines = [
|
|
562
|
-
item.title ? `Resource link: ${item.title}` : `Resource link: ${item.name}`,
|
|
563
|
-
`URI: ${item.uri}`,
|
|
564
|
-
];
|
|
565
|
-
|
|
566
|
-
if (item.description) {
|
|
567
|
-
lines.push(`Description: ${item.description}`);
|
|
568
|
-
}
|
|
569
|
-
if (item.mimeType) {
|
|
570
|
-
lines.push(`MIME type: ${item.mimeType}`);
|
|
571
|
-
}
|
|
572
|
-
if (item.size != null) {
|
|
573
|
-
lines.push(`Size: ${item.size}`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return lines.join("\n");
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
function formatUnknownValue(value: unknown): string {
|
|
580
|
-
if (typeof value === "string") {
|
|
581
|
-
return value;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
try {
|
|
585
|
-
return JSON.stringify(value, null, 2);
|
|
586
|
-
} catch {
|
|
587
|
-
return String(value);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function validateMcpToolArgs<TParameters extends TSchema>(
|
|
592
|
-
tool: Tool<TParameters>,
|
|
593
|
-
args: Record<string, unknown>,
|
|
594
|
-
): Static<TParameters> {
|
|
595
|
-
return validateToolArguments(tool, {
|
|
596
|
-
type: "toolCall",
|
|
597
|
-
id: tool.name,
|
|
598
|
-
name: tool.name,
|
|
599
|
-
arguments: args,
|
|
600
|
-
} satisfies ToolCall) as Static<TParameters>;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
async function closeQuietly(transport: McpTransportLike): Promise<void> {
|
|
604
|
-
try {
|
|
605
|
-
await transport.close();
|
|
606
|
-
} catch {
|
|
607
|
-
// Best effort only.
|
|
608
|
-
}
|
|
609
|
-
}
|
package/src/paths.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Filesystem path normalization helpers.
|
|
3
|
-
*
|
|
4
|
-
* Provides a small shared policy for path identity across the app:
|
|
5
|
-
* when paths are used for comparison or persistence, we canonicalize them
|
|
6
|
-
* to an absolute, symlink-resolved spelling.
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { realpathSync } from "node:fs";
|
|
12
|
-
import { resolve } from "node:path";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Return the canonical absolute path for an existing filesystem entry.
|
|
16
|
-
*
|
|
17
|
-
* Canonicalization resolves `.`/`..` segments and follows symlinks,
|
|
18
|
-
* producing a stable spelling suitable for path equality checks and
|
|
19
|
-
* persistence keys.
|
|
20
|
-
*
|
|
21
|
-
* @param path - An existing filesystem path.
|
|
22
|
-
* @returns The canonical absolute path.
|
|
23
|
-
*/
|
|
24
|
-
export function canonicalizePath(path: string): string {
|
|
25
|
-
return realpathSync(resolve(path));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Check whether two paths refer to the same existing filesystem entry.
|
|
30
|
-
*
|
|
31
|
-
* @param a - The first path to compare.
|
|
32
|
-
* @param b - The second path to compare.
|
|
33
|
-
* @returns `true` when both paths resolve to the same canonical location.
|
|
34
|
-
*/
|
|
35
|
-
export function isSamePath(a: string, b: string): boolean {
|
|
36
|
-
return canonicalizePath(a) === canonicalizePath(b);
|
|
37
|
-
}
|