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.
- package/.claude/settings.local.json +2 -1
- package/README.md +53 -4
- package/dist/parsers.js +14 -0
- package/dist/server.js +104 -26
- package/package.json +2 -2
- package/src/parsers.ts +15 -1
- package/src/server.ts +134 -39
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
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
4
|
-
"description": "MCP server for AI CLI tools (Claude and
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
565
|
-
|
|
648
|
+
const processes: ProcessListItem[] = [];
|
|
649
|
+
|
|
566
650
|
for (const [pid, process] of processManager.entries()) {
|
|
567
|
-
const processInfo:
|
|
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);
|