hanzo-mcp 0.8.3__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 +1 -1
- hanzo_mcp/cli.py +45 -21
- hanzo_mcp/core/base_agent.py +4 -5
- hanzo_mcp/core/model_registry.py +2 -2
- hanzo_mcp/tools/agent/__init__.py +55 -28
- hanzo_mcp/tools/agent/agent_tool.py +12 -1
- hanzo_mcp/tools/agent/cli_tools.py +1 -2
- hanzo_mcp/tools/agent/network_tool.py +11 -55
- hanzo_mcp/tools/agent/unified_cli_tools.py +3 -3
- hanzo_mcp/tools/config/config_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_unified.py +2 -2
- hanzo_mcp/tools/shell/__init__.py +7 -1
- 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.3.dist-info → hanzo_mcp-0.8.4.dist-info}/RECORD +19 -18
- hanzo_mcp-0.8.3.dist-info/METADATA +0 -526
- {hanzo_mcp-0.8.3.dist-info → hanzo_mcp-0.8.4.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.3.dist-info → hanzo_mcp-0.8.4.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.3.dist-info → hanzo_mcp-0.8.4.dist-info}/top_level.txt +0 -0
hanzo_mcp/__init__.py
CHANGED
hanzo_mcp/cli.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
"""Command-line interface for the Hanzo AI server.
|
|
1
|
+
"""Command-line interface for the Hanzo AI server.
|
|
2
|
+
|
|
3
|
+
This module intentionally defers heavy imports (like the server and its
|
|
4
|
+
dependencies) until after we determine the transport and configure logging.
|
|
5
|
+
This prevents any stdout/stderr noise from imports that would corrupt the
|
|
6
|
+
MCP stdio transport used by Claude Desktop and other MCP clients.
|
|
7
|
+
"""
|
|
2
8
|
|
|
3
9
|
import os
|
|
4
10
|
import sys
|
|
@@ -9,52 +15,62 @@ import argparse
|
|
|
9
15
|
from typing import Any, cast
|
|
10
16
|
from pathlib import Path
|
|
11
17
|
|
|
12
|
-
from hanzo_mcp.server import HanzoMCPServer
|
|
13
|
-
|
|
14
18
|
|
|
15
19
|
def main() -> None:
|
|
16
20
|
"""Run the CLI for the Hanzo AI server."""
|
|
17
|
-
|
|
18
|
-
# Pre-parse arguments to check transport type early
|
|
19
|
-
import sys
|
|
20
|
-
|
|
21
|
+
# Pre-parse arguments to check transport type early, BEFORE importing server
|
|
21
22
|
early_parser = argparse.ArgumentParser(add_help=False)
|
|
22
23
|
early_parser.add_argument("--transport", choices=["stdio", "sse"], default="stdio")
|
|
23
24
|
early_args, _ = early_parser.parse_known_args()
|
|
24
25
|
|
|
25
26
|
# Configure logging VERY early based on transport
|
|
27
|
+
suppress_stdout = False
|
|
28
|
+
original_stdout = sys.stdout
|
|
26
29
|
if early_args.transport == "stdio":
|
|
27
|
-
# Set environment variable for server to detect stdio mode
|
|
28
|
-
import os
|
|
29
|
-
|
|
30
|
+
# Set environment variable for server to detect stdio mode as early as possible
|
|
30
31
|
os.environ["HANZO_MCP_TRANSPORT"] = "stdio"
|
|
32
|
+
# Aggressively quiet common dependency loggers/warnings in stdio mode
|
|
33
|
+
os.environ.setdefault("PYTHONWARNINGS", "ignore")
|
|
34
|
+
os.environ.setdefault("LITELLM_LOG", "ERROR")
|
|
35
|
+
os.environ.setdefault("LITELLM_LOGGING_LEVEL", "ERROR")
|
|
36
|
+
os.environ.setdefault("FASTMCP_LOG_LEVEL", "ERROR")
|
|
31
37
|
|
|
32
|
-
#
|
|
33
|
-
|
|
38
|
+
# Suppress FastMCP logging (if available) and all standard logging
|
|
39
|
+
try:
|
|
40
|
+
from fastmcp.utilities.logging import configure_logging # type: ignore
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
configure_logging(level="ERROR")
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
37
45
|
|
|
38
|
-
# Also configure standard logging to ERROR level
|
|
39
46
|
logging.basicConfig(
|
|
40
47
|
level=logging.ERROR, # Only show errors
|
|
41
48
|
handlers=[], # No handlers for stdio to prevent protocol corruption
|
|
42
49
|
)
|
|
43
50
|
|
|
44
51
|
# Redirect stderr to devnull for stdio transport to prevent any output
|
|
45
|
-
import sys
|
|
46
|
-
|
|
47
52
|
sys.stderr = open(os.devnull, "w")
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
# Suppress stdout during potentially noisy imports unless user requested help/version
|
|
55
|
+
if not any(flag in sys.argv for flag in ("--version", "-h", "--help")):
|
|
56
|
+
sys.stdout = open(os.devnull, "w")
|
|
57
|
+
suppress_stdout = True
|
|
58
|
+
|
|
59
|
+
# Import the server only AFTER transport/logging have been configured to avoid import-time noise
|
|
60
|
+
from hanzo_mcp.server import HanzoMCPServer
|
|
61
|
+
|
|
62
|
+
# Avoid importing hanzo_mcp package just to get version (it can have side-effects).
|
|
63
|
+
try:
|
|
64
|
+
from importlib.metadata import version as _pkg_version # py3.8+
|
|
65
|
+
_version = _pkg_version("hanzo-mcp")
|
|
66
|
+
except Exception:
|
|
67
|
+
_version = "unknown"
|
|
50
68
|
|
|
51
69
|
parser = argparse.ArgumentParser(
|
|
52
70
|
description="MCP server implementing Hanzo AI capabilities"
|
|
53
71
|
)
|
|
54
72
|
|
|
55
|
-
parser.add_argument(
|
|
56
|
-
"--version", action="version", version=f"hanzo-mcp {__version__}"
|
|
57
|
-
)
|
|
73
|
+
parser.add_argument("--version", action="version", version=f"hanzo-mcp {_version}")
|
|
58
74
|
|
|
59
75
|
_ = parser.add_argument(
|
|
60
76
|
"--transport",
|
|
@@ -199,6 +215,14 @@ def main() -> None:
|
|
|
199
215
|
|
|
200
216
|
args = parser.parse_args()
|
|
201
217
|
|
|
218
|
+
# Restore stdout after parsing, before any explicit output or server start
|
|
219
|
+
if suppress_stdout:
|
|
220
|
+
try:
|
|
221
|
+
sys.stdout.close() # Close devnull handle
|
|
222
|
+
except Exception:
|
|
223
|
+
pass
|
|
224
|
+
sys.stdout = original_stdout
|
|
225
|
+
|
|
202
226
|
# Cast args attributes to appropriate types to avoid 'Any' warnings
|
|
203
227
|
name: str = cast(str, args.name)
|
|
204
228
|
install: bool = cast(bool, args.install)
|
hanzo_mcp/core/base_agent.py
CHANGED
|
@@ -10,13 +10,12 @@ import os
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import logging
|
|
12
12
|
from abc import ABC, abstractmethod
|
|
13
|
-
from typing import Any, Dict, List,
|
|
14
|
-
from dataclasses import dataclass, field
|
|
15
|
-
from datetime import datetime
|
|
13
|
+
from typing import Any, Dict, List, Generic, TypeVar, Optional, Protocol, runtime_checkable
|
|
16
14
|
from pathlib import Path
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from dataclasses import field, dataclass
|
|
17
17
|
|
|
18
|
-
from .model_registry import
|
|
19
|
-
|
|
18
|
+
from .model_registry import ModelConfig, registry
|
|
20
19
|
|
|
21
20
|
logger = logging.getLogger(__name__)
|
|
22
21
|
|
hanzo_mcp/core/model_registry.py
CHANGED
|
@@ -8,9 +8,9 @@ Thread-safe singleton implementation.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import threading
|
|
11
|
-
from dataclasses import dataclass, field
|
|
12
|
-
from typing import Dict, List, Optional, Set, Any
|
|
13
11
|
from enum import Enum
|
|
12
|
+
from typing import Any, Set, Dict, List, Optional
|
|
13
|
+
from dataclasses import field, dataclass
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class ModelProvider(Enum):
|
|
@@ -8,25 +8,25 @@ from mcp.server import FastMCP
|
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
10
10
|
|
|
11
|
-
# Import the main implementations (using hanzo-agents SDK)
|
|
12
|
-
from hanzo_mcp.tools.agent.agent_tool import AgentTool
|
|
13
|
-
from hanzo_mcp.tools.agent.swarm_tool import SwarmTool
|
|
14
|
-
from hanzo_mcp.tools.agent.network_tool import NetworkTool
|
|
15
|
-
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
16
11
|
# Import unified CLI tools (single source of truth)
|
|
17
12
|
from hanzo_mcp.tools.agent.cli_tools import (
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
GrokCLITool,
|
|
14
|
+
AiderCLITool,
|
|
15
|
+
ClineCLITool,
|
|
20
16
|
CodexCLITool,
|
|
17
|
+
ClaudeCLITool,
|
|
21
18
|
GeminiCLITool,
|
|
22
|
-
|
|
19
|
+
HanzoDevCLITool,
|
|
23
20
|
OpenHandsCLITool,
|
|
21
|
+
ClaudeCodeCLITool, # cc alias
|
|
24
22
|
OpenHandsShortCLITool, # oh alias
|
|
25
|
-
HanzoDevCLITool,
|
|
26
|
-
ClineCLITool,
|
|
27
|
-
AiderCLITool,
|
|
28
23
|
register_cli_tools,
|
|
29
24
|
)
|
|
25
|
+
|
|
26
|
+
# Import the main implementations (using hanzo-agents SDK)
|
|
27
|
+
from hanzo_mcp.tools.agent.agent_tool import AgentTool
|
|
28
|
+
from hanzo_mcp.tools.agent.network_tool import NetworkTool
|
|
29
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
30
30
|
from hanzo_mcp.tools.agent.code_auth_tool import CodeAuthTool
|
|
31
31
|
|
|
32
32
|
|
|
@@ -67,16 +67,48 @@ def register_agent_tools(
|
|
|
67
67
|
max_tool_uses=agent_max_tool_uses,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
# Register a swarm alias that forwards to AgentTool with default concurrency
|
|
71
|
+
class SwarmAliasTool(BaseTool):
|
|
72
|
+
name = "swarm"
|
|
73
|
+
description = (
|
|
74
|
+
"Alias for agent with concurrency. swarm == agent:5 by default.\n"
|
|
75
|
+
"Use 'swarm' for parallel multi-agent runs; 'swarm:N' for N agents."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def __init__(self, agent_tool: AgentTool):
|
|
79
|
+
self._agent = agent_tool
|
|
80
|
+
|
|
81
|
+
async def call(self, ctx, **params): # type: ignore[override]
|
|
82
|
+
# Default to 5 agents unless explicitly provided
|
|
83
|
+
params = dict(params)
|
|
84
|
+
params.setdefault("concurrency", 5)
|
|
85
|
+
return await self._agent.call(ctx, **params)
|
|
86
|
+
|
|
87
|
+
def register(self, mcp_server: FastMCP): # type: ignore[override]
|
|
88
|
+
tool_self = self
|
|
89
|
+
|
|
90
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
91
|
+
async def swarm(
|
|
92
|
+
ctx,
|
|
93
|
+
prompts: str | list[str], # forwarded
|
|
94
|
+
concurrency: int | None = None,
|
|
95
|
+
model: str | None = None,
|
|
96
|
+
use_memory: bool | None = None,
|
|
97
|
+
memory_backend: str | None = None,
|
|
98
|
+
) -> str:
|
|
99
|
+
p = {
|
|
100
|
+
"prompts": prompts,
|
|
101
|
+
}
|
|
102
|
+
if concurrency is not None:
|
|
103
|
+
p["concurrency"] = concurrency
|
|
104
|
+
if model is not None:
|
|
105
|
+
p["model"] = model
|
|
106
|
+
if use_memory is not None:
|
|
107
|
+
p["use_memory"] = use_memory
|
|
108
|
+
if memory_backend is not None:
|
|
109
|
+
p["memory_backend"] = memory_backend
|
|
110
|
+
return await tool_self.call(ctx, **p)
|
|
111
|
+
return tool_self
|
|
80
112
|
|
|
81
113
|
# Create auth management tool
|
|
82
114
|
code_auth_tool = CodeAuthTool()
|
|
@@ -89,7 +121,7 @@ def register_agent_tools(
|
|
|
89
121
|
|
|
90
122
|
# Register core agent tools
|
|
91
123
|
ToolRegistry.register_tool(mcp_server, agent_tool)
|
|
92
|
-
ToolRegistry.register_tool(mcp_server,
|
|
124
|
+
ToolRegistry.register_tool(mcp_server, SwarmAliasTool(agent_tool))
|
|
93
125
|
ToolRegistry.register_tool(mcp_server, network_tool)
|
|
94
126
|
ToolRegistry.register_tool(mcp_server, code_auth_tool)
|
|
95
127
|
|
|
@@ -97,9 +129,4 @@ def register_agent_tools(
|
|
|
97
129
|
cli_tools = register_cli_tools(mcp_server, permission_manager)
|
|
98
130
|
|
|
99
131
|
# Return list of registered tools
|
|
100
|
-
return [
|
|
101
|
-
agent_tool,
|
|
102
|
-
swarm_tool,
|
|
103
|
-
network_tool,
|
|
104
|
-
code_auth_tool,
|
|
105
|
-
] + cli_tools
|
|
132
|
+
return [agent_tool, network_tool, code_auth_tool] + cli_tools
|
|
@@ -109,6 +109,7 @@ class AgentToolParams(TypedDict, total=False):
|
|
|
109
109
|
model: Optional[str]
|
|
110
110
|
use_memory: Optional[bool]
|
|
111
111
|
memory_backend: Optional[str]
|
|
112
|
+
concurrency: Optional[int]
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
class MCPAgentState(State):
|
|
@@ -387,7 +388,17 @@ Usage notes:
|
|
|
387
388
|
await tool_ctx.error("hanzo-agents SDK is required but not available")
|
|
388
389
|
return "Error: hanzo-agents SDK is required for agent tool functionality. Please install it with: pip install hanzo-agents"
|
|
389
390
|
|
|
390
|
-
#
|
|
391
|
+
# Determine concurrency (parallel agents)
|
|
392
|
+
concurrency = params.get("concurrency")
|
|
393
|
+
if concurrency is not None and isinstance(concurrency, int) and concurrency > 0:
|
|
394
|
+
# Expand prompt list to match concurrency
|
|
395
|
+
if len(prompt_list) == 1:
|
|
396
|
+
prompt_list = prompt_list * concurrency
|
|
397
|
+
elif len(prompt_list) < concurrency:
|
|
398
|
+
# Repeat prompts to reach concurrency
|
|
399
|
+
times = (concurrency + len(prompt_list) - 1) // len(prompt_list)
|
|
400
|
+
prompt_list = (prompt_list * times)[:concurrency]
|
|
401
|
+
|
|
391
402
|
await tool_ctx.info(
|
|
392
403
|
f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK"
|
|
393
404
|
)
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
10
|
import asyncio
|
|
11
|
-
from typing import Any,
|
|
11
|
+
from typing import Any, Dict, List, Unpack, Optional, Annotated, TypedDict, final, override
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from pydantic import Field
|
|
@@ -19,7 +19,6 @@ from hanzo_mcp.tools.common.base import BaseTool
|
|
|
19
19
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
20
20
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
# Parameter types for CLI tools
|
|
24
23
|
Prompt = Annotated[
|
|
25
24
|
str,
|
|
@@ -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
|
|
@@ -7,16 +7,16 @@ following Python best practices and eliminating all duplication.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
-
from typing import Any,
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
13
|
from mcp.server import FastMCP
|
|
14
14
|
from mcp.server.fastmcp import Context
|
|
15
15
|
|
|
16
|
-
from ...core.model_registry import registry
|
|
17
|
-
from ...core.base_agent import CLIAgent, AgentConfig
|
|
18
16
|
from ..common.base import BaseTool
|
|
17
|
+
from ...core.base_agent import CLIAgent, AgentConfig
|
|
19
18
|
from ..common.permissions import PermissionManager
|
|
19
|
+
from ...core.model_registry import registry
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class UnifiedCLITool(BaseTool, CLIAgent):
|
|
@@ -9,10 +9,10 @@ from pathlib import Path
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
from mcp.server.fastmcp import Context as MCPContext
|
|
11
11
|
|
|
12
|
+
from hanzo_mcp.config import load_settings, save_settings
|
|
12
13
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
13
14
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
14
15
|
from hanzo_mcp.tools.config.index_config import IndexScope, IndexConfig
|
|
15
|
-
from hanzo_mcp.config import load_settings, save_settings
|
|
16
16
|
|
|
17
17
|
# Parameter types
|
|
18
18
|
Action = Annotated[
|
|
@@ -223,7 +223,7 @@ class UnifiedLLMTool(BaseTool):
|
|
|
223
223
|
try:
|
|
224
224
|
with open(self.CONFIG_FILE, "r") as f:
|
|
225
225
|
return json.load(f)
|
|
226
|
-
except:
|
|
226
|
+
except Exception:
|
|
227
227
|
pass
|
|
228
228
|
|
|
229
229
|
# Default config
|
|
@@ -276,7 +276,7 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
276
276
|
tool_ctx = create_tool_context(ctx)
|
|
277
277
|
if tool_ctx:
|
|
278
278
|
await tool_ctx.set_tool_info(self.name)
|
|
279
|
-
except:
|
|
279
|
+
except Exception:
|
|
280
280
|
# Running in test mode without MCP context
|
|
281
281
|
pass
|
|
282
282
|
|
|
@@ -9,6 +9,7 @@ from hanzo_mcp.tools.shell.open import open_tool
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
10
10
|
from hanzo_mcp.tools.shell.npx_tool import npx_tool
|
|
11
11
|
from hanzo_mcp.tools.shell.uvx_tool import uvx_tool
|
|
12
|
+
from hanzo_mcp.tools.shell.zsh_tool import zsh_tool, shell_tool
|
|
12
13
|
|
|
13
14
|
# Import tools
|
|
14
15
|
from hanzo_mcp.tools.shell.bash_tool import bash_tool
|
|
@@ -37,14 +38,19 @@ def get_shell_tools(
|
|
|
37
38
|
"""
|
|
38
39
|
# Set permission manager for tools that need it
|
|
39
40
|
bash_tool.permission_manager = permission_manager
|
|
41
|
+
zsh_tool.permission_manager = permission_manager
|
|
42
|
+
shell_tool.permission_manager = permission_manager
|
|
40
43
|
npx_tool.permission_manager = permission_manager
|
|
41
44
|
uvx_tool.permission_manager = permission_manager
|
|
42
45
|
|
|
43
46
|
# Note: StreamingCommandTool is abstract and shouldn't be instantiated directly
|
|
44
47
|
# It's used as a base class for other streaming tools
|
|
45
48
|
|
|
49
|
+
# Return shell_tool first (smart default), then specific shells
|
|
46
50
|
return [
|
|
47
|
-
|
|
51
|
+
shell_tool, # Smart shell (prefers zsh if available)
|
|
52
|
+
zsh_tool, # Explicit zsh
|
|
53
|
+
bash_tool, # Explicit bash
|
|
48
54
|
npx_tool,
|
|
49
55
|
uvx_tool,
|
|
50
56
|
process_tool,
|
|
@@ -59,29 +59,20 @@ bash "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
59
59
|
|
|
60
60
|
@override
|
|
61
61
|
def get_interpreter(self) -> str:
|
|
62
|
-
"""Get the
|
|
62
|
+
"""Get the bash interpreter."""
|
|
63
63
|
if platform.system() == "Windows":
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if zshrc_path.exists():
|
|
77
|
-
return shell # Use full path to zsh
|
|
78
|
-
elif shell_name == "fish":
|
|
79
|
-
# Check for fish config
|
|
80
|
-
fish_config = Path.home() / ".config" / "fish" / "config.fish"
|
|
81
|
-
if fish_config.exists():
|
|
82
|
-
return shell # Use full path to fish
|
|
83
|
-
|
|
84
|
-
# Default to bash if no special shell config found
|
|
64
|
+
# Try to find bash on Windows (Git Bash, WSL, etc.)
|
|
65
|
+
bash_paths = [
|
|
66
|
+
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
67
|
+
"C:\\cygwin64\\bin\\bash.exe",
|
|
68
|
+
"C:\\msys64\\usr\\bin\\bash.exe",
|
|
69
|
+
]
|
|
70
|
+
for path in bash_paths:
|
|
71
|
+
if Path(path).exists():
|
|
72
|
+
return path
|
|
73
|
+
return "cmd.exe" # Fall back to cmd if no bash found
|
|
74
|
+
|
|
75
|
+
# On Unix-like systems, always use bash
|
|
85
76
|
return "bash"
|
|
86
77
|
|
|
87
78
|
@override
|
|
@@ -94,12 +85,7 @@ bash "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
94
85
|
@override
|
|
95
86
|
def get_tool_name(self) -> str:
|
|
96
87
|
"""Get the tool name."""
|
|
97
|
-
|
|
98
|
-
return "shell"
|
|
99
|
-
|
|
100
|
-
# Return the actual shell being used
|
|
101
|
-
interpreter = self.get_interpreter()
|
|
102
|
-
return os.path.basename(interpreter)
|
|
88
|
+
return "bash"
|
|
103
89
|
|
|
104
90
|
@override
|
|
105
91
|
async def run(
|