argsbarg 7.1.1 → 7.1.2
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/CHANGELOG.md +15 -1
- package/docs/mcp.md +33 -4
- package/index.d.ts +62 -0
- package/package.json +1 -1
- package/src/config/validate.test.ts +157 -0
- package/src/config/validate.ts +352 -7
- package/src/core/document-leaf.test.ts +53 -0
- package/src/core/types.ts +33 -0
- package/src/core/validate.ts +4 -0
- package/src/docs/docs.test.ts +7 -0
- package/src/docs/mcp-guide.ts +43 -1
- package/src/index.ts +3 -0
- package/src/mcp/server.ts +28 -4
- package/src/mcp/tools.test.ts +158 -0
- package/src/mcp/tools.ts +94 -1
- package/src/runtime/cli.ts +6 -1
- package/src/server/context.ts +6 -0
- package/src/test/integration/mcp.test.ts +73 -0
- package/src/test/mcp-integration-fixture.ts +1 -0
- package/src/test/mcp-size-fixture.ts +31 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for mcp/tools module: MCP tool derivation, size reporting, and per-leaf MCP-only notes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { cliPresentationRoot } from "../builtins/presentation.ts";
|
|
7
|
+
import { CliOptionKind } from "../core/types.ts";
|
|
8
|
+
import { cliHelpRender } from "../help.ts";
|
|
9
|
+
import { testProgram } from "../test/fixtures.ts";
|
|
10
|
+
import { collectMcpTools, DEFAULT_MCP_SIZE_LIMITS, mcpSizeReport } from "./tools.ts";
|
|
11
|
+
|
|
12
|
+
describe("mcpSizeReport", () => {
|
|
13
|
+
test("measures description and pretty definition against a hand-built JSON.stringify", () => {
|
|
14
|
+
const program = testProgram({
|
|
15
|
+
key: "sizetest",
|
|
16
|
+
description: "size test",
|
|
17
|
+
mcpServer: { enabled: true },
|
|
18
|
+
commands: [{ key: "run", description: "Run it.", handler: () => {} }],
|
|
19
|
+
});
|
|
20
|
+
const report = mcpSizeReport(program);
|
|
21
|
+
const [tool] = collectMcpTools(program);
|
|
22
|
+
expect(tool).toBeDefined();
|
|
23
|
+
|
|
24
|
+
const expectedJson = JSON.stringify(
|
|
25
|
+
{ name: tool?.name, description: tool?.description, inputSchema: tool?.inputSchema },
|
|
26
|
+
null,
|
|
27
|
+
2,
|
|
28
|
+
);
|
|
29
|
+
expect(report.tools).toHaveLength(1);
|
|
30
|
+
expect(report.tools[0]).toEqual({
|
|
31
|
+
name: tool?.name,
|
|
32
|
+
descriptionChars: tool?.description.length,
|
|
33
|
+
definitionBytes: Buffer.byteLength(expectedJson, "utf8"),
|
|
34
|
+
definitionLines: expectedJson.split("\n").length,
|
|
35
|
+
});
|
|
36
|
+
expect(report.warnings).toEqual([]);
|
|
37
|
+
expect(report.instructionsChars).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("warns past default limits for description and definition size", () => {
|
|
41
|
+
const program = testProgram({
|
|
42
|
+
key: "sizetest2",
|
|
43
|
+
description: "size test 2",
|
|
44
|
+
mcpServer: { enabled: true },
|
|
45
|
+
commands: [
|
|
46
|
+
{
|
|
47
|
+
key: "small",
|
|
48
|
+
description: "Small tool.",
|
|
49
|
+
notes: "x".repeat(3_000),
|
|
50
|
+
handler: () => {},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "big",
|
|
54
|
+
description: "Big tool.",
|
|
55
|
+
options: [
|
|
56
|
+
{
|
|
57
|
+
name: "mode",
|
|
58
|
+
description: "Mode.",
|
|
59
|
+
kind: CliOptionKind.Enum,
|
|
60
|
+
choices: Array.from({ length: 4_000 }, (_, i) => `choice-${i}`),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
handler: () => {},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
const report = mcpSizeReport(program);
|
|
68
|
+
|
|
69
|
+
const smallWarning = report.warnings.find((w) => w.includes('"small"'));
|
|
70
|
+
expect(smallWarning).toBeDefined();
|
|
71
|
+
expect(smallWarning).toContain("description is");
|
|
72
|
+
expect(smallWarning).toContain(`limit ${DEFAULT_MCP_SIZE_LIMITS.descriptionChars.toLocaleString()}`);
|
|
73
|
+
|
|
74
|
+
const bigWarning = report.warnings.find((w) => w.includes('"big"'));
|
|
75
|
+
expect(bigWarning).toBeDefined();
|
|
76
|
+
expect(bigWarning).toContain("definition is");
|
|
77
|
+
expect(bigWarning).toContain("pretty-printed");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("sizeLimits overrides raise or lower the threshold", () => {
|
|
81
|
+
const program = testProgram({
|
|
82
|
+
key: "sizetest3",
|
|
83
|
+
description: "size test 3",
|
|
84
|
+
mcpServer: { enabled: true, sizeLimits: { descriptionChars: 5 } },
|
|
85
|
+
commands: [
|
|
86
|
+
{ key: "run", description: "Run it, with a description longer than five characters.", handler: () => {} },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
const report = mcpSizeReport(program);
|
|
90
|
+
expect(report.warnings.some((w) => w.includes("description is"))).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("sizeLimits: false disables a check entirely", () => {
|
|
94
|
+
const program = testProgram({
|
|
95
|
+
key: "sizetest4",
|
|
96
|
+
description: "size test 4",
|
|
97
|
+
mcpServer: { enabled: true, sizeLimits: { descriptionChars: false } },
|
|
98
|
+
commands: [
|
|
99
|
+
{ key: "run", description: "Run it, with a description longer than five characters.", handler: () => {} },
|
|
100
|
+
],
|
|
101
|
+
});
|
|
102
|
+
const report = mcpSizeReport(program);
|
|
103
|
+
expect(report.warnings.some((w) => w.includes("description is"))).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("warns when instructions exceed the limit", () => {
|
|
107
|
+
const program = testProgram({
|
|
108
|
+
key: "sizetest5",
|
|
109
|
+
description: "size test 5",
|
|
110
|
+
mcpServer: { enabled: true, instructions: "x".repeat(3_000) },
|
|
111
|
+
commands: [{ key: "run", description: "Run it.", handler: () => {} }],
|
|
112
|
+
});
|
|
113
|
+
const report = mcpSizeReport(program);
|
|
114
|
+
expect(report.instructionsChars).toBe(3_000);
|
|
115
|
+
expect(report.warnings.some((w) => w.startsWith("MCP instructions are"))).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("mcpTool.notes", () => {
|
|
120
|
+
function programWithNotesOverride(notesOverride: string | false | undefined) {
|
|
121
|
+
return testProgram({
|
|
122
|
+
key: "notestest",
|
|
123
|
+
description: "notes test",
|
|
124
|
+
mcpServer: { enabled: true },
|
|
125
|
+
commands: [
|
|
126
|
+
{
|
|
127
|
+
key: "run",
|
|
128
|
+
description: "Run it.",
|
|
129
|
+
notes: "Original CLI notes.",
|
|
130
|
+
...(notesOverride === undefined ? {} : { mcpTool: { notes: notesOverride } }),
|
|
131
|
+
handler: () => {},
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
test("false omits notes from the MCP description", () => {
|
|
138
|
+
const [tool] = collectMcpTools(programWithNotesOverride(false));
|
|
139
|
+
expect(tool?.description).not.toContain("Original CLI notes.");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("a string replaces the leaf's notes in the MCP description", () => {
|
|
143
|
+
const [tool] = collectMcpTools(programWithNotesOverride("Custom MCP-only note."));
|
|
144
|
+
expect(tool?.description).toContain("Custom MCP-only note.");
|
|
145
|
+
expect(tool?.description).not.toContain("Original CLI notes.");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("omitted falls through to the leaf's own notes", () => {
|
|
149
|
+
const [tool] = collectMcpTools(programWithNotesOverride(undefined));
|
|
150
|
+
expect(tool?.description).toContain("Original CLI notes.");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("CLI help always shows the leaf's own notes regardless of mcpTool.notes", () => {
|
|
154
|
+
const program = programWithNotesOverride(false);
|
|
155
|
+
const help = cliHelpRender(cliPresentationRoot(program), ["run"], false);
|
|
156
|
+
expect(help).toContain("Original CLI notes.");
|
|
157
|
+
});
|
|
158
|
+
});
|
package/src/mcp/tools.ts
CHANGED
|
@@ -6,6 +6,7 @@ flat JSON tool arguments into argv for Cli.invoke.
|
|
|
6
6
|
import { cliSchemaJson } from "../core/schema.ts";
|
|
7
7
|
import {
|
|
8
8
|
type CliLeaf,
|
|
9
|
+
type CliMcpSizeLimits,
|
|
9
10
|
type CliNode,
|
|
10
11
|
type CliOption,
|
|
11
12
|
CliOptionKind,
|
|
@@ -107,7 +108,13 @@ function resolveToolDescription(root: CliProgram, path: string[], leaf: CliLeaf)
|
|
|
107
108
|
} else {
|
|
108
109
|
desc = mcpToolDescription(path, root.key, leaf.description);
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
+
// `mcpTool.notes` overrides what CLI help shows (leaf.notes) for the MCP description only:
|
|
112
|
+
// `false` omits notes entirely; a string replaces them; omitted falls through to leaf.notes.
|
|
113
|
+
const notesOverride = leaf.mcpTool?.notes;
|
|
114
|
+
if (notesOverride === false) {
|
|
115
|
+
return desc;
|
|
116
|
+
}
|
|
117
|
+
const notes = (typeof notesOverride === "string" ? notesOverride : (leaf.notes ?? "")).trim();
|
|
111
118
|
if (notes.length > 0) {
|
|
112
119
|
desc += `\n\n${cliResolveNotes(notes, root.key)}`;
|
|
113
120
|
}
|
|
@@ -272,3 +279,89 @@ export function mcpToolCallToArgv(
|
|
|
272
279
|
|
|
273
280
|
return argv;
|
|
274
281
|
}
|
|
282
|
+
|
|
283
|
+
/** Default {@link CliMcpSizeLimits}; see that type for what each limit approximates and why. */
|
|
284
|
+
export const DEFAULT_MCP_SIZE_LIMITS: Required<CliMcpSizeLimits> = {
|
|
285
|
+
definitionBytes: 51_200,
|
|
286
|
+
definitionLines: 2_000,
|
|
287
|
+
descriptionChars: 2_048,
|
|
288
|
+
instructionsChars: 2_048,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/** Measured size of one MCP tool's description and pretty-printed definition. */
|
|
292
|
+
export interface McpToolSize {
|
|
293
|
+
/** Pretty-printed `{name, description, inputSchema, outputSchema}`, in UTF-8 bytes. */
|
|
294
|
+
definitionBytes: number;
|
|
295
|
+
/** Line count of the same pretty-printed definition. */
|
|
296
|
+
definitionLines: number;
|
|
297
|
+
/** Character length of `description` alone. */
|
|
298
|
+
descriptionChars: number;
|
|
299
|
+
/** MCP tool name. */
|
|
300
|
+
name: string;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Per-tool sizes plus any warnings past {@link CliMcpSizeLimits} (defaults or `mcpServer.sizeLimits`). */
|
|
304
|
+
export interface McpSizeReport {
|
|
305
|
+
/** Character length of `mcpServer.instructions`, or 0 when unset. */
|
|
306
|
+
instructionsChars: number;
|
|
307
|
+
/** One entry per MCP tool, in `tools/list` order. */
|
|
308
|
+
tools: McpToolSize[];
|
|
309
|
+
/** Human-readable warnings for anything past its limit; empty when everything fits. */
|
|
310
|
+
warnings: string[];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Formats a definition's pretty-printed JSON exactly as Cursor's synced tool file would show it. */
|
|
314
|
+
function mcpToolDefinitionJson(tool: McpToolDef): string {
|
|
315
|
+
return JSON.stringify(
|
|
316
|
+
{
|
|
317
|
+
name: tool.name,
|
|
318
|
+
description: tool.description,
|
|
319
|
+
inputSchema: tool.inputSchema,
|
|
320
|
+
...(tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema }),
|
|
321
|
+
},
|
|
322
|
+
null,
|
|
323
|
+
2,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Measures every MCP tool's description and definition size against {@link CliMcpSizeLimits}. */
|
|
328
|
+
export function mcpSizeReport(root: CliProgram): McpSizeReport {
|
|
329
|
+
const limits = { ...DEFAULT_MCP_SIZE_LIMITS, ...root.mcpServer?.sizeLimits };
|
|
330
|
+
const warnings: string[] = [];
|
|
331
|
+
|
|
332
|
+
const tools = collectMcpTools(root).map((tool): McpToolSize => {
|
|
333
|
+
const definitionJson = mcpToolDefinitionJson(tool);
|
|
334
|
+
const definitionBytes = Buffer.byteLength(definitionJson, "utf8");
|
|
335
|
+
const definitionLines = definitionJson.split("\n").length;
|
|
336
|
+
const descriptionChars = tool.description.length;
|
|
337
|
+
|
|
338
|
+
if (limits.descriptionChars !== false && descriptionChars > limits.descriptionChars) {
|
|
339
|
+
warnings.push(
|
|
340
|
+
`MCP tool "${tool.name}" description is ${descriptionChars.toLocaleString()} chars ` +
|
|
341
|
+
`(limit ${limits.descriptionChars.toLocaleString()}; Claude Code truncates longer descriptions)`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
const overBytes = limits.definitionBytes !== false && definitionBytes > limits.definitionBytes;
|
|
345
|
+
const overLines = limits.definitionLines !== false && definitionLines > limits.definitionLines;
|
|
346
|
+
if (overBytes || overLines) {
|
|
347
|
+
const byteLimit = limits.definitionBytes === false ? "∞" : limits.definitionBytes.toLocaleString();
|
|
348
|
+
const lineLimit = limits.definitionLines === false ? "∞" : limits.definitionLines.toLocaleString();
|
|
349
|
+
warnings.push(
|
|
350
|
+
`MCP tool "${tool.name}" definition is ${definitionBytes.toLocaleString()} bytes / ` +
|
|
351
|
+
`${definitionLines.toLocaleString()} lines pretty-printed (limit ${byteLimit} bytes / ${lineLimit} lines; ` +
|
|
352
|
+
`Cursor reads tool definitions in chunks of at most that size)`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return { definitionBytes, definitionLines, descriptionChars, name: tool.name };
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const instructionsChars = (root.mcpServer?.instructions ?? "").length;
|
|
360
|
+
if (limits.instructionsChars !== false && instructionsChars > limits.instructionsChars) {
|
|
361
|
+
warnings.push(
|
|
362
|
+
`MCP instructions are ${instructionsChars.toLocaleString()} chars (limit ${limits.instructionsChars.toLocaleString()})`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { instructionsChars, tools, warnings };
|
|
367
|
+
}
|
package/src/runtime/cli.ts
CHANGED
|
@@ -33,7 +33,8 @@ import { buildInvokeHookContext, classifyFailureKind, runErrorPipeline, runHook
|
|
|
33
33
|
import { httpServeHttp } from "../http/server.ts";
|
|
34
34
|
import { LogEmitter } from "../log/emitter.ts";
|
|
35
35
|
import { bootstrapMcpEnv } from "../mcp/env.ts";
|
|
36
|
-
import { mcpServeStdioLoop } from "../mcp/server.ts";
|
|
36
|
+
import { MCP_PROTOCOL_VERSIONS, mcpServeStdioLoop } from "../mcp/server.ts";
|
|
37
|
+
import { mcpSizeReport } from "../mcp/tools.ts";
|
|
37
38
|
import { createServerRuntime, type ServerHandleContext } from "../server/context.ts";
|
|
38
39
|
import { resolveHttpServeConfig, resolveMcpServeConfig, type ServeOverrides } from "../server/overrides.ts";
|
|
39
40
|
import {
|
|
@@ -414,6 +415,7 @@ export class Cli {
|
|
|
414
415
|
emitter,
|
|
415
416
|
mcp: resolved,
|
|
416
417
|
mcpHooks: this.program.mcpServer?.hooks,
|
|
418
|
+
mcpProtocolVersion: MCP_PROTOCOL_VERSIONS[0],
|
|
417
419
|
};
|
|
418
420
|
bootstrapAppConfig(this.program, { validateFile: "soft", runtime, emitter });
|
|
419
421
|
const shutdown = () => {
|
|
@@ -422,6 +424,9 @@ export class Cli {
|
|
|
422
424
|
};
|
|
423
425
|
process.once("SIGINT", shutdown);
|
|
424
426
|
process.once("SIGTERM", shutdown);
|
|
427
|
+
for (const message of mcpSizeReport(this.program).warnings) {
|
|
428
|
+
emitter.emit({ level: "warn", message, action: "mcp.size" });
|
|
429
|
+
}
|
|
425
430
|
emitter.emitLifecycle(`${this.program.key} ${this.program.version} — MCP ready (stdio)`, "mcp.server.ready");
|
|
426
431
|
await mcpServeStdioLoop(this);
|
|
427
432
|
process.exit(0);
|
package/src/server/context.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface ServerHandleContext {
|
|
|
14
14
|
mcp?: ResolvedMcpServeConfig;
|
|
15
15
|
httpHooks?: CliHttpWireHooks;
|
|
16
16
|
mcpHooks?: CliMcpWireHooks;
|
|
17
|
+
/**
|
|
18
|
+
* The MCP protocol version negotiated with `initialize`, or the newest supported version before
|
|
19
|
+
* `initialize` has been handled. Later requests (`tools/list`, `tools/call`) gate version-specific
|
|
20
|
+
* response fields (e.g. `outputSchema`, `structuredContent`) on this.
|
|
21
|
+
*/
|
|
22
|
+
mcpProtocolVersion?: string;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/** Creates a fresh {@link ServerRuntime} for HTTP or MCP. */
|
|
@@ -353,6 +353,79 @@ test("MCP initialize returns tools and resources capabilities", async () => {
|
|
|
353
353
|
expect(res.result.capabilities.resources).toBeDefined();
|
|
354
354
|
});
|
|
355
355
|
|
|
356
|
+
test("MCP initialize echoes a supported protocol version", async () => {
|
|
357
|
+
for (const version of ["2024-11-05", "2025-06-18"]) {
|
|
358
|
+
const responses = await mcpRequest([
|
|
359
|
+
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: version } },
|
|
360
|
+
]);
|
|
361
|
+
const res = responses.get(1) as { result: { protocolVersion: string } };
|
|
362
|
+
expect(res.result.protocolVersion).toBe(version);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("MCP initialize answers unsupported or missing versions with the newest", async () => {
|
|
367
|
+
for (const params of [{ protocolVersion: "2099-01-01" }, {}]) {
|
|
368
|
+
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 1, method: "initialize", params }]);
|
|
369
|
+
const res = responses.get(1) as { result: { protocolVersion: string } };
|
|
370
|
+
expect(res.result.protocolVersion).toBe("2025-06-18");
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("2024-11-05 sessions omit outputSchema and structuredContent", async () => {
|
|
375
|
+
const readme = join(import.meta.dir, "..", "..", "..", "README.md");
|
|
376
|
+
const responses = await mcpRequest([
|
|
377
|
+
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05" } },
|
|
378
|
+
{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
|
|
379
|
+
{
|
|
380
|
+
jsonrpc: "2.0",
|
|
381
|
+
id: 3,
|
|
382
|
+
method: "tools/call",
|
|
383
|
+
params: { name: "stat_owner_lookup", arguments: { path: readme, "user-name": "test" } },
|
|
384
|
+
},
|
|
385
|
+
]);
|
|
386
|
+
const listRes = responses.get(2) as { result: { tools: { name: string; outputSchema?: unknown }[] } };
|
|
387
|
+
const lookup = listRes.result.tools.find((t) => t.name === "stat_owner_lookup");
|
|
388
|
+
expect(lookup).toBeDefined();
|
|
389
|
+
expect(lookup?.outputSchema).toBeUndefined();
|
|
390
|
+
|
|
391
|
+
const callRes = responses.get(3) as { result: { structuredContent?: unknown; isError: boolean } };
|
|
392
|
+
expect(callRes.result.isError).toBe(false);
|
|
393
|
+
expect(callRes.result.structuredContent).toBeUndefined();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("MCP initialize includes configured instructions", async () => {
|
|
397
|
+
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }], {
|
|
398
|
+
script: "src/test/mcp-integration-fixture.ts",
|
|
399
|
+
});
|
|
400
|
+
const res = responses.get(1) as { result: { instructions?: string } };
|
|
401
|
+
expect(res.result.instructions).toBe("Read the fixture skill.");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("MCP startup warns about oversized tools on stderr", async () => {
|
|
405
|
+
const proc = Bun.spawn(["bun", "run", "src/test/mcp-size-fixture.ts", "mcp"], {
|
|
406
|
+
stdin: "pipe",
|
|
407
|
+
stdout: "pipe",
|
|
408
|
+
stderr: "pipe",
|
|
409
|
+
});
|
|
410
|
+
proc.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} })}\n`);
|
|
411
|
+
proc.stdin.end();
|
|
412
|
+
const timeout = setTimeout(() => proc.kill(), 10_000);
|
|
413
|
+
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
414
|
+
await proc.exited;
|
|
415
|
+
clearTimeout(timeout);
|
|
416
|
+
|
|
417
|
+
expect(stderr).toContain("description is 3,");
|
|
418
|
+
|
|
419
|
+
const lines = stdout
|
|
420
|
+
.split("\n")
|
|
421
|
+
.map((l) => l.trim())
|
|
422
|
+
.filter(Boolean);
|
|
423
|
+
expect(lines.length).toBeGreaterThan(0);
|
|
424
|
+
for (const line of lines) {
|
|
425
|
+
expect(() => JSON.parse(line)).not.toThrow();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
356
429
|
test("MCP tools/list includes stat_owner_lookup", async () => {
|
|
357
430
|
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }]);
|
|
358
431
|
const res = responses.get(2) as {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/*
|
|
3
|
+
MCP size-warning integration test fixture (not a public example). One leaf with oversized notes,
|
|
4
|
+
triggering the "description" startup size warning on stderr.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Cli, type CliProgram } from "../index.ts";
|
|
8
|
+
|
|
9
|
+
const program = {
|
|
10
|
+
commands: [
|
|
11
|
+
{
|
|
12
|
+
key: "run",
|
|
13
|
+
description: "Run it.",
|
|
14
|
+
notes: "x".repeat(3_000),
|
|
15
|
+
handler: (ctx) => {
|
|
16
|
+
if (ctx.invocation === "cli") {
|
|
17
|
+
console.log("ran");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
return "ran";
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
description: "MCP size-warning test fixture.",
|
|
25
|
+
key: "mcp-size-test",
|
|
26
|
+
mcpServer: { enabled: true },
|
|
27
|
+
version: "0.0.0-test",
|
|
28
|
+
} satisfies CliProgram;
|
|
29
|
+
|
|
30
|
+
const cli = new Cli(program);
|
|
31
|
+
await cli.run();
|