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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Zsh shell tool for command execution with enhanced features."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import platform
|
|
6
|
+
from typing import Optional, override
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from mcp.server import FastMCP
|
|
10
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
+
|
|
12
|
+
from hanzo_mcp.tools.shell.base_process import BaseScriptTool
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ZshTool(BaseScriptTool):
|
|
16
|
+
"""Tool for running commands in Zsh shell with enhanced features."""
|
|
17
|
+
|
|
18
|
+
name = "zsh"
|
|
19
|
+
|
|
20
|
+
def register(self, server: FastMCP) -> None:
|
|
21
|
+
"""Register the tool with the MCP server."""
|
|
22
|
+
tool_self = self
|
|
23
|
+
|
|
24
|
+
@server.tool(name=self.name, description=self.description)
|
|
25
|
+
async def zsh(
|
|
26
|
+
ctx: MCPContext,
|
|
27
|
+
command: str,
|
|
28
|
+
cwd: Optional[str] = None,
|
|
29
|
+
env: Optional[dict[str, str]] = None,
|
|
30
|
+
timeout: Optional[int] = None,
|
|
31
|
+
) -> str:
|
|
32
|
+
return await tool_self.run(
|
|
33
|
+
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
async def call(self, ctx: MCPContext, **params) -> str:
|
|
37
|
+
"""Call the tool with arguments."""
|
|
38
|
+
return await self.run(
|
|
39
|
+
ctx,
|
|
40
|
+
command=params["command"],
|
|
41
|
+
cwd=params.get("cwd"),
|
|
42
|
+
env=params.get("env"),
|
|
43
|
+
timeout=params.get("timeout"),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
@override
|
|
48
|
+
def description(self) -> str:
|
|
49
|
+
"""Get the tool description."""
|
|
50
|
+
return """Run commands in Zsh shell with enhanced features like better globbing and completion.
|
|
51
|
+
|
|
52
|
+
Zsh provides advanced features over bash:
|
|
53
|
+
- Extended globbing patterns
|
|
54
|
+
- Better tab completion
|
|
55
|
+
- Array and associative array support
|
|
56
|
+
- Powerful command line editing
|
|
57
|
+
- Plugin ecosystem (oh-my-zsh, etc.)
|
|
58
|
+
|
|
59
|
+
Commands that run for more than 2 minutes will automatically continue in the background.
|
|
60
|
+
|
|
61
|
+
Usage:
|
|
62
|
+
zsh "ls -la"
|
|
63
|
+
zsh "echo $ZSH_VERSION"
|
|
64
|
+
zsh "git status && git diff"
|
|
65
|
+
zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
66
|
+
|
|
67
|
+
@override
|
|
68
|
+
def get_interpreter(self) -> str:
|
|
69
|
+
"""Get the zsh interpreter path."""
|
|
70
|
+
if platform.system() == "Windows":
|
|
71
|
+
# Try to find zsh on Windows (WSL, Git Bash, etc.)
|
|
72
|
+
zsh_paths = [
|
|
73
|
+
"C:\\Program Files\\Git\\usr\\bin\\zsh.exe",
|
|
74
|
+
"C:\\cygwin64\\bin\\zsh.exe",
|
|
75
|
+
"C:\\msys64\\usr\\bin\\zsh.exe",
|
|
76
|
+
]
|
|
77
|
+
for path in zsh_paths:
|
|
78
|
+
if Path(path).exists():
|
|
79
|
+
return path
|
|
80
|
+
# Fall back to bash if no zsh found
|
|
81
|
+
return "bash"
|
|
82
|
+
|
|
83
|
+
# On Unix-like systems, check for zsh
|
|
84
|
+
zsh_path = shutil.which("zsh")
|
|
85
|
+
if zsh_path:
|
|
86
|
+
return zsh_path
|
|
87
|
+
|
|
88
|
+
# Fall back to bash if zsh not found
|
|
89
|
+
return "bash"
|
|
90
|
+
|
|
91
|
+
@override
|
|
92
|
+
def get_script_flags(self) -> list[str]:
|
|
93
|
+
"""Get interpreter flags."""
|
|
94
|
+
if platform.system() == "Windows" and self.get_interpreter().endswith(".exe"):
|
|
95
|
+
return ["-c"]
|
|
96
|
+
return ["-c"]
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
def get_tool_name(self) -> str:
|
|
100
|
+
"""Get the tool name."""
|
|
101
|
+
return "zsh"
|
|
102
|
+
|
|
103
|
+
@override
|
|
104
|
+
async def run(
|
|
105
|
+
self,
|
|
106
|
+
ctx: MCPContext,
|
|
107
|
+
command: str,
|
|
108
|
+
cwd: Optional[str] = None,
|
|
109
|
+
env: Optional[dict[str, str]] = None,
|
|
110
|
+
timeout: Optional[int] = None,
|
|
111
|
+
) -> str:
|
|
112
|
+
"""Run a zsh command with auto-backgrounding.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
ctx: MCP context
|
|
116
|
+
command: Zsh command to execute
|
|
117
|
+
cwd: Working directory
|
|
118
|
+
env: Environment variables
|
|
119
|
+
timeout: Command timeout in seconds (ignored - auto-backgrounds after 2 minutes)
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Command output or background status
|
|
123
|
+
"""
|
|
124
|
+
# Check if zsh is available
|
|
125
|
+
if not shutil.which("zsh") and platform.system() != "Windows":
|
|
126
|
+
return "Error: Zsh is not installed. Please install zsh first."
|
|
127
|
+
|
|
128
|
+
# Prepare working directory
|
|
129
|
+
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
|
|
130
|
+
|
|
131
|
+
# Use execute_sync which has auto-backgrounding
|
|
132
|
+
output = await self.execute_sync(
|
|
133
|
+
command, cwd=work_dir, env=env, timeout=timeout
|
|
134
|
+
)
|
|
135
|
+
return output if output else "Command completed successfully (no output)"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ShellTool(BaseScriptTool):
|
|
139
|
+
"""Smart shell tool that uses the best available shell (zsh > bash)."""
|
|
140
|
+
|
|
141
|
+
name = "shell"
|
|
142
|
+
|
|
143
|
+
def __init__(self):
|
|
144
|
+
"""Initialize and detect the best shell."""
|
|
145
|
+
super().__init__()
|
|
146
|
+
self._best_shell = self._detect_best_shell()
|
|
147
|
+
|
|
148
|
+
def _detect_best_shell(self) -> str:
|
|
149
|
+
"""Detect the best available shell."""
|
|
150
|
+
# Check for zsh first
|
|
151
|
+
if shutil.which("zsh"):
|
|
152
|
+
# Also check if .zshrc exists
|
|
153
|
+
if (Path.home() / ".zshrc").exists():
|
|
154
|
+
return "zsh"
|
|
155
|
+
|
|
156
|
+
# Check for user's preferred shell
|
|
157
|
+
user_shell = os.environ.get("SHELL", "")
|
|
158
|
+
if user_shell and Path(user_shell).exists():
|
|
159
|
+
return user_shell
|
|
160
|
+
|
|
161
|
+
# Default to bash
|
|
162
|
+
return "bash"
|
|
163
|
+
|
|
164
|
+
def register(self, server: FastMCP) -> None:
|
|
165
|
+
"""Register the tool with the MCP server."""
|
|
166
|
+
tool_self = self
|
|
167
|
+
|
|
168
|
+
@server.tool(name=self.name, description=self.description)
|
|
169
|
+
async def shell(
|
|
170
|
+
ctx: MCPContext,
|
|
171
|
+
command: str,
|
|
172
|
+
cwd: Optional[str] = None,
|
|
173
|
+
env: Optional[dict[str, str]] = None,
|
|
174
|
+
timeout: Optional[int] = None,
|
|
175
|
+
) -> str:
|
|
176
|
+
return await tool_self.run(
|
|
177
|
+
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
async def call(self, ctx: MCPContext, **params) -> str:
|
|
181
|
+
"""Call the tool with arguments."""
|
|
182
|
+
return await self.run(
|
|
183
|
+
ctx,
|
|
184
|
+
command=params["command"],
|
|
185
|
+
cwd=params.get("cwd"),
|
|
186
|
+
env=params.get("env"),
|
|
187
|
+
timeout=params.get("timeout"),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
@override
|
|
192
|
+
def description(self) -> str:
|
|
193
|
+
"""Get the tool description."""
|
|
194
|
+
return f"""Run shell commands using the best available shell (currently: {os.path.basename(self._best_shell)}).
|
|
195
|
+
|
|
196
|
+
Automatically selects:
|
|
197
|
+
- Zsh if available (with .zshrc)
|
|
198
|
+
- User's preferred shell ($SHELL)
|
|
199
|
+
- Bash as fallback
|
|
200
|
+
|
|
201
|
+
Commands that run for more than 2 minutes will automatically continue in the background.
|
|
202
|
+
|
|
203
|
+
Usage:
|
|
204
|
+
shell "ls -la"
|
|
205
|
+
shell "echo $SHELL" # Shows which shell is being used
|
|
206
|
+
shell "git status && git diff"
|
|
207
|
+
shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
208
|
+
|
|
209
|
+
@override
|
|
210
|
+
def get_interpreter(self) -> str:
|
|
211
|
+
"""Get the best shell interpreter."""
|
|
212
|
+
return self._best_shell
|
|
213
|
+
|
|
214
|
+
@override
|
|
215
|
+
def get_script_flags(self) -> list[str]:
|
|
216
|
+
"""Get interpreter flags."""
|
|
217
|
+
if platform.system() == "Windows":
|
|
218
|
+
return ["/c"] if self._best_shell == "cmd.exe" else ["-c"]
|
|
219
|
+
return ["-c"]
|
|
220
|
+
|
|
221
|
+
@override
|
|
222
|
+
def get_tool_name(self) -> str:
|
|
223
|
+
"""Get the tool name."""
|
|
224
|
+
return "shell"
|
|
225
|
+
|
|
226
|
+
@override
|
|
227
|
+
async def run(
|
|
228
|
+
self,
|
|
229
|
+
ctx: MCPContext,
|
|
230
|
+
command: str,
|
|
231
|
+
cwd: Optional[str] = None,
|
|
232
|
+
env: Optional[dict[str, str]] = None,
|
|
233
|
+
timeout: Optional[int] = None,
|
|
234
|
+
) -> str:
|
|
235
|
+
"""Run a shell command with auto-backgrounding.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
ctx: MCP context
|
|
239
|
+
command: Shell command to execute
|
|
240
|
+
cwd: Working directory
|
|
241
|
+
env: Environment variables
|
|
242
|
+
timeout: Command timeout in seconds (ignored - auto-backgrounds after 2 minutes)
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Command output or background status
|
|
246
|
+
"""
|
|
247
|
+
# Prepare working directory
|
|
248
|
+
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
|
|
249
|
+
|
|
250
|
+
# Add shell info to output if verbose
|
|
251
|
+
shell_name = os.path.basename(self._best_shell)
|
|
252
|
+
|
|
253
|
+
# Use execute_sync which has auto-backgrounding
|
|
254
|
+
output = await self.execute_sync(
|
|
255
|
+
command, cwd=work_dir, env=env, timeout=timeout
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if output:
|
|
259
|
+
return output
|
|
260
|
+
else:
|
|
261
|
+
return f"Command completed successfully in {shell_name} (no output)"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# Create tool instances
|
|
265
|
+
zsh_tool = ZshTool()
|
|
266
|
+
shell_tool = ShellTool() # Smart shell that prefers zsh
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hanzo-mcp
|
|
3
|
+
Version: 0.8.4
|
|
4
|
+
Summary: The Zen of Hanzo MCP: One server to rule them all. The ultimate MCP that orchestrates all others.
|
|
5
|
+
Author-email: Hanzo Industries Inc <dev@hanzo.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hanzoai/mcp
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/hanzoai/mcp/issues
|
|
9
|
+
Project-URL: Documentation, https://mcp.hanzo.ai
|
|
10
|
+
Keywords: mcp,claude,hanzo,code,agent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: mcp>=1.9.4
|
|
17
|
+
Requires-Dist: fastmcp>=2.9.2
|
|
18
|
+
Requires-Dist: httpx>=0.28.1
|
|
19
|
+
Requires-Dist: uvicorn>=0.34.0
|
|
20
|
+
Requires-Dist: openai>=1.62.0
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
22
|
+
Requires-Dist: litellm>=1.73.2
|
|
23
|
+
Requires-Dist: grep-ast>=0.8.1
|
|
24
|
+
Requires-Dist: bashlex>=0.18
|
|
25
|
+
Requires-Dist: libtmux>=0.39.0
|
|
26
|
+
Requires-Dist: nbformat>=5.10.4
|
|
27
|
+
Requires-Dist: psutil>=6.0.0
|
|
28
|
+
Requires-Dist: pydantic>=2.9.2
|
|
29
|
+
Requires-Dist: pydantic-settings>=2.7.0
|
|
30
|
+
Requires-Dist: typing-extensions>=4.13.0
|
|
31
|
+
Requires-Dist: watchdog>=6.0.0
|
|
32
|
+
Requires-Dist: keyring>=24.0.0
|
|
33
|
+
Requires-Dist: ffind>=1.3.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
38
|
+
Requires-Dist: black>=23.3.0; extra == "dev"
|
|
39
|
+
Requires-Dist: sphinx>=8.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: myst-parser>=4.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: sphinx-copybutton>=0.5.0; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
44
|
+
Requires-Dist: types-aiofiles>=23.2.0; extra == "dev"
|
|
45
|
+
Requires-Dist: types-psutil>=5.9.5; extra == "dev"
|
|
46
|
+
Requires-Dist: types-setuptools>=69.5.0; extra == "dev"
|
|
47
|
+
Provides-Extra: docs
|
|
48
|
+
Requires-Dist: sphinx>=8.0.0; extra == "docs"
|
|
49
|
+
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "docs"
|
|
50
|
+
Requires-Dist: myst-parser>=4.0.0; extra == "docs"
|
|
51
|
+
Requires-Dist: sphinx-copybutton>=0.5.0; extra == "docs"
|
|
52
|
+
Provides-Extra: analytics
|
|
53
|
+
Requires-Dist: posthog>=3.0.0; extra == "analytics"
|
|
54
|
+
Provides-Extra: test
|
|
55
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
56
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
|
|
57
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "test"
|
|
58
|
+
Requires-Dist: pytest-asyncio>=0.25.3; extra == "test"
|
|
59
|
+
Requires-Dist: twisted; extra == "test"
|
|
60
|
+
Provides-Extra: agents
|
|
61
|
+
Requires-Dist: hanzo-agents>=0.1.0; extra == "agents"
|
|
62
|
+
Provides-Extra: memory
|
|
63
|
+
Requires-Dist: hanzo-memory>=1.0.0; extra == "memory"
|
|
64
|
+
Provides-Extra: performance
|
|
65
|
+
Requires-Dist: ujson>=5.7.0; extra == "performance"
|
|
66
|
+
Requires-Dist: orjson>=3.9.0; extra == "performance"
|
|
67
|
+
Provides-Extra: publish
|
|
68
|
+
Requires-Dist: twine>=4.0.2; extra == "publish"
|
|
69
|
+
Requires-Dist: build>=1.0.3; extra == "publish"
|
|
70
|
+
|
|
71
|
+
# Hanzo Model Context Protocol (MCP)
|
|
72
|
+
|
|
73
|
+
[](https://pypi.org/project/hanzo-mcp/)
|
|
74
|
+
[](https://pypi.org/project/hanzo-mcp/)
|
|
75
|
+
|
|
76
|
+
Model Context Protocol implementation for advanced tool use and context management.
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install hanzo-mcp
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
- **Tool Management**: Register and manage AI tools
|
|
87
|
+
- **File Operations**: Read, write, edit files
|
|
88
|
+
- **Code Intelligence**: AST analysis, symbol search
|
|
89
|
+
- **Shell Execution**: Run commands safely
|
|
90
|
+
- **Agent Delegation**: Recursive agent capabilities
|
|
91
|
+
- **Memory Integration**: Persistent context storage
|
|
92
|
+
- **Batch Operations**: Execute multiple tools efficiently
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
### Basic Usage
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from hanzo_mcp import create_mcp_server
|
|
100
|
+
|
|
101
|
+
# Create MCP server
|
|
102
|
+
server = create_mcp_server()
|
|
103
|
+
|
|
104
|
+
# Register tools
|
|
105
|
+
server.register_filesystem_tools()
|
|
106
|
+
server.register_shell_tools()
|
|
107
|
+
server.register_agent_tools()
|
|
108
|
+
|
|
109
|
+
# Start server
|
|
110
|
+
await server.start()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Tool Categories
|
|
114
|
+
|
|
115
|
+
#### Filesystem Tools
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Read file
|
|
119
|
+
content = await server.tools.read(file_path="/path/to/file.py")
|
|
120
|
+
|
|
121
|
+
# Write file
|
|
122
|
+
await server.tools.write(
|
|
123
|
+
file_path="/path/to/new.py",
|
|
124
|
+
content="print('Hello')"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Edit file
|
|
128
|
+
await server.tools.edit(
|
|
129
|
+
file_path="/path/to/file.py",
|
|
130
|
+
old_string="old code",
|
|
131
|
+
new_string="new code"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Multi-edit
|
|
135
|
+
await server.tools.multi_edit(
|
|
136
|
+
file_path="/path/to/file.py",
|
|
137
|
+
edits=[
|
|
138
|
+
{"old_string": "foo", "new_string": "bar"},
|
|
139
|
+
{"old_string": "baz", "new_string": "qux"}
|
|
140
|
+
]
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Search Tools
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Unified search (grep + AST + semantic)
|
|
148
|
+
results = await server.tools.search(
|
|
149
|
+
pattern="function_name",
|
|
150
|
+
path="/project"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# AST-aware search
|
|
154
|
+
results = await server.tools.grep_ast(
|
|
155
|
+
pattern="class.*Service",
|
|
156
|
+
path="/src"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Symbol search
|
|
160
|
+
symbols = await server.tools.symbols(
|
|
161
|
+
pattern="def test_",
|
|
162
|
+
path="/tests"
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Shell Tools
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# Run command
|
|
170
|
+
result = await server.tools.bash(
|
|
171
|
+
command="ls -la",
|
|
172
|
+
cwd="/project"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Run with auto-backgrounding
|
|
176
|
+
result = await server.tools.bash(
|
|
177
|
+
command="python server.py",
|
|
178
|
+
timeout=120000 # Auto-backgrounds after 2 min
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Manage processes
|
|
182
|
+
processes = await server.tools.process(action="list")
|
|
183
|
+
logs = await server.tools.process(
|
|
184
|
+
action="logs",
|
|
185
|
+
id="bash_abc123"
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Agent Tools
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# Dispatch agent for complex tasks
|
|
193
|
+
result = await server.tools.dispatch_agent(
|
|
194
|
+
prompt="Analyze the codebase architecture",
|
|
195
|
+
path="/project"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Network of agents
|
|
199
|
+
result = await server.tools.network(
|
|
200
|
+
task="Implement user authentication",
|
|
201
|
+
agents=["architect", "developer", "tester"]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# CLI tool integration
|
|
205
|
+
result = await server.tools.claude(
|
|
206
|
+
args=["--analyze", "main.py"]
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Batch Operations
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# Execute multiple tools in parallel
|
|
214
|
+
results = await server.tools.batch(
|
|
215
|
+
description="Read multiple files",
|
|
216
|
+
invocations=[
|
|
217
|
+
{"tool_name": "read", "input": {"file_path": "file1.py"}},
|
|
218
|
+
{"tool_name": "read", "input": {"file_path": "file2.py"}},
|
|
219
|
+
{"tool_name": "grep", "input": {"pattern": "TODO"}}
|
|
220
|
+
]
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Advanced Features
|
|
225
|
+
|
|
226
|
+
### Custom Tools
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from hanzo_mcp import Tool
|
|
230
|
+
|
|
231
|
+
class MyCustomTool(Tool):
|
|
232
|
+
name = "my_tool"
|
|
233
|
+
description = "Custom tool"
|
|
234
|
+
|
|
235
|
+
async def call(self, ctx, **params):
|
|
236
|
+
# Tool implementation
|
|
237
|
+
return "Result"
|
|
238
|
+
|
|
239
|
+
# Register custom tool
|
|
240
|
+
server.register_tool(MyCustomTool())
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Permission Management
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from hanzo_mcp import PermissionManager
|
|
247
|
+
|
|
248
|
+
# Create permission manager
|
|
249
|
+
pm = PermissionManager()
|
|
250
|
+
|
|
251
|
+
# Set permission mode
|
|
252
|
+
pm.set_mode("review") # review, auto_approve, auto_deny
|
|
253
|
+
|
|
254
|
+
# Check permission
|
|
255
|
+
allowed = await pm.check_permission(
|
|
256
|
+
tool="write",
|
|
257
|
+
params={"file_path": "/etc/passwd"}
|
|
258
|
+
)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Context Management
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from hanzo_mcp import ToolContext
|
|
265
|
+
|
|
266
|
+
# Create context
|
|
267
|
+
ctx = ToolContext(
|
|
268
|
+
cwd="/project",
|
|
269
|
+
env={"API_KEY": "secret"},
|
|
270
|
+
timeout=30000
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Use with tools
|
|
274
|
+
result = await tool.call(ctx, **params)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Configuration
|
|
278
|
+
|
|
279
|
+
### Environment Variables
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# API keys for agent tools
|
|
283
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
284
|
+
OPENAI_API_KEY=sk-...
|
|
285
|
+
|
|
286
|
+
# Tool settings
|
|
287
|
+
MCP_PERMISSION_MODE=review
|
|
288
|
+
MCP_MAX_FILE_SIZE=10485760
|
|
289
|
+
MCP_TIMEOUT=120000
|
|
290
|
+
|
|
291
|
+
# Search settings
|
|
292
|
+
MCP_SEARCH_IGNORE=node_modules,*.pyc
|
|
293
|
+
MCP_SEARCH_MAX_RESULTS=100
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Configuration File
|
|
297
|
+
|
|
298
|
+
```yaml
|
|
299
|
+
tools:
|
|
300
|
+
filesystem:
|
|
301
|
+
enabled: true
|
|
302
|
+
max_file_size: 10MB
|
|
303
|
+
allowed_paths:
|
|
304
|
+
- /home/user/projects
|
|
305
|
+
- /tmp
|
|
306
|
+
|
|
307
|
+
shell:
|
|
308
|
+
enabled: true
|
|
309
|
+
timeout: 120000
|
|
310
|
+
auto_background: true
|
|
311
|
+
|
|
312
|
+
agent:
|
|
313
|
+
enabled: true
|
|
314
|
+
models:
|
|
315
|
+
- claude-3-opus
|
|
316
|
+
- gpt-4
|
|
317
|
+
|
|
318
|
+
search:
|
|
319
|
+
ignore_patterns:
|
|
320
|
+
- node_modules
|
|
321
|
+
- "*.pyc"
|
|
322
|
+
- .git
|
|
323
|
+
max_results: 100
|
|
324
|
+
|
|
325
|
+
permissions:
|
|
326
|
+
mode: review # review, auto_approve, auto_deny
|
|
327
|
+
whitelist:
|
|
328
|
+
- read
|
|
329
|
+
- grep
|
|
330
|
+
- search
|
|
331
|
+
blacklist:
|
|
332
|
+
- rm
|
|
333
|
+
- sudo
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## CLI Usage
|
|
337
|
+
|
|
338
|
+
### Installation to Claude Desktop
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Install to Claude Desktop
|
|
342
|
+
hanzo-mcp install-desktop
|
|
343
|
+
|
|
344
|
+
# Serve MCP
|
|
345
|
+
hanzo-mcp serve --port 3000
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Standalone Server
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# Start MCP server
|
|
352
|
+
hanzo-mcp serve
|
|
353
|
+
|
|
354
|
+
# With custom config
|
|
355
|
+
hanzo-mcp serve --config mcp-config.yaml
|
|
356
|
+
|
|
357
|
+
# With specific tools
|
|
358
|
+
hanzo-mcp serve --tools filesystem,shell,agent
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Development
|
|
362
|
+
|
|
363
|
+
### Setup
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
cd pkg/hanzo-mcp
|
|
367
|
+
uv sync --all-extras
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Testing
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# Unit tests
|
|
374
|
+
pytest tests/ -v
|
|
375
|
+
|
|
376
|
+
# Integration tests
|
|
377
|
+
pytest tests/ -m integration
|
|
378
|
+
|
|
379
|
+
# With coverage
|
|
380
|
+
pytest tests/ --cov=hanzo_mcp
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Building
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
uv build
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Architecture
|
|
390
|
+
|
|
391
|
+
### Tool Categories
|
|
392
|
+
|
|
393
|
+
- **Filesystem**: File operations (read, write, edit)
|
|
394
|
+
- **Search**: Code search (grep, AST, semantic)
|
|
395
|
+
- **Shell**: Command execution and process management
|
|
396
|
+
- **Agent**: AI agent delegation and orchestration
|
|
397
|
+
- **Memory**: Context and knowledge persistence
|
|
398
|
+
- **Config**: Configuration management
|
|
399
|
+
- **LLM**: Direct LLM interactions
|
|
400
|
+
|
|
401
|
+
### Security
|
|
402
|
+
|
|
403
|
+
- Permission system for dangerous operations
|
|
404
|
+
- Path validation and sandboxing
|
|
405
|
+
- Command injection protection
|
|
406
|
+
- Rate limiting on operations
|
|
407
|
+
- Audit logging
|
|
408
|
+
|
|
409
|
+
## License
|
|
410
|
+
|
|
411
|
+
Apache License 2.0
|