ai-cli-mcp 2.2.0 → 2.3.1
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/.claude/settings.local.json +4 -2
- package/dist/__tests__/e2e.test.js +32 -38
- package/dist/__tests__/edge-cases.test.js +12 -12
- package/dist/__tests__/error-cases.test.js +17 -22
- package/dist/__tests__/process-management.test.js +46 -48
- package/dist/__tests__/server.test.js +51 -35
- package/dist/__tests__/validation.test.js +48 -25
- package/dist/__tests__/version-print.test.js +5 -5
- package/dist/__tests__/wait.test.js +229 -0
- package/dist/server.js +100 -13
- package/package.json +1 -1
- package/src/__tests__/e2e.test.ts +35 -43
- package/src/__tests__/edge-cases.test.ts +12 -12
- package/src/__tests__/error-cases.test.ts +17 -24
- package/src/__tests__/process-management.test.ts +54 -56
- package/src/__tests__/server.test.ts +44 -32
- package/src/__tests__/validation.test.ts +60 -38
- package/src/__tests__/version-print.test.ts +5 -5
- package/src/__tests__/wait.test.ts +264 -0
- package/src/server.ts +114 -14
- package/data/rooms/refactor-haiku-alias-main/messages.jsonl +0 -5
- package/data/rooms/refactor-haiku-alias-main/presence.json +0 -20
- package/data/rooms.json +0 -10
- package/hello.txt +0 -3
- package/implementation-log.md +0 -110
- package/implementation-plan.md +0 -189
- package/investigation-report.md +0 -135
- package/quality-score.json +0 -47
- package/refactoring-requirements.md +0 -25
- package/review-report.md +0 -132
- package/test-results.md +0 -119
- package/xx.txt +0 -1
|
@@ -76,7 +76,7 @@ describe('Process Management Tests', () => {
|
|
|
76
76
|
const server = new ClaudeCodeServer();
|
|
77
77
|
return { server, module, handlers };
|
|
78
78
|
}
|
|
79
|
-
describe('
|
|
79
|
+
describe('run tool with PID return', () => {
|
|
80
80
|
it('should return PID immediately when starting a process', async () => {
|
|
81
81
|
const { handlers } = await setupServer();
|
|
82
82
|
// Create a mock process
|
|
@@ -89,7 +89,7 @@ describe('Process Management Tests', () => {
|
|
|
89
89
|
const callToolHandler = handlers.get('callTool');
|
|
90
90
|
const result = await callToolHandler({
|
|
91
91
|
params: {
|
|
92
|
-
name: '
|
|
92
|
+
name: 'run',
|
|
93
93
|
arguments: {
|
|
94
94
|
prompt: 'test prompt',
|
|
95
95
|
workFolder: '/tmp'
|
|
@@ -99,7 +99,7 @@ describe('Process Management Tests', () => {
|
|
|
99
99
|
const response = JSON.parse(result.content[0].text);
|
|
100
100
|
expect(response.pid).toBe(12345);
|
|
101
101
|
expect(response.status).toBe('started');
|
|
102
|
-
expect(response.message).toBe('
|
|
102
|
+
expect(response.message).toBe('claude process started successfully');
|
|
103
103
|
});
|
|
104
104
|
it('should handle process with model parameter', async () => {
|
|
105
105
|
const { handlers } = await setupServer();
|
|
@@ -112,7 +112,7 @@ describe('Process Management Tests', () => {
|
|
|
112
112
|
const callToolHandler = handlers.get('callTool');
|
|
113
113
|
await callToolHandler({
|
|
114
114
|
params: {
|
|
115
|
-
name: '
|
|
115
|
+
name: 'run',
|
|
116
116
|
arguments: {
|
|
117
117
|
prompt: 'test prompt',
|
|
118
118
|
workFolder: '/tmp',
|
|
@@ -137,7 +137,7 @@ describe('Process Management Tests', () => {
|
|
|
137
137
|
const callToolHandler = handlers.get('callTool');
|
|
138
138
|
const result = await callToolHandler({
|
|
139
139
|
params: {
|
|
140
|
-
name: '
|
|
140
|
+
name: 'run',
|
|
141
141
|
arguments: {
|
|
142
142
|
prompt: japanesePrompt,
|
|
143
143
|
workFolder: '/tmp'
|
|
@@ -152,7 +152,7 @@ describe('Process Management Tests', () => {
|
|
|
152
152
|
// Verify the prompt is stored correctly in process manager
|
|
153
153
|
const getResult = await callToolHandler({
|
|
154
154
|
params: {
|
|
155
|
-
name: '
|
|
155
|
+
name: 'get_result',
|
|
156
156
|
arguments: {
|
|
157
157
|
pid: 12360
|
|
158
158
|
}
|
|
@@ -197,7 +197,7 @@ describe('Process Management Tests', () => {
|
|
|
197
197
|
const callToolHandler = handlers.get('callTool');
|
|
198
198
|
const result = await callToolHandler({
|
|
199
199
|
params: {
|
|
200
|
-
name: '
|
|
200
|
+
name: 'run',
|
|
201
201
|
arguments: {
|
|
202
202
|
prompt: longJapanesePrompt,
|
|
203
203
|
workFolder: '/tmp',
|
|
@@ -210,18 +210,18 @@ describe('Process Management Tests', () => {
|
|
|
210
210
|
expect(response.pid).toBe(12361);
|
|
211
211
|
// Verify spawn was called with the complete long prompt
|
|
212
212
|
expect(mockSpawn).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining(['-p', longJapanesePrompt]), expect.any(Object));
|
|
213
|
-
//
|
|
213
|
+
// Verify list_processes returns basic info
|
|
214
214
|
const listResult = await callToolHandler({
|
|
215
215
|
params: {
|
|
216
|
-
name: '
|
|
216
|
+
name: 'list_processes',
|
|
217
217
|
arguments: {}
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
220
|
const processes = JSON.parse(listResult.content[0].text);
|
|
221
221
|
const process = processes.find((p) => p.pid === 12361);
|
|
222
|
-
expect(process.
|
|
223
|
-
expect(process.
|
|
224
|
-
expect(process.
|
|
222
|
+
expect(process.pid).toBe(12361);
|
|
223
|
+
expect(process.agent).toBe('claude');
|
|
224
|
+
expect(process.status).toBe('running');
|
|
225
225
|
});
|
|
226
226
|
it('should handle prompts with special characters and escape sequences', async () => {
|
|
227
227
|
const { handlers } = await setupServer();
|
|
@@ -242,7 +242,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
242
242
|
const callToolHandler = handlers.get('callTool');
|
|
243
243
|
const result = await callToolHandler({
|
|
244
244
|
params: {
|
|
245
|
-
name: '
|
|
245
|
+
name: 'run',
|
|
246
246
|
arguments: {
|
|
247
247
|
prompt: specialPrompt,
|
|
248
248
|
workFolder: '/tmp'
|
|
@@ -265,16 +265,16 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
265
265
|
const callToolHandler = handlers.get('callTool');
|
|
266
266
|
await expect(callToolHandler({
|
|
267
267
|
params: {
|
|
268
|
-
name: '
|
|
268
|
+
name: 'run',
|
|
269
269
|
arguments: {
|
|
270
270
|
prompt: 'test prompt',
|
|
271
271
|
workFolder: '/tmp/test'
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
|
-
})).rejects.toThrow('Failed to start
|
|
274
|
+
})).rejects.toThrow('Failed to start claude CLI process');
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
|
-
describe('
|
|
277
|
+
describe('list_processes tool', () => {
|
|
278
278
|
it('should list all processes', async () => {
|
|
279
279
|
const { handlers } = await setupServer();
|
|
280
280
|
// Start a process first
|
|
@@ -288,7 +288,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
288
288
|
// Start a process
|
|
289
289
|
await callToolHandler({
|
|
290
290
|
params: {
|
|
291
|
-
name: '
|
|
291
|
+
name: 'run',
|
|
292
292
|
arguments: {
|
|
293
293
|
prompt: 'test prompt for listing',
|
|
294
294
|
workFolder: '/tmp',
|
|
@@ -305,7 +305,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
305
305
|
// List processes
|
|
306
306
|
const listResult = await callToolHandler({
|
|
307
307
|
params: {
|
|
308
|
-
name: '
|
|
308
|
+
name: 'list_processes',
|
|
309
309
|
arguments: {}
|
|
310
310
|
}
|
|
311
311
|
});
|
|
@@ -313,11 +313,9 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
313
313
|
expect(processes).toHaveLength(1);
|
|
314
314
|
expect(processes[0].pid).toBe(12347);
|
|
315
315
|
expect(processes[0].status).toBe('running');
|
|
316
|
-
expect(processes[0].
|
|
317
|
-
expect(processes[0].model).toBe('sonnet');
|
|
318
|
-
expect(processes[0].session_id).toBe('list-test-session-789');
|
|
316
|
+
expect(processes[0].agent).toBe('claude');
|
|
319
317
|
});
|
|
320
|
-
it('should
|
|
318
|
+
it('should list process with correct agent type', async () => {
|
|
321
319
|
const { handlers } = await setupServer();
|
|
322
320
|
const mockProcess = new EventEmitter();
|
|
323
321
|
mockProcess.pid = 12348;
|
|
@@ -326,13 +324,12 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
326
324
|
mockProcess.kill = vi.fn();
|
|
327
325
|
mockSpawn.mockReturnValue(mockProcess);
|
|
328
326
|
const callToolHandler = handlers.get('callTool');
|
|
329
|
-
// Start a process
|
|
330
|
-
const longPrompt = 'a'.repeat(150);
|
|
327
|
+
// Start a process
|
|
331
328
|
await callToolHandler({
|
|
332
329
|
params: {
|
|
333
|
-
name: '
|
|
330
|
+
name: 'run',
|
|
334
331
|
arguments: {
|
|
335
|
-
prompt:
|
|
332
|
+
prompt: 'test prompt',
|
|
336
333
|
workFolder: '/tmp'
|
|
337
334
|
}
|
|
338
335
|
}
|
|
@@ -340,16 +337,17 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
340
337
|
// List processes
|
|
341
338
|
const listResult = await callToolHandler({
|
|
342
339
|
params: {
|
|
343
|
-
name: '
|
|
340
|
+
name: 'list_processes',
|
|
344
341
|
arguments: {}
|
|
345
342
|
}
|
|
346
343
|
});
|
|
347
344
|
const processes = JSON.parse(listResult.content[0].text);
|
|
348
|
-
expect(processes[0].
|
|
349
|
-
expect(processes[0].
|
|
345
|
+
expect(processes[0].pid).toBe(12348);
|
|
346
|
+
expect(processes[0].agent).toBe('claude');
|
|
347
|
+
expect(processes[0].status).toBe('running');
|
|
350
348
|
});
|
|
351
349
|
});
|
|
352
|
-
describe('
|
|
350
|
+
describe('get_result tool', () => {
|
|
353
351
|
it('should get process output', async () => {
|
|
354
352
|
const { handlers } = await setupServer();
|
|
355
353
|
const mockProcess = new EventEmitter();
|
|
@@ -362,7 +360,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
362
360
|
// Start a process
|
|
363
361
|
await callToolHandler({
|
|
364
362
|
params: {
|
|
365
|
-
name: '
|
|
363
|
+
name: 'run',
|
|
366
364
|
arguments: {
|
|
367
365
|
prompt: 'test prompt',
|
|
368
366
|
workFolder: '/tmp'
|
|
@@ -380,7 +378,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
380
378
|
// Get result
|
|
381
379
|
const result = await callToolHandler({
|
|
382
380
|
params: {
|
|
383
|
-
name: '
|
|
381
|
+
name: 'get_result',
|
|
384
382
|
arguments: {
|
|
385
383
|
pid: 12349
|
|
386
384
|
}
|
|
@@ -389,7 +387,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
389
387
|
const processInfo = JSON.parse(result.content[0].text);
|
|
390
388
|
expect(processInfo.pid).toBe(12349);
|
|
391
389
|
expect(processInfo.status).toBe('running');
|
|
392
|
-
expect(processInfo.
|
|
390
|
+
expect(processInfo.agentOutput).toEqual(claudeJsonOutput);
|
|
393
391
|
expect(processInfo.session_id).toBe('test-session-123');
|
|
394
392
|
});
|
|
395
393
|
it('should show completed status when process exits', async () => {
|
|
@@ -404,7 +402,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
404
402
|
// Start a process
|
|
405
403
|
await callToolHandler({
|
|
406
404
|
params: {
|
|
407
|
-
name: '
|
|
405
|
+
name: 'run',
|
|
408
406
|
arguments: {
|
|
409
407
|
prompt: 'test prompt',
|
|
410
408
|
workFolder: '/tmp'
|
|
@@ -423,7 +421,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
423
421
|
// Get result
|
|
424
422
|
const result = await callToolHandler({
|
|
425
423
|
params: {
|
|
426
|
-
name: '
|
|
424
|
+
name: 'get_result',
|
|
427
425
|
arguments: {
|
|
428
426
|
pid: 12350
|
|
429
427
|
}
|
|
@@ -432,7 +430,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
432
430
|
const processInfo = JSON.parse(result.content[0].text);
|
|
433
431
|
expect(processInfo.status).toBe('completed');
|
|
434
432
|
expect(processInfo.exitCode).toBe(0);
|
|
435
|
-
expect(processInfo.
|
|
433
|
+
expect(processInfo.agentOutput).toEqual(completedJsonOutput);
|
|
436
434
|
expect(processInfo.session_id).toBe('completed-session-456');
|
|
437
435
|
});
|
|
438
436
|
it('should throw error for non-existent PID', async () => {
|
|
@@ -440,7 +438,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
440
438
|
const callToolHandler = handlers.get('callTool');
|
|
441
439
|
await expect(callToolHandler({
|
|
442
440
|
params: {
|
|
443
|
-
name: '
|
|
441
|
+
name: 'get_result',
|
|
444
442
|
arguments: {
|
|
445
443
|
pid: 99999
|
|
446
444
|
}
|
|
@@ -452,7 +450,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
452
450
|
const callToolHandler = handlers.get('callTool');
|
|
453
451
|
await expect(callToolHandler({
|
|
454
452
|
params: {
|
|
455
|
-
name: '
|
|
453
|
+
name: 'get_result',
|
|
456
454
|
arguments: {
|
|
457
455
|
pid: 'not-a-number'
|
|
458
456
|
}
|
|
@@ -471,7 +469,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
471
469
|
// Start a process
|
|
472
470
|
await callToolHandler({
|
|
473
471
|
params: {
|
|
474
|
-
name: '
|
|
472
|
+
name: 'run',
|
|
475
473
|
arguments: {
|
|
476
474
|
prompt: 'test prompt',
|
|
477
475
|
workFolder: '/tmp'
|
|
@@ -484,7 +482,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
484
482
|
// Get result
|
|
485
483
|
const result = await callToolHandler({
|
|
486
484
|
params: {
|
|
487
|
-
name: '
|
|
485
|
+
name: 'get_result',
|
|
488
486
|
arguments: {
|
|
489
487
|
pid: 12355
|
|
490
488
|
}
|
|
@@ -499,7 +497,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
499
497
|
expect(processInfo.session_id).toBeUndefined();
|
|
500
498
|
});
|
|
501
499
|
});
|
|
502
|
-
describe('
|
|
500
|
+
describe('kill_process tool', () => {
|
|
503
501
|
it('should kill a running process', async () => {
|
|
504
502
|
const { handlers } = await setupServer();
|
|
505
503
|
const mockProcess = new EventEmitter();
|
|
@@ -512,7 +510,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
512
510
|
// Start a process
|
|
513
511
|
await callToolHandler({
|
|
514
512
|
params: {
|
|
515
|
-
name: '
|
|
513
|
+
name: 'run',
|
|
516
514
|
arguments: {
|
|
517
515
|
prompt: 'test prompt',
|
|
518
516
|
workFolder: '/tmp'
|
|
@@ -522,7 +520,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
522
520
|
// Kill the process
|
|
523
521
|
const killResult = await callToolHandler({
|
|
524
522
|
params: {
|
|
525
|
-
name: '
|
|
523
|
+
name: 'kill_process',
|
|
526
524
|
arguments: {
|
|
527
525
|
pid: 12351
|
|
528
526
|
}
|
|
@@ -545,7 +543,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
545
543
|
// Start and complete a process
|
|
546
544
|
await callToolHandler({
|
|
547
545
|
params: {
|
|
548
|
-
name: '
|
|
546
|
+
name: 'run',
|
|
549
547
|
arguments: {
|
|
550
548
|
prompt: 'test prompt',
|
|
551
549
|
workFolder: '/tmp'
|
|
@@ -557,7 +555,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
557
555
|
// Try to kill the already completed process
|
|
558
556
|
const killResult = await callToolHandler({
|
|
559
557
|
params: {
|
|
560
|
-
name: '
|
|
558
|
+
name: 'kill_process',
|
|
561
559
|
arguments: {
|
|
562
560
|
pid: 12352
|
|
563
561
|
}
|
|
@@ -573,7 +571,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
573
571
|
const callToolHandler = handlers.get('callTool');
|
|
574
572
|
await expect(callToolHandler({
|
|
575
573
|
params: {
|
|
576
|
-
name: '
|
|
574
|
+
name: 'kill_process',
|
|
577
575
|
arguments: {
|
|
578
576
|
pid: 99999
|
|
579
577
|
}
|
|
@@ -606,7 +604,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
606
604
|
// Start a process
|
|
607
605
|
await callToolHandler({
|
|
608
606
|
params: {
|
|
609
|
-
name: '
|
|
607
|
+
name: 'run',
|
|
610
608
|
arguments: {
|
|
611
609
|
prompt: 'test prompt',
|
|
612
610
|
workFolder: '/tmp'
|
|
@@ -618,7 +616,7 @@ Unicodeテスト: 🎌 🗾 ✨
|
|
|
618
616
|
// Get result to check error was recorded
|
|
619
617
|
const result = await callToolHandler({
|
|
620
618
|
params: {
|
|
621
|
-
name: '
|
|
619
|
+
name: 'get_result',
|
|
622
620
|
arguments: {
|
|
623
621
|
pid: 12353
|
|
624
622
|
}
|
|
@@ -23,11 +23,13 @@ vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
|
|
23
23
|
MethodNotFound: 'MethodNotFound',
|
|
24
24
|
InvalidParams: 'InvalidParams'
|
|
25
25
|
},
|
|
26
|
-
McpError:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
McpError: class extends Error {
|
|
27
|
+
code;
|
|
28
|
+
constructor(code, message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
31
33
|
}));
|
|
32
34
|
vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
|
|
33
35
|
Server: vi.fn().mockImplementation(function () {
|
|
@@ -350,12 +352,14 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
350
352
|
// Test the handler
|
|
351
353
|
const handler = listToolsCall[1];
|
|
352
354
|
const result = await handler();
|
|
353
|
-
expect(result.tools).toHaveLength(
|
|
354
|
-
expect(result.tools[0].name).toBe('
|
|
355
|
-
expect(result.tools[0].description).toContain('
|
|
356
|
-
expect(result.tools[1].name).toBe('
|
|
357
|
-
expect(result.tools[2].name).toBe('
|
|
358
|
-
expect(result.tools[3].name).toBe('
|
|
355
|
+
expect(result.tools).toHaveLength(6);
|
|
356
|
+
expect(result.tools[0].name).toBe('run');
|
|
357
|
+
expect(result.tools[0].description).toContain('AI Agent Runner');
|
|
358
|
+
expect(result.tools[1].name).toBe('list_processes');
|
|
359
|
+
expect(result.tools[2].name).toBe('get_result');
|
|
360
|
+
expect(result.tools[3].name).toBe('wait');
|
|
361
|
+
expect(result.tools[4].name).toBe('kill_process');
|
|
362
|
+
expect(result.tools[5].name).toBe('cleanup_processes');
|
|
359
363
|
});
|
|
360
364
|
it('should handle CallToolRequest', async () => {
|
|
361
365
|
mockHomedir.mockReturnValue('/home/user');
|
|
@@ -383,14 +387,14 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
383
387
|
const handler = callToolCall[1];
|
|
384
388
|
const result = await handler({
|
|
385
389
|
params: {
|
|
386
|
-
name: '
|
|
390
|
+
name: 'run',
|
|
387
391
|
arguments: {
|
|
388
392
|
prompt: 'test prompt',
|
|
389
393
|
workFolder: '/tmp'
|
|
390
394
|
}
|
|
391
395
|
}
|
|
392
396
|
});
|
|
393
|
-
//
|
|
397
|
+
// run now returns PID immediately
|
|
394
398
|
expect(result.content[0].type).toBe('text');
|
|
395
399
|
const response = JSON.parse(result.content[0].text);
|
|
396
400
|
expect(response.pid).toBe(12345);
|
|
@@ -410,14 +414,20 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
410
414
|
const callToolCall = mockServerInstance.setRequestHandler.mock.calls.find((call) => call[0].name === 'callTool');
|
|
411
415
|
const handler = callToolCall[1];
|
|
412
416
|
// Test missing workFolder
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
417
|
+
try {
|
|
418
|
+
await handler({
|
|
419
|
+
params: {
|
|
420
|
+
name: 'run',
|
|
421
|
+
arguments: {
|
|
422
|
+
prompt: 'test'
|
|
423
|
+
}
|
|
418
424
|
}
|
|
419
|
-
}
|
|
420
|
-
|
|
425
|
+
});
|
|
426
|
+
expect.fail('Should have thrown');
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
expect(error.message).toContain('Missing or invalid required parameter: workFolder');
|
|
430
|
+
}
|
|
421
431
|
});
|
|
422
432
|
it('should handle non-existent workFolder', async () => {
|
|
423
433
|
mockHomedir.mockReturnValue('/home/user');
|
|
@@ -440,15 +450,21 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
440
450
|
const callToolCall = mockServerInstance.setRequestHandler.mock.calls.find((call) => call[0].name === 'callTool');
|
|
441
451
|
const handler = callToolCall[1];
|
|
442
452
|
// Should throw error for non-existent workFolder
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
453
|
+
try {
|
|
454
|
+
await handler({
|
|
455
|
+
params: {
|
|
456
|
+
name: 'run',
|
|
457
|
+
arguments: {
|
|
458
|
+
prompt: 'test',
|
|
459
|
+
workFolder: '/nonexistent'
|
|
460
|
+
}
|
|
449
461
|
}
|
|
450
|
-
}
|
|
451
|
-
|
|
462
|
+
});
|
|
463
|
+
expect.fail('Should have thrown');
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
expect(error.message).toContain('Working folder does not exist');
|
|
467
|
+
}
|
|
452
468
|
});
|
|
453
469
|
it('should handle session_id parameter', async () => {
|
|
454
470
|
mockHomedir.mockReturnValue('/home/user');
|
|
@@ -474,7 +490,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
474
490
|
mockSpawn.mockReturnValue(mockProcess);
|
|
475
491
|
const result = await handler({
|
|
476
492
|
params: {
|
|
477
|
-
name: '
|
|
493
|
+
name: 'run',
|
|
478
494
|
arguments: {
|
|
479
495
|
prompt: 'test prompt',
|
|
480
496
|
workFolder: '/tmp',
|
|
@@ -523,7 +539,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
523
539
|
mockSpawn.mockReturnValue(mockProcess);
|
|
524
540
|
const result = await handler({
|
|
525
541
|
params: {
|
|
526
|
-
name: '
|
|
542
|
+
name: 'run',
|
|
527
543
|
arguments: {
|
|
528
544
|
prompt_file: '/tmp/prompt.txt',
|
|
529
545
|
workFolder: '/tmp'
|
|
@@ -533,7 +549,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
533
549
|
// Verify file was read and spawn was called with content
|
|
534
550
|
expect(mockSpawn).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining(['-p', 'Content from file']), expect.any(Object));
|
|
535
551
|
});
|
|
536
|
-
it('should resolve model aliases when calling
|
|
552
|
+
it('should resolve model aliases when calling run tool', async () => {
|
|
537
553
|
mockHomedir.mockReturnValue('/home/user');
|
|
538
554
|
mockExistsSync.mockReturnValue(true);
|
|
539
555
|
// Set up spawn mock to return a process
|
|
@@ -555,7 +571,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
555
571
|
// Test with haiku alias
|
|
556
572
|
const result = await handler({
|
|
557
573
|
params: {
|
|
558
|
-
name: '
|
|
574
|
+
name: 'run',
|
|
559
575
|
arguments: {
|
|
560
576
|
prompt: 'test prompt',
|
|
561
577
|
workFolder: '/tmp',
|
|
@@ -590,7 +606,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
590
606
|
// Test with non-alias model name
|
|
591
607
|
const result = await handler({
|
|
592
608
|
params: {
|
|
593
|
-
name: '
|
|
609
|
+
name: 'run',
|
|
594
610
|
arguments: {
|
|
595
611
|
prompt: 'test prompt',
|
|
596
612
|
workFolder: '/tmp',
|
|
@@ -618,7 +634,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
618
634
|
try {
|
|
619
635
|
await handler({
|
|
620
636
|
params: {
|
|
621
|
-
name: '
|
|
637
|
+
name: 'run',
|
|
622
638
|
arguments: {
|
|
623
639
|
prompt: 'test prompt',
|
|
624
640
|
prompt_file: '/tmp/prompt.txt',
|
|
@@ -649,7 +665,7 @@ describe('ClaudeCodeServer Unit Tests', () => {
|
|
|
649
665
|
try {
|
|
650
666
|
await handler({
|
|
651
667
|
params: {
|
|
652
|
-
name: '
|
|
668
|
+
name: 'run',
|
|
653
669
|
arguments: {
|
|
654
670
|
workFolder: '/tmp'
|
|
655
671
|
}
|
|
@@ -31,11 +31,14 @@ vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
|
|
31
31
|
MethodNotFound: 'MethodNotFound',
|
|
32
32
|
InvalidParams: 'InvalidParams'
|
|
33
33
|
},
|
|
34
|
-
McpError:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
McpError: class McpError extends Error {
|
|
35
|
+
code;
|
|
36
|
+
constructor(code, message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.name = 'McpError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
39
42
|
}));
|
|
40
43
|
const mockExistsSync = vi.mocked(existsSync);
|
|
41
44
|
const mockHomedir = vi.mocked(homedir);
|
|
@@ -164,21 +167,49 @@ describe('Argument Validation Tests', () => {
|
|
|
164
167
|
});
|
|
165
168
|
});
|
|
166
169
|
describe('Runtime Argument Validation', () => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
let handlers;
|
|
171
|
+
let mockServerInstance;
|
|
172
|
+
async function setupServer() {
|
|
173
|
+
// Reset modules to ensure fresh import
|
|
174
|
+
vi.resetModules();
|
|
175
|
+
// Re-setup mocks after reset
|
|
176
|
+
const { existsSync } = await import('node:fs');
|
|
177
|
+
const { homedir } = await import('node:os');
|
|
178
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
179
|
+
vi.mocked(homedir).mockReturnValue('/home/user');
|
|
180
|
+
const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
|
|
181
|
+
vi.mocked(Server).mockImplementation(() => {
|
|
182
|
+
mockServerInstance = {
|
|
183
|
+
setRequestHandler: vi.fn((schema, handler) => {
|
|
184
|
+
handlers.set(schema.name, handler);
|
|
185
|
+
}),
|
|
186
|
+
connect: vi.fn(),
|
|
187
|
+
close: vi.fn(),
|
|
188
|
+
onerror: undefined
|
|
189
|
+
};
|
|
190
|
+
return mockServerInstance;
|
|
191
|
+
});
|
|
171
192
|
const module = await import('../server.js');
|
|
172
193
|
// @ts-ignore
|
|
173
194
|
const { ClaudeCodeServer } = module;
|
|
174
195
|
const server = new ClaudeCodeServer();
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
return { server, handlers };
|
|
197
|
+
}
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
handlers = new Map();
|
|
200
|
+
// Re-setup mocks after vi.resetModules() in outer beforeEach
|
|
201
|
+
mockHomedir.mockReturnValue('/home/user');
|
|
202
|
+
mockExistsSync.mockReturnValue(true);
|
|
203
|
+
});
|
|
204
|
+
it('should validate workFolder is a string when provided', async () => {
|
|
205
|
+
mockHomedir.mockReturnValue('/home/user');
|
|
206
|
+
mockExistsSync.mockReturnValue(true);
|
|
207
|
+
await setupServer();
|
|
208
|
+
const handler = handlers.get('callTool');
|
|
178
209
|
// Test with non-string workFolder
|
|
179
210
|
await expect(handler({
|
|
180
211
|
params: {
|
|
181
|
-
name: '
|
|
212
|
+
name: 'run',
|
|
182
213
|
arguments: {
|
|
183
214
|
prompt: 'test',
|
|
184
215
|
workFolder: 123 // Invalid type
|
|
@@ -187,26 +218,18 @@ describe('Argument Validation Tests', () => {
|
|
|
187
218
|
})).rejects.toThrow();
|
|
188
219
|
});
|
|
189
220
|
it('should reject empty string prompt', async () => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
setupServerMock();
|
|
193
|
-
const module = await import('../server.js');
|
|
194
|
-
// @ts-ignore
|
|
195
|
-
const { ClaudeCodeServer } = module;
|
|
196
|
-
const server = new ClaudeCodeServer();
|
|
197
|
-
const mockServerInstance = vi.mocked(Server).mock.results[0].value;
|
|
198
|
-
const callToolCall = mockServerInstance.setRequestHandler.mock.calls.find((call) => call[0].name === 'callTool');
|
|
199
|
-
const handler = callToolCall[1];
|
|
221
|
+
await setupServer();
|
|
222
|
+
const handler = handlers.get('callTool');
|
|
200
223
|
// Empty string prompt should be rejected
|
|
201
224
|
await expect(handler({
|
|
202
225
|
params: {
|
|
203
|
-
name: '
|
|
226
|
+
name: 'run',
|
|
204
227
|
arguments: {
|
|
205
228
|
prompt: '', // Empty prompt
|
|
206
229
|
workFolder: '/tmp'
|
|
207
230
|
}
|
|
208
231
|
}
|
|
209
|
-
})).rejects.toThrow('
|
|
232
|
+
})).rejects.toThrow('Either prompt or prompt_file must be provided');
|
|
210
233
|
});
|
|
211
234
|
});
|
|
212
235
|
});
|
|
@@ -32,7 +32,7 @@ describe('Version Print on First Use', () => {
|
|
|
32
32
|
});
|
|
33
33
|
it('should print version and startup time only on first use', async () => {
|
|
34
34
|
// First tool call
|
|
35
|
-
await client.callTool('
|
|
35
|
+
await client.callTool('run', {
|
|
36
36
|
prompt: 'echo "test 1"',
|
|
37
37
|
workFolder: testDir,
|
|
38
38
|
});
|
|
@@ -40,17 +40,17 @@ describe('Version Print on First Use', () => {
|
|
|
40
40
|
const findVersionCall = (calls) => {
|
|
41
41
|
return calls.find(call => {
|
|
42
42
|
const str = call[1] || call[0]; // message might be first or second param
|
|
43
|
-
return typeof str === 'string' && str.includes('
|
|
43
|
+
return typeof str === 'string' && str.includes('ai_cli_mcp v') && str.includes('started at');
|
|
44
44
|
});
|
|
45
45
|
};
|
|
46
46
|
// Check that version was printed on first use
|
|
47
47
|
const versionCall = findVersionCall(consoleErrorSpy.mock.calls);
|
|
48
48
|
expect(versionCall).toBeDefined();
|
|
49
|
-
expect(versionCall[1]).toMatch(/
|
|
49
|
+
expect(versionCall[1]).toMatch(/ai_cli_mcp v[0-9]+\.[0-9]+\.[0-9]+ started at \d{4}-\d{2}-\d{2}T/);
|
|
50
50
|
// Clear the spy but keep the spy active
|
|
51
51
|
consoleErrorSpy.mockClear();
|
|
52
52
|
// Second tool call
|
|
53
|
-
await client.callTool('
|
|
53
|
+
await client.callTool('run', {
|
|
54
54
|
prompt: 'echo "test 2"',
|
|
55
55
|
workFolder: testDir,
|
|
56
56
|
});
|
|
@@ -58,7 +58,7 @@ describe('Version Print on First Use', () => {
|
|
|
58
58
|
const secondVersionCall = findVersionCall(consoleErrorSpy.mock.calls);
|
|
59
59
|
expect(secondVersionCall).toBeUndefined();
|
|
60
60
|
// Third tool call
|
|
61
|
-
await client.callTool('
|
|
61
|
+
await client.callTool('run', {
|
|
62
62
|
prompt: 'echo "test 3"',
|
|
63
63
|
workFolder: testDir,
|
|
64
64
|
});
|