ai-cli-mcp 2.11.0 → 2.13.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 (55) hide show
  1. package/.github/workflows/publish.yml +25 -0
  2. package/CHANGELOG.md +23 -0
  3. package/README.ja.md +112 -8
  4. package/README.md +112 -9
  5. package/dist/__tests__/app-cli.test.js +293 -0
  6. package/dist/__tests__/cli-bin-smoke.test.js +58 -0
  7. package/dist/__tests__/cli-builder.test.js +37 -0
  8. package/dist/__tests__/cli-process-service.test.js +279 -0
  9. package/dist/__tests__/cli-utils.test.js +140 -0
  10. package/dist/__tests__/error-cases.test.js +2 -1
  11. package/dist/__tests__/mcp-contract.test.js +343 -0
  12. package/dist/__tests__/parsers.test.js +37 -1
  13. package/dist/__tests__/process-management.test.js +15 -8
  14. package/dist/__tests__/server.test.js +29 -3
  15. package/dist/__tests__/wait.test.js +31 -0
  16. package/dist/app/cli.js +304 -0
  17. package/dist/app/mcp.js +366 -0
  18. package/dist/bin/ai-cli-mcp.js +6 -0
  19. package/dist/bin/ai-cli.js +10 -0
  20. package/dist/cli-builder.js +15 -6
  21. package/dist/cli-parse.js +8 -5
  22. package/dist/cli-process-service.js +332 -0
  23. package/dist/cli-utils.js +159 -88
  24. package/dist/cli.js +4 -3
  25. package/dist/model-catalog.js +53 -0
  26. package/dist/parsers.js +55 -0
  27. package/dist/process-service.js +201 -0
  28. package/dist/server.js +4 -578
  29. package/docs/cli-architecture.md +275 -0
  30. package/package.json +4 -3
  31. package/server.json +1 -1
  32. package/src/__tests__/app-cli.test.ts +370 -0
  33. package/src/__tests__/cli-bin-smoke.test.ts +75 -0
  34. package/src/__tests__/cli-builder.test.ts +47 -0
  35. package/src/__tests__/cli-process-service.test.ts +334 -0
  36. package/src/__tests__/cli-utils.test.ts +166 -0
  37. package/src/__tests__/error-cases.test.ts +3 -4
  38. package/src/__tests__/mcp-contract.test.ts +422 -0
  39. package/src/__tests__/parsers.test.ts +44 -1
  40. package/src/__tests__/process-management.test.ts +15 -9
  41. package/src/__tests__/server.test.ts +27 -6
  42. package/src/__tests__/wait.test.ts +38 -0
  43. package/src/app/cli.ts +373 -0
  44. package/src/app/mcp.ts +402 -0
  45. package/src/bin/ai-cli-mcp.ts +7 -0
  46. package/src/bin/ai-cli.ts +11 -0
  47. package/src/cli-builder.ts +19 -10
  48. package/src/cli-parse.ts +8 -5
  49. package/src/cli-process-service.ts +418 -0
  50. package/src/cli-utils.ts +205 -99
  51. package/src/cli.ts +4 -3
  52. package/src/model-catalog.ts +64 -0
  53. package/src/parsers.ts +61 -0
  54. package/src/process-service.ts +263 -0
  55. package/src/server.ts +4 -668
package/src/server.ts CHANGED
@@ -1,674 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import {
5
- CallToolRequestSchema,
6
- ErrorCode,
7
- ListToolsRequestSchema,
8
- McpError,
9
- type ServerResult,
10
- } from '@modelcontextprotocol/sdk/types.js';
11
- import { spawn, ChildProcess } from 'node:child_process';
12
- import { parseCodexOutput, parseClaudeOutput, parseGeminiOutput } from './parsers.js';
13
- import { buildCliCommand } from './cli-builder.js';
14
- import { debugLog, findClaudeCli, findCodexCli, findGeminiCli } from './cli-utils.js';
15
-
16
- // Re-export for backward compatibility
17
- export { debugLog, findClaudeCli, findCodexCli, findGeminiCli } from './cli-utils.js';
2
+ export { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli } from './cli-utils.js';
18
3
  export { resolveModelAlias } from './cli-builder.js';
4
+ export { ClaudeCodeServer, runMcpServer, spawnAsync } from './app/mcp.js';
19
5
 
20
- // Server version - update this when releasing new versions
21
- const SERVER_VERSION = "2.2.0";
22
-
23
- // Track if this is the first tool use for version printing
24
- let isFirstToolUse = true;
25
-
26
- // Capture server startup time when the module loads
27
- const serverStartupTime = new Date().toISOString();
28
-
29
- // Process tracking
30
- interface ClaudeProcess {
31
- pid: number;
32
- process: ChildProcess;
33
- prompt: string;
34
- workFolder: string;
35
- model?: string;
36
- toolType: 'claude' | 'codex' | 'gemini'; // Identify which CLI tool
37
- startTime: string;
38
- stdout: string;
39
- stderr: string;
40
- status: 'running' | 'completed' | 'failed';
41
- exitCode?: number;
42
- }
43
-
44
- // Type definition for list_processes return value
45
- interface ProcessListItem {
46
- pid: number; // プロセスID
47
- agent: 'claude' | 'codex' | 'gemini'; // エージェントタイプ
48
- status: 'running' | 'completed' | 'failed'; // プロセスの状態
49
- }
50
-
51
- // Global process manager
52
- const processManager = new Map<number, ClaudeProcess>();
53
-
54
- // Ensure spawnAsync is defined correctly *before* the class
55
- export async function spawnAsync(command: string, args: string[], options?: { timeout?: number, cwd?: string }): Promise<{ stdout: string; stderr: string }> {
56
- return new Promise((resolve, reject) => {
57
- debugLog(`[Spawn] Running command: ${command} ${args.join(' ')}`);
58
- const process = spawn(command, args, {
59
- shell: false, // Reverted to false
60
- timeout: options?.timeout,
61
- cwd: options?.cwd,
62
- stdio: ['ignore', 'pipe', 'pipe']
63
- });
64
-
65
- let stdout = '';
66
- let stderr = '';
67
-
68
- process.stdout.on('data', (data) => { stdout += data.toString(); });
69
- process.stderr.on('data', (data) => {
70
- stderr += data.toString();
71
- debugLog(`[Spawn Stderr Chunk] ${data.toString()}`);
72
- });
73
-
74
- process.on('error', (error: NodeJS.ErrnoException) => {
75
- debugLog(`[Spawn Error Event] Full error object:`, error);
76
- let errorMessage = `Spawn error: ${error.message}`;
77
- if (error.path) {
78
- errorMessage += ` | Path: ${error.path}`;
79
- }
80
- if (error.syscall) {
81
- errorMessage += ` | Syscall: ${error.syscall}`;
82
- }
83
- errorMessage += `\nStderr: ${stderr.trim()}`;
84
- reject(new Error(errorMessage));
85
- });
86
-
87
- process.on('close', (code) => {
88
- debugLog(`[Spawn Close] Exit code: ${code}`);
89
- debugLog(`[Spawn Stderr Full] ${stderr.trim()}`);
90
- debugLog(`[Spawn Stdout Full] ${stdout.trim()}`);
91
- if (code === 0) {
92
- resolve({ stdout, stderr });
93
- } else {
94
- reject(new Error(`Command failed with exit code ${code}\nStderr: ${stderr.trim()}\nStdout: ${stdout.trim()}`));
95
- }
96
- });
97
- });
98
- }
99
-
100
- /**
101
- * MCP Server for Claude Code
102
- * Provides a simple MCP tool to run Claude CLI in one-shot mode
103
- */
104
- export class ClaudeCodeServer {
105
- private server: Server;
106
- private claudeCliPath: string;
107
- private codexCliPath: string;
108
- private geminiCliPath: string;
109
- private sigintHandler?: () => Promise<void>;
110
- private packageVersion: string;
111
-
112
- constructor() {
113
- // Use the simplified findClaudeCli function
114
- this.claudeCliPath = findClaudeCli(); // Removed debugMode argument
115
- this.codexCliPath = findCodexCli();
116
- this.geminiCliPath = findGeminiCli();
117
- console.error(`[Setup] Using Claude CLI command/path: ${this.claudeCliPath}`);
118
- console.error(`[Setup] Using Codex CLI command/path: ${this.codexCliPath}`);
119
- console.error(`[Setup] Using Gemini CLI command/path: ${this.geminiCliPath}`);
120
- this.packageVersion = SERVER_VERSION;
121
-
122
- this.server = new Server(
123
- {
124
- name: 'ai_cli_mcp',
125
- version: SERVER_VERSION,
126
- },
127
- {
128
- capabilities: {
129
- tools: {},
130
- },
131
- }
132
- );
133
-
134
- this.setupToolHandlers();
135
-
136
- this.server.onerror = (error) => console.error('[Error]', error);
137
- this.sigintHandler = async () => {
138
- await this.server.close();
139
- process.exit(0);
140
- };
141
- process.on('SIGINT', this.sigintHandler);
142
- }
143
-
144
- /**
145
- * Set up the MCP tool handlers
146
- */
147
- private setupToolHandlers(): void {
148
- // Define available tools
149
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
150
- tools: [
151
- {
152
- name: 'run',
153
- description: `AI Agent Runner: Starts a Claude, Codex, or Gemini CLI process in the background and returns a PID immediately. Use list_processes and get_result to monitor progress.
154
-
155
- • File ops: Create, read, (fuzzy) edit, move, copy, delete, list files, analyze/ocr images, file content analysis
156
- • Code: Generate / analyse / refactor / fix
157
- • Git: Stage ▸ commit ▸ push ▸ tag (any workflow)
158
- • Terminal: Run any CLI cmd or open URLs
159
- • Web search + summarise content on-the-fly
160
- • Multi-step workflows & GitHub integration
161
-
162
- **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
163
-
164
- **Supported models**:
165
- "claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
166
-
167
- **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
168
-
169
- **Prompt tips**
170
- 1. Be concise, explicit & step-by-step for complex tasks.
171
- 2. Check process status with list_processes
172
- 3. Get results with get_result using the returned PID
173
- 4. Kill long-running processes with kill_process if needed
174
-
175
- `,
176
- inputSchema: {
177
- type: 'object',
178
- properties: {
179
- prompt: {
180
- type: 'string',
181
- description: 'The detailed natural language prompt for the agent to execute. Either this or prompt_file is required.',
182
- },
183
- prompt_file: {
184
- type: 'string',
185
- description: 'Path to a file containing the prompt. Either this or prompt is required. Must be an absolute path or relative to workFolder.',
186
- },
187
- workFolder: {
188
- type: 'string',
189
- description: 'The working directory for the agent execution. Must be an absolute path.',
190
- },
191
- model: {
192
- type: 'string',
193
- description: 'The model to use. Aliases: "claude-ultra" (auto high effort), "codex-ultra" (auto xhigh reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
194
- },
195
- reasoning_effort: {
196
- type: 'string',
197
- description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh".',
198
- },
199
- session_id: {
200
- type: 'string',
201
- description: 'Optional session ID to resume a previous session. Supported for: haiku, sonnet, opus, gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview.',
202
- },
203
- },
204
- required: ['workFolder'],
205
- },
206
- },
207
- {
208
- name: 'list_processes',
209
- description: 'List all running and completed AI agent processes. Returns a simple list with PID, agent type, and status for each process.',
210
- inputSchema: {
211
- type: 'object',
212
- properties: {},
213
- },
214
- },
215
- {
216
- name: 'get_result',
217
- description: 'Get the current output and status of an AI agent process by PID. Returns the output from the agent including session_id (if applicable), along with process metadata.',
218
- inputSchema: {
219
- type: 'object',
220
- properties: {
221
- pid: {
222
- type: 'number',
223
- description: 'The process ID returned by run tool.',
224
- },
225
- verbose: {
226
- type: 'boolean',
227
- description: 'Optional: If true, returns detailed execution information including tool usage history. Defaults to false.',
228
- }
229
- },
230
- required: ['pid'],
231
- },
232
- },
233
- {
234
- name: 'wait',
235
- description: 'Wait for multiple AI agent processes to complete and return their results. Blocks until all specified PIDs finish or timeout occurs.',
236
- inputSchema: {
237
- type: 'object',
238
- properties: {
239
- pids: {
240
- type: 'array',
241
- items: { type: 'number' },
242
- description: 'List of process IDs to wait for (returned by the run tool).',
243
- },
244
- timeout: {
245
- type: 'number',
246
- description: 'Optional: Maximum time to wait in seconds. Defaults to 180 (3 minutes).',
247
- },
248
- },
249
- required: ['pids'],
250
- },
251
- },
252
- {
253
- name: 'kill_process',
254
- description: 'Terminate a running AI agent process by PID.',
255
- inputSchema: {
256
- type: 'object',
257
- properties: {
258
- pid: {
259
- type: 'number',
260
- description: 'The process ID to terminate.',
261
- },
262
- },
263
- required: ['pid'],
264
- },
265
- },
266
- {
267
- name: 'cleanup_processes',
268
- description: 'Remove all completed and failed processes from the process list to free up memory.',
269
- inputSchema: {
270
- type: 'object',
271
- properties: {},
272
- },
273
- }
274
- ],
275
- }));
276
-
277
- // Handle tool calls
278
- const executionTimeoutMs = 1800000; // 30 minutes timeout
279
-
280
- this.server.setRequestHandler(CallToolRequestSchema, async (args, call): Promise<ServerResult> => {
281
- debugLog('[Debug] Handling CallToolRequest:', args);
282
-
283
- const toolName = args.params.name;
284
- const toolArguments = args.params.arguments || {};
285
-
286
- switch (toolName) {
287
- case 'run':
288
- return this.handleRun(toolArguments);
289
- case 'list_processes':
290
- return this.handleListProcesses();
291
- case 'get_result':
292
- return this.handleGetResult(toolArguments);
293
- case 'wait':
294
- return this.handleWait(toolArguments);
295
- case 'kill_process':
296
- return this.handleKillProcess(toolArguments);
297
- case 'cleanup_processes':
298
- return this.handleCleanupProcesses();
299
- default:
300
- throw new McpError(ErrorCode.MethodNotFound, `Tool ${toolName} not found`);
301
- }
302
- });
303
- }
304
-
305
- /**
306
- * Handle run tool - starts Claude or Codex process and returns PID immediately
307
- */
308
- private async handleRun(toolArguments: any): Promise<ServerResult> {
309
- // Print version on first use
310
- if (isFirstToolUse) {
311
- console.error(`ai_cli_mcp v${SERVER_VERSION} started at ${serverStartupTime}`);
312
- isFirstToolUse = false;
313
- }
314
-
315
- // Build CLI command (validation + args assembly)
316
- let cmd;
317
- try {
318
- cmd = buildCliCommand({
319
- prompt: toolArguments.prompt,
320
- prompt_file: toolArguments.prompt_file,
321
- workFolder: toolArguments.workFolder,
322
- model: toolArguments.model,
323
- session_id: toolArguments.session_id,
324
- reasoning_effort: toolArguments.reasoning_effort,
325
- cliPaths: {
326
- claude: this.claudeCliPath,
327
- codex: this.codexCliPath,
328
- gemini: this.geminiCliPath,
329
- },
330
- });
331
- } catch (error: any) {
332
- throw new McpError(ErrorCode.InvalidParams, error.message);
333
- }
334
-
335
- const { cliPath, args: processArgs, cwd: effectiveCwd, agent, prompt } = cmd;
336
-
337
- // Spawn process without waiting
338
- const childProcess = spawn(cliPath, processArgs, {
339
- cwd: effectiveCwd,
340
- stdio: ['ignore', 'pipe', 'pipe'],
341
- detached: false
342
- });
343
-
344
- const pid = childProcess.pid;
345
- if (!pid) {
346
- throw new McpError(ErrorCode.InternalError, `Failed to start ${agent} CLI process`);
347
- }
348
-
349
- // Create process tracking entry
350
- const processEntry: ClaudeProcess = {
351
- pid,
352
- process: childProcess,
353
- prompt,
354
- workFolder: effectiveCwd,
355
- model: toolArguments.model,
356
- toolType: agent,
357
- startTime: new Date().toISOString(),
358
- stdout: '',
359
- stderr: '',
360
- status: 'running'
361
- };
362
-
363
- // Track the process
364
- processManager.set(pid, processEntry);
365
-
366
- // Set up output collection
367
- childProcess.stdout.on('data', (data) => {
368
- const entry = processManager.get(pid);
369
- if (entry) {
370
- entry.stdout += data.toString();
371
- }
372
- });
373
-
374
- childProcess.stderr.on('data', (data) => {
375
- const entry = processManager.get(pid);
376
- if (entry) {
377
- entry.stderr += data.toString();
378
- }
379
- });
380
-
381
- childProcess.on('close', (code) => {
382
- const entry = processManager.get(pid);
383
- if (entry) {
384
- entry.status = code === 0 ? 'completed' : 'failed';
385
- entry.exitCode = code !== null ? code : undefined;
386
- }
387
- });
388
-
389
- childProcess.on('error', (error) => {
390
- const entry = processManager.get(pid);
391
- if (entry) {
392
- entry.status = 'failed';
393
- entry.stderr += `\nProcess error: ${error.message}`;
394
- }
395
- });
396
-
397
- // Return PID immediately
398
- return {
399
- content: [{
400
- type: 'text',
401
- text: JSON.stringify({
402
- pid,
403
- status: 'started',
404
- agent,
405
- message: `${agent} process started successfully`
406
- }, null, 2)
407
- }]
408
- };
409
- }
410
-
411
- /**
412
- * Handle list_processes tool
413
- */
414
- private async handleListProcesses(): Promise<ServerResult> {
415
- const processes: ProcessListItem[] = [];
416
-
417
- for (const [pid, process] of processManager.entries()) {
418
- const processInfo: ProcessListItem = {
419
- pid,
420
- agent: process.toolType,
421
- status: process.status
422
- };
423
-
424
- processes.push(processInfo);
425
- }
426
-
427
- return {
428
- content: [{
429
- type: 'text',
430
- text: JSON.stringify(processes, null, 2)
431
- }]
432
- };
433
- }
434
-
435
- /**
436
- * Helper to get process result object
437
- */
438
- private getProcessResultHelper(pid: number, verbose: boolean = false): any {
439
- const process = processManager.get(pid);
440
-
441
- if (!process) {
442
- throw new McpError(ErrorCode.InvalidParams, `Process with PID ${pid} not found`);
443
- }
444
-
445
- // Parse output based on agent type
446
- let agentOutput: any = null;
447
- if (process.toolType === 'codex') {
448
- // Codex may output structured logs to stderr
449
- const combinedOutput = (process.stdout || '') + '\n' + (process.stderr || '');
450
- agentOutput = parseCodexOutput(combinedOutput);
451
- } else if (process.stdout) {
452
- if (process.toolType === 'claude') {
453
- agentOutput = parseClaudeOutput(process.stdout);
454
- } else if (process.toolType === 'gemini') {
455
- agentOutput = parseGeminiOutput(process.stdout);
456
- }
457
- }
458
-
459
- // Construct response with agent's output and process metadata
460
- const response: any = {
461
- pid,
462
- agent: process.toolType,
463
- status: process.status,
464
- exitCode: process.exitCode,
465
- startTime: process.startTime,
466
- workFolder: process.workFolder,
467
- prompt: process.prompt,
468
- model: process.model
469
- };
470
-
471
- // If we have valid output from agent, include it
472
- if (agentOutput) {
473
- // Filter out tools if not verbose
474
- if (!verbose && agentOutput.tools) {
475
- const { tools, ...rest } = agentOutput;
476
- response.agentOutput = rest;
477
- } else {
478
- response.agentOutput = agentOutput;
479
- }
480
-
481
- // Extract session_id if available
482
- if (agentOutput.session_id) {
483
- response.session_id = agentOutput.session_id;
484
- }
485
- } else {
486
- // Fallback to raw output
487
- response.stdout = process.stdout;
488
- response.stderr = process.stderr;
489
- }
490
-
491
- return response;
492
- }
493
-
494
- /**
495
- * Handle get_result tool
496
- */
497
- private async handleGetResult(toolArguments: any): Promise<ServerResult> {
498
- if (!toolArguments.pid || typeof toolArguments.pid !== 'number') {
499
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
500
- }
501
-
502
- const pid = toolArguments.pid;
503
- const verbose = !!toolArguments.verbose;
504
- const response = this.getProcessResultHelper(pid, verbose);
505
-
506
- return {
507
- content: [{
508
- type: 'text',
509
- text: JSON.stringify(response, null, 2)
510
- }]
511
- };
512
- }
513
-
514
- /**
515
- * Handle wait tool
516
- */
517
- private async handleWait(toolArguments: any): Promise<ServerResult> {
518
- if (!toolArguments.pids || !Array.isArray(toolArguments.pids) || toolArguments.pids.length === 0) {
519
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pids (must be a non-empty array of numbers)');
520
- }
521
-
522
- const pids: number[] = toolArguments.pids;
523
- // Default timeout: 3 minutes (180 seconds)
524
- const timeoutSeconds = typeof toolArguments.timeout === 'number' ? toolArguments.timeout : 180;
525
- const timeoutMs = timeoutSeconds * 1000;
526
-
527
- // Validate all PIDs exist first
528
- for (const pid of pids) {
529
- if (!processManager.has(pid)) {
530
- throw new McpError(ErrorCode.InvalidParams, `Process with PID ${pid} not found`);
531
- }
532
- }
533
-
534
- // Create promises for each process
535
- const waitPromises = pids.map(pid => {
536
- const processEntry = processManager.get(pid)!;
537
-
538
- if (processEntry.status !== 'running') {
539
- return Promise.resolve();
540
- }
541
-
542
- return new Promise<void>((resolve) => {
543
- processEntry.process.once('close', () => {
544
- resolve();
545
- });
546
- });
547
- });
548
-
549
- // Create a timeout promise
550
- const timeoutPromise = new Promise<void>((_, reject) => {
551
- setTimeout(() => {
552
- reject(new Error(`Timed out after ${timeoutSeconds} seconds waiting for processes`));
553
- }, timeoutMs);
554
- });
555
-
556
- try {
557
- // Wait for all processes to finish or timeout
558
- await Promise.race([Promise.all(waitPromises), timeoutPromise]);
559
- } catch (error: any) {
560
- throw new McpError(ErrorCode.InternalError, error.message);
561
- }
562
-
563
- // Collect results (verbose=false for wait)
564
- const results = pids.map(pid => this.getProcessResultHelper(pid, false));
565
-
566
- return {
567
- content: [{
568
- type: 'text',
569
- text: JSON.stringify(results, null, 2)
570
- }]
571
- };
572
- }
573
-
574
- /**
575
- * Handle kill_process tool
576
- */
577
-
578
- private async handleKillProcess(toolArguments: any): Promise<ServerResult> {
579
- if (!toolArguments.pid || typeof toolArguments.pid !== 'number') {
580
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
581
- }
582
-
583
- const pid = toolArguments.pid;
584
- const processEntry = processManager.get(pid);
585
-
586
- if (!processEntry) {
587
- throw new McpError(ErrorCode.InvalidParams, `Process with PID ${pid} not found`);
588
- }
589
-
590
- if (processEntry.status !== 'running') {
591
- return {
592
- content: [{
593
- type: 'text',
594
- text: JSON.stringify({
595
- pid,
596
- status: processEntry.status,
597
- message: 'Process already terminated'
598
- }, null, 2)
599
- }]
600
- };
601
- }
602
-
603
- try {
604
- processEntry.process.kill('SIGTERM');
605
- processEntry.status = 'failed';
606
- processEntry.stderr += '\nProcess terminated by user';
607
-
608
- return {
609
- content: [{
610
- type: 'text',
611
- text: JSON.stringify({
612
- pid,
613
- status: 'terminated',
614
- message: 'Process terminated successfully'
615
- }, null, 2)
616
- }]
617
- };
618
- } catch (error: any) {
619
- throw new McpError(ErrorCode.InternalError, `Failed to terminate process: ${error.message}`);
620
- }
621
- }
622
-
623
- /**
624
- * Handle cleanup_processes tool
625
- */
626
- private async handleCleanupProcesses(): Promise<ServerResult> {
627
- const removedPids: number[] = [];
628
-
629
- // Iterate through all processes and collect PIDs to remove
630
- for (const [pid, process] of processManager.entries()) {
631
- if (process.status === 'completed' || process.status === 'failed') {
632
- removedPids.push(pid);
633
- processManager.delete(pid);
634
- }
635
- }
636
-
637
- return {
638
- content: [{
639
- type: 'text',
640
- text: JSON.stringify({
641
- removed: removedPids.length,
642
- removedPids,
643
- message: `Cleaned up ${removedPids.length} finished process(es)`
644
- }, null, 2)
645
- }]
646
- };
647
- }
648
-
649
- /**
650
- * Start the MCP server
651
- */
652
- async run(): Promise<void> {
653
- // Revert to original server start logic if listen caused errors
654
- const transport = new StdioServerTransport();
655
- await this.server.connect(transport);
656
- console.error('AI CLI MCP server running on stdio');
657
- }
658
-
659
- /**
660
- * Clean up resources (for testing)
661
- */
662
- async cleanup(): Promise<void> {
663
- if (this.sigintHandler) {
664
- process.removeListener('SIGINT', this.sigintHandler);
665
- }
666
- await this.server.close();
667
- }
668
- }
6
+ import { runMcpServer } from './app/mcp.js';
669
7
 
670
- // Create and run the server (skip during tests)
671
8
  if (!process.env.VITEST) {
672
- const server = new ClaudeCodeServer();
673
- server.run().catch(console.error);
9
+ runMcpServer().catch(console.error);
674
10
  }