ai-cli-mcp 2.10.0 → 2.12.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 (51) hide show
  1. package/.github/workflows/watch-session-prs.yml +276 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.ja.md +104 -5
  4. package/README.md +104 -5
  5. package/dist/__tests__/app-cli.test.js +285 -0
  6. package/dist/__tests__/cli-bin-smoke.test.js +54 -0
  7. package/dist/__tests__/cli-builder.test.js +49 -2
  8. package/dist/__tests__/cli-process-service.test.js +233 -0
  9. package/dist/__tests__/cli-utils.test.js +109 -0
  10. package/dist/__tests__/error-cases.test.js +2 -1
  11. package/dist/__tests__/mcp-contract.test.js +195 -0
  12. package/dist/__tests__/process-management.test.js +15 -8
  13. package/dist/__tests__/server.test.js +29 -3
  14. package/dist/__tests__/validation.test.js +2 -2
  15. package/dist/__tests__/wait.test.js +31 -0
  16. package/dist/app/cli.js +304 -0
  17. package/dist/app/mcp.js +362 -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 +29 -22
  21. package/dist/cli-process-service.js +328 -0
  22. package/dist/cli-utils.js +142 -88
  23. package/dist/cli.js +1 -1
  24. package/dist/model-catalog.js +50 -0
  25. package/dist/process-service.js +198 -0
  26. package/dist/server.js +3 -577
  27. package/docs/cli-architecture.md +275 -0
  28. package/package.json +3 -2
  29. package/src/__tests__/app-cli.test.ts +362 -0
  30. package/src/__tests__/cli-bin-smoke.test.ts +71 -0
  31. package/src/__tests__/cli-builder.test.ts +62 -3
  32. package/src/__tests__/cli-process-service.test.ts +278 -0
  33. package/src/__tests__/cli-utils.test.ts +132 -0
  34. package/src/__tests__/error-cases.test.ts +3 -4
  35. package/src/__tests__/mcp-contract.test.ts +250 -0
  36. package/src/__tests__/process-management.test.ts +15 -9
  37. package/src/__tests__/server.test.ts +27 -6
  38. package/src/__tests__/validation.test.ts +2 -2
  39. package/src/__tests__/wait.test.ts +38 -0
  40. package/src/app/cli.ts +373 -0
  41. package/src/app/mcp.ts +398 -0
  42. package/src/bin/ai-cli-mcp.ts +7 -0
  43. package/src/bin/ai-cli.ts +11 -0
  44. package/src/cli-builder.ts +32 -22
  45. package/src/cli-process-service.ts +415 -0
  46. package/src/cli-utils.ts +185 -99
  47. package/src/cli.ts +1 -1
  48. package/src/model-catalog.ts +60 -0
  49. package/src/process-service.ts +261 -0
  50. package/src/server.ts +3 -667
  51. package/.github/workflows/watch-codex-fork-pr.yml +0 -98
@@ -0,0 +1,304 @@
1
+ import { runMcpServer } from './mcp.js';
2
+ import { CliProcessService } from '../cli-process-service.js';
3
+ import { getCliDoctorStatus } from '../cli-utils.js';
4
+ import { getModelsPayload } from '../model-catalog.js';
5
+ export const CLI_HELP_TEXT = `Usage: ai-cli <command> [options]
6
+
7
+ Commands:
8
+ run Start an AI CLI process in the background
9
+ wait Wait for one or more pids
10
+ ps List tracked processes
11
+ result Get the current result for a pid
12
+ kill Terminate a tracked pid
13
+ cleanup Remove completed and failed tracked processes
14
+ doctor Check supported AI CLI binaries
15
+ models List supported models and aliases
16
+ mcp Start the MCP server
17
+ help Show this help message
18
+ `;
19
+ export const RUN_HELP_TEXT = `Usage: ai-cli run --cwd <path> [options]
20
+
21
+ Start an AI CLI process in the background.
22
+
23
+ Options:
24
+ --cwd <path> Working directory
25
+ --prompt <text> Prompt text
26
+ --prompt-file <path> Path to a prompt file
27
+ --model <model> Model name or alias (e.g. sonnet, claude-ultra, gpt-5.2-codex, codex-ultra, gemini-2.5-pro, gemini-ultra)
28
+ --session-id <id> Resume a previous session
29
+ --reasoning-effort <level> Reasoning level for Claude/Codex
30
+ --help, -h Show this help message
31
+
32
+ Compatibility aliases:
33
+ --workFolder, --work-folder
34
+ --prompt_file
35
+ --session_id
36
+ --reasoning_effort
37
+ `;
38
+ export const WAIT_HELP_TEXT = `Usage: ai-cli wait <pid...> [options]
39
+
40
+ Wait for one or more tracked processes to finish.
41
+
42
+ Options:
43
+ --timeout <seconds> Maximum wait time in seconds
44
+ --help, -h Show this help message
45
+ `;
46
+ export const RESULT_HELP_TEXT = `Usage: ai-cli result <pid> [options]
47
+
48
+ Get the current result for a tracked process.
49
+
50
+ Options:
51
+ --verbose Include verbose parsed output
52
+ --help, -h Show this help message
53
+ `;
54
+ export const KILL_HELP_TEXT = `Usage: ai-cli kill <pid>
55
+
56
+ Terminate a tracked process.
57
+
58
+ Options:
59
+ --help, -h Show this help message
60
+ `;
61
+ export const CLEANUP_HELP_TEXT = `Usage: ai-cli cleanup
62
+
63
+ Remove completed and failed tracked processes.
64
+
65
+ Options:
66
+ --help, -h Show this help message
67
+ `;
68
+ export const PS_HELP_TEXT = `Usage: ai-cli ps
69
+
70
+ List tracked processes.
71
+
72
+ Options:
73
+ --help, -h Show this help message
74
+ `;
75
+ export const MODELS_HELP_TEXT = `Usage: ai-cli models
76
+
77
+ List supported models and aliases.
78
+
79
+ Options:
80
+ --help, -h Show this help message
81
+ `;
82
+ export const DOCTOR_HELP_TEXT = `Usage: ai-cli doctor
83
+
84
+ Check whether supported AI CLI binaries are available.
85
+
86
+ Options:
87
+ --help, -h Show this help message
88
+ `;
89
+ export const MCP_HELP_TEXT = `Usage: ai-cli mcp
90
+
91
+ Start the MCP server.
92
+ `;
93
+ let cliProcessService = null;
94
+ function getCliProcessService() {
95
+ if (!cliProcessService) {
96
+ cliProcessService = new CliProcessService();
97
+ }
98
+ return cliProcessService;
99
+ }
100
+ const defaultDeps = {
101
+ stdout: (text) => process.stdout.write(text),
102
+ stderr: (text) => process.stderr.write(text),
103
+ startMcpServer: () => runMcpServer(),
104
+ runProcess: (options) => getCliProcessService().startProcess(options),
105
+ listProcesses: () => getCliProcessService().listProcesses(),
106
+ getProcessResult: (pid, verbose) => getCliProcessService().getProcessResult(pid, verbose),
107
+ waitForProcesses: (pids, timeoutSeconds) => getCliProcessService().waitForProcesses(pids, timeoutSeconds),
108
+ killProcess: (pid) => getCliProcessService().killProcess(pid),
109
+ cleanupProcesses: () => getCliProcessService().cleanupProcesses(),
110
+ getDoctorStatus: () => getCliDoctorStatus(),
111
+ };
112
+ function parseArgs(argv) {
113
+ const positionals = [];
114
+ const flags = {};
115
+ for (let i = 0; i < argv.length; i++) {
116
+ const arg = argv[i];
117
+ if (arg === '-h') {
118
+ flags.h = '';
119
+ continue;
120
+ }
121
+ if (!arg.startsWith('--')) {
122
+ positionals.push(arg);
123
+ continue;
124
+ }
125
+ const eqIdx = arg.indexOf('=');
126
+ if (eqIdx !== -1) {
127
+ flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
128
+ continue;
129
+ }
130
+ const next = argv[i + 1];
131
+ if (next !== undefined && !next.startsWith('--')) {
132
+ flags[arg.slice(2)] = next;
133
+ i++;
134
+ }
135
+ else {
136
+ flags[arg.slice(2)] = '';
137
+ }
138
+ }
139
+ return { positionals, flags };
140
+ }
141
+ function getFirstFlag(flags, names) {
142
+ for (const name of names) {
143
+ if (name in flags) {
144
+ return flags[name];
145
+ }
146
+ }
147
+ return undefined;
148
+ }
149
+ function parsePositivePid(value) {
150
+ const pid = Number(value);
151
+ if (!Number.isInteger(pid) || pid <= 0) {
152
+ return null;
153
+ }
154
+ return pid;
155
+ }
156
+ function writeJson(stdout, value) {
157
+ stdout(`${JSON.stringify(value, null, 2)}\n`);
158
+ }
159
+ function hasHelpFlag(flags) {
160
+ return 'help' in flags || 'h' in flags;
161
+ }
162
+ export async function runCli(argv, deps = {}) {
163
+ const { stdout, stderr, startMcpServer, runProcess, listProcesses, getProcessResult, waitForProcesses, killProcess, cleanupProcesses, getDoctorStatus, } = { ...defaultDeps, ...deps };
164
+ const [command] = argv;
165
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
166
+ stdout(CLI_HELP_TEXT);
167
+ return 0;
168
+ }
169
+ if (command === 'mcp') {
170
+ const { flags } = parseArgs(argv.slice(1));
171
+ if (hasHelpFlag(flags)) {
172
+ stdout(MCP_HELP_TEXT);
173
+ return 0;
174
+ }
175
+ await startMcpServer();
176
+ return 0;
177
+ }
178
+ if (command === 'run') {
179
+ const { flags } = parseArgs(argv.slice(1));
180
+ if (hasHelpFlag(flags)) {
181
+ stdout(RUN_HELP_TEXT);
182
+ return 0;
183
+ }
184
+ const cwd = getFirstFlag(flags, ['cwd', 'workFolder', 'work-folder']);
185
+ if (!cwd) {
186
+ stderr('Missing required option: --cwd\n');
187
+ stdout(CLI_HELP_TEXT);
188
+ return 1;
189
+ }
190
+ const prompt = getFirstFlag(flags, ['prompt']);
191
+ const promptFile = getFirstFlag(flags, ['prompt-file', 'prompt_file']);
192
+ if (!prompt && !promptFile) {
193
+ stderr('Missing required option: --prompt or --prompt-file\n');
194
+ stdout(CLI_HELP_TEXT);
195
+ return 1;
196
+ }
197
+ const result = await runProcess({
198
+ cwd,
199
+ prompt: prompt || undefined,
200
+ prompt_file: promptFile || undefined,
201
+ model: getFirstFlag(flags, ['model']) || undefined,
202
+ session_id: getFirstFlag(flags, ['session-id', 'session_id']) || undefined,
203
+ reasoning_effort: getFirstFlag(flags, ['reasoning-effort', 'reasoning_effort']) || undefined,
204
+ });
205
+ writeJson(stdout, result);
206
+ return 0;
207
+ }
208
+ if (command === 'ps') {
209
+ const { flags } = parseArgs(argv.slice(1));
210
+ if (hasHelpFlag(flags)) {
211
+ stdout(PS_HELP_TEXT);
212
+ return 0;
213
+ }
214
+ writeJson(stdout, await listProcesses());
215
+ return 0;
216
+ }
217
+ if (command === 'result') {
218
+ const { positionals, flags } = parseArgs(argv.slice(1));
219
+ if (hasHelpFlag(flags)) {
220
+ stdout(RESULT_HELP_TEXT);
221
+ return 0;
222
+ }
223
+ const pid = parsePositivePid(positionals[0]);
224
+ if (pid === null) {
225
+ stderr('Missing required pid argument\n');
226
+ stdout(CLI_HELP_TEXT);
227
+ return 1;
228
+ }
229
+ writeJson(stdout, await getProcessResult(pid, 'verbose' in flags));
230
+ return 0;
231
+ }
232
+ if (command === 'wait') {
233
+ const { positionals, flags } = parseArgs(argv.slice(1));
234
+ if (hasHelpFlag(flags)) {
235
+ stdout(WAIT_HELP_TEXT);
236
+ return 0;
237
+ }
238
+ const pids = positionals.map((value) => parsePositivePid(value));
239
+ if (pids.length === 0) {
240
+ stderr('Missing required pid arguments\n');
241
+ stdout(CLI_HELP_TEXT);
242
+ return 1;
243
+ }
244
+ if (pids.some((pid) => pid === null)) {
245
+ stderr('All pid arguments must be positive integers\n');
246
+ stdout(CLI_HELP_TEXT);
247
+ return 1;
248
+ }
249
+ const timeoutRaw = getFirstFlag(flags, ['timeout']);
250
+ const timeout = timeoutRaw ? Number(timeoutRaw) : undefined;
251
+ if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) {
252
+ stderr('Invalid --timeout value\n');
253
+ stdout(CLI_HELP_TEXT);
254
+ return 1;
255
+ }
256
+ writeJson(stdout, await waitForProcesses(pids, timeout));
257
+ return 0;
258
+ }
259
+ if (command === 'kill') {
260
+ const { positionals, flags } = parseArgs(argv.slice(1));
261
+ if (hasHelpFlag(flags)) {
262
+ stdout(KILL_HELP_TEXT);
263
+ return 0;
264
+ }
265
+ const pid = parsePositivePid(positionals[0]);
266
+ if (pid === null) {
267
+ stderr('Missing required pid argument\n');
268
+ stdout(CLI_HELP_TEXT);
269
+ return 1;
270
+ }
271
+ writeJson(stdout, await killProcess(pid));
272
+ return 0;
273
+ }
274
+ if (command === 'cleanup') {
275
+ const { flags } = parseArgs(argv.slice(1));
276
+ if (hasHelpFlag(flags)) {
277
+ stdout(CLEANUP_HELP_TEXT);
278
+ return 0;
279
+ }
280
+ writeJson(stdout, await cleanupProcesses());
281
+ return 0;
282
+ }
283
+ if (command === 'models') {
284
+ const { flags } = parseArgs(argv.slice(1));
285
+ if (hasHelpFlag(flags)) {
286
+ stdout(MODELS_HELP_TEXT);
287
+ return 0;
288
+ }
289
+ writeJson(stdout, getModelsPayload());
290
+ return 0;
291
+ }
292
+ if (command === 'doctor') {
293
+ const { flags } = parseArgs(argv.slice(1));
294
+ if (hasHelpFlag(flags)) {
295
+ stdout(DOCTOR_HELP_TEXT);
296
+ return 0;
297
+ }
298
+ writeJson(stdout, getDoctorStatus());
299
+ return 0;
300
+ }
301
+ stderr(`Unknown subcommand: ${command}\n`);
302
+ stdout(CLI_HELP_TEXT);
303
+ return 1;
304
+ }
@@ -0,0 +1,362 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
4
+ import { spawn } from 'node:child_process';
5
+ import { debugLog, findClaudeCli, findCodexCli, findGeminiCli } from '../cli-utils.js';
6
+ import { getModelParameterDescription, getSupportedModelsDescription } from '../model-catalog.js';
7
+ import { ProcessService } from '../process-service.js';
8
+ // Server version - update this when releasing new versions
9
+ const SERVER_VERSION = "2.2.0";
10
+ // Track if this is the first tool use for version printing
11
+ let isFirstToolUse = true;
12
+ // Capture server startup time when the module loads
13
+ const serverStartupTime = new Date().toISOString();
14
+ // Ensure spawnAsync is defined correctly *before* the class
15
+ export async function spawnAsync(command, args, options) {
16
+ return new Promise((resolve, reject) => {
17
+ debugLog(`[Spawn] Running command: ${command} ${args.join(' ')}`);
18
+ const process = spawn(command, args, {
19
+ shell: false,
20
+ timeout: options?.timeout,
21
+ cwd: options?.cwd,
22
+ stdio: ['ignore', 'pipe', 'pipe']
23
+ });
24
+ let stdout = '';
25
+ let stderr = '';
26
+ process.stdout.on('data', (data) => { stdout += data.toString(); });
27
+ process.stderr.on('data', (data) => {
28
+ stderr += data.toString();
29
+ debugLog(`[Spawn Stderr Chunk] ${data.toString()}`);
30
+ });
31
+ process.on('error', (error) => {
32
+ debugLog(`[Spawn Error Event] Full error object:`, error);
33
+ let errorMessage = `Spawn error: ${error.message}`;
34
+ if (error.path) {
35
+ errorMessage += ` | Path: ${error.path}`;
36
+ }
37
+ if (error.syscall) {
38
+ errorMessage += ` | Syscall: ${error.syscall}`;
39
+ }
40
+ errorMessage += `\nStderr: ${stderr.trim()}`;
41
+ reject(new Error(errorMessage));
42
+ });
43
+ process.on('close', (code) => {
44
+ debugLog(`[Spawn Close] Exit code: ${code}`);
45
+ debugLog(`[Spawn Stderr Full] ${stderr.trim()}`);
46
+ debugLog(`[Spawn Stdout Full] ${stdout.trim()}`);
47
+ if (code === 0) {
48
+ resolve({ stdout, stderr });
49
+ }
50
+ else {
51
+ reject(new Error(`Command failed with exit code ${code}\nStderr: ${stderr.trim()}\nStdout: ${stdout.trim()}`));
52
+ }
53
+ });
54
+ });
55
+ }
56
+ export class ClaudeCodeServer {
57
+ server;
58
+ claudeCliPath;
59
+ codexCliPath;
60
+ geminiCliPath;
61
+ processService;
62
+ sigintHandler;
63
+ packageVersion;
64
+ constructor() {
65
+ this.claudeCliPath = findClaudeCli();
66
+ this.codexCliPath = findCodexCli();
67
+ this.geminiCliPath = findGeminiCli();
68
+ console.error(`[Setup] Using Claude CLI command/path: ${this.claudeCliPath}`);
69
+ console.error(`[Setup] Using Codex CLI command/path: ${this.codexCliPath}`);
70
+ console.error(`[Setup] Using Gemini CLI command/path: ${this.geminiCliPath}`);
71
+ this.packageVersion = SERVER_VERSION;
72
+ this.processService = new ProcessService({
73
+ cliPaths: {
74
+ claude: this.claudeCliPath,
75
+ codex: this.codexCliPath,
76
+ gemini: this.geminiCliPath,
77
+ },
78
+ });
79
+ this.server = new Server({
80
+ name: 'ai_cli_mcp',
81
+ version: SERVER_VERSION,
82
+ }, {
83
+ capabilities: {
84
+ tools: {},
85
+ },
86
+ });
87
+ this.setupToolHandlers();
88
+ this.server.onerror = (error) => console.error('[Error]', error);
89
+ this.sigintHandler = async () => {
90
+ await this.server.close();
91
+ process.exit(0);
92
+ };
93
+ process.on('SIGINT', this.sigintHandler);
94
+ }
95
+ setupToolHandlers() {
96
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
97
+ tools: [
98
+ {
99
+ name: 'run',
100
+ 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.
101
+
102
+ • File ops: Create, read, (fuzzy) edit, move, copy, delete, list files, analyze/ocr images, file content analysis
103
+ • Code: Generate / analyse / refactor / fix
104
+ • Git: Stage ▸ commit ▸ push ▸ tag (any workflow)
105
+ • Terminal: Run any CLI cmd or open URLs
106
+ • Web search + summarise content on-the-fly
107
+ • Multi-step workflows & GitHub integration
108
+
109
+ **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
110
+
111
+ **Supported models**:
112
+ ${getSupportedModelsDescription()}
113
+
114
+ **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
115
+
116
+ **Prompt tips**
117
+ 1. Be concise, explicit & step-by-step for complex tasks.
118
+ 2. Check process status with list_processes
119
+ 3. Get results with get_result using the returned PID
120
+ 4. Kill long-running processes with kill_process if needed
121
+
122
+ `,
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ prompt: {
127
+ type: 'string',
128
+ description: 'The detailed natural language prompt for the agent to execute. Either this or prompt_file is required.',
129
+ },
130
+ prompt_file: {
131
+ type: 'string',
132
+ description: 'Path to a file containing the prompt. Either this or prompt is required. Must be an absolute path or relative to workFolder.',
133
+ },
134
+ workFolder: {
135
+ type: 'string',
136
+ description: 'The working directory for the agent execution. Must be an absolute path.',
137
+ },
138
+ model: {
139
+ type: 'string',
140
+ description: getModelParameterDescription(),
141
+ },
142
+ reasoning_effort: {
143
+ type: 'string',
144
+ description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh".',
145
+ },
146
+ session_id: {
147
+ type: 'string',
148
+ 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.',
149
+ },
150
+ },
151
+ required: ['workFolder'],
152
+ },
153
+ },
154
+ {
155
+ name: 'list_processes',
156
+ description: 'List all running and completed AI agent processes. Returns a simple list with PID, agent type, and status for each process.',
157
+ inputSchema: {
158
+ type: 'object',
159
+ properties: {},
160
+ },
161
+ },
162
+ {
163
+ name: 'get_result',
164
+ 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.',
165
+ inputSchema: {
166
+ type: 'object',
167
+ properties: {
168
+ pid: {
169
+ type: 'number',
170
+ description: 'The process ID returned by run tool.',
171
+ },
172
+ verbose: {
173
+ type: 'boolean',
174
+ description: 'Optional: If true, returns detailed execution information including tool usage history. Defaults to false.',
175
+ }
176
+ },
177
+ required: ['pid'],
178
+ },
179
+ },
180
+ {
181
+ name: 'wait',
182
+ description: 'Wait for multiple AI agent processes to complete and return their results. Blocks until all specified PIDs finish or timeout occurs.',
183
+ inputSchema: {
184
+ type: 'object',
185
+ properties: {
186
+ pids: {
187
+ type: 'array',
188
+ items: { type: 'number' },
189
+ description: 'List of process IDs to wait for (returned by the run tool).',
190
+ },
191
+ timeout: {
192
+ type: 'number',
193
+ description: 'Optional: Maximum time to wait in seconds. Defaults to 180 (3 minutes).',
194
+ },
195
+ },
196
+ required: ['pids'],
197
+ },
198
+ },
199
+ {
200
+ name: 'kill_process',
201
+ description: 'Terminate a running AI agent process by PID.',
202
+ inputSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ pid: {
206
+ type: 'number',
207
+ description: 'The process ID to terminate.',
208
+ },
209
+ },
210
+ required: ['pid'],
211
+ },
212
+ },
213
+ {
214
+ name: 'cleanup_processes',
215
+ description: 'Remove all completed and failed processes from the process list to free up memory.',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {},
219
+ },
220
+ }
221
+ ],
222
+ }));
223
+ this.server.setRequestHandler(CallToolRequestSchema, async (args) => {
224
+ debugLog('[Debug] Handling CallToolRequest:', args);
225
+ const toolName = args.params.name;
226
+ const toolArguments = args.params.arguments || {};
227
+ switch (toolName) {
228
+ case 'run':
229
+ return this.handleRun(toolArguments);
230
+ case 'list_processes':
231
+ return this.handleListProcesses();
232
+ case 'get_result':
233
+ return this.handleGetResult(toolArguments);
234
+ case 'wait':
235
+ return this.handleWait(toolArguments);
236
+ case 'kill_process':
237
+ return this.handleKillProcess(toolArguments);
238
+ case 'cleanup_processes':
239
+ return this.handleCleanupProcesses();
240
+ default:
241
+ throw new McpError(ErrorCode.MethodNotFound, `Tool ${toolName} not found`);
242
+ }
243
+ });
244
+ }
245
+ async handleRun(toolArguments) {
246
+ if (isFirstToolUse) {
247
+ console.error(`ai_cli_mcp v${SERVER_VERSION} started at ${serverStartupTime}`);
248
+ isFirstToolUse = false;
249
+ }
250
+ try {
251
+ const result = this.processService.startProcess({
252
+ prompt: toolArguments.prompt,
253
+ prompt_file: toolArguments.prompt_file,
254
+ workFolder: toolArguments.workFolder,
255
+ model: toolArguments.model,
256
+ session_id: toolArguments.session_id,
257
+ reasoning_effort: toolArguments.reasoning_effort,
258
+ });
259
+ return {
260
+ content: [{
261
+ type: 'text',
262
+ text: JSON.stringify(result, null, 2)
263
+ }]
264
+ };
265
+ }
266
+ catch (error) {
267
+ const code = /Failed to start/.test(error.message) ? ErrorCode.InternalError : ErrorCode.InvalidParams;
268
+ throw new McpError(code, error.message);
269
+ }
270
+ }
271
+ async handleListProcesses() {
272
+ return {
273
+ content: [{
274
+ type: 'text',
275
+ text: JSON.stringify(this.processService.listProcesses(), null, 2)
276
+ }]
277
+ };
278
+ }
279
+ async handleGetResult(toolArguments) {
280
+ if (!toolArguments.pid || typeof toolArguments.pid !== 'number') {
281
+ throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
282
+ }
283
+ const pid = toolArguments.pid;
284
+ const verbose = !!toolArguments.verbose;
285
+ try {
286
+ const response = this.processService.getProcessResult(pid, verbose);
287
+ return {
288
+ content: [{
289
+ type: 'text',
290
+ text: JSON.stringify(response, null, 2)
291
+ }]
292
+ };
293
+ }
294
+ catch (error) {
295
+ const code = /not found/.test(error.message) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
296
+ throw new McpError(code, error.message);
297
+ }
298
+ }
299
+ async handleWait(toolArguments) {
300
+ if (!toolArguments.pids || !Array.isArray(toolArguments.pids) || toolArguments.pids.length === 0) {
301
+ throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pids (must be a non-empty array of numbers)');
302
+ }
303
+ try {
304
+ const results = await this.processService.waitForProcesses(toolArguments.pids, typeof toolArguments.timeout === 'number' ? toolArguments.timeout : 180);
305
+ return {
306
+ content: [{
307
+ type: 'text',
308
+ text: JSON.stringify(results, null, 2)
309
+ }]
310
+ };
311
+ }
312
+ catch (error) {
313
+ const code = /not found/.test(error.message) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
314
+ throw new McpError(code, error.message);
315
+ }
316
+ }
317
+ async handleKillProcess(toolArguments) {
318
+ if (!toolArguments.pid || typeof toolArguments.pid !== 'number') {
319
+ throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
320
+ }
321
+ const pid = toolArguments.pid;
322
+ try {
323
+ const response = this.processService.killProcess(pid);
324
+ return {
325
+ content: [{
326
+ type: 'text',
327
+ text: JSON.stringify(response, null, 2)
328
+ }]
329
+ };
330
+ }
331
+ catch (error) {
332
+ const code = /not found/.test(error.message) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
333
+ const message = code === ErrorCode.InternalError
334
+ ? `Failed to terminate process: ${error.message}`
335
+ : error.message;
336
+ throw new McpError(code, message);
337
+ }
338
+ }
339
+ async handleCleanupProcesses() {
340
+ return {
341
+ content: [{
342
+ type: 'text',
343
+ text: JSON.stringify(this.processService.cleanupProcesses(), null, 2)
344
+ }]
345
+ };
346
+ }
347
+ async run() {
348
+ const transport = new StdioServerTransport();
349
+ await this.server.connect(transport);
350
+ console.error('AI CLI MCP server running on stdio');
351
+ }
352
+ async cleanup() {
353
+ if (this.sigintHandler) {
354
+ process.removeListener('SIGINT', this.sigintHandler);
355
+ }
356
+ await this.server.close();
357
+ }
358
+ }
359
+ export async function runMcpServer() {
360
+ const server = new ClaudeCodeServer();
361
+ await server.run();
362
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { runMcpServer } from '../app/mcp.js';
3
+ runMcpServer().catch((error) => {
4
+ process.stderr.write(`${error.message}\n`);
5
+ process.exit(1);
6
+ });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from '../app/cli.js';
3
+ runCli(process.argv.slice(2))
4
+ .then((exitCode) => {
5
+ process.exit(exitCode);
6
+ })
7
+ .catch((error) => {
8
+ process.stderr.write(`${error.message}\n`);
9
+ process.exit(1);
10
+ });