pi-codemcp 1.0.0 → 1.1.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 +144 -13
- package/extensions/index.ts +4 -1
- package/package.json +7 -7
- package/sidecar/catalog_cache.py +2 -1
- package/sidecar/cli.py +402 -0
- package/sidecar/executor.py +349 -36
- package/sidecar/gateway.py +499 -60
- package/sidecar/models.py +41 -4
- package/sidecar/pyproject.toml +33 -40
- package/sidecar/runtime_paths.py +71 -0
- package/sidecar/settings.py +7 -3
- package/sidecar/stats.py +424 -0
- package/sidecar/tool_catalog.py +311 -46
- package/sidecar/uv.lock +99 -244
- package/src/chains.ts +1 -2
- package/src/execution-rendering.ts +7 -5
- package/src/mcp-client.ts +5 -1
- package/src/modal.ts +258 -14
- package/src/output.ts +27 -24
- package/src/prompts.ts +24 -0
- package/src/settings.ts +18 -17
- package/src/tools.ts +270 -36
package/src/tools.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { type ExtensionAPI, highlightCode, keyHint } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ChainScope,
|
|
6
|
+
nativeChainToolName,
|
|
7
|
+
type SavedChainManager,
|
|
8
|
+
type SavedChainView,
|
|
9
|
+
} from "./chains.js";
|
|
5
10
|
import {
|
|
6
11
|
getTextContent,
|
|
7
12
|
previewExecutionValue,
|
|
@@ -9,34 +14,72 @@ import {
|
|
|
9
14
|
} from "./execution-rendering.js";
|
|
10
15
|
import type { CodeMcpLifecycle } from "./lifecycle.js";
|
|
11
16
|
import { type CodeMcpOutputDetails, formatCodeMcpOutput } from "./output.js";
|
|
17
|
+
import {
|
|
18
|
+
EXECUTE_PROMPT_GUIDELINES,
|
|
19
|
+
INSPECT_PROMPT_GUIDELINES,
|
|
20
|
+
MANAGE_CHAIN_PROMPT_GUIDELINES,
|
|
21
|
+
SAVE_CHAIN_PROMPT_GUIDELINES,
|
|
22
|
+
SEARCH_PROMPT_GUIDELINES,
|
|
23
|
+
} from "./prompts.js";
|
|
12
24
|
|
|
13
25
|
interface SearchRenderDetails extends CodeMcpOutputDetails {
|
|
14
26
|
matchCount: number;
|
|
15
27
|
totalToolCount: number;
|
|
16
28
|
serverCount: number;
|
|
29
|
+
hasMore: boolean;
|
|
30
|
+
nextCursor?: number;
|
|
31
|
+
detail: string;
|
|
17
32
|
preview: string[];
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
const SearchParameters = Type.Object({
|
|
21
|
-
query: Type.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
query: Type.Optional(
|
|
37
|
+
Type.String({
|
|
38
|
+
minLength: 1,
|
|
39
|
+
description: "Capability words for search mode; omit for inventory mode",
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
mode: Type.Optional(
|
|
43
|
+
Type.String({
|
|
44
|
+
enum: ["search", "inventory"],
|
|
45
|
+
description: "Rank by capability (default) or page through compact inventory",
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
detail: Type.Optional(
|
|
49
|
+
Type.String({
|
|
50
|
+
enum: ["names", "signatures", "full"],
|
|
51
|
+
description: "Disclosure level (default signatures); use inspect for selected full stubs",
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
25
54
|
limit: Type.Optional(
|
|
26
55
|
Type.Integer({
|
|
27
56
|
minimum: 1,
|
|
28
57
|
maximum: 20,
|
|
29
|
-
description: "Maximum
|
|
58
|
+
description: "Maximum matches per page (default 5)",
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
cursor: Type.Optional(
|
|
62
|
+
Type.Integer({
|
|
63
|
+
minimum: 0,
|
|
64
|
+
description: "Pagination cursor returned by a previous search",
|
|
30
65
|
}),
|
|
31
66
|
),
|
|
32
67
|
server: Type.Optional(
|
|
33
68
|
Type.String({
|
|
34
69
|
minLength: 1,
|
|
35
|
-
description: "
|
|
70
|
+
description: "Exact configured server name, or chains for saved chains",
|
|
36
71
|
}),
|
|
37
72
|
),
|
|
38
73
|
});
|
|
39
74
|
|
|
75
|
+
const InspectParameters = Type.Object({
|
|
76
|
+
calls: Type.Array(Type.String({ minLength: 1 }), {
|
|
77
|
+
minItems: 1,
|
|
78
|
+
maxItems: 20,
|
|
79
|
+
description: "Exact call identifiers returned by search, such as grafana.query_prometheus",
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
40
83
|
const SaveChainParameters = Type.Object({
|
|
41
84
|
scope: Type.Optional(
|
|
42
85
|
Type.String({
|
|
@@ -68,6 +111,32 @@ const SaveChainParameters = Type.Object({
|
|
|
68
111
|
}),
|
|
69
112
|
});
|
|
70
113
|
|
|
114
|
+
const ManageChainsParameters = Type.Object({
|
|
115
|
+
action: Type.String({
|
|
116
|
+
enum: ["list", "enable", "disable", "revalidate", "delete"],
|
|
117
|
+
description: "Chain management action",
|
|
118
|
+
}),
|
|
119
|
+
name: Type.Optional(
|
|
120
|
+
Type.String({
|
|
121
|
+
minLength: 1,
|
|
122
|
+
maxLength: 64,
|
|
123
|
+
pattern: "^[a-z][a-z0-9_]{0,63}$",
|
|
124
|
+
description: "Saved-chain name; required for mutations",
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
scope: Type.Optional(
|
|
128
|
+
Type.String({
|
|
129
|
+
enum: ["project", "global"],
|
|
130
|
+
description: "Saved-chain scope; required for mutations",
|
|
131
|
+
}),
|
|
132
|
+
),
|
|
133
|
+
confirmedByUser: Type.Optional(
|
|
134
|
+
Type.Boolean({
|
|
135
|
+
description: "Must be true for enable, disable, revalidate, or delete",
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
});
|
|
139
|
+
|
|
71
140
|
const ExecuteParameters = Type.Object({
|
|
72
141
|
code: Type.String({
|
|
73
142
|
minLength: 1,
|
|
@@ -85,11 +154,9 @@ export function registerCodeMcpTools(
|
|
|
85
154
|
name: "codemcp_search",
|
|
86
155
|
label: "MCP Search",
|
|
87
156
|
description:
|
|
88
|
-
"Search configured
|
|
89
|
-
promptSnippet: "
|
|
90
|
-
promptGuidelines: [
|
|
91
|
-
"Use codemcp_search before codemcp_execute; every upstream or saved-chain match includes the complete typed SDK stub needed to write the execution.",
|
|
92
|
-
],
|
|
157
|
+
"Search configured MCP capabilities or page through compact inventory. Default signature search includes exact stubs for up to three top matches plus compact alternatives; codemcp_inspect loads other selected stubs. Returns ranking evidence, pagination, scope, and execution limits; invalid server names fail with suggestions.",
|
|
158
|
+
promptSnippet: "Discover compact MCP capabilities or inventory",
|
|
159
|
+
promptGuidelines: [...SEARCH_PROMPT_GUIDELINES],
|
|
93
160
|
parameters: SearchParameters,
|
|
94
161
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
95
162
|
onUpdate?.({
|
|
@@ -99,8 +166,11 @@ export function registerCodeMcpTools(
|
|
|
99
166
|
const result = await lifecycle.request(
|
|
100
167
|
"search",
|
|
101
168
|
{
|
|
102
|
-
query: params.query,
|
|
169
|
+
...(params.query === undefined ? {} : { query: params.query }),
|
|
170
|
+
mode: params.mode ?? "search",
|
|
171
|
+
detail: params.detail ?? "signatures",
|
|
103
172
|
limit: params.limit ?? 5,
|
|
173
|
+
cursor: params.cursor ?? 0,
|
|
104
174
|
...(params.server === undefined ? {} : { server: params.server }),
|
|
105
175
|
},
|
|
106
176
|
signal,
|
|
@@ -123,13 +193,17 @@ export function registerCodeMcpTools(
|
|
|
123
193
|
matchCount: results.length,
|
|
124
194
|
totalToolCount: Number(result.total_tool_count ?? 0),
|
|
125
195
|
serverCount: servers.length,
|
|
196
|
+
hasMore: result.has_more === true,
|
|
197
|
+
nextCursor: typeof result.next_cursor === "number" ? result.next_cursor : undefined,
|
|
198
|
+
detail: typeof result.detail === "string" ? result.detail : "signatures",
|
|
126
199
|
preview,
|
|
127
200
|
},
|
|
128
201
|
};
|
|
129
202
|
},
|
|
130
203
|
renderCall(args, theme) {
|
|
204
|
+
const subject = args.mode === "inventory" ? "inventory" : `"${args.query ?? ""}"`;
|
|
131
205
|
return new Text(
|
|
132
|
-
`${theme.fg("toolTitle", theme.bold("MCP Search "))}${theme.fg("accent",
|
|
206
|
+
`${theme.fg("toolTitle", theme.bold("MCP Search "))}${theme.fg("accent", subject)}`,
|
|
133
207
|
0,
|
|
134
208
|
0,
|
|
135
209
|
);
|
|
@@ -140,27 +214,75 @@ export function registerCodeMcpTools(
|
|
|
140
214
|
const details = result.details as SearchRenderDetails | undefined;
|
|
141
215
|
let text = theme.fg(
|
|
142
216
|
"success",
|
|
143
|
-
`${details?.matchCount ?? 0} matches · ${details?.totalToolCount ?? 0} tools · ${details?.serverCount ?? 0} servers`,
|
|
217
|
+
`${details?.matchCount ?? 0} matches · ${details?.detail ?? "signatures"} · ${details?.totalToolCount ?? 0} tools · ${details?.serverCount ?? 0} servers`,
|
|
144
218
|
);
|
|
145
219
|
for (const name of details?.preview ?? []) {
|
|
146
220
|
text += `\n${theme.fg("dim", ` ${name}`)}`;
|
|
147
221
|
}
|
|
222
|
+
if (details?.hasMore) {
|
|
223
|
+
text += `\n${theme.fg("muted", ` more at cursor ${details.nextCursor ?? "?"}`)}`;
|
|
224
|
+
}
|
|
148
225
|
text += `\n${theme.fg("muted", keyHint("app.tools.expand", "full results"))}`;
|
|
149
226
|
return new Text(text, 0, 0);
|
|
150
227
|
},
|
|
151
228
|
});
|
|
152
229
|
|
|
230
|
+
pi.registerTool({
|
|
231
|
+
name: "codemcp_inspect",
|
|
232
|
+
label: "MCP Inspect",
|
|
233
|
+
description:
|
|
234
|
+
"Return the exact typed SDK stubs for selected call identifiers from codemcp_search. The response deduplicates the shared JsonValue/type prelude.",
|
|
235
|
+
promptSnippet: "Load exact typed contracts for selected MCP calls",
|
|
236
|
+
promptGuidelines: [...INSPECT_PROMPT_GUIDELINES],
|
|
237
|
+
parameters: InspectParameters,
|
|
238
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
239
|
+
onUpdate?.({
|
|
240
|
+
content: [{ type: "text", text: "Inspecting MCP tool contracts..." }],
|
|
241
|
+
details: undefined,
|
|
242
|
+
});
|
|
243
|
+
const result = await lifecycle.request("inspect", { calls: params.calls }, signal);
|
|
244
|
+
const output = formatCodeMcpOutput(result, outputLimits(lifecycle));
|
|
245
|
+
const results = Array.isArray(result.results) ? result.results : [];
|
|
246
|
+
return {
|
|
247
|
+
content: [{ type: "text", text: output.text }],
|
|
248
|
+
details: {
|
|
249
|
+
...output.details,
|
|
250
|
+
matchCount: results.length,
|
|
251
|
+
preview: results
|
|
252
|
+
.slice(0, 3)
|
|
253
|
+
.flatMap((item) =>
|
|
254
|
+
isRecord(item) && typeof item.call === "string" ? [item.call] : [],
|
|
255
|
+
),
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
renderCall(args, theme) {
|
|
260
|
+
return new Text(
|
|
261
|
+
`${theme.fg("toolTitle", theme.bold("MCP Inspect "))}${theme.fg("accent", `${args.calls.length} calls`)}`,
|
|
262
|
+
0,
|
|
263
|
+
0,
|
|
264
|
+
);
|
|
265
|
+
},
|
|
266
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
267
|
+
if (isPartial) return new Text(theme.fg("warning", "Loading exact contracts..."), 0, 0);
|
|
268
|
+
if (expanded) return renderExpandedJson(result.content);
|
|
269
|
+
const details = result.details as
|
|
270
|
+
| (CodeMcpOutputDetails & { matchCount: number; preview: string[] })
|
|
271
|
+
| undefined;
|
|
272
|
+
let text = theme.fg("success", `${details?.matchCount ?? 0} exact contracts`);
|
|
273
|
+
for (const call of details?.preview ?? []) text += `\n${theme.fg("dim", ` ${call}`)}`;
|
|
274
|
+
text += `\n${theme.fg("muted", keyHint("app.tools.expand", "full stubs"))}`;
|
|
275
|
+
return new Text(text, 0, 0);
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
153
279
|
pi.registerTool({
|
|
154
280
|
name: "codemcp_execute",
|
|
155
281
|
label: "MCP Execute",
|
|
156
282
|
description:
|
|
157
|
-
"Type-check and execute one sandboxed Python MCP call graph.
|
|
158
|
-
promptSnippet: "Run a typed
|
|
159
|
-
promptGuidelines: [
|
|
160
|
-
"Use codemcp_execute if you know tool schemas; call the returned server.method facade and use top-level return for the compact final value.",
|
|
161
|
-
"It is always better to execute multiple MCP calls in one codemcp_execute call rather than multiple single-call invocations.",
|
|
162
|
-
"You can compose upstream SDK calls and saved chains.* calls, running independent work with asyncio.gather or dependent work sequentially.",
|
|
163
|
-
],
|
|
283
|
+
"Type-check and execute one bounded sandboxed Python MCP call graph. Use it when code can deterministically process intermediate results; preserve a model turn for semantic decisions or approvals. The sandbox has no host filesystem, environment, network, or subprocess access. Oversized results fail with bounded shape, size, and sample diagnostics.",
|
|
284
|
+
promptSnippet: "Run a bounded typed MCP workflow and return a compact result",
|
|
285
|
+
promptGuidelines: [...EXECUTE_PROMPT_GUIDELINES],
|
|
164
286
|
parameters: ExecuteParameters,
|
|
165
287
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
166
288
|
onUpdate?.({
|
|
@@ -168,8 +290,17 @@ export function registerCodeMcpTools(
|
|
|
168
290
|
details: undefined,
|
|
169
291
|
});
|
|
170
292
|
const result = await lifecycle.request("execute", { code: params.code }, signal);
|
|
171
|
-
const output = formatCodeMcpOutput(result, outputLimits(lifecycle));
|
|
172
293
|
const ok = result.ok === true;
|
|
294
|
+
const modelValue = ok
|
|
295
|
+
? result.result
|
|
296
|
+
: {
|
|
297
|
+
failure_stage: result.failure_stage,
|
|
298
|
+
error: result.error,
|
|
299
|
+
shape: result.shape,
|
|
300
|
+
calls_made: result.calls_made,
|
|
301
|
+
chain_calls: result.chain_calls,
|
|
302
|
+
};
|
|
303
|
+
const output = formatCodeMcpOutput(modelValue, outputLimits(lifecycle));
|
|
173
304
|
return {
|
|
174
305
|
content: [{ type: "text", text: output.text }],
|
|
175
306
|
details: {
|
|
@@ -178,6 +309,7 @@ export function registerCodeMcpTools(
|
|
|
178
309
|
failureStage: typeof result.failure_stage === "string" ? result.failure_stage : undefined,
|
|
179
310
|
callsMade: Number(result.calls_made ?? 0),
|
|
180
311
|
chainCalls: Number(result.chain_calls ?? 0),
|
|
312
|
+
...(isRecord(result.timings) ? { timings: result.timings } : {}),
|
|
181
313
|
preview: previewExecutionValue(ok ? result.result : result.error),
|
|
182
314
|
},
|
|
183
315
|
};
|
|
@@ -219,19 +351,19 @@ export function registerCodeMcpTools(
|
|
|
219
351
|
description:
|
|
220
352
|
"Validate and persist a reusable typed MCP chain in project scope by default or global scope when explicitly requested. Project chains override same-named global chains. The effective chain is immediately registered as a native mcp_chain_<name> tool and as chains.<name> inside CodeMCP. Requires explicit input and output JSON Schemas. Saving the same scoped name updates and re-enables it.",
|
|
221
353
|
promptSnippet: "Save a repeated MCP execution as a typed reusable native tool",
|
|
222
|
-
promptGuidelines: [
|
|
223
|
-
"If a user repeatedly performs the same MCP workflow, you may offer to save it with codemcp_save_chain, but do not persist it until the user explicitly asks or accepts.",
|
|
224
|
-
"Use codemcp_save_chain only after the user explicitly asks to save a chain or accepts your suggestion to do so.",
|
|
225
|
-
"When using codemcp_save_chain, parameterize repeated values through the typed input object and provide exact inputSchema and outputSchema contracts.",
|
|
226
|
-
"Save chains in project scope unless the user explicitly asks to make one available globally across projects.",
|
|
227
|
-
],
|
|
354
|
+
promptGuidelines: [...SAVE_CHAIN_PROMPT_GUIDELINES],
|
|
228
355
|
parameters: SaveChainParameters,
|
|
229
356
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
357
|
+
const scope = requireChainScope(params.scope ?? "project");
|
|
358
|
+
if (scope === "project" && lifecycle.projectChainsPath === undefined) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
"Project saved-chain scope is unavailable in this session; ask before using global scope",
|
|
361
|
+
);
|
|
362
|
+
}
|
|
230
363
|
onUpdate?.({
|
|
231
364
|
content: [{ type: "text", text: `Validating saved chain ${params.name}...` }],
|
|
232
365
|
details: undefined,
|
|
233
366
|
});
|
|
234
|
-
const scope = requireChainScope(params.scope ?? "project");
|
|
235
367
|
const view = await chains.save(
|
|
236
368
|
{
|
|
237
369
|
scope,
|
|
@@ -302,18 +434,120 @@ export function registerCodeMcpTools(
|
|
|
302
434
|
);
|
|
303
435
|
},
|
|
304
436
|
});
|
|
437
|
+
|
|
438
|
+
pi.registerTool({
|
|
439
|
+
name: "codemcp_manage_chains",
|
|
440
|
+
label: "Manage MCP Chains",
|
|
441
|
+
description:
|
|
442
|
+
"List saved chains, or explicitly enable, disable, revalidate, or delete one scoped chain. Mutations require confirmedByUser=true and never bypass dependency checks.",
|
|
443
|
+
promptSnippet: "List or explicitly manage saved MCP chains",
|
|
444
|
+
promptGuidelines: [...MANAGE_CHAIN_PROMPT_GUIDELINES],
|
|
445
|
+
parameters: ManageChainsParameters,
|
|
446
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
447
|
+
const action = params.action;
|
|
448
|
+
let views: SavedChainView[];
|
|
449
|
+
if (action === "list") {
|
|
450
|
+
views = await chains.list(signal);
|
|
451
|
+
} else {
|
|
452
|
+
if (params.confirmedByUser !== true) {
|
|
453
|
+
throw new Error(`${action} requires confirmedByUser=true after explicit user approval`);
|
|
454
|
+
}
|
|
455
|
+
if (params.name === undefined || params.scope === undefined) {
|
|
456
|
+
throw new Error(`${action} requires name and scope`);
|
|
457
|
+
}
|
|
458
|
+
const scope = requireChainScope(params.scope);
|
|
459
|
+
if (scope === "project" && lifecycle.projectChainsPath === undefined) {
|
|
460
|
+
throw new Error("Project saved-chain scope is unavailable in this session");
|
|
461
|
+
}
|
|
462
|
+
onUpdate?.({
|
|
463
|
+
content: [{ type: "text", text: `${action} saved chain ${params.name}...` }],
|
|
464
|
+
details: undefined,
|
|
465
|
+
});
|
|
466
|
+
if (action === "enable" || action === "disable") {
|
|
467
|
+
const applied = await chains.applyEnabled(
|
|
468
|
+
[{ name: params.name, scope, enabled: action === "enable" }],
|
|
469
|
+
signal,
|
|
470
|
+
);
|
|
471
|
+
views = applied.chains;
|
|
472
|
+
} else if (action === "revalidate") {
|
|
473
|
+
await chains.revalidate(params.name, scope, signal);
|
|
474
|
+
views = await chains.list(signal);
|
|
475
|
+
} else {
|
|
476
|
+
views = await chains.delete(params.name, scope, signal);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const result = {
|
|
480
|
+
action,
|
|
481
|
+
project_scope_available: lifecycle.projectChainsPath !== undefined,
|
|
482
|
+
chains: views.map(compactChainView),
|
|
483
|
+
};
|
|
484
|
+
const output = formatCodeMcpOutput(result, outputLimits(lifecycle));
|
|
485
|
+
return {
|
|
486
|
+
content: [{ type: "text", text: output.text }],
|
|
487
|
+
details: {
|
|
488
|
+
...output.details,
|
|
489
|
+
action,
|
|
490
|
+
chainCount: views.length,
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
},
|
|
494
|
+
renderCall(args, theme) {
|
|
495
|
+
return new Text(
|
|
496
|
+
`${theme.fg("toolTitle", theme.bold("Manage MCP Chains "))}${theme.fg("accent", args.action)}${args.name ? ` ${theme.fg("muted", args.name)}` : ""}`,
|
|
497
|
+
0,
|
|
498
|
+
0,
|
|
499
|
+
);
|
|
500
|
+
},
|
|
501
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
502
|
+
if (isPartial) return new Text(theme.fg("warning", "Managing saved chains..."), 0, 0);
|
|
503
|
+
const details = result.details as
|
|
504
|
+
| (CodeMcpOutputDetails & { action: string; chainCount: number })
|
|
505
|
+
| undefined;
|
|
506
|
+
if (context.isError || !details) {
|
|
507
|
+
return new Text(
|
|
508
|
+
theme.fg("error", getTextContent(result.content).trim() || "Chain management failed"),
|
|
509
|
+
0,
|
|
510
|
+
0,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (expanded) return renderExpandedJson(result.content);
|
|
514
|
+
return new Text(
|
|
515
|
+
theme.fg("success", `${details.action} · ${details.chainCount} chains`),
|
|
516
|
+
0,
|
|
517
|
+
0,
|
|
518
|
+
);
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function compactChainView(view: SavedChainView) {
|
|
524
|
+
return {
|
|
525
|
+
name: view.chain.name,
|
|
526
|
+
scope: view.scope,
|
|
527
|
+
status: view.status,
|
|
528
|
+
enabled: view.chain.enabled,
|
|
529
|
+
description: view.chain.description,
|
|
530
|
+
native_tool: nativeChainToolName(view.chain.name),
|
|
531
|
+
call: `chains.${view.chain.name}`,
|
|
532
|
+
stale_dependencies: view.staleDependencies,
|
|
533
|
+
called_by: view.calledBy,
|
|
534
|
+
};
|
|
305
535
|
}
|
|
306
536
|
|
|
307
537
|
function renderExpandedJson(content: readonly unknown[]): Text {
|
|
308
|
-
|
|
538
|
+
const raw = getTextContent(content);
|
|
539
|
+
let formatted = raw;
|
|
540
|
+
try {
|
|
541
|
+
formatted = JSON.stringify(JSON.parse(raw), null, 2);
|
|
542
|
+
} catch {
|
|
543
|
+
// Keep explicit non-JSON errors readable.
|
|
544
|
+
}
|
|
545
|
+
return new Text(highlightCode(formatted, "json").join("\n"), 0, 0);
|
|
309
546
|
}
|
|
310
547
|
|
|
311
|
-
function outputLimits(lifecycle: CodeMcpLifecycle): { maxBytes: number
|
|
548
|
+
function outputLimits(lifecycle: CodeMcpLifecycle): { maxBytes: number } {
|
|
312
549
|
const settings = lifecycle.loadSettings();
|
|
313
|
-
return {
|
|
314
|
-
maxBytes: settings.outputLimitKiB * 1024,
|
|
315
|
-
maxLines: settings.outputLineLimit,
|
|
316
|
-
};
|
|
550
|
+
return { maxBytes: settings.outputLimitKiB * 1024 };
|
|
317
551
|
}
|
|
318
552
|
|
|
319
553
|
function truncate(value: string, maxLength: number): string {
|