beth-copilot 1.0.18 → 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/CHANGELOG.md +41 -28
- package/README.md +87 -247
- package/bin/cli.js +158 -358
- package/dist/__tests__/smoke.test.d.ts +8 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +49 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/cli/commands/beads.e2e.test.d.ts +13 -0
- package/dist/cli/commands/beads.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/beads.e2e.test.js +526 -0
- package/dist/cli/commands/beads.e2e.test.js.map +1 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.d.ts +32 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.js +162 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.js.map +1 -0
- package/dist/cli/commands/close.d.ts +89 -0
- package/dist/cli/commands/close.d.ts.map +1 -0
- package/dist/cli/commands/close.e2e.test.d.ts +27 -0
- package/dist/cli/commands/close.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/close.e2e.test.js +252 -0
- package/dist/cli/commands/close.e2e.test.js.map +1 -0
- package/dist/cli/commands/close.js +309 -0
- package/dist/cli/commands/close.js.map +1 -0
- package/dist/cli/commands/close.test.d.ts +15 -0
- package/dist/cli/commands/close.test.d.ts.map +1 -0
- package/dist/cli/commands/close.test.js +634 -0
- package/dist/cli/commands/close.test.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +23 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +93 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/doctor.test.js +209 -0
- package/dist/cli/commands/doctor.test.js.map +1 -1
- package/dist/cli/commands/framework-isolation.test.d.ts +30 -0
- package/dist/cli/commands/framework-isolation.test.d.ts.map +1 -0
- package/dist/cli/commands/framework-isolation.test.js +119 -0
- package/dist/cli/commands/framework-isolation.test.js.map +1 -0
- package/dist/cli/commands/help.e2e.test.js +4 -4
- package/dist/cli/commands/help.e2e.test.js.map +1 -1
- package/dist/cli/commands/init-logic.e2e.test.d.ts +37 -0
- package/dist/cli/commands/init-logic.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/init-logic.e2e.test.js +305 -0
- package/dist/cli/commands/init-logic.e2e.test.js.map +1 -0
- package/dist/cli/commands/land.d.ts +142 -0
- package/dist/cli/commands/land.d.ts.map +1 -0
- package/dist/cli/commands/land.js +647 -0
- package/dist/cli/commands/land.js.map +1 -0
- package/dist/cli/commands/land.test.d.ts +20 -0
- package/dist/cli/commands/land.test.d.ts.map +1 -0
- package/dist/cli/commands/land.test.js +622 -0
- package/dist/cli/commands/land.test.js.map +1 -0
- package/dist/cli/commands/mcp.e2e.test.js +22 -29
- package/dist/cli/commands/mcp.e2e.test.js.map +1 -1
- package/dist/cli/commands/pipeline.e2e.test.js +20 -20
- package/dist/cli/commands/pipeline.e2e.test.js.map +1 -1
- package/dist/cli/commands/pre-push-guard.d.ts +84 -0
- package/dist/cli/commands/pre-push-guard.d.ts.map +1 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.d.ts +24 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.js +171 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.js.map +1 -0
- package/dist/cli/commands/pre-push-guard.js +257 -0
- package/dist/cli/commands/pre-push-guard.js.map +1 -0
- package/dist/cli/commands/pre-push-guard.test.d.ts +15 -0
- package/dist/cli/commands/pre-push-guard.test.d.ts.map +1 -0
- package/dist/cli/commands/pre-push-guard.test.js +397 -0
- package/dist/cli/commands/pre-push-guard.test.js.map +1 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.d.ts +23 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.js +179 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.js.map +1 -0
- package/dist/cli/commands/quickstart.d.ts.map +1 -1
- package/dist/cli/commands/quickstart.js +7 -23
- package/dist/cli/commands/quickstart.js.map +1 -1
- package/dist/cli/commands/quickstart.test.js +40 -67
- package/dist/cli/commands/quickstart.test.js.map +1 -1
- package/dist/core/agents/suite.test.js +4 -2
- package/dist/core/agents/suite.test.js.map +1 -1
- package/dist/core/agents/tools.test.js +5 -1
- package/dist/core/agents/tools.test.js.map +1 -1
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -10
- package/dist/index.js.map +1 -1
- package/package.json +15 -9
- package/sbom.json +2011 -819
- package/templates/.github/agents/beth.agent.md +220 -66
- package/templates/.github/agents/developer.agent.md +53 -90
- package/templates/.github/agents/product-manager.agent.md +15 -68
- package/templates/.github/agents/researcher.agent.md +20 -71
- package/templates/.github/agents/security-reviewer.agent.md +29 -81
- package/templates/.github/agents/tester.agent.md +40 -69
- package/templates/.github/agents/ux-designer.agent.md +20 -74
- package/templates/.github/copilot-instructions.md +217 -225
- package/templates/AGENTS.md +108 -20
- package/templates/mcp.json.example +0 -3
- package/dist/cli/commands/client-config.d.ts +0 -31
- package/dist/cli/commands/client-config.d.ts.map +0 -1
- package/dist/cli/commands/client-config.e2e.test.d.ts +0 -15
- package/dist/cli/commands/client-config.e2e.test.d.ts.map +0 -1
- package/dist/cli/commands/client-config.e2e.test.js +0 -556
- package/dist/cli/commands/client-config.e2e.test.js.map +0 -1
- package/dist/cli/commands/client-config.js +0 -73
- package/dist/cli/commands/client-config.js.map +0 -1
- package/dist/cli/commands/client-config.test.d.ts +0 -6
- package/dist/cli/commands/client-config.test.d.ts.map +0 -1
- package/dist/cli/commands/client-config.test.js +0 -133
- package/dist/cli/commands/client-config.test.js.map +0 -1
- package/dist/cli/commands/init-quickstart.e2e.test.d.ts +0 -11
- package/dist/cli/commands/init-quickstart.e2e.test.d.ts.map +0 -1
- package/dist/cli/commands/init-quickstart.e2e.test.js +0 -221
- package/dist/cli/commands/init-quickstart.e2e.test.js.map +0 -1
- package/dist/core/context.d.ts +0 -171
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js +0 -353
- package/dist/core/context.js.map +0 -1
- package/dist/core/context.test.d.ts +0 -8
- package/dist/core/context.test.d.ts.map +0 -1
- package/dist/core/context.test.js +0 -253
- package/dist/core/context.test.js.map +0 -1
- package/dist/core/handoffs.d.ts +0 -151
- package/dist/core/handoffs.d.ts.map +0 -1
- package/dist/core/handoffs.js +0 -220
- package/dist/core/handoffs.js.map +0 -1
- package/dist/core/handoffs.test.d.ts +0 -8
- package/dist/core/handoffs.test.d.ts.map +0 -1
- package/dist/core/handoffs.test.js +0 -231
- package/dist/core/handoffs.test.js.map +0 -1
- package/dist/core/orchestrator.d.ts +0 -246
- package/dist/core/orchestrator.d.ts.map +0 -1
- package/dist/core/orchestrator.js +0 -514
- package/dist/core/orchestrator.js.map +0 -1
- package/dist/core/orchestrator.test.d.ts +0 -8
- package/dist/core/orchestrator.test.d.ts.map +0 -1
- package/dist/core/orchestrator.test.js +0 -517
- package/dist/core/orchestrator.test.js.map +0 -1
- package/dist/core/router.d.ts +0 -102
- package/dist/core/router.d.ts.map +0 -1
- package/dist/core/router.js +0 -178
- package/dist/core/router.js.map +0 -1
- package/dist/core/router.test.d.ts +0 -8
- package/dist/core/router.test.d.ts.map +0 -1
- package/dist/core/router.test.js +0 -215
- package/dist/core/router.test.js.map +0 -1
- package/dist/init.test.js +0 -288
- package/dist/providers/azure.d.ts +0 -147
- package/dist/providers/azure.d.ts.map +0 -1
- package/dist/providers/azure.js +0 -491
- package/dist/providers/azure.js.map +0 -1
- package/dist/providers/azure.test.d.ts +0 -11
- package/dist/providers/azure.test.d.ts.map +0 -1
- package/dist/providers/azure.test.js +0 -330
- package/dist/providers/azure.test.js.map +0 -1
- package/dist/providers/config.d.ts +0 -87
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -193
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -7
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -370
- package/dist/providers/config.test.js.map +0 -1
- package/dist/providers/index.d.ts +0 -18
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js +0 -14
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/interface.d.ts +0 -191
- package/dist/providers/interface.d.ts.map +0 -1
- package/dist/providers/interface.js +0 -94
- package/dist/providers/interface.js.map +0 -1
- package/dist/providers/retry.d.ts +0 -128
- package/dist/providers/retry.d.ts.map +0 -1
- package/dist/providers/retry.js +0 -205
- package/dist/providers/retry.js.map +0 -1
- package/dist/providers/retry.test.d.ts +0 -7
- package/dist/providers/retry.test.d.ts.map +0 -1
- package/dist/providers/retry.test.js +0 -439
- package/dist/providers/retry.test.js.map +0 -1
- package/dist/providers/streaming.d.ts +0 -157
- package/dist/providers/streaming.d.ts.map +0 -1
- package/dist/providers/streaming.js +0 -233
- package/dist/providers/streaming.js.map +0 -1
- package/dist/providers/streaming.test.d.ts +0 -7
- package/dist/providers/streaming.test.d.ts.map +0 -1
- package/dist/providers/streaming.test.js +0 -372
- package/dist/providers/streaming.test.js.map +0 -1
- package/dist/providers/types.d.ts +0 -209
- package/dist/providers/types.d.ts.map +0 -1
- package/dist/providers/types.js +0 -53
- package/dist/providers/types.js.map +0 -1
- package/dist/providers/types.test.d.ts +0 -7
- package/dist/providers/types.test.d.ts.map +0 -1
- package/dist/providers/types.test.js +0 -141
- package/dist/providers/types.test.js.map +0 -1
- package/dist/tools/cli/beads.d.ts +0 -27
- package/dist/tools/cli/beads.d.ts.map +0 -1
- package/dist/tools/cli/beads.js +0 -172
- package/dist/tools/cli/beads.js.map +0 -1
- package/dist/tools/cli/beads.test.d.ts +0 -8
- package/dist/tools/cli/beads.test.d.ts.map +0 -1
- package/dist/tools/cli/beads.test.js +0 -264
- package/dist/tools/cli/beads.test.js.map +0 -1
- package/dist/tools/cli/editFile.d.ts +0 -17
- package/dist/tools/cli/editFile.d.ts.map +0 -1
- package/dist/tools/cli/editFile.js +0 -125
- package/dist/tools/cli/editFile.js.map +0 -1
- package/dist/tools/cli/editFile.test.d.ts +0 -8
- package/dist/tools/cli/editFile.test.d.ts.map +0 -1
- package/dist/tools/cli/editFile.test.js +0 -177
- package/dist/tools/cli/editFile.test.js.map +0 -1
- package/dist/tools/cli/readFile.d.ts +0 -25
- package/dist/tools/cli/readFile.d.ts.map +0 -1
- package/dist/tools/cli/readFile.js +0 -118
- package/dist/tools/cli/readFile.js.map +0 -1
- package/dist/tools/cli/readFile.test.d.ts +0 -8
- package/dist/tools/cli/readFile.test.d.ts.map +0 -1
- package/dist/tools/cli/readFile.test.js +0 -194
- package/dist/tools/cli/readFile.test.js.map +0 -1
- package/dist/tools/cli/search.d.ts +0 -16
- package/dist/tools/cli/search.d.ts.map +0 -1
- package/dist/tools/cli/search.js +0 -261
- package/dist/tools/cli/search.js.map +0 -1
- package/dist/tools/cli/search.test.d.ts +0 -8
- package/dist/tools/cli/search.test.d.ts.map +0 -1
- package/dist/tools/cli/search.test.js +0 -172
- package/dist/tools/cli/search.test.js.map +0 -1
- package/dist/tools/cli/subagent.d.ts +0 -43
- package/dist/tools/cli/subagent.d.ts.map +0 -1
- package/dist/tools/cli/subagent.js +0 -99
- package/dist/tools/cli/subagent.js.map +0 -1
- package/dist/tools/cli/subagent.test.d.ts +0 -8
- package/dist/tools/cli/subagent.test.d.ts.map +0 -1
- package/dist/tools/cli/subagent.test.js +0 -190
- package/dist/tools/cli/subagent.test.js.map +0 -1
- package/dist/tools/cli/terminal.d.ts +0 -19
- package/dist/tools/cli/terminal.d.ts.map +0 -1
- package/dist/tools/cli/terminal.js +0 -164
- package/dist/tools/cli/terminal.js.map +0 -1
- package/dist/tools/cli/terminal.test.d.ts +0 -8
- package/dist/tools/cli/terminal.test.d.ts.map +0 -1
- package/dist/tools/cli/terminal.test.js +0 -161
- package/dist/tools/cli/terminal.test.js.map +0 -1
- package/dist/tools/index.d.ts +0 -25
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -41
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/interface.d.ts +0 -64
- package/dist/tools/interface.d.ts.map +0 -1
- package/dist/tools/interface.js +0 -37
- package/dist/tools/interface.js.map +0 -1
- package/dist/tools/interface.test.d.ts +0 -7
- package/dist/tools/interface.test.d.ts.map +0 -1
- package/dist/tools/interface.test.js +0 -179
- package/dist/tools/interface.test.js.map +0 -1
- package/dist/tools/mcp/bridge.d.ts +0 -48
- package/dist/tools/mcp/bridge.d.ts.map +0 -1
- package/dist/tools/mcp/bridge.js +0 -128
- package/dist/tools/mcp/bridge.js.map +0 -1
- package/dist/tools/mcp/bridge.test.d.ts +0 -8
- package/dist/tools/mcp/bridge.test.d.ts.map +0 -1
- package/dist/tools/mcp/bridge.test.js +0 -300
- package/dist/tools/mcp/bridge.test.js.map +0 -1
- package/dist/tools/mcp/client.d.ts +0 -135
- package/dist/tools/mcp/client.d.ts.map +0 -1
- package/dist/tools/mcp/client.js +0 -263
- package/dist/tools/mcp/client.js.map +0 -1
- package/dist/tools/mcp/client.test.d.ts +0 -8
- package/dist/tools/mcp/client.test.d.ts.map +0 -1
- package/dist/tools/mcp/client.test.js +0 -390
- package/dist/tools/mcp/client.test.js.map +0 -1
- package/dist/tools/registry.d.ts +0 -82
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/registry.js +0 -99
- package/dist/tools/registry.js.map +0 -1
- package/dist/tools/registry.test.d.ts +0 -7
- package/dist/tools/registry.test.d.ts.map +0 -1
- package/dist/tools/registry.test.js +0 -199
- package/dist/tools/registry.test.js.map +0 -1
- package/dist/tools/suite.test.d.ts +0 -11
- package/dist/tools/suite.test.d.ts.map +0 -1
- package/dist/tools/suite.test.js +0 -119
- package/dist/tools/suite.test.js.map +0 -1
- package/dist/tools/types.d.ts +0 -75
- package/dist/tools/types.d.ts.map +0 -1
- package/dist/tools/types.js +0 -30
- package/dist/tools/types.js.map +0 -1
- package/dist/tools/types.test.d.ts +0 -7
- package/dist/tools/types.test.d.ts.map +0 -1
- package/dist/tools/types.test.js +0 -178
- package/dist/tools/types.test.js.map +0 -1
- package/templates/.vscode/mcp.json +0 -20
- package/templates/CLAUDE.md +0 -129
package/dist/tools/mcp/client.js
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Client
|
|
3
|
-
*
|
|
4
|
-
* Minimal Model Context Protocol client that communicates with
|
|
5
|
-
* MCP servers over stdio transport using JSON-RPC 2.0.
|
|
6
|
-
*/
|
|
7
|
-
import { spawn } from 'node:child_process';
|
|
8
|
-
import { createInterface } from 'node:readline';
|
|
9
|
-
/** Default timeout for MCP requests in milliseconds */
|
|
10
|
-
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
|
-
/** MCP protocol version */
|
|
12
|
-
const PROTOCOL_VERSION = '2024-11-05';
|
|
13
|
-
/** Client info sent during initialization */
|
|
14
|
-
const CLIENT_INFO = { name: 'beth-cli', version: '1.0.15' };
|
|
15
|
-
// =============================================================================
|
|
16
|
-
// MCP Client
|
|
17
|
-
// =============================================================================
|
|
18
|
-
/**
|
|
19
|
-
* MCP protocol client for stdio-based MCP servers.
|
|
20
|
-
*
|
|
21
|
-
* Handles the JSON-RPC 2.0 transport, initialization handshake,
|
|
22
|
-
* tool listing, and tool invocation.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const client = new MCPClient('shadcn', {
|
|
27
|
-
* command: 'npx',
|
|
28
|
-
* args: ['shadcn@3.7.0', 'mcp'],
|
|
29
|
-
* });
|
|
30
|
-
* await client.connect();
|
|
31
|
-
* const tools = await client.listTools();
|
|
32
|
-
* const result = await client.callTool('search', { query: 'button' });
|
|
33
|
-
* await client.disconnect();
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export class MCPClient {
|
|
37
|
-
/** Server name (for logging/namespacing) */
|
|
38
|
-
name;
|
|
39
|
-
/** Server configuration */
|
|
40
|
-
config;
|
|
41
|
-
/** Spawn function (injectable for testing) */
|
|
42
|
-
spawnFn;
|
|
43
|
-
/** Request timeout in milliseconds */
|
|
44
|
-
timeoutMs;
|
|
45
|
-
/** The spawned server process */
|
|
46
|
-
process = null;
|
|
47
|
-
/** Readline interface for reading stdout line by line */
|
|
48
|
-
reader = null;
|
|
49
|
-
/** Incrementing request ID counter */
|
|
50
|
-
nextId = 1;
|
|
51
|
-
/** Whether the initialization handshake has completed */
|
|
52
|
-
initialized = false;
|
|
53
|
-
/** Pending request resolvers keyed by request ID */
|
|
54
|
-
pending = new Map();
|
|
55
|
-
constructor(name, config, options) {
|
|
56
|
-
this.name = name;
|
|
57
|
-
this.config = config;
|
|
58
|
-
this.spawnFn = options?.spawnFn ?? spawn;
|
|
59
|
-
this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Whether the client is connected and initialized.
|
|
63
|
-
*/
|
|
64
|
-
get connected() {
|
|
65
|
-
return this.initialized && this.process !== null;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Start the MCP server process and perform the initialization handshake.
|
|
69
|
-
*
|
|
70
|
-
* @throws Error if the process fails to start or initialization times out
|
|
71
|
-
*/
|
|
72
|
-
async connect() {
|
|
73
|
-
if (this.process) {
|
|
74
|
-
throw new Error(`MCP client "${this.name}" is already connected`);
|
|
75
|
-
}
|
|
76
|
-
const env = this.config.env
|
|
77
|
-
? { ...process.env, ...this.config.env }
|
|
78
|
-
: process.env;
|
|
79
|
-
this.process = this.spawnFn(this.config.command, this.config.args, {
|
|
80
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
81
|
-
env,
|
|
82
|
-
});
|
|
83
|
-
// Handle unexpected process exit
|
|
84
|
-
this.process.on('error', (err) => {
|
|
85
|
-
this.rejectAllPending(new Error(`MCP server "${this.name}" process error: ${err.message}`));
|
|
86
|
-
this.cleanup();
|
|
87
|
-
});
|
|
88
|
-
this.process.on('close', () => {
|
|
89
|
-
this.rejectAllPending(new Error(`MCP server "${this.name}" process exited unexpectedly`));
|
|
90
|
-
this.cleanup();
|
|
91
|
-
});
|
|
92
|
-
// Set up line-delimited JSON reader on stdout
|
|
93
|
-
if (!this.process.stdout) {
|
|
94
|
-
throw new Error(`MCP server "${this.name}" has no stdout`);
|
|
95
|
-
}
|
|
96
|
-
this.reader = createInterface({ input: this.process.stdout });
|
|
97
|
-
this.reader.on('line', (line) => this.handleLine(line));
|
|
98
|
-
// Perform initialization handshake
|
|
99
|
-
await this.sendRequest('initialize', {
|
|
100
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
101
|
-
capabilities: {},
|
|
102
|
-
clientInfo: CLIENT_INFO,
|
|
103
|
-
});
|
|
104
|
-
this.sendNotification('notifications/initialized');
|
|
105
|
-
this.initialized = true;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* List available tools from the MCP server.
|
|
109
|
-
*
|
|
110
|
-
* @returns Array of tool information
|
|
111
|
-
* @throws Error if not connected
|
|
112
|
-
*/
|
|
113
|
-
async listTools() {
|
|
114
|
-
this.ensureConnected();
|
|
115
|
-
const result = await this.sendRequest('tools/list', {});
|
|
116
|
-
const tools = result.tools;
|
|
117
|
-
if (!Array.isArray(tools)) {
|
|
118
|
-
return [];
|
|
119
|
-
}
|
|
120
|
-
return tools.map((t) => ({
|
|
121
|
-
name: String(t.name ?? ''),
|
|
122
|
-
description: String(t.description ?? ''),
|
|
123
|
-
inputSchema: t.inputSchema ?? {},
|
|
124
|
-
}));
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Call a tool on the MCP server.
|
|
128
|
-
*
|
|
129
|
-
* @param toolName - Name of the tool to call
|
|
130
|
-
* @param args - Arguments to pass to the tool
|
|
131
|
-
* @returns Tool execution result
|
|
132
|
-
* @throws Error if not connected or the tool call fails
|
|
133
|
-
*/
|
|
134
|
-
async callTool(toolName, args) {
|
|
135
|
-
this.ensureConnected();
|
|
136
|
-
return this.sendRequest('tools/call', {
|
|
137
|
-
name: toolName,
|
|
138
|
-
arguments: args,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Shut down the MCP server process.
|
|
143
|
-
*/
|
|
144
|
-
async disconnect() {
|
|
145
|
-
this.rejectAllPending(new Error(`MCP client "${this.name}" disconnecting`));
|
|
146
|
-
this.cleanup();
|
|
147
|
-
}
|
|
148
|
-
// ===========================================================================
|
|
149
|
-
// Internal: JSON-RPC transport
|
|
150
|
-
// ===========================================================================
|
|
151
|
-
/**
|
|
152
|
-
* Send a JSON-RPC request and wait for the matching response.
|
|
153
|
-
*/
|
|
154
|
-
sendRequest(method, params) {
|
|
155
|
-
const id = this.nextId++;
|
|
156
|
-
const message = {
|
|
157
|
-
jsonrpc: '2.0',
|
|
158
|
-
id,
|
|
159
|
-
method,
|
|
160
|
-
params,
|
|
161
|
-
};
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
// Set up timeout
|
|
164
|
-
const timer = setTimeout(() => {
|
|
165
|
-
this.pending.delete(id);
|
|
166
|
-
reject(new Error(`MCP request "${method}" timed out after ${this.timeoutMs}ms`));
|
|
167
|
-
}, this.timeoutMs);
|
|
168
|
-
this.pending.set(id, {
|
|
169
|
-
resolve: (value) => {
|
|
170
|
-
clearTimeout(timer);
|
|
171
|
-
resolve(value);
|
|
172
|
-
},
|
|
173
|
-
reject: (reason) => {
|
|
174
|
-
clearTimeout(timer);
|
|
175
|
-
reject(reason);
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
this.writeMessage(JSON.stringify(message));
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Send a JSON-RPC notification (no response expected).
|
|
183
|
-
*/
|
|
184
|
-
sendNotification(method, params) {
|
|
185
|
-
const message = {
|
|
186
|
-
jsonrpc: '2.0',
|
|
187
|
-
method,
|
|
188
|
-
...(params !== undefined ? { params } : {}),
|
|
189
|
-
};
|
|
190
|
-
this.writeMessage(JSON.stringify(message));
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Write a line-delimited JSON message to the process stdin.
|
|
194
|
-
*/
|
|
195
|
-
writeMessage(json) {
|
|
196
|
-
if (!this.process?.stdin?.writable) {
|
|
197
|
-
throw new Error(`MCP server "${this.name}" stdin is not writable`);
|
|
198
|
-
}
|
|
199
|
-
this.process.stdin.write(json + '\n');
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Handle a line of JSON from the server's stdout.
|
|
203
|
-
*/
|
|
204
|
-
handleLine(line) {
|
|
205
|
-
const trimmed = line.trim();
|
|
206
|
-
if (!trimmed)
|
|
207
|
-
return;
|
|
208
|
-
let parsed;
|
|
209
|
-
try {
|
|
210
|
-
parsed = JSON.parse(trimmed);
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
// Ignore malformed lines (e.g., server logging to stdout)
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
// Only handle responses with an id that we're waiting for
|
|
217
|
-
if (parsed.id === undefined || parsed.id === null)
|
|
218
|
-
return;
|
|
219
|
-
const pending = this.pending.get(parsed.id);
|
|
220
|
-
if (!pending)
|
|
221
|
-
return;
|
|
222
|
-
this.pending.delete(parsed.id);
|
|
223
|
-
if (parsed.error) {
|
|
224
|
-
pending.reject(new Error(`MCP error (${parsed.error.code}): ${parsed.error.message}`));
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
pending.resolve(parsed.result);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Assert that the client is connected.
|
|
232
|
-
*/
|
|
233
|
-
ensureConnected() {
|
|
234
|
-
if (!this.connected) {
|
|
235
|
-
throw new Error(`MCP client "${this.name}" is not connected`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Reject all pending requests.
|
|
240
|
-
*/
|
|
241
|
-
rejectAllPending(error) {
|
|
242
|
-
for (const [id, pending] of this.pending) {
|
|
243
|
-
pending.reject(error);
|
|
244
|
-
this.pending.delete(id);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Clean up the process and reader.
|
|
249
|
-
*/
|
|
250
|
-
cleanup() {
|
|
251
|
-
this.initialized = false;
|
|
252
|
-
if (this.reader) {
|
|
253
|
-
this.reader.close();
|
|
254
|
-
this.reader = null;
|
|
255
|
-
}
|
|
256
|
-
if (this.process) {
|
|
257
|
-
this.process.stdin?.end();
|
|
258
|
-
this.process.kill();
|
|
259
|
-
this.process = null;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
//# sourceMappingURL=client.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/tools/mcp/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../../src/tools/mcp/client.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Client Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the MCP protocol client including JSON-RPC message
|
|
5
|
-
* formatting, request lifecycle, and timeout handling.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it } from 'node:test';
|
|
8
|
-
import assert from 'node:assert';
|
|
9
|
-
import { EventEmitter } from 'node:events';
|
|
10
|
-
import { PassThrough } from 'node:stream';
|
|
11
|
-
import { MCPClient } from './client.js';
|
|
12
|
-
/**
|
|
13
|
-
* Create a mock child process with controllable stdin/stdout.
|
|
14
|
-
*/
|
|
15
|
-
function createMockProcess() {
|
|
16
|
-
const proc = new EventEmitter();
|
|
17
|
-
proc.stdin = new PassThrough();
|
|
18
|
-
proc.stdout = new PassThrough();
|
|
19
|
-
proc.stderr = new PassThrough();
|
|
20
|
-
proc.killed = false;
|
|
21
|
-
proc.kill = () => { proc.killed = true; };
|
|
22
|
-
return proc;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Collect all messages written to a PassThrough stream as parsed JSON-RPC.
|
|
26
|
-
*/
|
|
27
|
-
function collectMessages(stream) {
|
|
28
|
-
const messages = [];
|
|
29
|
-
let buffer = '';
|
|
30
|
-
stream.on('data', (chunk) => {
|
|
31
|
-
buffer += chunk.toString();
|
|
32
|
-
const lines = buffer.split('\n');
|
|
33
|
-
// Keep the last incomplete line in the buffer
|
|
34
|
-
buffer = lines.pop() ?? '';
|
|
35
|
-
for (const line of lines) {
|
|
36
|
-
if (line.trim()) {
|
|
37
|
-
messages.push(JSON.parse(line));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
return messages;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Create a spawn function that returns the given mock process.
|
|
45
|
-
*/
|
|
46
|
-
function createMockSpawn(proc) {
|
|
47
|
-
return (() => proc);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Respond to pending requests on a mock process's stdout.
|
|
51
|
-
* Reads from stdin, auto-responds with matching id.
|
|
52
|
-
*/
|
|
53
|
-
function autoRespond(proc, handler) {
|
|
54
|
-
let buffer = '';
|
|
55
|
-
proc.stdin.on('data', (chunk) => {
|
|
56
|
-
buffer += chunk.toString();
|
|
57
|
-
const lines = buffer.split('\n');
|
|
58
|
-
buffer = lines.pop() ?? '';
|
|
59
|
-
for (const line of lines) {
|
|
60
|
-
if (!line.trim())
|
|
61
|
-
continue;
|
|
62
|
-
const msg = JSON.parse(line);
|
|
63
|
-
// Only respond to requests (those with an id), not notifications
|
|
64
|
-
if (msg.id !== undefined) {
|
|
65
|
-
const result = handler(msg);
|
|
66
|
-
const response = JSON.stringify({ jsonrpc: '2.0', id: msg.id, result });
|
|
67
|
-
proc.stdout.push(response + '\n');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
// =============================================================================
|
|
73
|
-
// Tests
|
|
74
|
-
// =============================================================================
|
|
75
|
-
describe('MCPClient', () => {
|
|
76
|
-
describe('construction', () => {
|
|
77
|
-
it('should create a client with name and config', () => {
|
|
78
|
-
const client = new MCPClient('test-server', {
|
|
79
|
-
command: 'npx',
|
|
80
|
-
args: ['-y', 'some-server'],
|
|
81
|
-
});
|
|
82
|
-
assert.strictEqual(client.name, 'test-server');
|
|
83
|
-
assert.strictEqual(client.connected, false);
|
|
84
|
-
});
|
|
85
|
-
it('should accept custom timeout', () => {
|
|
86
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
87
|
-
timeoutMs: 5000,
|
|
88
|
-
});
|
|
89
|
-
assert.strictEqual(client.name, 'test');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
describe('connect', () => {
|
|
93
|
-
it('should spawn the server process and perform handshake', async () => {
|
|
94
|
-
const proc = createMockProcess();
|
|
95
|
-
let spawnCalled = false;
|
|
96
|
-
let spawnCmd = '';
|
|
97
|
-
let spawnArgs = [];
|
|
98
|
-
const mockSpawn = ((cmd, args) => {
|
|
99
|
-
spawnCalled = true;
|
|
100
|
-
spawnCmd = cmd;
|
|
101
|
-
spawnArgs = args;
|
|
102
|
-
return proc;
|
|
103
|
-
});
|
|
104
|
-
autoRespond(proc, (msg) => {
|
|
105
|
-
if (msg.method === 'initialize') {
|
|
106
|
-
return { protocolVersion: '2024-11-05', capabilities: {} };
|
|
107
|
-
}
|
|
108
|
-
return {};
|
|
109
|
-
});
|
|
110
|
-
const client = new MCPClient('test', {
|
|
111
|
-
command: 'npx',
|
|
112
|
-
args: ['-y', 'test-server'],
|
|
113
|
-
}, { spawnFn: mockSpawn });
|
|
114
|
-
await client.connect();
|
|
115
|
-
assert.strictEqual(spawnCalled, true);
|
|
116
|
-
assert.strictEqual(spawnCmd, 'npx');
|
|
117
|
-
assert.deepStrictEqual(spawnArgs, ['-y', 'test-server']);
|
|
118
|
-
assert.strictEqual(client.connected, true);
|
|
119
|
-
await client.disconnect();
|
|
120
|
-
});
|
|
121
|
-
it('should throw if already connected', async () => {
|
|
122
|
-
const proc = createMockProcess();
|
|
123
|
-
autoRespond(proc, () => ({ protocolVersion: '2024-11-05', capabilities: {} }));
|
|
124
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
125
|
-
spawnFn: createMockSpawn(proc),
|
|
126
|
-
});
|
|
127
|
-
await client.connect();
|
|
128
|
-
await assert.rejects(() => client.connect(), (err) => {
|
|
129
|
-
assert.ok(err.message.includes('already connected'));
|
|
130
|
-
return true;
|
|
131
|
-
});
|
|
132
|
-
await client.disconnect();
|
|
133
|
-
});
|
|
134
|
-
it('should send initialize request with correct params', async () => {
|
|
135
|
-
const proc = createMockProcess();
|
|
136
|
-
const messages = collectMessages(proc.stdin);
|
|
137
|
-
autoRespond(proc, () => ({ protocolVersion: '2024-11-05', capabilities: {} }));
|
|
138
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
139
|
-
spawnFn: createMockSpawn(proc),
|
|
140
|
-
});
|
|
141
|
-
await client.connect();
|
|
142
|
-
// First message should be the initialize request
|
|
143
|
-
const initMsg = messages[0];
|
|
144
|
-
assert.strictEqual(initMsg.jsonrpc, '2.0');
|
|
145
|
-
assert.strictEqual(initMsg.method, 'initialize');
|
|
146
|
-
assert.strictEqual(initMsg.id, 1);
|
|
147
|
-
const params = initMsg.params;
|
|
148
|
-
assert.strictEqual(params.protocolVersion, '2024-11-05');
|
|
149
|
-
assert.deepStrictEqual(params.clientInfo, { name: 'beth-cli', version: '1.0.15' });
|
|
150
|
-
await client.disconnect();
|
|
151
|
-
});
|
|
152
|
-
it('should send initialized notification after handshake', async () => {
|
|
153
|
-
const proc = createMockProcess();
|
|
154
|
-
const messages = collectMessages(proc.stdin);
|
|
155
|
-
autoRespond(proc, () => ({ protocolVersion: '2024-11-05', capabilities: {} }));
|
|
156
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
157
|
-
spawnFn: createMockSpawn(proc),
|
|
158
|
-
});
|
|
159
|
-
await client.connect();
|
|
160
|
-
// Second message should be the initialized notification (no id)
|
|
161
|
-
const notifMsg = messages[1];
|
|
162
|
-
assert.strictEqual(notifMsg.jsonrpc, '2.0');
|
|
163
|
-
assert.strictEqual(notifMsg.method, 'notifications/initialized');
|
|
164
|
-
assert.strictEqual(notifMsg.id, undefined);
|
|
165
|
-
await client.disconnect();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
describe('request IDs', () => {
|
|
169
|
-
it('should increment request IDs', async () => {
|
|
170
|
-
const proc = createMockProcess();
|
|
171
|
-
const messages = collectMessages(proc.stdin);
|
|
172
|
-
autoRespond(proc, () => ({ protocolVersion: '2024-11-05', capabilities: {}, tools: [] }));
|
|
173
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
174
|
-
spawnFn: createMockSpawn(proc),
|
|
175
|
-
});
|
|
176
|
-
await client.connect(); // id: 1 (initialize)
|
|
177
|
-
await client.listTools(); // id: 2 (tools/list)
|
|
178
|
-
const initMsg = messages[0];
|
|
179
|
-
assert.strictEqual(initMsg.id, 1);
|
|
180
|
-
// messages[1] is the notification (no id), messages[2] is tools/list
|
|
181
|
-
const listMsg = messages[2];
|
|
182
|
-
assert.strictEqual(listMsg.id, 2);
|
|
183
|
-
await client.disconnect();
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe('listTools', () => {
|
|
187
|
-
it('should return tool info from server', async () => {
|
|
188
|
-
const proc = createMockProcess();
|
|
189
|
-
autoRespond(proc, (msg) => {
|
|
190
|
-
if (msg.method === 'initialize') {
|
|
191
|
-
return { protocolVersion: '2024-11-05', capabilities: {} };
|
|
192
|
-
}
|
|
193
|
-
if (msg.method === 'tools/list') {
|
|
194
|
-
return {
|
|
195
|
-
tools: [
|
|
196
|
-
{
|
|
197
|
-
name: 'search',
|
|
198
|
-
description: 'Search for components',
|
|
199
|
-
inputSchema: { type: 'object', properties: { query: { type: 'string' } } },
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: 'install',
|
|
203
|
-
description: 'Install a component',
|
|
204
|
-
inputSchema: { type: 'object', properties: { name: { type: 'string' } } },
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
return {};
|
|
210
|
-
});
|
|
211
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
212
|
-
spawnFn: createMockSpawn(proc),
|
|
213
|
-
});
|
|
214
|
-
await client.connect();
|
|
215
|
-
const tools = await client.listTools();
|
|
216
|
-
assert.strictEqual(tools.length, 2);
|
|
217
|
-
assert.strictEqual(tools[0].name, 'search');
|
|
218
|
-
assert.strictEqual(tools[0].description, 'Search for components');
|
|
219
|
-
assert.deepStrictEqual(tools[0].inputSchema, {
|
|
220
|
-
type: 'object',
|
|
221
|
-
properties: { query: { type: 'string' } },
|
|
222
|
-
});
|
|
223
|
-
assert.strictEqual(tools[1].name, 'install');
|
|
224
|
-
await client.disconnect();
|
|
225
|
-
});
|
|
226
|
-
it('should return empty array when server returns no tools', async () => {
|
|
227
|
-
const proc = createMockProcess();
|
|
228
|
-
autoRespond(proc, (msg) => {
|
|
229
|
-
if (msg.method === 'initialize') {
|
|
230
|
-
return { protocolVersion: '2024-11-05', capabilities: {} };
|
|
231
|
-
}
|
|
232
|
-
return { tools: [] };
|
|
233
|
-
});
|
|
234
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
235
|
-
spawnFn: createMockSpawn(proc),
|
|
236
|
-
});
|
|
237
|
-
await client.connect();
|
|
238
|
-
const tools = await client.listTools();
|
|
239
|
-
assert.deepStrictEqual(tools, []);
|
|
240
|
-
await client.disconnect();
|
|
241
|
-
});
|
|
242
|
-
it('should throw if not connected', async () => {
|
|
243
|
-
const client = new MCPClient('test', { command: 'node', args: [] });
|
|
244
|
-
await assert.rejects(() => client.listTools(), (err) => {
|
|
245
|
-
assert.ok(err.message.includes('not connected'));
|
|
246
|
-
return true;
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
describe('callTool', () => {
|
|
251
|
-
it('should send tools/call request with name and arguments', async () => {
|
|
252
|
-
const proc = createMockProcess();
|
|
253
|
-
const messages = collectMessages(proc.stdin);
|
|
254
|
-
autoRespond(proc, (msg) => {
|
|
255
|
-
if (msg.method === 'initialize') {
|
|
256
|
-
return { protocolVersion: '2024-11-05', capabilities: {} };
|
|
257
|
-
}
|
|
258
|
-
if (msg.method === 'tools/call') {
|
|
259
|
-
return { content: [{ type: 'text', text: 'result data' }] };
|
|
260
|
-
}
|
|
261
|
-
return {};
|
|
262
|
-
});
|
|
263
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
264
|
-
spawnFn: createMockSpawn(proc),
|
|
265
|
-
});
|
|
266
|
-
await client.connect();
|
|
267
|
-
const result = await client.callTool('search', { query: 'button' });
|
|
268
|
-
// Verify the request format (messages[2] is the callTool request)
|
|
269
|
-
const callMsg = messages[2];
|
|
270
|
-
assert.strictEqual(callMsg.method, 'tools/call');
|
|
271
|
-
const params = callMsg.params;
|
|
272
|
-
assert.strictEqual(params.name, 'search');
|
|
273
|
-
assert.deepStrictEqual(params.arguments, { query: 'button' });
|
|
274
|
-
// Verify the result
|
|
275
|
-
assert.deepStrictEqual(result, { content: [{ type: 'text', text: 'result data' }] });
|
|
276
|
-
await client.disconnect();
|
|
277
|
-
});
|
|
278
|
-
it('should throw if not connected', async () => {
|
|
279
|
-
const client = new MCPClient('test', { command: 'node', args: [] });
|
|
280
|
-
await assert.rejects(() => client.callTool('search', {}), (err) => {
|
|
281
|
-
assert.ok(err.message.includes('not connected'));
|
|
282
|
-
return true;
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
describe('disconnect', () => {
|
|
287
|
-
it('should set connected to false', async () => {
|
|
288
|
-
const proc = createMockProcess();
|
|
289
|
-
autoRespond(proc, () => ({ protocolVersion: '2024-11-05', capabilities: {} }));
|
|
290
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
291
|
-
spawnFn: createMockSpawn(proc),
|
|
292
|
-
});
|
|
293
|
-
await client.connect();
|
|
294
|
-
assert.strictEqual(client.connected, true);
|
|
295
|
-
await client.disconnect();
|
|
296
|
-
assert.strictEqual(client.connected, false);
|
|
297
|
-
});
|
|
298
|
-
it('should be safe to call when not connected', async () => {
|
|
299
|
-
const client = new MCPClient('test', { command: 'node', args: [] });
|
|
300
|
-
// Should not throw
|
|
301
|
-
await client.disconnect();
|
|
302
|
-
assert.strictEqual(client.connected, false);
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
describe('timeout handling', () => {
|
|
306
|
-
it('should reject request on timeout', async () => {
|
|
307
|
-
const proc = createMockProcess();
|
|
308
|
-
// Don't auto-respond to anything — let it time out
|
|
309
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
310
|
-
spawnFn: createMockSpawn(proc),
|
|
311
|
-
timeoutMs: 50, // Very short timeout for test
|
|
312
|
-
});
|
|
313
|
-
await assert.rejects(() => client.connect(), // initialize will time out
|
|
314
|
-
(err) => {
|
|
315
|
-
assert.ok(err.message.includes('timed out'));
|
|
316
|
-
return true;
|
|
317
|
-
});
|
|
318
|
-
await client.disconnect();
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
describe('error responses', () => {
|
|
322
|
-
it('should reject on JSON-RPC error response', async () => {
|
|
323
|
-
const proc = createMockProcess();
|
|
324
|
-
let initDone = false;
|
|
325
|
-
// Custom handler: respond to initialize, error on tools/list
|
|
326
|
-
let buffer = '';
|
|
327
|
-
proc.stdin.on('data', (chunk) => {
|
|
328
|
-
buffer += chunk.toString();
|
|
329
|
-
const lines = buffer.split('\n');
|
|
330
|
-
buffer = lines.pop() ?? '';
|
|
331
|
-
for (const line of lines) {
|
|
332
|
-
if (!line.trim())
|
|
333
|
-
continue;
|
|
334
|
-
const msg = JSON.parse(line);
|
|
335
|
-
if (msg.id === undefined)
|
|
336
|
-
continue;
|
|
337
|
-
if (msg.method === 'initialize') {
|
|
338
|
-
const resp = JSON.stringify({
|
|
339
|
-
jsonrpc: '2.0',
|
|
340
|
-
id: msg.id,
|
|
341
|
-
result: { protocolVersion: '2024-11-05', capabilities: {} },
|
|
342
|
-
});
|
|
343
|
-
proc.stdout.push(resp + '\n');
|
|
344
|
-
initDone = true;
|
|
345
|
-
}
|
|
346
|
-
else if (msg.method === 'tools/list' && initDone) {
|
|
347
|
-
const resp = JSON.stringify({
|
|
348
|
-
jsonrpc: '2.0',
|
|
349
|
-
id: msg.id,
|
|
350
|
-
error: { code: -32600, message: 'Not supported' },
|
|
351
|
-
});
|
|
352
|
-
proc.stdout.push(resp + '\n');
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
357
|
-
spawnFn: createMockSpawn(proc),
|
|
358
|
-
});
|
|
359
|
-
await client.connect();
|
|
360
|
-
await assert.rejects(() => client.listTools(), (err) => {
|
|
361
|
-
assert.ok(err.message.includes('Not supported'));
|
|
362
|
-
return true;
|
|
363
|
-
});
|
|
364
|
-
await client.disconnect();
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
describe('malformed responses', () => {
|
|
368
|
-
it('should ignore non-JSON lines from server', async () => {
|
|
369
|
-
const proc = createMockProcess();
|
|
370
|
-
// Send garbage before the real response
|
|
371
|
-
autoRespond(proc, (msg) => {
|
|
372
|
-
if (msg.method === 'initialize') {
|
|
373
|
-
// Push garbage first
|
|
374
|
-
proc.stdout.push('This is not JSON\n');
|
|
375
|
-
proc.stdout.push('DEBUG: server starting\n');
|
|
376
|
-
return { protocolVersion: '2024-11-05', capabilities: {} };
|
|
377
|
-
}
|
|
378
|
-
return {};
|
|
379
|
-
});
|
|
380
|
-
const client = new MCPClient('test', { command: 'node', args: [] }, {
|
|
381
|
-
spawnFn: createMockSpawn(proc),
|
|
382
|
-
});
|
|
383
|
-
// Should not throw despite garbage lines
|
|
384
|
-
await client.connect();
|
|
385
|
-
assert.strictEqual(client.connected, true);
|
|
386
|
-
await client.disconnect();
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
//# sourceMappingURL=client.test.js.map
|