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
|
@@ -31,13 +31,13 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
31
31
|
await cleanupSharedMock();
|
|
32
32
|
});
|
|
33
33
|
describe('Tool Registration', () => {
|
|
34
|
-
it('should register
|
|
34
|
+
it('should register run tool', async () => {
|
|
35
35
|
const tools = await client.listTools();
|
|
36
|
-
expect(tools).toHaveLength(
|
|
37
|
-
const claudeCodeTool = tools.find((t) => t.name === '
|
|
36
|
+
expect(tools).toHaveLength(6);
|
|
37
|
+
const claudeCodeTool = tools.find((t) => t.name === 'run');
|
|
38
38
|
expect(claudeCodeTool).toEqual({
|
|
39
|
-
name: '
|
|
40
|
-
description: expect.stringContaining('
|
|
39
|
+
name: 'run',
|
|
40
|
+
description: expect.stringContaining('AI Agent Runner'),
|
|
41
41
|
inputSchema: {
|
|
42
42
|
type: 'object',
|
|
43
43
|
properties: {
|
|
@@ -55,7 +55,7 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
55
55
|
},
|
|
56
56
|
model: {
|
|
57
57
|
type: 'string',
|
|
58
|
-
description: expect.stringContaining('
|
|
58
|
+
description: expect.stringContaining('sonnet'),
|
|
59
59
|
},
|
|
60
60
|
session_id: {
|
|
61
61
|
type: 'string',
|
|
@@ -66,14 +66,14 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
// Verify other tools exist
|
|
69
|
-
expect(tools.some((t) => t.name === '
|
|
70
|
-
expect(tools.some((t) => t.name === '
|
|
71
|
-
expect(tools.some((t) => t.name === '
|
|
69
|
+
expect(tools.some((t) => t.name === 'list_processes')).toBe(true);
|
|
70
|
+
expect(tools.some((t) => t.name === 'get_result')).toBe(true);
|
|
71
|
+
expect(tools.some((t) => t.name === 'kill_process')).toBe(true);
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
describe('Basic Operations', () => {
|
|
75
75
|
it('should execute a simple prompt', async () => {
|
|
76
|
-
const response = await client.callTool('
|
|
76
|
+
const response = await client.callTool('run', {
|
|
77
77
|
prompt: 'create a file called test.txt with content "Hello World"',
|
|
78
78
|
workFolder: testDir,
|
|
79
79
|
});
|
|
@@ -83,8 +83,8 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
83
83
|
}]);
|
|
84
84
|
});
|
|
85
85
|
it('should handle process management correctly', async () => {
|
|
86
|
-
//
|
|
87
|
-
const response = await client.callTool('
|
|
86
|
+
// run now returns a PID immediately
|
|
87
|
+
const response = await client.callTool('run', {
|
|
88
88
|
prompt: 'error',
|
|
89
89
|
workFolder: testDir,
|
|
90
90
|
});
|
|
@@ -98,14 +98,14 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
98
98
|
expect(pidMatch).toBeTruthy();
|
|
99
99
|
});
|
|
100
100
|
it('should reject missing workFolder', async () => {
|
|
101
|
-
await expect(client.callTool('
|
|
101
|
+
await expect(client.callTool('run', {
|
|
102
102
|
prompt: 'List files in current directory',
|
|
103
103
|
})).rejects.toThrow(/workFolder/i);
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
106
|
describe('Working Directory Handling', () => {
|
|
107
107
|
it('should respect custom working directory', async () => {
|
|
108
|
-
const response = await client.callTool('
|
|
108
|
+
const response = await client.callTool('run', {
|
|
109
109
|
prompt: 'Show current working directory',
|
|
110
110
|
workFolder: testDir,
|
|
111
111
|
});
|
|
@@ -113,7 +113,7 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
113
113
|
});
|
|
114
114
|
it('should reject non-existent working directory', async () => {
|
|
115
115
|
const nonExistentDir = join(testDir, 'non-existent');
|
|
116
|
-
await expect(client.callTool('
|
|
116
|
+
await expect(client.callTool('run', {
|
|
117
117
|
prompt: 'Test prompt',
|
|
118
118
|
workFolder: nonExistentDir,
|
|
119
119
|
})).rejects.toThrow(/does not exist/i);
|
|
@@ -127,8 +127,8 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
127
127
|
});
|
|
128
128
|
});
|
|
129
129
|
describe('Model Alias Handling', () => {
|
|
130
|
-
it('should resolve haiku alias when calling
|
|
131
|
-
const response = await client.callTool('
|
|
130
|
+
it('should resolve haiku alias when calling run', async () => {
|
|
131
|
+
const response = await client.callTool('run', {
|
|
132
132
|
prompt: 'Test with haiku model',
|
|
133
133
|
workFolder: testDir,
|
|
134
134
|
model: 'haiku'
|
|
@@ -141,19 +141,16 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
141
141
|
const responseText = response[0].text;
|
|
142
142
|
const pidMatch = responseText.match(/"pid":\s*(\d+)/);
|
|
143
143
|
expect(pidMatch).toBeTruthy();
|
|
144
|
-
// Get the PID and check the process
|
|
144
|
+
// Get the PID and check the process using get_result
|
|
145
145
|
const pid = parseInt(pidMatch[1]);
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
const processData = JSON.parse(
|
|
149
|
-
// Find our process
|
|
150
|
-
const ourProcess = processData.find((p) => p.pid === pid);
|
|
151
|
-
expect(ourProcess).toBeTruthy();
|
|
146
|
+
const result = await client.callTool('get_result', { pid });
|
|
147
|
+
const resultText = result[0].text;
|
|
148
|
+
const processData = JSON.parse(resultText);
|
|
152
149
|
// Verify that the model was set correctly
|
|
153
|
-
expect(
|
|
150
|
+
expect(processData.model).toBe('haiku');
|
|
154
151
|
});
|
|
155
152
|
it('should pass non-alias model names unchanged', async () => {
|
|
156
|
-
const response = await client.callTool('
|
|
153
|
+
const response = await client.callTool('run', {
|
|
157
154
|
prompt: 'Test with sonnet model',
|
|
158
155
|
workFolder: testDir,
|
|
159
156
|
model: 'sonnet'
|
|
@@ -166,18 +163,15 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
166
163
|
const responseText = response[0].text;
|
|
167
164
|
const pidMatch = responseText.match(/"pid":\s*(\d+)/);
|
|
168
165
|
const pid = parseInt(pidMatch[1]);
|
|
169
|
-
// Check the process
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const processData = JSON.parse(
|
|
173
|
-
// Find our process
|
|
174
|
-
const ourProcess = processData.find((p) => p.pid === pid);
|
|
175
|
-
expect(ourProcess).toBeTruthy();
|
|
166
|
+
// Check the process using get_result
|
|
167
|
+
const result = await client.callTool('get_result', { pid });
|
|
168
|
+
const resultText = result[0].text;
|
|
169
|
+
const processData = JSON.parse(resultText);
|
|
176
170
|
// The model should be unchanged
|
|
177
|
-
expect(
|
|
171
|
+
expect(processData.model).toBe('sonnet');
|
|
178
172
|
});
|
|
179
173
|
it('should work without specifying a model', async () => {
|
|
180
|
-
const response = await client.callTool('
|
|
174
|
+
const response = await client.callTool('run', {
|
|
181
175
|
prompt: 'Test without model parameter',
|
|
182
176
|
workFolder: testDir
|
|
183
177
|
});
|
|
@@ -190,7 +184,7 @@ describe('Claude Code MCP E2E Tests', () => {
|
|
|
190
184
|
describe('Debug Mode', () => {
|
|
191
185
|
it('should log debug information when enabled', async () => {
|
|
192
186
|
// Debug logs go to stderr, which we capture in the client
|
|
193
|
-
const response = await client.callTool('
|
|
187
|
+
const response = await client.callTool('run', {
|
|
194
188
|
prompt: 'Debug test prompt',
|
|
195
189
|
workFolder: testDir,
|
|
196
190
|
});
|
|
@@ -217,7 +211,7 @@ describe('Integration Tests (Local Only)', () => {
|
|
|
217
211
|
// These tests will only run locally when Claude is available
|
|
218
212
|
it.skip('should create a file with real Claude CLI', async () => {
|
|
219
213
|
await client.connect();
|
|
220
|
-
const response = await client.callTool('
|
|
214
|
+
const response = await client.callTool('run', {
|
|
221
215
|
prompt: 'Create a file called hello.txt with content "Hello from Claude"',
|
|
222
216
|
workFolder: testDir,
|
|
223
217
|
});
|
|
@@ -228,7 +222,7 @@ describe('Integration Tests (Local Only)', () => {
|
|
|
228
222
|
it.skip('should handle git operations with real Claude CLI', async () => {
|
|
229
223
|
await client.connect();
|
|
230
224
|
// Initialize git repo
|
|
231
|
-
const response = await client.callTool('
|
|
225
|
+
const response = await client.callTool('run', {
|
|
232
226
|
prompt: 'Initialize a git repository and create a README.md file',
|
|
233
227
|
workFolder: testDir,
|
|
234
228
|
});
|
|
@@ -30,24 +30,24 @@ describe('Claude Code Edge Cases', () => {
|
|
|
30
30
|
});
|
|
31
31
|
describe('Input Validation', () => {
|
|
32
32
|
it('should reject missing prompt', async () => {
|
|
33
|
-
await expect(client.callTool('
|
|
33
|
+
await expect(client.callTool('run', {
|
|
34
34
|
workFolder: testDir,
|
|
35
35
|
})).rejects.toThrow(/prompt/i);
|
|
36
36
|
});
|
|
37
37
|
it('should reject invalid prompt type', async () => {
|
|
38
|
-
await expect(client.callTool('
|
|
38
|
+
await expect(client.callTool('run', {
|
|
39
39
|
prompt: 123, // Should be string
|
|
40
40
|
workFolder: testDir,
|
|
41
41
|
})).rejects.toThrow();
|
|
42
42
|
});
|
|
43
43
|
it('should reject invalid workFolder type', async () => {
|
|
44
|
-
await expect(client.callTool('
|
|
44
|
+
await expect(client.callTool('run', {
|
|
45
45
|
prompt: 'Test prompt',
|
|
46
46
|
workFolder: 123, // Should be string
|
|
47
47
|
})).rejects.toThrow(/workFolder/i);
|
|
48
48
|
});
|
|
49
49
|
it('should reject empty prompt', async () => {
|
|
50
|
-
await expect(client.callTool('
|
|
50
|
+
await expect(client.callTool('run', {
|
|
51
51
|
prompt: '',
|
|
52
52
|
workFolder: testDir,
|
|
53
53
|
})).rejects.toThrow(/prompt/i);
|
|
@@ -56,21 +56,21 @@ describe('Claude Code Edge Cases', () => {
|
|
|
56
56
|
describe('Special Characters', () => {
|
|
57
57
|
it.skip('should handle prompts with quotes', async () => {
|
|
58
58
|
// Skipping: This test fails in CI when mock is not found at expected path
|
|
59
|
-
const response = await client.callTool('
|
|
59
|
+
const response = await client.callTool('run', {
|
|
60
60
|
prompt: 'Create a file with content "Hello \\"World\\""',
|
|
61
61
|
workFolder: testDir,
|
|
62
62
|
});
|
|
63
63
|
expect(response).toBeTruthy();
|
|
64
64
|
});
|
|
65
65
|
it('should handle prompts with newlines', async () => {
|
|
66
|
-
const response = await client.callTool('
|
|
66
|
+
const response = await client.callTool('run', {
|
|
67
67
|
prompt: 'Create a file with content:\\nLine 1\\nLine 2',
|
|
68
68
|
workFolder: testDir,
|
|
69
69
|
});
|
|
70
70
|
expect(response).toBeTruthy();
|
|
71
71
|
});
|
|
72
72
|
it('should handle prompts with shell special characters', async () => {
|
|
73
|
-
const response = await client.callTool('
|
|
73
|
+
const response = await client.callTool('run', {
|
|
74
74
|
prompt: 'Create a file named test$file.txt',
|
|
75
75
|
workFolder: testDir,
|
|
76
76
|
});
|
|
@@ -85,7 +85,7 @@ describe('Claude Code Edge Cases', () => {
|
|
|
85
85
|
CLAUDE_CLI_NAME: 'non-existent-claude',
|
|
86
86
|
});
|
|
87
87
|
await errorClient.connect();
|
|
88
|
-
await expect(errorClient.callTool('
|
|
88
|
+
await expect(errorClient.callTool('run', {
|
|
89
89
|
prompt: 'Test prompt',
|
|
90
90
|
workFolder: testDir,
|
|
91
91
|
})).rejects.toThrow();
|
|
@@ -94,7 +94,7 @@ describe('Claude Code Edge Cases', () => {
|
|
|
94
94
|
it('should handle permission denied errors', async () => {
|
|
95
95
|
const restrictedDir = '/root/restricted';
|
|
96
96
|
// Non-existent directories now throw an error
|
|
97
|
-
await expect(client.callTool('
|
|
97
|
+
await expect(client.callTool('run', {
|
|
98
98
|
prompt: 'Test prompt',
|
|
99
99
|
workFolder: restrictedDir,
|
|
100
100
|
})).rejects.toThrow(/does not exist/i);
|
|
@@ -102,7 +102,7 @@ describe('Claude Code Edge Cases', () => {
|
|
|
102
102
|
});
|
|
103
103
|
describe('Concurrent Requests', () => {
|
|
104
104
|
it('should handle multiple simultaneous requests', async () => {
|
|
105
|
-
const promises = Array(5).fill(null).map((_, i) => client.callTool('
|
|
105
|
+
const promises = Array(5).fill(null).map((_, i) => client.callTool('run', {
|
|
106
106
|
prompt: `Create file test${i}.txt`,
|
|
107
107
|
workFolder: testDir,
|
|
108
108
|
}));
|
|
@@ -114,7 +114,7 @@ describe('Claude Code Edge Cases', () => {
|
|
|
114
114
|
describe('Large Prompts', () => {
|
|
115
115
|
it('should handle very long prompts', async () => {
|
|
116
116
|
const longPrompt = 'Create a file with content: ' + 'x'.repeat(10000);
|
|
117
|
-
const response = await client.callTool('
|
|
117
|
+
const response = await client.callTool('run', {
|
|
118
118
|
prompt: longPrompt,
|
|
119
119
|
workFolder: testDir,
|
|
120
120
|
});
|
|
@@ -126,7 +126,7 @@ describe('Claude Code Edge Cases', () => {
|
|
|
126
126
|
const maliciousPath = join(testDir, '..', '..', 'etc', 'passwd');
|
|
127
127
|
// Server resolves paths and checks existence
|
|
128
128
|
// The path /etc/passwd may exist but be a file, not a directory
|
|
129
|
-
await expect(client.callTool('
|
|
129
|
+
await expect(client.callTool('run', {
|
|
130
130
|
prompt: 'Read file',
|
|
131
131
|
workFolder: maliciousPath,
|
|
132
132
|
})).rejects.toThrow(/(does not exist|ENOTDIR)/i);
|
|
@@ -30,11 +30,13 @@ vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
|
|
30
30
|
MethodNotFound: 'MethodNotFound',
|
|
31
31
|
InvalidParams: 'InvalidParams'
|
|
32
32
|
},
|
|
33
|
-
McpError:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
McpError: class extends Error {
|
|
34
|
+
code;
|
|
35
|
+
constructor(code, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
38
40
|
}));
|
|
39
41
|
const mockExistsSync = vi.mocked(existsSync);
|
|
40
42
|
const mockSpawn = vi.mocked(spawn);
|
|
@@ -118,22 +120,15 @@ describe('Error Handling Tests', () => {
|
|
|
118
120
|
return mockProcess;
|
|
119
121
|
});
|
|
120
122
|
// Call handler
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
workFolder: '/tmp'
|
|
128
|
-
}
|
|
123
|
+
await expect(callToolHandler({
|
|
124
|
+
params: {
|
|
125
|
+
name: 'run',
|
|
126
|
+
arguments: {
|
|
127
|
+
prompt: 'test',
|
|
128
|
+
workFolder: '/tmp'
|
|
129
129
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
catch (err) {
|
|
134
|
-
// Check if McpError was called with the process start failure message
|
|
135
|
-
expect(McpError).toHaveBeenCalledWith('InternalError', 'Failed to start Claude CLI process');
|
|
136
|
-
}
|
|
130
|
+
}
|
|
131
|
+
})).rejects.toThrow('Failed to start claude CLI process');
|
|
137
132
|
});
|
|
138
133
|
it('should handle invalid argument types', async () => {
|
|
139
134
|
mockHomedir.mockReturnValue('/home/user');
|
|
@@ -148,7 +143,7 @@ describe('Error Handling Tests', () => {
|
|
|
148
143
|
const handler = callToolCall[1];
|
|
149
144
|
await expect(handler({
|
|
150
145
|
params: {
|
|
151
|
-
name: '
|
|
146
|
+
name: 'run',
|
|
152
147
|
arguments: 'invalid-should-be-object'
|
|
153
148
|
}
|
|
154
149
|
})).rejects.toThrow();
|
|
@@ -192,7 +187,7 @@ describe('Error Handling Tests', () => {
|
|
|
192
187
|
});
|
|
193
188
|
await expect(handler({
|
|
194
189
|
params: {
|
|
195
|
-
name: '
|
|
190
|
+
name: 'run',
|
|
196
191
|
arguments: {
|
|
197
192
|
prompt: 'test',
|
|
198
193
|
workFolder: '/tmp'
|