decorated-pi 0.5.4 → 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/.codegraph/daemon.pid +6 -0
- package/AGENTS.md +92 -0
- package/README.md +53 -64
- package/commands/dp-model.ts +23 -0
- package/commands/dp-settings.ts +23 -0
- package/commands/mcp-status.ts +62 -0
- package/commands/retry.ts +19 -0
- package/commands/usage.ts +544 -0
- package/hooks/compaction.ts +204 -0
- package/hooks/externalize.ts +70 -0
- package/hooks/image-vision.ts +132 -0
- package/hooks/inject-agents-md.ts +164 -0
- package/hooks/mcp.ts +340 -0
- package/hooks/normalize-codeblocks.ts +88 -0
- package/hooks/pi-tool-filter.ts +28 -0
- package/{extensions/safety → hooks/redact}/types.ts +1 -8
- package/hooks/redact.ts +104 -0
- package/{extensions → hooks}/rtk.ts +92 -115
- package/hooks/session-title.ts +54 -0
- package/hooks/skeleton.ts +212 -0
- package/hooks/smart-at.ts +318 -0
- package/{extensions/file-times.ts → hooks/track-mtime.ts} +40 -38
- package/{extensions → hooks}/wakatime.ts +120 -122
- package/index.ts +155 -1
- package/package.json +5 -4
- package/{extensions/settings.ts → settings.ts} +32 -0
- package/{extensions → tools}/lsp/client.ts +0 -25
- package/{extensions → tools}/lsp/format.ts +2 -99
- package/{extensions → tools}/lsp/index.ts +1 -1
- package/{extensions → tools}/lsp/servers.ts +1 -1
- package/{extensions → tools}/lsp/tools.ts +1 -66
- package/{extensions → tools}/lsp/types.ts +0 -11
- package/tools/mcp/builtin/codegraph.ts +45 -0
- package/tools/mcp/builtin/context7.ts +10 -0
- package/tools/mcp/builtin/exa.ts +10 -0
- package/tools/mcp/builtin/index.ts +19 -0
- package/tools/mcp/cache.ts +124 -0
- package/{extensions → tools}/mcp/client.ts +2 -2
- package/{extensions/mcp/builtin.ts → tools/mcp/config.ts} +21 -181
- package/tools/mcp/index.ts +44 -0
- package/tools/mcp/tool-definition.ts +112 -0
- package/{extensions/patch.ts → tools/patch/core.ts} +336 -65
- package/{extensions/io.ts → tools/patch/index.ts} +29 -205
- package/tsconfig.json +10 -1
- package/ui/mcp-status.ts +202 -0
- package/ui/model-picker.ts +162 -0
- package/ui/module-settings.ts +83 -0
- package/ui/usage.ts +396 -0
- package/extensions/index.ts +0 -154
- package/extensions/io-tool-output.ts +0 -33
- package/extensions/mcp/index.ts +0 -435
- package/extensions/model-integration.ts +0 -531
- package/extensions/providers/ark-coding.ts +0 -75
- package/extensions/providers/index.ts +0 -9
- package/extensions/providers/ollama-cloud.ts +0 -101
- package/extensions/providers/qianfan-coding.ts +0 -71
- package/extensions/safety/index.ts +0 -102
- package/extensions/session-title.ts +0 -40
- package/extensions/slash.ts +0 -458
- package/extensions/smart-at.ts +0 -481
- package/extensions/subdir-agents.ts +0 -151
- /package/{extensions/safety → hooks/redact}/detect.ts +0 -0
- /package/{extensions/safety → hooks/redact}/entropy.ts +0 -0
- /package/{extensions/safety → hooks/redact}/patterns.ts +0 -0
- /package/{extensions → tools}/lsp/env.ts +0 -0
- /package/{extensions → tools}/lsp/manager.ts +0 -0
- /package/{extensions → tools}/lsp/prompt.ts +0 -0
- /package/{extensions → tools}/lsp/protocol.ts +0 -0
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP server configuration —
|
|
2
|
+
* MCP server configuration — type, discovery, resolution, toggle.
|
|
3
|
+
*
|
|
4
|
+
* Sources (in priority order): builtin → global → project. The first
|
|
5
|
+
* hit sets defaults; later sources can override specific fields.
|
|
6
|
+
*
|
|
7
|
+
* Persistence locations:
|
|
8
|
+
* - global: `~/.pi/agent/decorated-pi.json` (under `mcpServers`)
|
|
9
|
+
* - project: `<cwd>/.pi/agent/mcp.json` (under `mcpServers`)
|
|
3
10
|
*/
|
|
4
11
|
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
12
|
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
7
14
|
import { spawnSync } from "node:child_process";
|
|
8
|
-
import { loadConfig } from "
|
|
9
|
-
import type { DependencyStatus } from "
|
|
15
|
+
import { loadConfig } from "../../settings.js";
|
|
16
|
+
import type { DependencyStatus } from "../../hooks/skeleton.js";
|
|
17
|
+
import { BUILTIN_MCP_SERVERS, codegraphEnabled } from "./builtin/index.js";
|
|
18
|
+
|
|
19
|
+
export { BUILTIN_MCP_SERVERS } from "./builtin/index.js";
|
|
10
20
|
|
|
11
21
|
export interface McpServerConfig {
|
|
12
22
|
name: string;
|
|
@@ -19,22 +29,6 @@ export interface McpServerConfig {
|
|
|
19
29
|
source: "builtin" | "global" | "project";
|
|
20
30
|
}
|
|
21
31
|
|
|
22
|
-
/** Builtin servers — zero-config, always available unless overridden. */
|
|
23
|
-
export const BUILTIN_MCP_SERVERS: Omit<McpServerConfig, "source">[] = [
|
|
24
|
-
{
|
|
25
|
-
name: "context7",
|
|
26
|
-
url: "https://mcp.context7.com/mcp",
|
|
27
|
-
enabled: true,
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: "exa",
|
|
31
|
-
url: "https://mcp.exa.ai/mcp",
|
|
32
|
-
enabled: true,
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// ── Project-level config discovery ─────────────────────────────────────────
|
|
37
|
-
|
|
38
32
|
function readMcpJson(filePath: string): Record<string, { url?: string; command?: string; args?: string[]; env?: Record<string, string>; enabled?: boolean; description?: string }> | null {
|
|
39
33
|
try {
|
|
40
34
|
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
@@ -104,9 +98,14 @@ export function isSseUrl(url: string): boolean {
|
|
|
104
98
|
export function resolveMcpConfigs(cwd: string): McpServerConfig[] {
|
|
105
99
|
const byName = new Map<string, McpServerConfig>();
|
|
106
100
|
|
|
107
|
-
// Builtin (lowest priority)
|
|
101
|
+
// Builtin (lowest priority). The codegraph entry's `enabled` flag is
|
|
102
|
+
// computed at resolve time from the dp-settings module toggle.
|
|
108
103
|
for (const s of BUILTIN_MCP_SERVERS) {
|
|
109
|
-
|
|
104
|
+
if (s.name === "codegraph") {
|
|
105
|
+
byName.set(s.name, { ...s, enabled: codegraphEnabled(), source: "builtin" });
|
|
106
|
+
} else {
|
|
107
|
+
byName.set(s.name, { ...s, source: "builtin" });
|
|
108
|
+
}
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
// Global — preserve url/command/description from builtin if not overridden
|
|
@@ -176,100 +175,6 @@ function commandExists(command: string): boolean {
|
|
|
176
175
|
return result.status === 0;
|
|
177
176
|
}
|
|
178
177
|
|
|
179
|
-
/** Write auto-generated description back to project mcp.json. */
|
|
180
|
-
export function saveProjectMcpDescription(cwd: string, name: string, description: string): void {
|
|
181
|
-
const filePath = path.join(cwd, ".pi/agent/mcp.json");
|
|
182
|
-
const servers = readMcpJson(filePath);
|
|
183
|
-
if (!servers || !servers[name]) return;
|
|
184
|
-
servers[name] = { ...servers[name], description };
|
|
185
|
-
const dir = path.dirname(filePath);
|
|
186
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
187
|
-
fs.writeFileSync(filePath, JSON.stringify({ mcpServers: servers }, null, 2) + "\n", "utf-8");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ── Metadata cache (tool descriptions + schemas) ──────────────────────────
|
|
191
|
-
|
|
192
|
-
export interface McpToolCache {
|
|
193
|
-
name: string;
|
|
194
|
-
description?: string;
|
|
195
|
-
inputSchema: Record<string, unknown>;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface McpServerCache {
|
|
199
|
-
description?: string;
|
|
200
|
-
tools: McpToolCache[];
|
|
201
|
-
cachedAt: number;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface McpCache {
|
|
205
|
-
servers: Record<string, McpServerCache>;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function globalCachePath(): string {
|
|
209
|
-
return path.join(os.homedir(), ".pi/agent/mcp-cache.json");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function projectCachePath(cwd: string): string {
|
|
213
|
-
return path.join(cwd, ".pi/agent/mcp-cache.json");
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function readCacheFile(p: string): McpCache | null {
|
|
217
|
-
try {
|
|
218
|
-
if (!fs.existsSync(p)) return null;
|
|
219
|
-
return JSON.parse(fs.readFileSync(p, "utf-8")) as McpCache;
|
|
220
|
-
} catch {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function writeCacheFile(p: string, cache: McpCache): void {
|
|
226
|
-
const dir = path.dirname(p);
|
|
227
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
228
|
-
const tmp = `${p}.tmp`;
|
|
229
|
-
fs.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
230
|
-
fs.renameSync(tmp, p);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** Load merged cache: global + project. */
|
|
234
|
-
export function loadMcpCache(cwd?: string): McpCache | null {
|
|
235
|
-
const merged: McpCache = { servers: {} };
|
|
236
|
-
|
|
237
|
-
const globalCache = readCacheFile(globalCachePath());
|
|
238
|
-
if (globalCache) {
|
|
239
|
-
merged.servers = { ...merged.servers, ...globalCache.servers };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (cwd) {
|
|
243
|
-
const projectCache = readCacheFile(projectCachePath(cwd));
|
|
244
|
-
if (projectCache) {
|
|
245
|
-
merged.servers = { ...merged.servers, ...projectCache.servers };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return merged;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** Save cache to global or project scope. */
|
|
253
|
-
export function saveMcpCache(cache: McpCache, scope: "global" | "project", cwd?: string): void {
|
|
254
|
-
const p = scope === "project" && cwd ? projectCachePath(cwd) : globalCachePath();
|
|
255
|
-
writeCacheFile(p, cache);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/** Update a single server's entry in the appropriate cache. */
|
|
259
|
-
export function updateServerCache(
|
|
260
|
-
serverName: string,
|
|
261
|
-
entry: McpServerCache,
|
|
262
|
-
scope: "global" | "project",
|
|
263
|
-
cwd?: string,
|
|
264
|
-
): void {
|
|
265
|
-
const p = scope === "project" && cwd ? projectCachePath(cwd) : globalCachePath();
|
|
266
|
-
const existing = readCacheFile(p) || { servers: {} };
|
|
267
|
-
existing.servers[serverName] = entry;
|
|
268
|
-
writeCacheFile(p, existing);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ── Enable / Disable helpers ────────────────────────────────────────────
|
|
272
|
-
|
|
273
178
|
function readMcpJsonSafe(filePath: string): Record<string, any> | null {
|
|
274
179
|
try {
|
|
275
180
|
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
@@ -296,7 +201,6 @@ export function toggleMcpServerEnabled(
|
|
|
296
201
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
297
202
|
fs.writeFileSync(filePath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
298
203
|
} else {
|
|
299
|
-
const { loadConfig } = require("../settings.js");
|
|
300
204
|
const config = loadConfig();
|
|
301
205
|
config.mcpServers = config.mcpServers || {};
|
|
302
206
|
config.mcpServers[serverName] = { ...(config.mcpServers[serverName] || {}), enabled };
|
|
@@ -310,67 +214,3 @@ export function toggleMcpServerEnabled(
|
|
|
310
214
|
return false;
|
|
311
215
|
}
|
|
312
216
|
}
|
|
313
|
-
|
|
314
|
-
function cleanupOneCache(p: string, names: Set<string>): void {
|
|
315
|
-
const cache = readCacheFile(p);
|
|
316
|
-
if (!cache) return;
|
|
317
|
-
let changed = false;
|
|
318
|
-
for (const name of Object.keys(cache.servers)) {
|
|
319
|
-
if (!names.has(name)) {
|
|
320
|
-
delete cache.servers[name];
|
|
321
|
-
changed = true;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (changed) writeCacheFile(p, cache);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export function cleanupStaleCache(configs: McpServerConfig[], cwd?: string): void {
|
|
328
|
-
const names = new Set(configs.map(c => c.name));
|
|
329
|
-
cleanupOneCache(globalCachePath(), names);
|
|
330
|
-
if (cwd) {
|
|
331
|
-
const projectCache = projectCachePath(cwd);
|
|
332
|
-
const projectMcpJson = path.join(cwd, ".pi/agent/mcp.json");
|
|
333
|
-
// If project mcp.json doesn't exist, remove project cache entirely
|
|
334
|
-
if (!fs.existsSync(projectMcpJson)) {
|
|
335
|
-
if (fs.existsSync(projectCache)) {
|
|
336
|
-
fs.unlinkSync(projectCache);
|
|
337
|
-
}
|
|
338
|
-
} else {
|
|
339
|
-
cleanupOneCache(projectCache, names);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// ── Large result externalization ────────────────────────────────────────
|
|
345
|
-
|
|
346
|
-
export const EXTERNALIZE_THRESHOLD = 50_000; // 50KB
|
|
347
|
-
export const EXTERNALIZE_PREVIEW_SIZE = 2_000; // 2KB preview
|
|
348
|
-
import { writeOutputToTemp } from "../io-tool-output.js";
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* If content exceeds threshold, save to temp file and return preview + path.
|
|
352
|
-
* Otherwise return original content unchanged.
|
|
353
|
-
* Falls back to returning full text if file write fails.
|
|
354
|
-
*/
|
|
355
|
-
export function maybeExternalizeMcpResult(
|
|
356
|
-
text: string,
|
|
357
|
-
toolName: string,
|
|
358
|
-
toolCallId: string,
|
|
359
|
-
): { content: Array<{ type: "text"; text: string }>; details: Record<string, unknown> } {
|
|
360
|
-
if (text.length <= EXTERNALIZE_THRESHOLD) {
|
|
361
|
-
return { content: [{ type: "text", text }], details: {} };
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const filePath = writeOutputToTemp(toolName, toolCallId, text);
|
|
365
|
-
if (!filePath) {
|
|
366
|
-
return { content: [{ type: "text", text }], details: {} };
|
|
367
|
-
}
|
|
368
|
-
const preview = text.slice(0, EXTERNALIZE_PREVIEW_SIZE);
|
|
369
|
-
return {
|
|
370
|
-
content: [{ type: "text", text: `${preview}\n\n[Truncated: ${text.length.toLocaleString()} chars total. Full output saved to: ${filePath}]` }],
|
|
371
|
-
details: {
|
|
372
|
-
fullOutputPath: filePath,
|
|
373
|
-
truncation: { truncated: true, outputChars: EXTERNALIZE_PREVIEW_SIZE, totalChars: text.length, maxBytes: EXTERNALIZE_THRESHOLD },
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp tool registration — reads cache, registers tools dynamically.
|
|
3
|
+
* Connection lifecycle is in hooks/mcp.ts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { loadMcpCache, type McpCache } from "./cache.js";
|
|
8
|
+
import { resolveMcpConfigs, type McpServerConfig } from "./config.js";
|
|
9
|
+
import { buildMcpTool, __mcpToolDefinitionTest } from "./tool-definition.js";
|
|
10
|
+
import { getActiveMcpConnections } from "../../hooks/mcp.js";
|
|
11
|
+
|
|
12
|
+
export function registerMcpToolsFromCache(pi: ExtensionAPI, cache: McpCache, configs: McpServerConfig[]): void {
|
|
13
|
+
for (const config of configs) {
|
|
14
|
+
if (!config.enabled) continue;
|
|
15
|
+
const entry = cache.servers[config.name];
|
|
16
|
+
if (!entry || entry.tools.length === 0) continue;
|
|
17
|
+
for (const t of entry.tools) {
|
|
18
|
+
try {
|
|
19
|
+
pi.registerTool(buildMcpTool(config, t, (name) => getActiveMcpConnections().find(c => c.serverName === name)) as any);
|
|
20
|
+
} catch {
|
|
21
|
+
// pi-core may throw on duplicate name (e.g. on /reload when the
|
|
22
|
+
// same tool is re-registered). The previous registration is
|
|
23
|
+
// still in effect, so silently ignore.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerMcpTools(pi: ExtensionAPI, cwd: string): void {
|
|
30
|
+
const cache = loadMcpCache(cwd);
|
|
31
|
+
if (!cache) return;
|
|
32
|
+
const configs = resolveMcpConfigs(cwd).filter(s => s.enabled);
|
|
33
|
+
registerMcpToolsFromCache(pi, cache, configs);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Re-exports
|
|
37
|
+
export { McpConnection } from "./client.js";
|
|
38
|
+
export { buildMcpTool } from "./tool-definition.js";
|
|
39
|
+
export { getActiveMcpConnections, getCachedMcpConfigs, getMcpStatus, updateConfigEnabled, refreshServerCache } from "../../hooks/mcp.js";
|
|
40
|
+
export type { McpServerConfig } from "./config.js";
|
|
41
|
+
export type { McpCache } from "./cache.js";
|
|
42
|
+
|
|
43
|
+
// Test exports
|
|
44
|
+
export const __mcpIndexTest = __mcpToolDefinitionTest;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure MCP tool-definition helpers.
|
|
3
|
+
*
|
|
4
|
+
* No hook/module state lives here. Callers provide `findConnection`
|
|
5
|
+
* so both hooks/mcp.ts and tools/mcp/index.ts can share the same tool
|
|
6
|
+
* factory without importing each other.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
11
|
+
import type { McpConnection } from "./client.js";
|
|
12
|
+
import type { McpServerConfig } from "./config.js";
|
|
13
|
+
|
|
14
|
+
const MCP_RESULT_FOLD_LINES = 45;
|
|
15
|
+
|
|
16
|
+
function trimTrailingEmptyLines(lines: string[]): string[] {
|
|
17
|
+
let end = lines.length;
|
|
18
|
+
while (end > 0 && lines[end - 1] === "") end -= 1;
|
|
19
|
+
return lines.slice(0, end);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function collapseMcpText(text: string, maxLines = MCP_RESULT_FOLD_LINES) {
|
|
23
|
+
const lines = trimTrailingEmptyLines(text.split("\n"));
|
|
24
|
+
return {
|
|
25
|
+
totalLines: lines.length,
|
|
26
|
+
displayLines: lines.slice(0, maxLines),
|
|
27
|
+
remainingLines: Math.max(0, lines.length - maxLines),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getTextContent(result: { content?: Array<{ type: string; text?: string }> }): string {
|
|
32
|
+
return (result.content ?? [])
|
|
33
|
+
.filter((c): c is { type: "text"; text?: string } => c.type === "text")
|
|
34
|
+
.map((c) => c.text ?? "")
|
|
35
|
+
.join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatMcpResultText(text: string, expanded: boolean, theme: any): string {
|
|
39
|
+
const { totalLines, displayLines, remainingLines } = collapseMcpText(text, expanded ? Number.MAX_SAFE_INTEGER : MCP_RESULT_FOLD_LINES);
|
|
40
|
+
const lastLine = displayLines[displayLines.length - 1] || "";
|
|
41
|
+
let outputLines = [...displayLines];
|
|
42
|
+
let truncationMsg = "";
|
|
43
|
+
if (lastLine.startsWith("[Truncated: ") && lastLine.endsWith("]")) {
|
|
44
|
+
truncationMsg = lastLine;
|
|
45
|
+
outputLines = outputLines.slice(0, -1);
|
|
46
|
+
}
|
|
47
|
+
const outputText = outputLines.join("\n");
|
|
48
|
+
let rendered = outputText ? theme.fg("toolOutput", outputText) : "";
|
|
49
|
+
if (truncationMsg) rendered += (rendered ? "\n" : "") + theme.fg("warning", truncationMsg);
|
|
50
|
+
if (!expanded && remainingLines > 0) {
|
|
51
|
+
rendered += `${theme.fg("muted", `\n... (${remainingLines} more lines, ${totalLines} total,`)} ${keyHint("app.tools.expand", "to expand")})`;
|
|
52
|
+
}
|
|
53
|
+
return rendered;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderMcpResult(result: any, options: { expanded: boolean }, theme: any, context: any) {
|
|
57
|
+
const component = context.lastComponent ?? new Text("", 0, 0);
|
|
58
|
+
component.setText(formatMcpResultText(getTextContent(result), options.expanded, theme));
|
|
59
|
+
return component;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function makeToolName(serverName: string, toolName: string): string {
|
|
63
|
+
return `${serverName}_${toolName}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeToolLabel(serverName: string, toolName: string, desc?: string): string {
|
|
67
|
+
return `MCP ${serverName}: ${toolName}${desc ? ` (${desc.slice(0, 20)})` : ""}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildMcpTool(
|
|
71
|
+
config: McpServerConfig,
|
|
72
|
+
toolEntry: { name: string; description?: string; inputSchema?: Record<string, unknown> },
|
|
73
|
+
findConnection: (serverName: string) => McpConnection | undefined,
|
|
74
|
+
): {
|
|
75
|
+
name: string;
|
|
76
|
+
label: string;
|
|
77
|
+
description: string;
|
|
78
|
+
promptSnippet: string;
|
|
79
|
+
renderResult: (result: any, options: { expanded: boolean }, theme: any, context: any) => any;
|
|
80
|
+
parameters: Record<string, unknown> | undefined;
|
|
81
|
+
execute: (id: string, params: any, signal: any, update: any, ctx: any) => Promise<any>;
|
|
82
|
+
} {
|
|
83
|
+
const toolName = makeToolName(config.name, toolEntry.name);
|
|
84
|
+
const desc = toolEntry.description || `${toolEntry.name} (MCP tool)`;
|
|
85
|
+
return {
|
|
86
|
+
name: toolName,
|
|
87
|
+
label: makeToolLabel(config.name, toolEntry.name, toolEntry.description),
|
|
88
|
+
description: desc,
|
|
89
|
+
promptSnippet: desc || `MCP tool ${config.name}/${toolEntry.name}`,
|
|
90
|
+
renderResult: renderMcpResult,
|
|
91
|
+
parameters: toolEntry.inputSchema,
|
|
92
|
+
execute: async (_id: string, params: any, _signal: any, _update: any, _ctx: any) => {
|
|
93
|
+
const conn = findConnection(config.name);
|
|
94
|
+
if (!conn) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: `MCP server "${config.name}" is not connected. Use /reload to retry.` }],
|
|
97
|
+
isError: false,
|
|
98
|
+
details: {},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const text = await conn.callTool(toolEntry.name, params ?? {});
|
|
103
|
+
return { content: [{ type: "text", text }], isError: false, details: {} };
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
return { content: [{ type: "text", text: `MCP tool "${toolName}" error: ${msg}` }], isError: true, details: {} };
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const __mcpToolDefinitionTest = { collapseMcpText, buildMcpTool };
|