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