@sparkleideas/testing 3.0.0-alpha.10
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 +547 -0
- package/__tests__/framework.test.ts +21 -0
- package/package.json +61 -0
- package/src/fixtures/agent-fixtures.ts +793 -0
- package/src/fixtures/agents.ts +212 -0
- package/src/fixtures/configurations.ts +491 -0
- package/src/fixtures/index.ts +21 -0
- package/src/fixtures/mcp-fixtures.ts +1030 -0
- package/src/fixtures/memory-entries.ts +328 -0
- package/src/fixtures/memory-fixtures.ts +750 -0
- package/src/fixtures/swarm-fixtures.ts +837 -0
- package/src/fixtures/tasks.ts +309 -0
- package/src/helpers/assertion-helpers.ts +616 -0
- package/src/helpers/assertions.ts +286 -0
- package/src/helpers/create-mock.ts +200 -0
- package/src/helpers/index.ts +182 -0
- package/src/helpers/mock-factory.ts +711 -0
- package/src/helpers/setup-teardown.ts +678 -0
- package/src/helpers/swarm-instance.ts +326 -0
- package/src/helpers/test-application.ts +310 -0
- package/src/helpers/test-utils.ts +670 -0
- package/src/index.ts +232 -0
- package/src/mocks/index.ts +29 -0
- package/src/mocks/mock-mcp-client.ts +723 -0
- package/src/mocks/mock-services.ts +793 -0
- package/src/regression/api-contract.ts +473 -0
- package/src/regression/index.ts +46 -0
- package/src/regression/integration-regression.ts +416 -0
- package/src/regression/performance-baseline.ts +356 -0
- package/src/regression/regression-runner.ts +339 -0
- package/src/regression/security-regression.ts +331 -0
- package/src/setup.ts +127 -0
- package/src/v2-compat/api-compat.test.ts +590 -0
- package/src/v2-compat/cli-compat.test.ts +484 -0
- package/src/v2-compat/compatibility-validator.ts +1072 -0
- package/src/v2-compat/hooks-compat.test.ts +602 -0
- package/src/v2-compat/index.ts +58 -0
- package/src/v2-compat/mcp-compat.test.ts +557 -0
- package/src/v2-compat/report-generator.ts +441 -0
- package/tmp.json +0 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sparkleideas/testing - Mock MCP Client
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive mock MCP client for testing CLI and server interactions.
|
|
5
|
+
* Simulates full MCP protocol behavior with request/response tracking.
|
|
6
|
+
*/
|
|
7
|
+
import { vi, type Mock } from 'vitest';
|
|
8
|
+
import type {
|
|
9
|
+
MCPTool,
|
|
10
|
+
MCPToolResult,
|
|
11
|
+
MCPResource,
|
|
12
|
+
MCPPrompt,
|
|
13
|
+
MCPServerConfig,
|
|
14
|
+
MCPSessionContext,
|
|
15
|
+
MCPError,
|
|
16
|
+
MCPContent,
|
|
17
|
+
mcpTools,
|
|
18
|
+
mcpResources,
|
|
19
|
+
mcpPrompts,
|
|
20
|
+
mcpToolResults,
|
|
21
|
+
mcpErrors,
|
|
22
|
+
} from '../fixtures/mcp-fixtures.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Mock MCP Client with full protocol simulation
|
|
26
|
+
*/
|
|
27
|
+
export class MockMCPClient {
|
|
28
|
+
private _connected = false;
|
|
29
|
+
private _session: MCPSessionContext | null = null;
|
|
30
|
+
private _tools = new Map<string, MCPTool>();
|
|
31
|
+
private _resources = new Map<string, MCPResource>();
|
|
32
|
+
private _prompts = new Map<string, MCPPrompt>();
|
|
33
|
+
private _requestHistory: MCPRequest[] = [];
|
|
34
|
+
private _responseHistory: MCPResponse[] = [];
|
|
35
|
+
private _toolHandlers = new Map<string, ToolHandler>();
|
|
36
|
+
private _errorSimulation: ErrorSimulation | null = null;
|
|
37
|
+
private _latencySimulation = 0;
|
|
38
|
+
|
|
39
|
+
// Mock methods for verification
|
|
40
|
+
connect = vi.fn(async () => {
|
|
41
|
+
if (this._errorSimulation?.onConnect) {
|
|
42
|
+
throw new Error(this._errorSimulation.onConnect);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await this.simulateLatency();
|
|
46
|
+
|
|
47
|
+
this._connected = true;
|
|
48
|
+
this._session = {
|
|
49
|
+
sessionId: `session-${Date.now()}`,
|
|
50
|
+
clientInfo: {
|
|
51
|
+
name: 'mock-client',
|
|
52
|
+
version: '1.0.0',
|
|
53
|
+
},
|
|
54
|
+
capabilities: {
|
|
55
|
+
tools: true,
|
|
56
|
+
resources: true,
|
|
57
|
+
prompts: true,
|
|
58
|
+
},
|
|
59
|
+
startedAt: new Date(),
|
|
60
|
+
lastActivity: new Date(),
|
|
61
|
+
requestCount: 0,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
disconnect = vi.fn(async () => {
|
|
66
|
+
if (this._errorSimulation?.onDisconnect) {
|
|
67
|
+
throw new Error(this._errorSimulation.onDisconnect);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this._connected = false;
|
|
71
|
+
this._session = null;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
callTool = vi.fn(async (name: string, params: Record<string, unknown>): Promise<MCPToolResult> => {
|
|
75
|
+
this.ensureConnected();
|
|
76
|
+
|
|
77
|
+
const request: MCPRequest = {
|
|
78
|
+
id: `req-${Date.now()}`,
|
|
79
|
+
method: 'tools/call',
|
|
80
|
+
params: { name, arguments: params },
|
|
81
|
+
timestamp: new Date(),
|
|
82
|
+
};
|
|
83
|
+
this._requestHistory.push(request);
|
|
84
|
+
|
|
85
|
+
if (this._session) {
|
|
86
|
+
this._session.requestCount++;
|
|
87
|
+
this._session.lastActivity = new Date();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await this.simulateLatency();
|
|
91
|
+
|
|
92
|
+
// Check for error simulation
|
|
93
|
+
if (this._errorSimulation?.onToolCall?.[name]) {
|
|
94
|
+
const error = this._errorSimulation.onToolCall[name];
|
|
95
|
+
const response: MCPResponse = {
|
|
96
|
+
id: request.id,
|
|
97
|
+
error: typeof error === 'string' ? { code: -32000, message: error } : error,
|
|
98
|
+
timestamp: new Date(),
|
|
99
|
+
};
|
|
100
|
+
this._responseHistory.push(response);
|
|
101
|
+
throw new MCPClientError(response.error!.message, response.error!.code);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check for custom handler
|
|
105
|
+
const handler = this._toolHandlers.get(name);
|
|
106
|
+
if (handler) {
|
|
107
|
+
const result = await handler(params);
|
|
108
|
+
const response: MCPResponse = {
|
|
109
|
+
id: request.id,
|
|
110
|
+
result,
|
|
111
|
+
timestamp: new Date(),
|
|
112
|
+
};
|
|
113
|
+
this._responseHistory.push(response);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check for registered tool
|
|
118
|
+
const tool = this._tools.get(name);
|
|
119
|
+
if (!tool) {
|
|
120
|
+
const response: MCPResponse = {
|
|
121
|
+
id: request.id,
|
|
122
|
+
error: { code: -32601, message: `Tool not found: ${name}` },
|
|
123
|
+
timestamp: new Date(),
|
|
124
|
+
};
|
|
125
|
+
this._responseHistory.push(response);
|
|
126
|
+
throw new MCPClientError(`Tool not found: ${name}`, -32601);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Default success response
|
|
130
|
+
const result: MCPToolResult = {
|
|
131
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true, tool: name, params }) }],
|
|
132
|
+
};
|
|
133
|
+
const response: MCPResponse = {
|
|
134
|
+
id: request.id,
|
|
135
|
+
result,
|
|
136
|
+
timestamp: new Date(),
|
|
137
|
+
};
|
|
138
|
+
this._responseHistory.push(response);
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
listTools = vi.fn(async (): Promise<MCPTool[]> => {
|
|
144
|
+
this.ensureConnected();
|
|
145
|
+
await this.simulateLatency();
|
|
146
|
+
return Array.from(this._tools.values());
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
readResource = vi.fn(async (uri: string): Promise<MCPResourceContent> => {
|
|
150
|
+
this.ensureConnected();
|
|
151
|
+
|
|
152
|
+
const request: MCPRequest = {
|
|
153
|
+
id: `req-${Date.now()}`,
|
|
154
|
+
method: 'resources/read',
|
|
155
|
+
params: { uri },
|
|
156
|
+
timestamp: new Date(),
|
|
157
|
+
};
|
|
158
|
+
this._requestHistory.push(request);
|
|
159
|
+
|
|
160
|
+
await this.simulateLatency();
|
|
161
|
+
|
|
162
|
+
const resource = this._resources.get(uri);
|
|
163
|
+
if (!resource) {
|
|
164
|
+
throw new MCPClientError(`Resource not found: ${uri}`, -32002);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
type: 'resource',
|
|
169
|
+
resource: {
|
|
170
|
+
uri,
|
|
171
|
+
mimeType: resource.mimeType,
|
|
172
|
+
text: JSON.stringify({ name: resource.name, description: resource.description }),
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
listResources = vi.fn(async (): Promise<MCPResource[]> => {
|
|
178
|
+
this.ensureConnected();
|
|
179
|
+
await this.simulateLatency();
|
|
180
|
+
return Array.from(this._resources.values());
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
getPrompt = vi.fn(async (name: string, args: Record<string, string>): Promise<MCPPromptResult> => {
|
|
184
|
+
this.ensureConnected();
|
|
185
|
+
|
|
186
|
+
const prompt = this._prompts.get(name);
|
|
187
|
+
if (!prompt) {
|
|
188
|
+
throw new MCPClientError(`Prompt not found: ${name}`, -32003);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await this.simulateLatency();
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
messages: [
|
|
195
|
+
{
|
|
196
|
+
role: 'user',
|
|
197
|
+
content: { type: 'text', text: `Prompt: ${name}\nArgs: ${JSON.stringify(args)}` },
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
listPrompts = vi.fn(async (): Promise<MCPPrompt[]> => {
|
|
204
|
+
this.ensureConnected();
|
|
205
|
+
await this.simulateLatency();
|
|
206
|
+
return Array.from(this._prompts.values());
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
isConnected = vi.fn(() => this._connected);
|
|
210
|
+
|
|
211
|
+
getSession = vi.fn(() => this._session);
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Register a tool
|
|
215
|
+
*/
|
|
216
|
+
registerTool(tool: MCPTool): void {
|
|
217
|
+
this._tools.set(tool.name, tool);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Register multiple tools
|
|
222
|
+
*/
|
|
223
|
+
registerTools(tools: MCPTool[]): void {
|
|
224
|
+
for (const tool of tools) {
|
|
225
|
+
this.registerTool(tool);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Register a custom tool handler
|
|
231
|
+
*/
|
|
232
|
+
setToolHandler(name: string, handler: ToolHandler): void {
|
|
233
|
+
this._toolHandlers.set(name, handler);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Register a resource
|
|
238
|
+
*/
|
|
239
|
+
registerResource(resource: MCPResource): void {
|
|
240
|
+
this._resources.set(resource.uri, resource);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Register multiple resources
|
|
245
|
+
*/
|
|
246
|
+
registerResources(resources: MCPResource[]): void {
|
|
247
|
+
for (const resource of resources) {
|
|
248
|
+
this.registerResource(resource);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Register a prompt
|
|
254
|
+
*/
|
|
255
|
+
registerPrompt(prompt: MCPPrompt): void {
|
|
256
|
+
this._prompts.set(prompt.name, prompt);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Register multiple prompts
|
|
261
|
+
*/
|
|
262
|
+
registerPrompts(prompts: MCPPrompt[]): void {
|
|
263
|
+
for (const prompt of prompts) {
|
|
264
|
+
this.registerPrompt(prompt);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Configure error simulation
|
|
270
|
+
*/
|
|
271
|
+
simulateErrors(config: ErrorSimulation): void {
|
|
272
|
+
this._errorSimulation = config;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Configure latency simulation
|
|
277
|
+
*/
|
|
278
|
+
setLatency(ms: number): void {
|
|
279
|
+
this._latencySimulation = ms;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get request history
|
|
284
|
+
*/
|
|
285
|
+
getRequestHistory(): MCPRequest[] {
|
|
286
|
+
return [...this._requestHistory];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get response history
|
|
291
|
+
*/
|
|
292
|
+
getResponseHistory(): MCPResponse[] {
|
|
293
|
+
return [...this._responseHistory];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get last request
|
|
298
|
+
*/
|
|
299
|
+
getLastRequest(): MCPRequest | undefined {
|
|
300
|
+
return this._requestHistory[this._requestHistory.length - 1];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get last response
|
|
305
|
+
*/
|
|
306
|
+
getLastResponse(): MCPResponse | undefined {
|
|
307
|
+
return this._responseHistory[this._responseHistory.length - 1];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Clear history
|
|
312
|
+
*/
|
|
313
|
+
clearHistory(): void {
|
|
314
|
+
this._requestHistory = [];
|
|
315
|
+
this._responseHistory = [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Reset client to initial state
|
|
320
|
+
*/
|
|
321
|
+
reset(): void {
|
|
322
|
+
this._connected = false;
|
|
323
|
+
this._session = null;
|
|
324
|
+
this._tools.clear();
|
|
325
|
+
this._resources.clear();
|
|
326
|
+
this._prompts.clear();
|
|
327
|
+
this._requestHistory = [];
|
|
328
|
+
this._responseHistory = [];
|
|
329
|
+
this._toolHandlers.clear();
|
|
330
|
+
this._errorSimulation = null;
|
|
331
|
+
this._latencySimulation = 0;
|
|
332
|
+
vi.clearAllMocks();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private ensureConnected(): void {
|
|
336
|
+
if (!this._connected) {
|
|
337
|
+
throw new MCPClientError('Not connected', -32000);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private async simulateLatency(): Promise<void> {
|
|
342
|
+
if (this._latencySimulation > 0) {
|
|
343
|
+
await new Promise(resolve => setTimeout(resolve, this._latencySimulation));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Mock MCP Server for testing server-side behavior
|
|
350
|
+
*/
|
|
351
|
+
export class MockMCPServer {
|
|
352
|
+
private _running = false;
|
|
353
|
+
private _config: MCPServerConfig | null = null;
|
|
354
|
+
private _tools = new Map<string, MCPTool>();
|
|
355
|
+
private _resources = new Map<string, MCPResource>();
|
|
356
|
+
private _prompts = new Map<string, MCPPrompt>();
|
|
357
|
+
private _connections: MockMCPConnection[] = [];
|
|
358
|
+
private _requestLog: MCPRequest[] = [];
|
|
359
|
+
private _errorCount = 0;
|
|
360
|
+
|
|
361
|
+
start = vi.fn(async (config: MCPServerConfig) => {
|
|
362
|
+
this._config = config;
|
|
363
|
+
this._running = true;
|
|
364
|
+
|
|
365
|
+
// Register configured tools/resources/prompts
|
|
366
|
+
if (config.tools) {
|
|
367
|
+
for (const tool of config.tools) {
|
|
368
|
+
this._tools.set(tool.name, tool);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (config.resources) {
|
|
372
|
+
for (const resource of config.resources) {
|
|
373
|
+
this._resources.set(resource.uri, resource);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (config.prompts) {
|
|
377
|
+
for (const prompt of config.prompts) {
|
|
378
|
+
this._prompts.set(prompt.name, prompt);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
stop = vi.fn(async () => {
|
|
384
|
+
for (const conn of this._connections) {
|
|
385
|
+
await conn.close();
|
|
386
|
+
}
|
|
387
|
+
this._connections = [];
|
|
388
|
+
this._running = false;
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
handleRequest = vi.fn(async (request: MCPRequest): Promise<MCPResponse> => {
|
|
392
|
+
this._requestLog.push(request);
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
switch (request.method) {
|
|
396
|
+
case 'tools/call':
|
|
397
|
+
return this.handleToolCall(request);
|
|
398
|
+
case 'resources/read':
|
|
399
|
+
return this.handleResourceRead(request);
|
|
400
|
+
case 'prompts/get':
|
|
401
|
+
return this.handlePromptGet(request);
|
|
402
|
+
case 'tools/list':
|
|
403
|
+
return { id: request.id, result: Array.from(this._tools.values()), timestamp: new Date() };
|
|
404
|
+
case 'resources/list':
|
|
405
|
+
return { id: request.id, result: Array.from(this._resources.values()), timestamp: new Date() };
|
|
406
|
+
case 'prompts/list':
|
|
407
|
+
return { id: request.id, result: Array.from(this._prompts.values()), timestamp: new Date() };
|
|
408
|
+
default:
|
|
409
|
+
throw new MCPClientError(`Unknown method: ${request.method}`, -32601);
|
|
410
|
+
}
|
|
411
|
+
} catch (error) {
|
|
412
|
+
this._errorCount++;
|
|
413
|
+
return {
|
|
414
|
+
id: request.id,
|
|
415
|
+
error: { code: -32000, message: (error as Error).message },
|
|
416
|
+
timestamp: new Date(),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
registerTool = vi.fn((tool: MCPTool) => {
|
|
422
|
+
this._tools.set(tool.name, tool);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
registerResource = vi.fn((resource: MCPResource) => {
|
|
426
|
+
this._resources.set(resource.uri, resource);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
registerPrompt = vi.fn((prompt: MCPPrompt) => {
|
|
430
|
+
this._prompts.set(prompt.name, prompt);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
getStatus = vi.fn((): MCPServerStatus => ({
|
|
434
|
+
running: this._running,
|
|
435
|
+
transport: this._config?.transport.type ?? 'stdio',
|
|
436
|
+
connectedClients: this._connections.length,
|
|
437
|
+
toolsRegistered: this._tools.size,
|
|
438
|
+
resourcesRegistered: this._resources.size,
|
|
439
|
+
promptsRegistered: this._prompts.size,
|
|
440
|
+
requestsHandled: this._requestLog.length,
|
|
441
|
+
errorsCount: this._errorCount,
|
|
442
|
+
uptime: this._running ? Date.now() : 0,
|
|
443
|
+
}));
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Simulate a client connection
|
|
447
|
+
*/
|
|
448
|
+
acceptConnection(): MockMCPConnection {
|
|
449
|
+
const conn = new MockMCPConnection(this);
|
|
450
|
+
this._connections.push(conn);
|
|
451
|
+
return conn;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get request log
|
|
456
|
+
*/
|
|
457
|
+
getRequestLog(): MCPRequest[] {
|
|
458
|
+
return [...this._requestLog];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Reset server
|
|
463
|
+
*/
|
|
464
|
+
reset(): void {
|
|
465
|
+
this._running = false;
|
|
466
|
+
this._config = null;
|
|
467
|
+
this._tools.clear();
|
|
468
|
+
this._resources.clear();
|
|
469
|
+
this._prompts.clear();
|
|
470
|
+
this._connections = [];
|
|
471
|
+
this._requestLog = [];
|
|
472
|
+
this._errorCount = 0;
|
|
473
|
+
vi.clearAllMocks();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private handleToolCall(request: MCPRequest): MCPResponse {
|
|
477
|
+
const { name, arguments: params } = request.params as { name: string; arguments: Record<string, unknown> };
|
|
478
|
+
const tool = this._tools.get(name);
|
|
479
|
+
|
|
480
|
+
if (!tool) {
|
|
481
|
+
return {
|
|
482
|
+
id: request.id,
|
|
483
|
+
error: { code: -32601, message: `Tool not found: ${name}` },
|
|
484
|
+
timestamp: new Date(),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
id: request.id,
|
|
490
|
+
result: {
|
|
491
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true, tool: name }) }],
|
|
492
|
+
},
|
|
493
|
+
timestamp: new Date(),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private handleResourceRead(request: MCPRequest): MCPResponse {
|
|
498
|
+
const { uri } = request.params as { uri: string };
|
|
499
|
+
const resource = this._resources.get(uri);
|
|
500
|
+
|
|
501
|
+
if (!resource) {
|
|
502
|
+
return {
|
|
503
|
+
id: request.id,
|
|
504
|
+
error: { code: -32002, message: `Resource not found: ${uri}` },
|
|
505
|
+
timestamp: new Date(),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
id: request.id,
|
|
511
|
+
result: {
|
|
512
|
+
type: 'resource',
|
|
513
|
+
resource: { uri, mimeType: resource.mimeType, text: '{}' },
|
|
514
|
+
},
|
|
515
|
+
timestamp: new Date(),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private handlePromptGet(request: MCPRequest): MCPResponse {
|
|
520
|
+
const { name } = request.params as { name: string };
|
|
521
|
+
const prompt = this._prompts.get(name);
|
|
522
|
+
|
|
523
|
+
if (!prompt) {
|
|
524
|
+
return {
|
|
525
|
+
id: request.id,
|
|
526
|
+
error: { code: -32003, message: `Prompt not found: ${name}` },
|
|
527
|
+
timestamp: new Date(),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
id: request.id,
|
|
533
|
+
result: {
|
|
534
|
+
messages: [{ role: 'user', content: { type: 'text', text: `Prompt: ${name}` } }],
|
|
535
|
+
},
|
|
536
|
+
timestamp: new Date(),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Mock MCP Connection
|
|
543
|
+
*/
|
|
544
|
+
export class MockMCPConnection {
|
|
545
|
+
private _open = true;
|
|
546
|
+
private _server: MockMCPServer;
|
|
547
|
+
|
|
548
|
+
constructor(server: MockMCPServer) {
|
|
549
|
+
this._server = server;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
send = vi.fn(async (request: MCPRequest): Promise<MCPResponse> => {
|
|
553
|
+
if (!this._open) {
|
|
554
|
+
throw new MCPClientError('Connection closed', -32000);
|
|
555
|
+
}
|
|
556
|
+
return this._server.handleRequest(request);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
close = vi.fn(async () => {
|
|
560
|
+
this._open = false;
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
isOpen(): boolean {
|
|
564
|
+
return this._open;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* MCP Client Error
|
|
570
|
+
*/
|
|
571
|
+
export class MCPClientError extends Error {
|
|
572
|
+
constructor(message: string, public code: number) {
|
|
573
|
+
super(message);
|
|
574
|
+
this.name = 'MCPClientError';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Supporting types
|
|
579
|
+
interface MCPRequest {
|
|
580
|
+
id: string;
|
|
581
|
+
method: string;
|
|
582
|
+
params?: Record<string, unknown>;
|
|
583
|
+
timestamp: Date;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
interface MCPResponse {
|
|
587
|
+
id: string;
|
|
588
|
+
result?: unknown;
|
|
589
|
+
error?: MCPError;
|
|
590
|
+
timestamp: Date;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
interface MCPResourceContent {
|
|
594
|
+
type: 'resource';
|
|
595
|
+
resource: {
|
|
596
|
+
uri: string;
|
|
597
|
+
mimeType?: string;
|
|
598
|
+
text?: string;
|
|
599
|
+
blob?: string;
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
interface MCPPromptResult {
|
|
604
|
+
messages: Array<{
|
|
605
|
+
role: 'user' | 'assistant';
|
|
606
|
+
content: MCPContent;
|
|
607
|
+
}>;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
interface MCPServerStatus {
|
|
611
|
+
running: boolean;
|
|
612
|
+
transport: string;
|
|
613
|
+
connectedClients: number;
|
|
614
|
+
toolsRegistered: number;
|
|
615
|
+
resourcesRegistered: number;
|
|
616
|
+
promptsRegistered: number;
|
|
617
|
+
requestsHandled: number;
|
|
618
|
+
errorsCount: number;
|
|
619
|
+
uptime: number;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
type ToolHandler = (params: Record<string, unknown>) => Promise<MCPToolResult>;
|
|
623
|
+
|
|
624
|
+
interface ErrorSimulation {
|
|
625
|
+
onConnect?: string;
|
|
626
|
+
onDisconnect?: string;
|
|
627
|
+
onToolCall?: Record<string, string | MCPError>;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Create a pre-configured mock MCP client with standard tools
|
|
632
|
+
*/
|
|
633
|
+
export function createStandardMockMCPClient(): MockMCPClient {
|
|
634
|
+
const client = new MockMCPClient();
|
|
635
|
+
|
|
636
|
+
// Register standard Claude-Flow tools
|
|
637
|
+
client.registerTool({
|
|
638
|
+
name: 'swarm_init',
|
|
639
|
+
description: 'Initialize a new swarm',
|
|
640
|
+
inputSchema: {
|
|
641
|
+
type: 'object',
|
|
642
|
+
properties: {
|
|
643
|
+
topology: { type: 'string' },
|
|
644
|
+
maxAgents: { type: 'number' },
|
|
645
|
+
},
|
|
646
|
+
required: ['topology'],
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
client.registerTool({
|
|
651
|
+
name: 'agent_spawn',
|
|
652
|
+
description: 'Spawn a new agent',
|
|
653
|
+
inputSchema: {
|
|
654
|
+
type: 'object',
|
|
655
|
+
properties: {
|
|
656
|
+
type: { type: 'string' },
|
|
657
|
+
name: { type: 'string' },
|
|
658
|
+
},
|
|
659
|
+
required: ['type'],
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
client.registerTool({
|
|
664
|
+
name: 'task_orchestrate',
|
|
665
|
+
description: 'Orchestrate a task',
|
|
666
|
+
inputSchema: {
|
|
667
|
+
type: 'object',
|
|
668
|
+
properties: {
|
|
669
|
+
taskName: { type: 'string' },
|
|
670
|
+
taskType: { type: 'string' },
|
|
671
|
+
},
|
|
672
|
+
required: ['taskName', 'taskType'],
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
client.registerTool({
|
|
677
|
+
name: 'memory_store',
|
|
678
|
+
description: 'Store a value in memory',
|
|
679
|
+
inputSchema: {
|
|
680
|
+
type: 'object',
|
|
681
|
+
properties: {
|
|
682
|
+
key: { type: 'string' },
|
|
683
|
+
value: { type: 'object' },
|
|
684
|
+
},
|
|
685
|
+
required: ['key', 'value'],
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
client.registerTool({
|
|
690
|
+
name: 'memory_search',
|
|
691
|
+
description: 'Search memory',
|
|
692
|
+
inputSchema: {
|
|
693
|
+
type: 'object',
|
|
694
|
+
properties: {
|
|
695
|
+
query: { type: 'string' },
|
|
696
|
+
topK: { type: 'number' },
|
|
697
|
+
},
|
|
698
|
+
required: ['query'],
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
return client;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Create a mock MCP client that simulates failures
|
|
707
|
+
*/
|
|
708
|
+
export function createFailingMockMCPClient(
|
|
709
|
+
errorConfig: ErrorSimulation
|
|
710
|
+
): MockMCPClient {
|
|
711
|
+
const client = new MockMCPClient();
|
|
712
|
+
client.simulateErrors(errorConfig);
|
|
713
|
+
return client;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Create a mock MCP client with latency
|
|
718
|
+
*/
|
|
719
|
+
export function createSlowMockMCPClient(latencyMs: number): MockMCPClient {
|
|
720
|
+
const client = new MockMCPClient();
|
|
721
|
+
client.setLatency(latencyMs);
|
|
722
|
+
return client;
|
|
723
|
+
}
|