ai-cli-mcp 2.0.1 → 2.2.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.
@@ -12,7 +12,8 @@
12
12
  "mcp__ccm__get_claude_result",
13
13
  "mcp__chat__agent_communication_list_rooms",
14
14
  "mcp__chat__agent_communication_get_messages",
15
- "mcp__ccm__list_claude_processes"
15
+ "mcp__ccm__list_claude_processes",
16
+ "Bash(gemini:*)"
16
17
  ],
17
18
  "deny": []
18
19
  }
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  > **📦 Package Migration Notice**: This package was formerly `@mkxultra/claude-code-mcp` and has been renamed to `ai-cli-mcp` to reflect its expanded support for multiple AI CLI tools.
7
7
 
8
- An MCP (Model Context Protocol) server that allows running AI CLI tools (Claude and Codex) in background processes with automatic permission handling.
8
+ An MCP (Model Context Protocol) server that allows running AI CLI tools (Claude, Codex, and Gemini) in background processes with automatic permission handling.
9
9
 
10
10
  Did you notice that Cursor sometimes struggles with complex, multi-step edits or operations? This server, with its powerful unified `run` tool, enables multiple AI agents to handle your coding tasks more effectively.
11
11
 
@@ -17,7 +17,8 @@ This MCP server provides tools that can be used by LLMs to interact with AI CLI
17
17
 
18
18
  - Run Claude CLI with all permissions bypassed (using `--dangerously-skip-permissions`)
19
19
  - Execute Codex CLI with automatic approval mode (using `--full-auto`)
20
- - Support multiple AI models: Claude (sonnet, opus, haiku) and Codex (gpt-5-low, gpt-5-medium, gpt-5-high)
20
+ - Execute Gemini CLI with automatic approval mode (using `-y`)
21
+ - Support multiple AI models: Claude (sonnet, opus, haiku), Codex (gpt-5-low, gpt-5-medium, gpt-5-high), and Gemini (gemini-2.5-pro, gemini-2.5-flash)
21
22
  - Manage background processes with PID tracking
22
23
  - Parse and return structured outputs from both tools
23
24
 
@@ -34,6 +35,7 @@ This MCP server provides tools that can be used by LLMs to interact with AI CLI
34
35
  - Node.js v20 or later (Use fnm or nvm to install)
35
36
  - Claude CLI installed locally (run it and call /doctor) and `--dangerously-skip-permissions` accepted
36
37
  - Codex CLI installed (optional, for Codex support)
38
+ - Gemini CLI installed (optional, for Gemini support)
37
39
 
38
40
  ## Configuration
39
41
 
@@ -41,9 +43,10 @@ This MCP server provides tools that can be used by LLMs to interact with AI CLI
41
43
 
42
44
  - `CLAUDE_CLI_NAME`: Override the Claude CLI binary name or provide an absolute path (default: `claude`)
43
45
  - `CODEX_CLI_NAME`: Override the Codex CLI binary name or provide an absolute path (default: `codex`)
46
+ - `GEMINI_CLI_NAME`: Override the Gemini CLI binary name or provide an absolute path (default: `gemini`)
44
47
  - `MCP_CLAUDE_DEBUG`: Enable debug logging (set to `true` for verbose output)
45
48
 
46
- Both CLI name variables support:
49
+ All CLI name variables support:
47
50
  - Simple name: `CLAUDE_CLI_NAME=claude-custom` or `CODEX_CLI_NAME=codex-v2`
48
51
  - Absolute path: `CLAUDE_CLI_NAME=/path/to/custom/claude`
49
52
 
@@ -108,7 +111,15 @@ Follow the prompts to accept. Once this is done, the MCP server will be able to
108
111
  codex login
109
112
  ```
110
113
 
111
- macOS might ask for folder permissions the first time either tool runs. If the first run fails, subsequent runs should work.
114
+ ### For Gemini CLI:
115
+
116
+ **For Gemini, ensure you're logged in and have configured your credentials:**
117
+
118
+ ```bash
119
+ gemini auth login
120
+ ```
121
+
122
+ macOS might ask for folder permissions the first time any of these tools run. If the first run fails, subsequent runs should work.
112
123
 
113
124
  ## Connecting to Your MCP Client
114
125
 
@@ -151,6 +162,7 @@ Executes a prompt using either Claude CLI or Codex CLI. The appropriate CLI is a
151
162
  - `model` (string, optional): The model to use:
152
163
  - Claude models: "sonnet", "opus", "haiku"
153
164
  - Codex models: "gpt-5-low", "gpt-5-medium", "gpt-5-high"
165
+ - Gemini models: "gemini-2.5-pro", "gemini-2.5-flash"
154
166
  - `session_id` (string, optional): Optional session ID to resume a previous session. Supported for: haiku, sonnet, opus.
155
167
 
156
168
  ### `list_processes`
@@ -195,6 +207,18 @@ Terminates a running AI agent process by PID.
195
207
  }
196
208
  ```
197
209
 
210
+ **Example with Gemini:**
211
+ ```json
212
+ {
213
+ "toolName": "run",
214
+ "arguments": {
215
+ "prompt": "Generate unit tests for the Calculator class",
216
+ "workFolder": "/Users/username/my_project",
217
+ "model": "gemini-2.5-pro"
218
+ }
219
+ }
220
+ ```
221
+
198
222
  ### Examples
199
223
 
200
224
  Here are some visual examples of the server in action:
@@ -304,6 +328,31 @@ npm run test:coverage
304
328
 
305
329
  For detailed testing documentation, see our [E2E Testing Guide](./docs/e2e-testing.md).
306
330
 
331
+ ## Manual Testing with MCP Inspector
332
+
333
+ You can manually test the MCP server using the Model Context Protocol Inspector:
334
+
335
+ ```bash
336
+ # Build the project first
337
+ npm run build
338
+
339
+ # Start the MCP Inspector with the server
340
+ npx @modelcontextprotocol/inspector node dist/server.js
341
+ ```
342
+
343
+ This will open a web interface where you can:
344
+ 1. View all available tools (`run`, `list_processes`, `get_result`, `kill_process`)
345
+ 2. Test each tool with different parameters
346
+ 3. Test different AI models including:
347
+ - Claude models: `sonnet`, `opus`, `haiku`
348
+ - Codex models: `gpt-5-low`, `gpt-5-medium`, `gpt-5-high`
349
+ - Gemini models: `gemini-2.5-pro`, `gemini-2.5-flash`
350
+
351
+ Example test: Select the `run` tool and provide:
352
+ - `prompt`: "What is 2+2?"
353
+ - `workFolder`: "/tmp"
354
+ - `model`: "gemini-2.5-flash"
355
+
307
356
  ## Configuration via Environment Variables
308
357
 
309
358
  The server's behavior can be customized using these environment variables:
package/dist/parsers.js CHANGED
@@ -52,3 +52,17 @@ export function parseClaudeOutput(stdout) {
52
52
  return null;
53
53
  }
54
54
  }
55
+ /**
56
+ * Parse Gemini JSON output
57
+ */
58
+ export function parseGeminiOutput(stdout) {
59
+ if (!stdout)
60
+ return null;
61
+ try {
62
+ return JSON.parse(stdout);
63
+ }
64
+ catch (e) {
65
+ debugLog(`[Debug] Failed to parse Gemini JSON output: ${e}`);
66
+ return null;
67
+ }
68
+ }
package/dist/server.js CHANGED
@@ -7,9 +7,9 @@ import { existsSync, readFileSync } from 'node:fs';
7
7
  import { homedir } from 'node:os';
8
8
  import { join, resolve as pathResolve } from 'node:path';
9
9
  import * as path from 'path';
10
- import { parseCodexOutput, parseClaudeOutput } from './parsers.js';
10
+ import { parseCodexOutput, parseClaudeOutput, parseGeminiOutput } from './parsers.js';
11
11
  // Server version - update this when releasing new versions
12
- const SERVER_VERSION = "2.0.1";
12
+ const SERVER_VERSION = "2.2.0";
13
13
  // Model alias mappings for user-friendly model names
14
14
  const MODEL_ALIASES = {
15
15
  'haiku': 'claude-3-5-haiku-20241022'
@@ -28,6 +28,42 @@ export function debugLog(message, ...optionalParams) {
28
28
  console.error(message, ...optionalParams);
29
29
  }
30
30
  }
31
+ /**
32
+ * Determine the Gemini CLI command/path.
33
+ * Similar to findClaudeCli but for Gemini
34
+ */
35
+ export function findGeminiCli() {
36
+ debugLog('[Debug] Attempting to find Gemini CLI...');
37
+ // Check for custom CLI name from environment variable
38
+ const customCliName = process.env.GEMINI_CLI_NAME;
39
+ if (customCliName) {
40
+ debugLog(`[Debug] Using custom Gemini CLI name from GEMINI_CLI_NAME: ${customCliName}`);
41
+ // If it's an absolute path, use it directly
42
+ if (path.isAbsolute(customCliName)) {
43
+ debugLog(`[Debug] GEMINI_CLI_NAME is an absolute path: ${customCliName}`);
44
+ return customCliName;
45
+ }
46
+ // If it starts with ~ or ./, reject as relative paths are not allowed
47
+ if (customCliName.startsWith('./') || customCliName.startsWith('../') || customCliName.includes('/')) {
48
+ throw new Error(`Invalid GEMINI_CLI_NAME: Relative paths are not allowed. Use either a simple name (e.g., 'gemini') or an absolute path (e.g., '/tmp/gemini-test')`);
49
+ }
50
+ }
51
+ const cliName = customCliName || 'gemini';
52
+ // Try local install path: ~/.gemini/local/gemini
53
+ const userPath = join(homedir(), '.gemini', 'local', 'gemini');
54
+ debugLog(`[Debug] Checking for Gemini CLI at local user path: ${userPath}`);
55
+ if (existsSync(userPath)) {
56
+ debugLog(`[Debug] Found Gemini CLI at local user path: ${userPath}. Using this path.`);
57
+ return userPath;
58
+ }
59
+ else {
60
+ debugLog(`[Debug] Gemini CLI not found at local user path: ${userPath}.`);
61
+ }
62
+ // Fallback to CLI name (PATH lookup)
63
+ debugLog(`[Debug] Falling back to "${cliName}" command name, relying on spawn/PATH lookup.`);
64
+ console.warn(`[Warning] Gemini CLI not found at ~/.gemini/local/gemini. Falling back to "${cliName}" in PATH. Ensure it is installed and accessible.`);
65
+ return cliName;
66
+ }
31
67
  /**
32
68
  * Determine the Codex CLI command/path.
33
69
  * Similar to findClaudeCli but for Codex
@@ -163,14 +199,17 @@ export class ClaudeCodeServer {
163
199
  server;
164
200
  claudeCliPath;
165
201
  codexCliPath;
202
+ geminiCliPath;
166
203
  sigintHandler;
167
204
  packageVersion;
168
205
  constructor() {
169
206
  // Use the simplified findClaudeCli function
170
207
  this.claudeCliPath = findClaudeCli(); // Removed debugMode argument
171
208
  this.codexCliPath = findCodexCli();
209
+ this.geminiCliPath = findGeminiCli();
172
210
  console.error(`[Setup] Using Claude CLI command/path: ${this.claudeCliPath}`);
173
211
  console.error(`[Setup] Using Codex CLI command/path: ${this.codexCliPath}`);
212
+ console.error(`[Setup] Using Gemini CLI command/path: ${this.geminiCliPath}`);
174
213
  this.packageVersion = SERVER_VERSION;
175
214
  this.server = new Server({
176
215
  name: 'ai_cli_mcp',
@@ -197,7 +236,7 @@ export class ClaudeCodeServer {
197
236
  tools: [
198
237
  {
199
238
  name: 'run',
200
- description: `AI Agent Runner: Starts a Claude or Codex CLI process in the background and returns a PID immediately. Use list_processes and get_result to monitor progress.
239
+ 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.
201
240
 
202
241
  • File ops: Create, read, (fuzzy) edit, move, copy, delete, list files, analyze/ocr images, file content analysis
203
242
  • Code: Generate / analyse / refactor / fix
@@ -208,8 +247,8 @@ export class ClaudeCodeServer {
208
247
 
209
248
  **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
210
249
 
211
- **Supported models**:
212
- "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high"
250
+ **Supported models**:
251
+ "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high", "gemini-2.5-pro", "gemini-2.5-flash"
213
252
 
214
253
  **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
215
254
 
@@ -237,7 +276,7 @@ export class ClaudeCodeServer {
237
276
  },
238
277
  model: {
239
278
  type: 'string',
240
- description: 'The model to use: "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high".',
279
+ description: 'The model to use: "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high", "gemini-2.5-pro", "gemini-2.5-flash".',
241
280
  },
242
281
  session_id: {
243
282
  type: 'string',
@@ -249,7 +288,7 @@ export class ClaudeCodeServer {
249
288
  },
250
289
  {
251
290
  name: 'list_processes',
252
- description: 'List all running and completed AI agent processes with their status, PID, and basic info.',
291
+ description: 'List all running and completed AI agent processes. Returns a simple list with PID, agent type, and status for each process.',
253
292
  inputSchema: {
254
293
  type: 'object',
255
294
  properties: {},
@@ -282,6 +321,14 @@ export class ClaudeCodeServer {
282
321
  },
283
322
  required: ['pid'],
284
323
  },
324
+ },
325
+ {
326
+ name: 'cleanup_processes',
327
+ description: 'Remove all completed and failed processes from the process list to free up memory.',
328
+ inputSchema: {
329
+ type: 'object',
330
+ properties: {},
331
+ },
285
332
  }
286
333
  ],
287
334
  }));
@@ -300,6 +347,8 @@ export class ClaudeCodeServer {
300
347
  return this.handleGetResult(toolArguments);
301
348
  case 'kill_process':
302
349
  return this.handleKillProcess(toolArguments);
350
+ case 'cleanup_processes':
351
+ return this.handleCleanupProcesses();
303
352
  default:
304
353
  throw new McpError(ErrorCode.MethodNotFound, `Tool ${toolName} not found`);
305
354
  }
@@ -355,7 +404,16 @@ export class ClaudeCodeServer {
355
404
  }
356
405
  // Determine which agent to use based on model name
357
406
  const model = toolArguments.model || '';
358
- const agent = model.startsWith('gpt-') ? 'codex' : 'claude';
407
+ let agent;
408
+ if (model.startsWith('gpt-')) {
409
+ agent = 'codex';
410
+ }
411
+ else if (model.startsWith('gemini')) {
412
+ agent = 'gemini';
413
+ }
414
+ else {
415
+ agent = 'claude';
416
+ }
359
417
  let cliPath;
360
418
  let processArgs;
361
419
  if (agent === 'codex') {
@@ -373,6 +431,17 @@ export class ClaudeCodeServer {
373
431
  }
374
432
  processArgs.push('--full-auto', '--json', prompt);
375
433
  }
434
+ else if (agent === 'gemini') {
435
+ // Handle Gemini
436
+ cliPath = this.geminiCliPath;
437
+ processArgs = ['-y', '--output-format', 'json'];
438
+ // Add model if specified
439
+ if (toolArguments.model) {
440
+ processArgs.push('--model', toolArguments.model);
441
+ }
442
+ // Add prompt as positional argument
443
+ processArgs.push(prompt);
444
+ }
376
445
  else {
377
446
  // Handle Claude (default)
378
447
  cliPath = this.claudeCliPath;
@@ -461,25 +530,8 @@ export class ClaudeCodeServer {
461
530
  const processInfo = {
462
531
  pid,
463
532
  agent: process.toolType,
464
- status: process.status,
465
- startTime: process.startTime,
466
- prompt: process.prompt.substring(0, 100) + (process.prompt.length > 100 ? '...' : ''),
467
- workFolder: process.workFolder,
468
- model: process.model,
469
- exitCode: process.exitCode
533
+ status: process.status
470
534
  };
471
- // Try to extract session_id from JSON output if available
472
- if (process.stdout) {
473
- try {
474
- const claudeOutput = JSON.parse(process.stdout);
475
- if (claudeOutput.session_id) {
476
- processInfo.session_id = claudeOutput.session_id;
477
- }
478
- }
479
- catch (e) {
480
- // Ignore parsing errors
481
- }
482
- }
483
535
  processes.push(processInfo);
484
536
  }
485
537
  return {
@@ -510,6 +562,9 @@ export class ClaudeCodeServer {
510
562
  else if (process.toolType === 'claude') {
511
563
  agentOutput = parseClaudeOutput(process.stdout);
512
564
  }
565
+ else if (process.toolType === 'gemini') {
566
+ agentOutput = parseGeminiOutput(process.stdout);
567
+ }
513
568
  }
514
569
  // Construct response with agent's output and process metadata
515
570
  const response = {
@@ -585,6 +640,29 @@ export class ClaudeCodeServer {
585
640
  throw new McpError(ErrorCode.InternalError, `Failed to terminate process: ${error.message}`);
586
641
  }
587
642
  }
643
+ /**
644
+ * Handle cleanup_processes tool
645
+ */
646
+ async handleCleanupProcesses() {
647
+ const removedPids = [];
648
+ // Iterate through all processes and collect PIDs to remove
649
+ for (const [pid, process] of processManager.entries()) {
650
+ if (process.status === 'completed' || process.status === 'failed') {
651
+ removedPids.push(pid);
652
+ processManager.delete(pid);
653
+ }
654
+ }
655
+ return {
656
+ content: [{
657
+ type: 'text',
658
+ text: JSON.stringify({
659
+ removed: removedPids.length,
660
+ removedPids,
661
+ message: `Cleaned up ${removedPids.length} finished process(es)`
662
+ }, null, 2)
663
+ }]
664
+ };
665
+ }
588
666
  /**
589
667
  * Start the MCP server
590
668
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-cli-mcp",
3
- "version": "2.0.1",
4
- "description": "MCP server for AI CLI tools (Claude and Codex) with background process management",
3
+ "version": "2.2.0",
4
+ "description": "MCP server for AI CLI tools (Claude, Codex, and Gemini) with background process management",
5
5
  "author": "mkXultra",
6
6
  "license": "MIT",
7
7
  "main": "dist/server.js",
package/src/parsers.ts CHANGED
@@ -45,11 +45,25 @@ export function parseCodexOutput(stdout: string): any {
45
45
  */
46
46
  export function parseClaudeOutput(stdout: string): any {
47
47
  if (!stdout) return null;
48
-
48
+
49
49
  try {
50
50
  return JSON.parse(stdout);
51
51
  } catch (e) {
52
52
  debugLog(`[Debug] Failed to parse Claude JSON output: ${e}`);
53
53
  return null;
54
54
  }
55
+ }
56
+
57
+ /**
58
+ * Parse Gemini JSON output
59
+ */
60
+ export function parseGeminiOutput(stdout: string): any {
61
+ if (!stdout) return null;
62
+
63
+ try {
64
+ return JSON.parse(stdout);
65
+ } catch (e) {
66
+ debugLog(`[Debug] Failed to parse Gemini JSON output: ${e}`);
67
+ return null;
68
+ }
55
69
  }
package/src/server.ts CHANGED
@@ -13,10 +13,10 @@ import { existsSync, readFileSync } from 'node:fs';
13
13
  import { homedir } from 'node:os';
14
14
  import { join, resolve as pathResolve } from 'node:path';
15
15
  import * as path from 'path';
16
- import { parseCodexOutput, parseClaudeOutput } from './parsers.js';
16
+ import { parseCodexOutput, parseClaudeOutput, parseGeminiOutput } from './parsers.js';
17
17
 
18
18
  // Server version - update this when releasing new versions
19
- const SERVER_VERSION = "2.0.1";
19
+ const SERVER_VERSION = "2.2.0";
20
20
 
21
21
  // Model alias mappings for user-friendly model names
22
22
  const MODEL_ALIASES: Record<string, string> = {
@@ -39,7 +39,7 @@ interface ClaudeProcess {
39
39
  prompt: string;
40
40
  workFolder: string;
41
41
  model?: string;
42
- toolType: 'claude' | 'codex'; // Identify which CLI tool
42
+ toolType: 'claude' | 'codex' | 'gemini'; // Identify which CLI tool
43
43
  startTime: string;
44
44
  stdout: string;
45
45
  stderr: string;
@@ -47,6 +47,13 @@ interface ClaudeProcess {
47
47
  exitCode?: number;
48
48
  }
49
49
 
50
+ // Type definition for list_processes return value
51
+ interface ProcessListItem {
52
+ pid: number; // プロセスID
53
+ agent: 'claude' | 'codex' | 'gemini'; // エージェントタイプ
54
+ status: 'running' | 'completed' | 'failed'; // プロセスの状態
55
+ }
56
+
50
57
  // Global process manager
51
58
  const processManager = new Map<number, ClaudeProcess>();
52
59
 
@@ -57,6 +64,49 @@ export function debugLog(message?: any, ...optionalParams: any[]): void {
57
64
  }
58
65
  }
59
66
 
67
+ /**
68
+ * Determine the Gemini CLI command/path.
69
+ * Similar to findClaudeCli but for Gemini
70
+ */
71
+ export function findGeminiCli(): string {
72
+ debugLog('[Debug] Attempting to find Gemini CLI...');
73
+
74
+ // Check for custom CLI name from environment variable
75
+ const customCliName = process.env.GEMINI_CLI_NAME;
76
+ if (customCliName) {
77
+ debugLog(`[Debug] Using custom Gemini CLI name from GEMINI_CLI_NAME: ${customCliName}`);
78
+
79
+ // If it's an absolute path, use it directly
80
+ if (path.isAbsolute(customCliName)) {
81
+ debugLog(`[Debug] GEMINI_CLI_NAME is an absolute path: ${customCliName}`);
82
+ return customCliName;
83
+ }
84
+
85
+ // If it starts with ~ or ./, reject as relative paths are not allowed
86
+ if (customCliName.startsWith('./') || customCliName.startsWith('../') || customCliName.includes('/')) {
87
+ throw new Error(`Invalid GEMINI_CLI_NAME: Relative paths are not allowed. Use either a simple name (e.g., 'gemini') or an absolute path (e.g., '/tmp/gemini-test')`);
88
+ }
89
+ }
90
+
91
+ const cliName = customCliName || 'gemini';
92
+
93
+ // Try local install path: ~/.gemini/local/gemini
94
+ const userPath = join(homedir(), '.gemini', 'local', 'gemini');
95
+ debugLog(`[Debug] Checking for Gemini CLI at local user path: ${userPath}`);
96
+
97
+ if (existsSync(userPath)) {
98
+ debugLog(`[Debug] Found Gemini CLI at local user path: ${userPath}. Using this path.`);
99
+ return userPath;
100
+ } else {
101
+ debugLog(`[Debug] Gemini CLI not found at local user path: ${userPath}.`);
102
+ }
103
+
104
+ // Fallback to CLI name (PATH lookup)
105
+ debugLog(`[Debug] Falling back to "${cliName}" command name, relying on spawn/PATH lookup.`);
106
+ console.warn(`[Warning] Gemini CLI not found at ~/.gemini/local/gemini. Falling back to "${cliName}" in PATH. Ensure it is installed and accessible.`);
107
+ return cliName;
108
+ }
109
+
60
110
  /**
61
111
  * Determine the Codex CLI command/path.
62
112
  * Similar to findClaudeCli but for Codex
@@ -232,6 +282,7 @@ export class ClaudeCodeServer {
232
282
  private server: Server;
233
283
  private claudeCliPath: string;
234
284
  private codexCliPath: string;
285
+ private geminiCliPath: string;
235
286
  private sigintHandler?: () => Promise<void>;
236
287
  private packageVersion: string;
237
288
 
@@ -239,8 +290,10 @@ export class ClaudeCodeServer {
239
290
  // Use the simplified findClaudeCli function
240
291
  this.claudeCliPath = findClaudeCli(); // Removed debugMode argument
241
292
  this.codexCliPath = findCodexCli();
293
+ this.geminiCliPath = findGeminiCli();
242
294
  console.error(`[Setup] Using Claude CLI command/path: ${this.claudeCliPath}`);
243
295
  console.error(`[Setup] Using Codex CLI command/path: ${this.codexCliPath}`);
296
+ console.error(`[Setup] Using Gemini CLI command/path: ${this.geminiCliPath}`);
244
297
  this.packageVersion = SERVER_VERSION;
245
298
 
246
299
  this.server = new Server(
@@ -274,7 +327,7 @@ export class ClaudeCodeServer {
274
327
  tools: [
275
328
  {
276
329
  name: 'run',
277
- description: `AI Agent Runner: Starts a Claude or Codex CLI process in the background and returns a PID immediately. Use list_processes and get_result to monitor progress.
330
+ 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.
278
331
 
279
332
  • File ops: Create, read, (fuzzy) edit, move, copy, delete, list files, analyze/ocr images, file content analysis
280
333
  • Code: Generate / analyse / refactor / fix
@@ -285,8 +338,8 @@ export class ClaudeCodeServer {
285
338
 
286
339
  **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
287
340
 
288
- **Supported models**:
289
- "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high"
341
+ **Supported models**:
342
+ "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high", "gemini-2.5-pro", "gemini-2.5-flash"
290
343
 
291
344
  **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
292
345
 
@@ -314,7 +367,7 @@ export class ClaudeCodeServer {
314
367
  },
315
368
  model: {
316
369
  type: 'string',
317
- description: 'The model to use: "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high".',
370
+ description: 'The model to use: "sonnet", "opus", "haiku", "gpt-5-low", "gpt-5-medium", "gpt-5-high", "gemini-2.5-pro", "gemini-2.5-flash".',
318
371
  },
319
372
  session_id: {
320
373
  type: 'string',
@@ -326,7 +379,7 @@ export class ClaudeCodeServer {
326
379
  },
327
380
  {
328
381
  name: 'list_processes',
329
- description: 'List all running and completed AI agent processes with their status, PID, and basic info.',
382
+ description: 'List all running and completed AI agent processes. Returns a simple list with PID, agent type, and status for each process.',
330
383
  inputSchema: {
331
384
  type: 'object',
332
385
  properties: {},
@@ -359,6 +412,14 @@ export class ClaudeCodeServer {
359
412
  },
360
413
  required: ['pid'],
361
414
  },
415
+ },
416
+ {
417
+ name: 'cleanup_processes',
418
+ description: 'Remove all completed and failed processes from the process list to free up memory.',
419
+ inputSchema: {
420
+ type: 'object',
421
+ properties: {},
422
+ },
362
423
  }
363
424
  ],
364
425
  }));
@@ -381,6 +442,8 @@ export class ClaudeCodeServer {
381
442
  return this.handleGetResult(toolArguments);
382
443
  case 'kill_process':
383
444
  return this.handleKillProcess(toolArguments);
445
+ case 'cleanup_processes':
446
+ return this.handleCleanupProcesses();
384
447
  default:
385
448
  throw new McpError(ErrorCode.MethodNotFound, `Tool ${toolName} not found`);
386
449
  }
@@ -444,16 +507,24 @@ export class ClaudeCodeServer {
444
507
 
445
508
  // Determine which agent to use based on model name
446
509
  const model = toolArguments.model || '';
447
- const agent = model.startsWith('gpt-') ? 'codex' : 'claude';
448
-
510
+ let agent: 'codex' | 'claude' | 'gemini';
511
+
512
+ if (model.startsWith('gpt-')) {
513
+ agent = 'codex';
514
+ } else if (model.startsWith('gemini')) {
515
+ agent = 'gemini';
516
+ } else {
517
+ agent = 'claude';
518
+ }
519
+
449
520
  let cliPath: string;
450
521
  let processArgs: string[];
451
-
522
+
452
523
  if (agent === 'codex') {
453
524
  // Handle Codex
454
525
  cliPath = this.codexCliPath;
455
526
  processArgs = ['exec'];
456
-
527
+
457
528
  // Parse model format for Codex (e.g., gpt-5-low -> model: gpt-5, effort: low)
458
529
  if (toolArguments.model) {
459
530
  // Split by "gpt-5-" to get the effort level
@@ -463,19 +534,32 @@ export class ClaudeCodeServer {
463
534
  }
464
535
  processArgs.push('--model', 'gpt-5');
465
536
  }
466
-
537
+
467
538
  processArgs.push('--full-auto', '--json', prompt);
468
-
539
+
540
+ } else if (agent === 'gemini') {
541
+ // Handle Gemini
542
+ cliPath = this.geminiCliPath;
543
+ processArgs = ['-y', '--output-format', 'json'];
544
+
545
+ // Add model if specified
546
+ if (toolArguments.model) {
547
+ processArgs.push('--model', toolArguments.model);
548
+ }
549
+
550
+ // Add prompt as positional argument
551
+ processArgs.push(prompt);
552
+
469
553
  } else {
470
554
  // Handle Claude (default)
471
555
  cliPath = this.claudeCliPath;
472
556
  processArgs = ['--dangerously-skip-permissions', '--output-format', 'json'];
473
-
557
+
474
558
  // Add session_id if provided (Claude only)
475
559
  if (toolArguments.session_id && typeof toolArguments.session_id === 'string') {
476
560
  processArgs.push('-r', toolArguments.session_id);
477
561
  }
478
-
562
+
479
563
  processArgs.push('-p', prompt);
480
564
  if (toolArguments.model && typeof toolArguments.model === 'string') {
481
565
  const resolvedModel = resolveModelAlias(toolArguments.model);
@@ -502,7 +586,7 @@ export class ClaudeCodeServer {
502
586
  prompt,
503
587
  workFolder: effectiveCwd,
504
588
  model: toolArguments.model,
505
- toolType: agent as 'claude' | 'codex',
589
+ toolType: agent,
506
590
  startTime: new Date().toISOString(),
507
591
  stdout: '',
508
592
  stderr: '',
@@ -561,32 +645,15 @@ export class ClaudeCodeServer {
561
645
  * Handle list_processes tool
562
646
  */
563
647
  private async handleListProcesses(): Promise<ServerResult> {
564
- const processes: any[] = [];
565
-
648
+ const processes: ProcessListItem[] = [];
649
+
566
650
  for (const [pid, process] of processManager.entries()) {
567
- const processInfo: any = {
651
+ const processInfo: ProcessListItem = {
568
652
  pid,
569
653
  agent: process.toolType,
570
- status: process.status,
571
- startTime: process.startTime,
572
- prompt: process.prompt.substring(0, 100) + (process.prompt.length > 100 ? '...' : ''),
573
- workFolder: process.workFolder,
574
- model: process.model,
575
- exitCode: process.exitCode
654
+ status: process.status
576
655
  };
577
656
 
578
- // Try to extract session_id from JSON output if available
579
- if (process.stdout) {
580
- try {
581
- const claudeOutput = JSON.parse(process.stdout);
582
- if (claudeOutput.session_id) {
583
- processInfo.session_id = claudeOutput.session_id;
584
- }
585
- } catch (e) {
586
- // Ignore parsing errors
587
- }
588
- }
589
-
590
657
  processes.push(processInfo);
591
658
  }
592
659
 
@@ -620,6 +687,8 @@ export class ClaudeCodeServer {
620
687
  agentOutput = parseCodexOutput(process.stdout);
621
688
  } else if (process.toolType === 'claude') {
622
689
  agentOutput = parseClaudeOutput(process.stdout);
690
+ } else if (process.toolType === 'gemini') {
691
+ agentOutput = parseGeminiOutput(process.stdout);
623
692
  }
624
693
  }
625
694
 
@@ -704,6 +773,32 @@ export class ClaudeCodeServer {
704
773
  }
705
774
  }
706
775
 
776
+ /**
777
+ * Handle cleanup_processes tool
778
+ */
779
+ private async handleCleanupProcesses(): Promise<ServerResult> {
780
+ const removedPids: number[] = [];
781
+
782
+ // Iterate through all processes and collect PIDs to remove
783
+ for (const [pid, process] of processManager.entries()) {
784
+ if (process.status === 'completed' || process.status === 'failed') {
785
+ removedPids.push(pid);
786
+ processManager.delete(pid);
787
+ }
788
+ }
789
+
790
+ return {
791
+ content: [{
792
+ type: 'text',
793
+ text: JSON.stringify({
794
+ removed: removedPids.length,
795
+ removedPids,
796
+ message: `Cleaned up ${removedPids.length} finished process(es)`
797
+ }, null, 2)
798
+ }]
799
+ };
800
+ }
801
+
707
802
  /**
708
803
  * Start the MCP server
709
804
  */
@@ -727,4 +822,4 @@ export class ClaudeCodeServer {
727
822
 
728
823
  // Create and run the server if this is the main module
729
824
  const server = new ClaudeCodeServer();
730
- server.run().catch(console.error);
825
+ server.run().catch(console.error);