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