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.
@@ -13,8 +13,10 @@
13
13
  "mcp__chat__agent_communication_list_rooms",
14
14
  "mcp__chat__agent_communication_get_messages",
15
15
  "mcp__ccm__list_claude_processes",
16
- "Bash(gemini:*)"
16
+ "Bash(gemini:*)",
17
+ "WebSearch",
18
+ "Bash(npm pack:*)"
17
19
  ],
18
20
  "deny": []
19
21
  }
20
- }
22
+ }
@@ -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 claude_code tool', async () => {
34
+ it('should register run tool', async () => {
35
35
  const tools = await client.listTools();
36
- expect(tools).toHaveLength(4);
37
- const claudeCodeTool = tools.find((t) => t.name === 'claude_code');
36
+ expect(tools).toHaveLength(6);
37
+ const claudeCodeTool = tools.find((t) => t.name === 'run');
38
38
  expect(claudeCodeTool).toEqual({
39
- name: 'claude_code',
40
- description: expect.stringContaining('Claude Code Agent'),
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('Claude model'),
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 === 'list_claude_processes')).toBe(true);
70
- expect(tools.some((t) => t.name === 'get_claude_result')).toBe(true);
71
- expect(tools.some((t) => t.name === 'kill_claude_process')).toBe(true);
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('claude_code', {
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
- // claude_code now returns a PID immediately
87
- const response = await client.callTool('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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 claude_code', async () => {
131
- const response = await client.callTool('claude_code', {
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 processes = await client.callTool('list_claude_processes', {});
147
- const processesText = processes[0].text;
148
- const processData = JSON.parse(processesText);
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(ourProcess.model).toBe('haiku');
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('claude_code', {
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 processes = await client.callTool('list_claude_processes', {});
171
- const processesText = processes[0].text;
172
- const processData = JSON.parse(processesText);
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(ourProcess.model).toBe('sonnet');
171
+ expect(processData.model).toBe('sonnet');
178
172
  });
179
173
  it('should work without specifying a model', async () => {
180
- const response = await client.callTool('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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('claude_code', {
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: vi.fn().mockImplementation((code, message) => {
34
- const error = new Error(message);
35
- error.code = code;
36
- return error;
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
- try {
122
- await callToolHandler({
123
- params: {
124
- name: 'claude_code',
125
- arguments: {
126
- prompt: 'test',
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
- expect.fail('Should have thrown');
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: 'claude_code',
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: 'claude_code',
190
+ name: 'run',
196
191
  arguments: {
197
192
  prompt: 'test',
198
193
  workFolder: '/tmp'