hanzo-mcp 0.8.1__py3-none-any.whl → 0.8.3__py3-none-any.whl
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.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +14 -1
- hanzo_mcp/bridge.py +484 -0
- hanzo_mcp/compute_nodes.py +192 -0
- hanzo_mcp/config/settings.py +11 -0
- hanzo_mcp/core/base_agent.py +521 -0
- hanzo_mcp/core/model_registry.py +436 -0
- hanzo_mcp/dev_server.py +12 -0
- hanzo_mcp/server.py +18 -2
- hanzo_mcp/tools/__init__.py +61 -46
- hanzo_mcp/tools/agent/__init__.py +19 -35
- hanzo_mcp/tools/agent/cli_tools.py +544 -0
- hanzo_mcp/tools/agent/unified_cli_tools.py +259 -0
- hanzo_mcp/tools/common/batch_tool.py +2 -0
- hanzo_mcp/tools/common/context.py +3 -1
- hanzo_mcp/tools/config/config_tool.py +121 -9
- hanzo_mcp/tools/filesystem/__init__.py +18 -0
- hanzo_mcp/tools/llm/__init__.py +44 -16
- hanzo_mcp/tools/llm/llm_tool.py +13 -0
- hanzo_mcp/tools/llm/llm_unified.py +911 -0
- hanzo_mcp/tools/shell/auto_background.py +24 -0
- {hanzo_mcp-0.8.1.dist-info → hanzo_mcp-0.8.3.dist-info}/METADATA +3 -3
- {hanzo_mcp-0.8.1.dist-info → hanzo_mcp-0.8.3.dist-info}/RECORD +25 -18
- {hanzo_mcp-0.8.1.dist-info → hanzo_mcp-0.8.3.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.1.dist-info → hanzo_mcp-0.8.3.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.1.dist-info → hanzo_mcp-0.8.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""CLI tool implementations for direct batch execution.
|
|
2
|
+
|
|
3
|
+
This module provides CLI tool wrappers that can be used directly in batch operations,
|
|
4
|
+
including claude (cc), codex, gemini, grok, openhands (oh), hanzo-dev, cline, and aider.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import asyncio
|
|
11
|
+
from typing import Any, Optional, Dict, List, Unpack, Annotated, TypedDict, override, final
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
from mcp.server import FastMCP
|
|
16
|
+
from mcp.server.fastmcp import Context
|
|
17
|
+
|
|
18
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
19
|
+
from hanzo_mcp.tools.common.context import create_tool_context
|
|
20
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Parameter types for CLI tools
|
|
24
|
+
Prompt = Annotated[
|
|
25
|
+
str,
|
|
26
|
+
Field(
|
|
27
|
+
description="The prompt or command to send to the CLI tool",
|
|
28
|
+
min_length=1,
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
Model = Annotated[
|
|
33
|
+
Optional[str],
|
|
34
|
+
Field(
|
|
35
|
+
description="Optional model override for the CLI tool",
|
|
36
|
+
default=None,
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
WorkingDir = Annotated[
|
|
41
|
+
Optional[str],
|
|
42
|
+
Field(
|
|
43
|
+
description="Working directory for the command",
|
|
44
|
+
default=None,
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
Timeout = Annotated[
|
|
49
|
+
Optional[int],
|
|
50
|
+
Field(
|
|
51
|
+
description="Timeout in seconds for the command",
|
|
52
|
+
default=300, # 5 minutes default
|
|
53
|
+
),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CLIToolParams(TypedDict, total=False):
|
|
58
|
+
"""Common parameters for CLI tools."""
|
|
59
|
+
prompt: str
|
|
60
|
+
model: Optional[str]
|
|
61
|
+
working_dir: Optional[str]
|
|
62
|
+
timeout: Optional[int]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BaseCLITool(BaseTool):
|
|
66
|
+
"""Base class for CLI tool implementations."""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
permission_manager: Optional[PermissionManager] = None,
|
|
71
|
+
default_model: Optional[str] = None,
|
|
72
|
+
api_key_env: Optional[str] = None,
|
|
73
|
+
):
|
|
74
|
+
"""Initialize CLI tool.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
permission_manager: Permission manager for access control
|
|
78
|
+
default_model: Default model to use
|
|
79
|
+
api_key_env: Environment variable name for API key
|
|
80
|
+
"""
|
|
81
|
+
self.permission_manager = permission_manager
|
|
82
|
+
self.default_model = default_model
|
|
83
|
+
self.api_key_env = api_key_env
|
|
84
|
+
|
|
85
|
+
def get_auth_env(self) -> dict[str, str]:
|
|
86
|
+
"""Get authentication environment variables."""
|
|
87
|
+
env = os.environ.copy()
|
|
88
|
+
|
|
89
|
+
# Add API key if configured
|
|
90
|
+
if self.api_key_env and self.api_key_env in os.environ:
|
|
91
|
+
env[self.api_key_env] = os.environ[self.api_key_env]
|
|
92
|
+
|
|
93
|
+
# Add Hanzo API key for unified auth
|
|
94
|
+
if "HANZO_API_KEY" in os.environ:
|
|
95
|
+
env["HANZO_API_KEY"] = os.environ["HANZO_API_KEY"]
|
|
96
|
+
|
|
97
|
+
return env
|
|
98
|
+
|
|
99
|
+
async def execute_cli(
|
|
100
|
+
self,
|
|
101
|
+
command: list[str],
|
|
102
|
+
input_text: Optional[str] = None,
|
|
103
|
+
working_dir: Optional[str] = None,
|
|
104
|
+
timeout: int = 300,
|
|
105
|
+
) -> str:
|
|
106
|
+
"""Execute CLI command with proper error handling.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
command: Command and arguments
|
|
110
|
+
input_text: Optional stdin input
|
|
111
|
+
working_dir: Working directory
|
|
112
|
+
timeout: Timeout in seconds
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Command output
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
# Set up environment with auth
|
|
119
|
+
env = self.get_auth_env()
|
|
120
|
+
|
|
121
|
+
# Execute command
|
|
122
|
+
process = await asyncio.create_subprocess_exec(
|
|
123
|
+
*command,
|
|
124
|
+
stdin=asyncio.subprocess.PIPE if input_text else None,
|
|
125
|
+
stdout=asyncio.subprocess.PIPE,
|
|
126
|
+
stderr=asyncio.subprocess.PIPE,
|
|
127
|
+
cwd=working_dir,
|
|
128
|
+
env=env,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Send input and get output
|
|
132
|
+
stdout, stderr = await asyncio.wait_for(
|
|
133
|
+
process.communicate(input_text.encode() if input_text else None),
|
|
134
|
+
timeout=timeout,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Check for errors
|
|
138
|
+
if process.returncode != 0:
|
|
139
|
+
error_msg = stderr.decode() if stderr else "Unknown error"
|
|
140
|
+
return f"Error: {error_msg}"
|
|
141
|
+
|
|
142
|
+
return stdout.decode()
|
|
143
|
+
|
|
144
|
+
except asyncio.TimeoutError:
|
|
145
|
+
return f"Error: Command timed out after {timeout} seconds"
|
|
146
|
+
except Exception as e:
|
|
147
|
+
return f"Error executing command: {str(e)}"
|
|
148
|
+
|
|
149
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
150
|
+
"""Register this tool with the MCP server.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
mcp_server: The FastMCP server instance
|
|
154
|
+
"""
|
|
155
|
+
tool_self = self # Create a reference to self for use in the closure
|
|
156
|
+
|
|
157
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
158
|
+
async def tool_wrapper(
|
|
159
|
+
prompt: str,
|
|
160
|
+
ctx: Context[Any, Any, Any],
|
|
161
|
+
model: Optional[str] = None,
|
|
162
|
+
working_dir: Optional[str] = None,
|
|
163
|
+
timeout: int = 300,
|
|
164
|
+
) -> str:
|
|
165
|
+
result: str = await tool_self.call(
|
|
166
|
+
ctx, prompt=prompt, model=model, working_dir=working_dir, timeout=timeout
|
|
167
|
+
)
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ClaudeCLITool(BaseCLITool):
|
|
172
|
+
"""Claude CLI tool (also available as 'cc' alias)."""
|
|
173
|
+
|
|
174
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
175
|
+
super().__init__(
|
|
176
|
+
permission_manager=permission_manager,
|
|
177
|
+
default_model="claude-3-5-sonnet-20241022",
|
|
178
|
+
api_key_env="ANTHROPIC_API_KEY",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def name(self) -> str:
|
|
183
|
+
return "claude"
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def description(self) -> str:
|
|
187
|
+
return "Execute Claude CLI for AI assistance using Anthropic's models"
|
|
188
|
+
|
|
189
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
190
|
+
prompt: str = params.get("prompt", "")
|
|
191
|
+
model: Optional[str] = params.get("model") or self.default_model
|
|
192
|
+
working_dir: Optional[str] = params.get("working_dir")
|
|
193
|
+
timeout: int = params.get("timeout", 300)
|
|
194
|
+
|
|
195
|
+
# Build command
|
|
196
|
+
command: list[str] = ["claude"]
|
|
197
|
+
if model:
|
|
198
|
+
command.extend(["--model", model])
|
|
199
|
+
|
|
200
|
+
# Execute
|
|
201
|
+
return await self.execute_cli(
|
|
202
|
+
command,
|
|
203
|
+
input_text=prompt,
|
|
204
|
+
working_dir=working_dir,
|
|
205
|
+
timeout=timeout,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ClaudeCodeCLITool(ClaudeCLITool):
|
|
210
|
+
"""Claude Code CLI tool (cc alias)."""
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def name(self) -> str:
|
|
214
|
+
return "cc"
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def description(self) -> str:
|
|
218
|
+
return "Claude Code CLI (alias for claude)"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class CodexCLITool(BaseCLITool):
|
|
222
|
+
"""OpenAI Codex/GPT-4 CLI tool."""
|
|
223
|
+
|
|
224
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
225
|
+
super().__init__(
|
|
226
|
+
permission_manager=permission_manager,
|
|
227
|
+
default_model="gpt-4-turbo",
|
|
228
|
+
api_key_env="OPENAI_API_KEY",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def name(self) -> str:
|
|
233
|
+
return "codex"
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def description(self) -> str:
|
|
237
|
+
return "Execute OpenAI Codex/GPT-4 CLI for code generation and AI assistance"
|
|
238
|
+
|
|
239
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
240
|
+
prompt: str = params.get("prompt", "")
|
|
241
|
+
model: Optional[str] = params.get("model") or self.default_model
|
|
242
|
+
working_dir: Optional[str] = params.get("working_dir")
|
|
243
|
+
timeout: int = params.get("timeout", 300)
|
|
244
|
+
|
|
245
|
+
# Build command (using openai CLI or custom wrapper)
|
|
246
|
+
command: list[str] = ["openai", "api", "chat.completions.create"]
|
|
247
|
+
if model:
|
|
248
|
+
command.extend(["-m", model])
|
|
249
|
+
command.extend(["-g", "user", prompt])
|
|
250
|
+
|
|
251
|
+
# Execute
|
|
252
|
+
return await self.execute_cli(
|
|
253
|
+
command,
|
|
254
|
+
working_dir=working_dir,
|
|
255
|
+
timeout=timeout,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class GeminiCLITool(BaseCLITool):
|
|
260
|
+
"""Google Gemini CLI tool."""
|
|
261
|
+
|
|
262
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
263
|
+
super().__init__(
|
|
264
|
+
permission_manager=permission_manager,
|
|
265
|
+
default_model="gemini-1.5-pro",
|
|
266
|
+
api_key_env="GEMINI_API_KEY",
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def name(self) -> str:
|
|
271
|
+
return "gemini"
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def description(self) -> str:
|
|
275
|
+
return "Execute Google Gemini CLI for multimodal AI assistance"
|
|
276
|
+
|
|
277
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
278
|
+
prompt: str = params.get("prompt", "")
|
|
279
|
+
model: Optional[str] = params.get("model") or self.default_model
|
|
280
|
+
working_dir: Optional[str] = params.get("working_dir")
|
|
281
|
+
timeout: int = params.get("timeout", 300)
|
|
282
|
+
|
|
283
|
+
# Build command
|
|
284
|
+
command: list[str] = ["gemini"]
|
|
285
|
+
if model:
|
|
286
|
+
command.extend(["--model", model])
|
|
287
|
+
command.append(prompt)
|
|
288
|
+
|
|
289
|
+
# Execute
|
|
290
|
+
return await self.execute_cli(
|
|
291
|
+
command,
|
|
292
|
+
working_dir=working_dir,
|
|
293
|
+
timeout=timeout,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class GrokCLITool(BaseCLITool):
|
|
298
|
+
"""xAI Grok CLI tool."""
|
|
299
|
+
|
|
300
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
301
|
+
super().__init__(
|
|
302
|
+
permission_manager=permission_manager,
|
|
303
|
+
default_model="grok-4",
|
|
304
|
+
api_key_env="XAI_API_KEY",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def name(self) -> str:
|
|
309
|
+
return "grok"
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def description(self) -> str:
|
|
313
|
+
return "Execute xAI Grok CLI for real-time AI assistance"
|
|
314
|
+
|
|
315
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
316
|
+
prompt: str = params.get("prompt", "")
|
|
317
|
+
model: Optional[str] = params.get("model") or self.default_model
|
|
318
|
+
working_dir: Optional[str] = params.get("working_dir")
|
|
319
|
+
timeout: int = params.get("timeout", 300)
|
|
320
|
+
|
|
321
|
+
# Build command
|
|
322
|
+
command: list[str] = ["grok"]
|
|
323
|
+
if model:
|
|
324
|
+
command.extend(["--model", model])
|
|
325
|
+
command.append(prompt)
|
|
326
|
+
|
|
327
|
+
# Execute
|
|
328
|
+
return await self.execute_cli(
|
|
329
|
+
command,
|
|
330
|
+
working_dir=working_dir,
|
|
331
|
+
timeout=timeout,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class OpenHandsCLITool(BaseCLITool):
|
|
336
|
+
"""OpenHands (OpenDevin) CLI tool."""
|
|
337
|
+
|
|
338
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
339
|
+
super().__init__(
|
|
340
|
+
permission_manager=permission_manager,
|
|
341
|
+
default_model="claude-3-5-sonnet-20241022",
|
|
342
|
+
api_key_env="OPENAI_API_KEY",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def name(self) -> str:
|
|
347
|
+
return "openhands"
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def description(self) -> str:
|
|
351
|
+
return "Execute OpenHands (OpenDevin) for autonomous coding assistance"
|
|
352
|
+
|
|
353
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
354
|
+
prompt = params.get("prompt", "")
|
|
355
|
+
model = params.get("model") or self.default_model
|
|
356
|
+
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
357
|
+
timeout: int = params.get("timeout", 600) # 10 minutes for OpenHands
|
|
358
|
+
|
|
359
|
+
# Build command
|
|
360
|
+
command: list[str] = ["openhands", "run", prompt]
|
|
361
|
+
if model:
|
|
362
|
+
command.extend(["--model", model])
|
|
363
|
+
command.extend(["--workspace", working_dir])
|
|
364
|
+
|
|
365
|
+
# Execute
|
|
366
|
+
return await self.execute_cli(
|
|
367
|
+
command,
|
|
368
|
+
working_dir=working_dir,
|
|
369
|
+
timeout=timeout,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class OpenHandsShortCLITool(OpenHandsCLITool):
|
|
374
|
+
"""OpenHands CLI tool (oh alias)."""
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def name(self) -> str:
|
|
378
|
+
return "oh"
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def description(self) -> str:
|
|
382
|
+
return "OpenHands CLI (alias for openhands)"
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class HanzoDevCLITool(BaseCLITool):
|
|
386
|
+
"""Hanzo Dev AI coding assistant."""
|
|
387
|
+
|
|
388
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
389
|
+
super().__init__(
|
|
390
|
+
permission_manager=permission_manager,
|
|
391
|
+
default_model="claude-3-5-sonnet-20241022",
|
|
392
|
+
api_key_env="HANZO_API_KEY",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def name(self) -> str:
|
|
397
|
+
return "hanzo_dev"
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def description(self) -> str:
|
|
401
|
+
return "Execute Hanzo Dev for AI-powered code editing and development"
|
|
402
|
+
|
|
403
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
404
|
+
prompt = params.get("prompt", "")
|
|
405
|
+
model = params.get("model") or self.default_model
|
|
406
|
+
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
407
|
+
timeout: int = params.get("timeout", 600)
|
|
408
|
+
|
|
409
|
+
# Build command
|
|
410
|
+
command: list[str] = ["hanzo", "dev"]
|
|
411
|
+
if model:
|
|
412
|
+
command.extend(["--model", model])
|
|
413
|
+
command.extend(["--prompt", prompt])
|
|
414
|
+
|
|
415
|
+
# Execute
|
|
416
|
+
return await self.execute_cli(
|
|
417
|
+
command,
|
|
418
|
+
working_dir=working_dir,
|
|
419
|
+
timeout=timeout,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class ClineCLITool(BaseCLITool):
|
|
424
|
+
"""Cline (formerly Claude Engineer) CLI tool."""
|
|
425
|
+
|
|
426
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
427
|
+
super().__init__(
|
|
428
|
+
permission_manager=permission_manager,
|
|
429
|
+
default_model="claude-3-5-sonnet-20241022",
|
|
430
|
+
api_key_env="ANTHROPIC_API_KEY",
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def name(self) -> str:
|
|
435
|
+
return "cline"
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def description(self) -> str:
|
|
439
|
+
return "Execute Cline for autonomous coding with Claude"
|
|
440
|
+
|
|
441
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
442
|
+
prompt = params.get("prompt", "")
|
|
443
|
+
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
444
|
+
timeout: int = params.get("timeout", 600)
|
|
445
|
+
|
|
446
|
+
# Build command
|
|
447
|
+
command: list[str] = ["cline", prompt]
|
|
448
|
+
command.extend(["--no-interactive"]) # Non-interactive mode for batch
|
|
449
|
+
|
|
450
|
+
# Execute
|
|
451
|
+
return await self.execute_cli(
|
|
452
|
+
command,
|
|
453
|
+
working_dir=working_dir,
|
|
454
|
+
timeout=timeout,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class AiderCLITool(BaseCLITool):
|
|
459
|
+
"""Aider AI pair programming tool."""
|
|
460
|
+
|
|
461
|
+
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
462
|
+
super().__init__(
|
|
463
|
+
permission_manager=permission_manager,
|
|
464
|
+
default_model="gpt-4-turbo",
|
|
465
|
+
api_key_env="OPENAI_API_KEY",
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def name(self) -> str:
|
|
470
|
+
return "aider"
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def description(self) -> str:
|
|
474
|
+
return "Execute Aider for AI pair programming"
|
|
475
|
+
|
|
476
|
+
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
477
|
+
prompt = params.get("prompt", "")
|
|
478
|
+
model = params.get("model") or self.default_model
|
|
479
|
+
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
480
|
+
timeout: int = params.get("timeout", 600)
|
|
481
|
+
|
|
482
|
+
# Build command
|
|
483
|
+
command: list[str] = ["aider"]
|
|
484
|
+
if model:
|
|
485
|
+
command.extend(["--model", model])
|
|
486
|
+
command.extend(["--message", prompt])
|
|
487
|
+
command.extend(["--yes"]) # Auto-approve changes
|
|
488
|
+
command.extend(["--no-stream"]) # No streaming for batch
|
|
489
|
+
|
|
490
|
+
# Execute
|
|
491
|
+
return await self.execute_cli(
|
|
492
|
+
command,
|
|
493
|
+
working_dir=working_dir,
|
|
494
|
+
timeout=timeout,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def register_cli_tools(
|
|
499
|
+
mcp_server: FastMCP,
|
|
500
|
+
permission_manager: Optional[PermissionManager] = None,
|
|
501
|
+
) -> list[BaseTool]:
|
|
502
|
+
"""Register all CLI tools with the MCP server.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
mcp_server: The FastMCP server instance
|
|
506
|
+
permission_manager: Permission manager for access control
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
List of registered CLI tools
|
|
510
|
+
"""
|
|
511
|
+
tools: list[BaseTool] = [
|
|
512
|
+
ClaudeCLITool(permission_manager),
|
|
513
|
+
ClaudeCodeCLITool(permission_manager), # cc alias
|
|
514
|
+
CodexCLITool(permission_manager),
|
|
515
|
+
GeminiCLITool(permission_manager),
|
|
516
|
+
GrokCLITool(permission_manager),
|
|
517
|
+
OpenHandsCLITool(permission_manager),
|
|
518
|
+
OpenHandsShortCLITool(permission_manager), # oh alias
|
|
519
|
+
HanzoDevCLITool(permission_manager),
|
|
520
|
+
ClineCLITool(permission_manager),
|
|
521
|
+
AiderCLITool(permission_manager),
|
|
522
|
+
]
|
|
523
|
+
|
|
524
|
+
# Register each tool
|
|
525
|
+
for tool in tools:
|
|
526
|
+
tool.register(mcp_server)
|
|
527
|
+
|
|
528
|
+
return tools
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
# Export all CLI tool classes
|
|
532
|
+
__all__ = [
|
|
533
|
+
"ClaudeCLITool",
|
|
534
|
+
"ClaudeCodeCLITool",
|
|
535
|
+
"CodexCLITool",
|
|
536
|
+
"GeminiCLITool",
|
|
537
|
+
"GrokCLITool",
|
|
538
|
+
"OpenHandsCLITool",
|
|
539
|
+
"OpenHandsShortCLITool",
|
|
540
|
+
"HanzoDevCLITool",
|
|
541
|
+
"ClineCLITool",
|
|
542
|
+
"AiderCLITool",
|
|
543
|
+
"register_cli_tools",
|
|
544
|
+
]
|