ai-cli-mcp 2.18.0 → 2.20.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.ja.md +37 -11
  3. package/README.md +44 -11
  4. package/dist/app/cli.js +2 -1
  5. package/dist/app/mcp.js +65 -13
  6. package/dist/cli-builder.js +13 -6
  7. package/dist/cli-process-service.js +81 -95
  8. package/dist/cli-utils.js +6 -0
  9. package/dist/cli.js +1 -1
  10. package/dist/model-catalog.js +3 -2
  11. package/dist/parsers.js +111 -8
  12. package/dist/process-service.js +5 -4
  13. package/package.json +26 -2
  14. package/server.json +3 -3
  15. package/.gemini/settings.json +0 -11
  16. package/.github/dependabot.yml +0 -28
  17. package/.github/pull_request_template.md +0 -28
  18. package/.github/workflows/ci.yml +0 -34
  19. package/.github/workflows/dependency-review.yml +0 -22
  20. package/.github/workflows/publish.yml +0 -89
  21. package/.github/workflows/test.yml +0 -20
  22. package/.github/workflows/watch-session-prs.yml +0 -276
  23. package/.husky/pre-commit +0 -1
  24. package/.mcp.json +0 -11
  25. package/.releaserc.json +0 -18
  26. package/.vscode/settings.json +0 -3
  27. package/CONTRIBUTING.md +0 -81
  28. package/dist/__tests__/app-cli.test.js +0 -392
  29. package/dist/__tests__/cli-bin-smoke.test.js +0 -101
  30. package/dist/__tests__/cli-builder.test.js +0 -442
  31. package/dist/__tests__/cli-process-service.test.js +0 -655
  32. package/dist/__tests__/cli-utils.test.js +0 -171
  33. package/dist/__tests__/e2e.test.js +0 -256
  34. package/dist/__tests__/edge-cases.test.js +0 -130
  35. package/dist/__tests__/error-cases.test.js +0 -292
  36. package/dist/__tests__/mcp-contract.test.js +0 -636
  37. package/dist/__tests__/mocks.js +0 -32
  38. package/dist/__tests__/model-alias.test.js +0 -36
  39. package/dist/__tests__/parsers.test.js +0 -500
  40. package/dist/__tests__/peek.test.js +0 -36
  41. package/dist/__tests__/process-management.test.js +0 -871
  42. package/dist/__tests__/server.test.js +0 -809
  43. package/dist/__tests__/setup.js +0 -11
  44. package/dist/__tests__/utils/claude-mock.js +0 -80
  45. package/dist/__tests__/utils/mcp-client.js +0 -121
  46. package/dist/__tests__/utils/opencode-mock.js +0 -91
  47. package/dist/__tests__/utils/persistent-mock.js +0 -28
  48. package/dist/__tests__/utils/test-helpers.js +0 -11
  49. package/dist/__tests__/validation.test.js +0 -308
  50. package/dist/__tests__/version-print.test.js +0 -65
  51. package/dist/__tests__/wait.test.js +0 -260
  52. package/docs/RELEASE_CHECKLIST.md +0 -65
  53. package/docs/cli-architecture.md +0 -275
  54. package/docs/concept.md +0 -154
  55. package/docs/development.md +0 -156
  56. package/docs/e2e-testing.md +0 -148
  57. package/docs/prd.md +0 -146
  58. package/docs/session-stacking.md +0 -67
  59. package/src/__tests__/app-cli.test.ts +0 -495
  60. package/src/__tests__/cli-bin-smoke.test.ts +0 -136
  61. package/src/__tests__/cli-builder.test.ts +0 -549
  62. package/src/__tests__/cli-process-service.test.ts +0 -759
  63. package/src/__tests__/cli-utils.test.ts +0 -200
  64. package/src/__tests__/e2e.test.ts +0 -311
  65. package/src/__tests__/edge-cases.test.ts +0 -176
  66. package/src/__tests__/error-cases.test.ts +0 -370
  67. package/src/__tests__/mcp-contract.test.ts +0 -755
  68. package/src/__tests__/mocks.ts +0 -35
  69. package/src/__tests__/model-alias.test.ts +0 -44
  70. package/src/__tests__/parsers.test.ts +0 -564
  71. package/src/__tests__/peek.test.ts +0 -44
  72. package/src/__tests__/process-management.test.ts +0 -1043
  73. package/src/__tests__/server.test.ts +0 -1020
  74. package/src/__tests__/setup.ts +0 -13
  75. package/src/__tests__/utils/claude-mock.ts +0 -87
  76. package/src/__tests__/utils/mcp-client.ts +0 -159
  77. package/src/__tests__/utils/opencode-mock.ts +0 -108
  78. package/src/__tests__/utils/persistent-mock.ts +0 -33
  79. package/src/__tests__/utils/test-helpers.ts +0 -13
  80. package/src/__tests__/validation.test.ts +0 -369
  81. package/src/__tests__/version-print.test.ts +0 -81
  82. package/src/__tests__/wait.test.ts +0 -302
  83. package/src/app/cli.ts +0 -424
  84. package/src/app/mcp.ts +0 -466
  85. package/src/bin/ai-cli-mcp.ts +0 -7
  86. package/src/bin/ai-cli.ts +0 -11
  87. package/src/cli-builder.ts +0 -274
  88. package/src/cli-parse.ts +0 -105
  89. package/src/cli-process-service.ts +0 -708
  90. package/src/cli-utils.ts +0 -258
  91. package/src/cli.ts +0 -124
  92. package/src/model-catalog.ts +0 -87
  93. package/src/parsers.ts +0 -840
  94. package/src/peek.ts +0 -95
  95. package/src/process-result.ts +0 -88
  96. package/src/process-service.ts +0 -367
  97. package/src/server.ts +0 -10
  98. package/tsconfig.json +0 -16
  99. package/vitest.config.e2e.ts +0 -27
  100. package/vitest.config.ts +0 -22
  101. package/vitest.config.unit.ts +0 -28
package/src/app/mcp.ts DELETED
@@ -1,466 +0,0 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import {
4
- CallToolRequestSchema,
5
- ErrorCode,
6
- ListToolsRequestSchema,
7
- McpError,
8
- type ServerResult,
9
- } from '@modelcontextprotocol/sdk/types.js';
10
- import { spawn } from 'node:child_process';
11
- import { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli, findOpencodeCli } from '../cli-utils.js';
12
- import { getModelParameterDescription, getSupportedModelsDescription } from '../model-catalog.js';
13
- import { validatePeekPids, validatePeekTimeSec } from '../peek.js';
14
- import { ProcessService } from '../process-service.js';
15
-
16
- // Server version - update this when releasing new versions
17
- const SERVER_VERSION = "2.2.0";
18
-
19
- // Track if this is the first tool use for version printing
20
- let isFirstToolUse = true;
21
-
22
- // Capture server startup time when the module loads
23
- const serverStartupTime = new Date().toISOString();
24
-
25
- // Ensure spawnAsync is defined correctly *before* the class
26
- export async function spawnAsync(command: string, args: string[], options?: { timeout?: number, cwd?: string }): Promise<{ stdout: string; stderr: string }> {
27
- return new Promise((resolve, reject) => {
28
- debugLog(`[Spawn] Running command: ${command} ${args.join(' ')}`);
29
- const process = spawn(command, args, {
30
- shell: false,
31
- timeout: options?.timeout,
32
- cwd: options?.cwd,
33
- stdio: ['ignore', 'pipe', 'pipe']
34
- });
35
-
36
- let stdout = '';
37
- let stderr = '';
38
-
39
- process.stdout.on('data', (data) => { stdout += data.toString(); });
40
- process.stderr.on('data', (data) => {
41
- stderr += data.toString();
42
- debugLog(`[Spawn Stderr Chunk] ${data.toString()}`);
43
- });
44
-
45
- process.on('error', (error: NodeJS.ErrnoException) => {
46
- debugLog(`[Spawn Error Event] Full error object:`, error);
47
- let errorMessage = `Spawn error: ${error.message}`;
48
- if (error.path) {
49
- errorMessage += ` | Path: ${error.path}`;
50
- }
51
- if (error.syscall) {
52
- errorMessage += ` | Syscall: ${error.syscall}`;
53
- }
54
- errorMessage += `\nStderr: ${stderr.trim()}`;
55
- reject(new Error(errorMessage));
56
- });
57
-
58
- process.on('close', (code) => {
59
- debugLog(`[Spawn Close] Exit code: ${code}`);
60
- debugLog(`[Spawn Stderr Full] ${stderr.trim()}`);
61
- debugLog(`[Spawn Stdout Full] ${stdout.trim()}`);
62
- if (code === 0) {
63
- resolve({ stdout, stderr });
64
- } else {
65
- reject(new Error(`Command failed with exit code ${code}\nStderr: ${stderr.trim()}\nStdout: ${stdout.trim()}`));
66
- }
67
- });
68
- });
69
- }
70
-
71
- export class ClaudeCodeServer {
72
- private server: Server;
73
- private claudeCliPath: string;
74
- private codexCliPath: string;
75
- private geminiCliPath: string;
76
- private forgeCliPath: string;
77
- private opencodeCliPath: string;
78
- private processService: ProcessService;
79
- private sigintHandler?: () => Promise<void>;
80
- private packageVersion: string;
81
-
82
- constructor() {
83
- this.claudeCliPath = findClaudeCli();
84
- this.codexCliPath = findCodexCli();
85
- this.geminiCliPath = findGeminiCli();
86
- this.forgeCliPath = findForgeCli();
87
- this.opencodeCliPath = findOpencodeCli();
88
- console.error(`[Setup] Using Claude CLI command/path: ${this.claudeCliPath}`);
89
- console.error(`[Setup] Using Codex CLI command/path: ${this.codexCliPath}`);
90
- console.error(`[Setup] Using Gemini CLI command/path: ${this.geminiCliPath}`);
91
- console.error(`[Setup] Using Forge CLI command/path: ${this.forgeCliPath}`);
92
- console.error(`[Setup] Using OpenCode CLI command/path: ${this.opencodeCliPath}`);
93
- this.packageVersion = SERVER_VERSION;
94
- this.processService = new ProcessService({
95
- cliPaths: {
96
- claude: this.claudeCliPath,
97
- codex: this.codexCliPath,
98
- gemini: this.geminiCliPath,
99
- forge: this.forgeCliPath,
100
- opencode: this.opencodeCliPath,
101
- },
102
- });
103
-
104
- this.server = new Server(
105
- {
106
- name: 'ai_cli_mcp',
107
- version: SERVER_VERSION,
108
- },
109
- {
110
- capabilities: {
111
- tools: {},
112
- },
113
- }
114
- );
115
-
116
- this.setupToolHandlers();
117
-
118
- this.server.onerror = (error) => console.error('[Error]', error);
119
- this.sigintHandler = async () => {
120
- await this.server.close();
121
- process.exit(0);
122
- };
123
- process.on('SIGINT', this.sigintHandler);
124
- }
125
-
126
- private setupToolHandlers(): void {
127
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
128
- tools: [
129
- {
130
- name: 'run',
131
- description: `AI Agent Runner: Starts a Claude, Codex, Gemini, Forge, or OpenCode CLI process in the background and returns a PID immediately. Use list_processes and get_result to monitor progress.
132
-
133
- • File ops: Create, read, (fuzzy) edit, move, copy, delete, list files, analyze/ocr images, file content analysis
134
- • Code: Generate / analyse / refactor / fix
135
- • Git: Stage ▸ commit ▸ push ▸ tag (any workflow)
136
- • Terminal: Run any CLI cmd or open URLs
137
- • Web search + summarise content on-the-fly
138
- • Multi-step workflows & GitHub integration
139
-
140
- **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
141
-
142
- **Supported models**:
143
- ${getSupportedModelsDescription()}
144
-
145
- **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
146
-
147
- **Prompt tips**
148
- 1. Be concise, explicit & step-by-step for complex tasks.
149
- 2. Check process status with list_processes
150
- 3. Get results with get_result using the returned PID
151
- 4. Kill long-running processes with kill_process if needed
152
-
153
- `,
154
- inputSchema: {
155
- type: 'object',
156
- properties: {
157
- prompt: {
158
- type: 'string',
159
- description: 'The detailed natural language prompt for the agent to execute. Either this or prompt_file is required.',
160
- },
161
- prompt_file: {
162
- type: 'string',
163
- description: 'Path to a file containing the prompt. Either this or prompt is required. Must be an absolute path or relative to workFolder.',
164
- },
165
- workFolder: {
166
- type: 'string',
167
- description: 'The working directory for the agent execution. Must be an absolute path.',
168
- },
169
- model: {
170
- type: 'string',
171
- description: getModelParameterDescription(),
172
- },
173
- reasoning_effort: {
174
- type: 'string',
175
- description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh". Gemini, Forge, and OpenCode do not support reasoning_effort in this integration.',
176
- },
177
- session_id: {
178
- type: 'string',
179
- description: 'Optional session ID to resume a previous session. Supported for Claude, Codex, Gemini, Forge, and OpenCode. OpenCode resumes in-place via --session and may also be combined with explicit oc-<provider/model> selection.',
180
- },
181
- },
182
- required: ['workFolder'],
183
- },
184
- },
185
- {
186
- name: 'list_processes',
187
- description: 'List all running and completed AI agent processes. Returns a simple list with PID, agent type, and status for each process.',
188
- inputSchema: {
189
- type: 'object',
190
- properties: {},
191
- },
192
- },
193
- {
194
- name: 'get_result',
195
- description: 'Get the current output and status of an AI agent process by PID. Defaults to a compact result shape; set verbose to true for full metadata and detailed parsed output.',
196
- inputSchema: {
197
- type: 'object',
198
- properties: {
199
- pid: {
200
- type: 'number',
201
- description: 'The process ID returned by run tool.',
202
- },
203
- verbose: {
204
- type: 'boolean',
205
- description: 'Optional: If true, returns the full result shape including metadata fields and detailed parsed output such as tool usage history. Defaults to false.',
206
- }
207
- },
208
- required: ['pid'],
209
- },
210
- },
211
- {
212
- name: 'wait',
213
- description: 'Wait for multiple AI agent processes to complete and return their results. Defaults to compact result items; set verbose to true for full metadata and detailed parsed output.',
214
- inputSchema: {
215
- type: 'object',
216
- properties: {
217
- pids: {
218
- type: 'array',
219
- items: { type: 'number' },
220
- description: 'List of process IDs to wait for (returned by the run tool).',
221
- },
222
- timeout: {
223
- type: 'number',
224
- description: 'Optional: Maximum time to wait in seconds. Defaults to 180 (3 minutes).',
225
- },
226
- verbose: {
227
- type: 'boolean',
228
- description: 'Optional: If true, each result item uses the full result shape including metadata fields and detailed parsed output. Defaults to false.',
229
- },
230
- },
231
- required: ['pids'],
232
- },
233
- },
234
- {
235
- name: 'peek',
236
- description: 'One-shot short observation window for running child agents. Returns only natural-language message events, and optionally normalized tool_call events, observed during this call; not a history API, not gapless streaming, and not stdout/stderr tailing. In v1, message extraction is supported for Codex, Claude, OpenCode, and Gemini; Forge returns status with events: []. Tool calls exclude raw tool output.',
237
- inputSchema: {
238
- type: 'object',
239
- properties: {
240
- pids: {
241
- type: 'array',
242
- items: { type: 'number' },
243
- description: 'Process IDs returned by run. Duplicates are deduplicated server-side, preserving first occurrence order. Unknown PIDs are returned per process as not_found.',
244
- },
245
- peek_time_sec: {
246
- type: 'number',
247
- description: 'Optional positive integer observation window in seconds. Defaults to 10; maximum is 60.',
248
- },
249
- include_tool_calls: {
250
- type: 'boolean',
251
- description: 'Optional: include normalized tool_call events without raw tool output. Defaults to false.',
252
- },
253
- },
254
- required: ['pids'],
255
- },
256
- },
257
- {
258
- name: 'kill_process',
259
- description: 'Terminate a running AI agent process by PID.',
260
- inputSchema: {
261
- type: 'object',
262
- properties: {
263
- pid: {
264
- type: 'number',
265
- description: 'The process ID to terminate.',
266
- },
267
- },
268
- required: ['pid'],
269
- },
270
- },
271
- {
272
- name: 'cleanup_processes',
273
- description: 'Remove all completed and failed processes from the process list to free up memory.',
274
- inputSchema: {
275
- type: 'object',
276
- properties: {},
277
- },
278
- }
279
- ],
280
- }));
281
-
282
- this.server.setRequestHandler(CallToolRequestSchema, async (args): Promise<ServerResult> => {
283
- debugLog('[Debug] Handling CallToolRequest:', args);
284
-
285
- const toolName = args.params.name;
286
- const toolArguments = args.params.arguments || {};
287
-
288
- switch (toolName) {
289
- case 'run':
290
- return this.handleRun(toolArguments);
291
- case 'list_processes':
292
- return this.handleListProcesses();
293
- case 'get_result':
294
- return this.handleGetResult(toolArguments);
295
- case 'wait':
296
- return this.handleWait(toolArguments);
297
- case 'peek':
298
- return this.handlePeek(toolArguments);
299
- case 'kill_process':
300
- return this.handleKillProcess(toolArguments);
301
- case 'cleanup_processes':
302
- return this.handleCleanupProcesses();
303
- default:
304
- throw new McpError(ErrorCode.MethodNotFound, `Tool ${toolName} not found`);
305
- }
306
- });
307
- }
308
-
309
- private async handleRun(toolArguments: any): Promise<ServerResult> {
310
- if (isFirstToolUse) {
311
- console.error(`ai_cli_mcp v${SERVER_VERSION} started at ${serverStartupTime}`);
312
- isFirstToolUse = false;
313
- }
314
-
315
- try {
316
- const result = this.processService.startProcess({
317
- prompt: toolArguments.prompt,
318
- prompt_file: toolArguments.prompt_file,
319
- workFolder: toolArguments.workFolder,
320
- model: toolArguments.model,
321
- session_id: toolArguments.session_id,
322
- reasoning_effort: toolArguments.reasoning_effort,
323
- });
324
- return {
325
- content: [{
326
- type: 'text',
327
- text: JSON.stringify(result, null, 2)
328
- }]
329
- };
330
- } catch (error: any) {
331
- const code = /Failed to start/.test(error.message) ? ErrorCode.InternalError : ErrorCode.InvalidParams;
332
- throw new McpError(code, error.message);
333
- }
334
- }
335
-
336
- private async handleListProcesses(): Promise<ServerResult> {
337
- return {
338
- content: [{
339
- type: 'text',
340
- text: JSON.stringify(this.processService.listProcesses(), null, 2)
341
- }]
342
- };
343
- }
344
-
345
- private async handleGetResult(toolArguments: any): Promise<ServerResult> {
346
- if (!toolArguments.pid || typeof toolArguments.pid !== 'number') {
347
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
348
- }
349
-
350
- const pid = toolArguments.pid;
351
- const verbose = !!toolArguments.verbose;
352
- try {
353
- const response = this.processService.getProcessResult(pid, verbose);
354
- return {
355
- content: [{
356
- type: 'text',
357
- text: JSON.stringify(response, null, 2)
358
- }]
359
- };
360
- } catch (error: any) {
361
- const code = /not found/.test(error.message) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
362
- throw new McpError(code, error.message);
363
- }
364
- }
365
-
366
- private async handleWait(toolArguments: any): Promise<ServerResult> {
367
- if (!toolArguments.pids || !Array.isArray(toolArguments.pids) || toolArguments.pids.length === 0) {
368
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pids (must be a non-empty array of numbers)');
369
- }
370
- try {
371
- const results = await this.processService.waitForProcesses(
372
- toolArguments.pids,
373
- typeof toolArguments.timeout === 'number' ? toolArguments.timeout : 180,
374
- !!toolArguments.verbose
375
- );
376
- return {
377
- content: [{
378
- type: 'text',
379
- text: JSON.stringify(results, null, 2)
380
- }]
381
- };
382
- } catch (error: any) {
383
- const code = /not found/.test(error.message) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
384
- throw new McpError(code, error.message);
385
- }
386
- }
387
-
388
- private async handlePeek(toolArguments: any): Promise<ServerResult> {
389
- let pids: number[];
390
- let peekTimeSec: number;
391
- let includeToolCalls: boolean;
392
-
393
- try {
394
- pids = validatePeekPids(toolArguments.pids);
395
- peekTimeSec = validatePeekTimeSec(toolArguments.peek_time_sec);
396
- if (toolArguments.include_tool_calls !== undefined && typeof toolArguments.include_tool_calls !== 'boolean') {
397
- throw new Error('include_tool_calls must be a boolean when provided');
398
- }
399
- includeToolCalls = toolArguments.include_tool_calls === true;
400
- } catch (error: any) {
401
- throw new McpError(ErrorCode.InvalidParams, error.message);
402
- }
403
-
404
- try {
405
- const response = await this.processService.peekProcesses(pids, peekTimeSec, includeToolCalls);
406
- return {
407
- content: [{
408
- type: 'text',
409
- text: JSON.stringify(response, null, 2)
410
- }]
411
- };
412
- } catch (error: any) {
413
- throw new McpError(ErrorCode.InternalError, `Failed to peek processes: ${error.message}`);
414
- }
415
- }
416
-
417
- private async handleKillProcess(toolArguments: any): Promise<ServerResult> {
418
- if (!toolArguments.pid || typeof toolArguments.pid !== 'number') {
419
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
420
- }
421
-
422
- const pid = toolArguments.pid;
423
- try {
424
- const response = this.processService.killProcess(pid);
425
- return {
426
- content: [{
427
- type: 'text',
428
- text: JSON.stringify(response, null, 2)
429
- }]
430
- };
431
- } catch (error: any) {
432
- const code = /not found/.test(error.message) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
433
- const message = code === ErrorCode.InternalError
434
- ? `Failed to terminate process: ${error.message}`
435
- : error.message;
436
- throw new McpError(code, message);
437
- }
438
- }
439
-
440
- private async handleCleanupProcesses(): Promise<ServerResult> {
441
- return {
442
- content: [{
443
- type: 'text',
444
- text: JSON.stringify(this.processService.cleanupProcesses(), null, 2)
445
- }]
446
- };
447
- }
448
-
449
- async run(): Promise<void> {
450
- const transport = new StdioServerTransport();
451
- await this.server.connect(transport);
452
- console.error('AI CLI MCP server running on stdio');
453
- }
454
-
455
- async cleanup(): Promise<void> {
456
- if (this.sigintHandler) {
457
- process.removeListener('SIGINT', this.sigintHandler);
458
- }
459
- await this.server.close();
460
- }
461
- }
462
-
463
- export async function runMcpServer(): Promise<void> {
464
- const server = new ClaudeCodeServer();
465
- await server.run();
466
- }
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import { runMcpServer } from '../app/mcp.js';
3
-
4
- runMcpServer().catch((error: Error) => {
5
- process.stderr.write(`${error.message}\n`);
6
- process.exit(1);
7
- });
package/src/bin/ai-cli.ts DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- import { runCli } from '../app/cli.js';
3
-
4
- runCli(process.argv.slice(2))
5
- .then((exitCode) => {
6
- process.exit(exitCode);
7
- })
8
- .catch((error: Error) => {
9
- process.stderr.write(`${error.message}\n`);
10
- process.exit(1);
11
- });