hanzo-mcp 0.5.2__py3-none-any.whl → 0.6.2__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 +32 -0
- hanzo_mcp/dev_server.py +246 -0
- hanzo_mcp/prompts/__init__.py +1 -1
- hanzo_mcp/prompts/project_system.py +43 -7
- hanzo_mcp/server.py +5 -1
- hanzo_mcp/tools/__init__.py +66 -35
- hanzo_mcp/tools/agent/__init__.py +1 -1
- hanzo_mcp/tools/agent/agent.py +401 -0
- hanzo_mcp/tools/agent/agent_tool.py +3 -4
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +2 -2
- hanzo_mcp/tools/common/batch_tool.py +3 -5
- hanzo_mcp/tools/common/config_tool.py +1 -1
- hanzo_mcp/tools/common/context.py +1 -1
- hanzo_mcp/tools/common/palette.py +344 -0
- hanzo_mcp/tools/common/palette_loader.py +108 -0
- hanzo_mcp/tools/common/stats.py +1 -1
- hanzo_mcp/tools/common/thinking_tool.py +3 -5
- hanzo_mcp/tools/common/tool_disable.py +1 -1
- hanzo_mcp/tools/common/tool_enable.py +1 -1
- hanzo_mcp/tools/common/tool_list.py +49 -52
- hanzo_mcp/tools/config/__init__.py +10 -0
- hanzo_mcp/tools/config/config_tool.py +212 -0
- hanzo_mcp/tools/config/index_config.py +176 -0
- hanzo_mcp/tools/config/palette_tool.py +166 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/database/graph.py +482 -0
- hanzo_mcp/tools/database/graph_add.py +1 -1
- hanzo_mcp/tools/database/graph_query.py +1 -1
- hanzo_mcp/tools/database/graph_remove.py +1 -1
- hanzo_mcp/tools/database/graph_search.py +1 -1
- hanzo_mcp/tools/database/graph_stats.py +1 -1
- hanzo_mcp/tools/database/sql.py +411 -0
- hanzo_mcp/tools/database/sql_query.py +1 -1
- hanzo_mcp/tools/database/sql_search.py +1 -1
- hanzo_mcp/tools/database/sql_stats.py +1 -1
- hanzo_mcp/tools/editor/neovim_command.py +1 -1
- hanzo_mcp/tools/editor/neovim_edit.py +1 -1
- hanzo_mcp/tools/editor/neovim_session.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +42 -13
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +4 -4
- hanzo_mcp/tools/filesystem/content_replace.py +3 -5
- hanzo_mcp/tools/filesystem/diff.py +193 -0
- hanzo_mcp/tools/filesystem/directory_tree.py +3 -5
- hanzo_mcp/tools/filesystem/edit.py +3 -5
- hanzo_mcp/tools/filesystem/find.py +443 -0
- hanzo_mcp/tools/filesystem/find_files.py +1 -1
- hanzo_mcp/tools/filesystem/git_search.py +1 -1
- hanzo_mcp/tools/filesystem/grep.py +2 -2
- hanzo_mcp/tools/filesystem/multi_edit.py +3 -5
- hanzo_mcp/tools/filesystem/read.py +17 -5
- hanzo_mcp/tools/filesystem/{grep_ast_tool.py → symbols.py} +17 -27
- hanzo_mcp/tools/filesystem/symbols_unified.py +376 -0
- hanzo_mcp/tools/filesystem/tree.py +268 -0
- hanzo_mcp/tools/filesystem/unified_search.py +711 -0
- hanzo_mcp/tools/filesystem/unix_aliases.py +99 -0
- hanzo_mcp/tools/filesystem/watch.py +174 -0
- hanzo_mcp/tools/filesystem/write.py +3 -5
- hanzo_mcp/tools/jupyter/__init__.py +9 -12
- hanzo_mcp/tools/jupyter/base.py +1 -1
- hanzo_mcp/tools/jupyter/jupyter.py +326 -0
- hanzo_mcp/tools/jupyter/notebook_edit.py +3 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +3 -5
- hanzo_mcp/tools/llm/__init__.py +4 -0
- hanzo_mcp/tools/llm/consensus_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_manage.py +1 -1
- hanzo_mcp/tools/llm/llm_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_unified.py +851 -0
- hanzo_mcp/tools/llm/provider_tools.py +1 -1
- hanzo_mcp/tools/mcp/__init__.py +4 -0
- hanzo_mcp/tools/mcp/mcp_add.py +1 -1
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -1
- hanzo_mcp/tools/mcp/mcp_unified.py +503 -0
- hanzo_mcp/tools/shell/__init__.py +20 -42
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +303 -0
- hanzo_mcp/tools/shell/bash_unified.py +134 -0
- hanzo_mcp/tools/shell/logs.py +1 -1
- hanzo_mcp/tools/shell/npx.py +1 -1
- hanzo_mcp/tools/shell/npx_background.py +1 -1
- hanzo_mcp/tools/shell/npx_unified.py +101 -0
- hanzo_mcp/tools/shell/open.py +107 -0
- hanzo_mcp/tools/shell/pkill.py +1 -1
- hanzo_mcp/tools/shell/process_unified.py +131 -0
- hanzo_mcp/tools/shell/processes.py +1 -1
- hanzo_mcp/tools/shell/run_background.py +1 -1
- hanzo_mcp/tools/shell/run_command.py +3 -4
- hanzo_mcp/tools/shell/run_command_windows.py +3 -4
- hanzo_mcp/tools/shell/uvx.py +1 -1
- hanzo_mcp/tools/shell/uvx_background.py +1 -1
- hanzo_mcp/tools/shell/uvx_unified.py +101 -0
- hanzo_mcp/tools/todo/__init__.py +1 -1
- hanzo_mcp/tools/todo/base.py +1 -1
- hanzo_mcp/tools/todo/todo.py +265 -0
- hanzo_mcp/tools/todo/todo_read.py +3 -5
- hanzo_mcp/tools/todo/todo_write.py +3 -5
- hanzo_mcp/tools/vector/__init__.py +1 -1
- hanzo_mcp/tools/vector/index_tool.py +1 -1
- hanzo_mcp/tools/vector/project_manager.py +27 -5
- hanzo_mcp/tools/vector/vector.py +311 -0
- hanzo_mcp/tools/vector/vector_index.py +1 -1
- hanzo_mcp/tools/vector/vector_search.py +1 -1
- hanzo_mcp-0.6.2.dist-info/METADATA +336 -0
- hanzo_mcp-0.6.2.dist-info/RECORD +134 -0
- hanzo_mcp-0.6.2.dist-info/entry_points.txt +3 -0
- hanzo_mcp-0.5.2.dist-info/METADATA +0 -276
- hanzo_mcp-0.5.2.dist-info/RECORD +0 -106
- hanzo_mcp-0.5.2.dist-info/entry_points.txt +0 -2
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.2.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.2.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import Annotated, Optional, TypedDict, Unpack, final, override, Dict, Any
|
|
5
5
|
|
|
6
|
-
from fastmcp import Context as MCPContext
|
|
6
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool
|
hanzo_mcp/tools/mcp/__init__.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""MCP management tools."""
|
|
2
2
|
|
|
3
|
+
from hanzo_mcp.tools.mcp.mcp_unified import UnifiedMCPTool
|
|
4
|
+
|
|
5
|
+
# Legacy imports
|
|
3
6
|
from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
|
|
4
7
|
from hanzo_mcp.tools.mcp.mcp_remove import McpRemoveTool
|
|
5
8
|
from hanzo_mcp.tools.mcp.mcp_stats import McpStatsTool
|
|
6
9
|
|
|
7
10
|
__all__ = [
|
|
11
|
+
"UnifiedMCPTool",
|
|
8
12
|
"McpAddTool",
|
|
9
13
|
"McpRemoveTool",
|
|
10
14
|
"McpStatsTool",
|
hanzo_mcp/tools/mcp/mcp_add.py
CHANGED
|
@@ -6,7 +6,7 @@ import shutil
|
|
|
6
6
|
from typing import Annotated, Optional, TypedDict, Unpack, final, override, Dict, Any
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from fastmcp import Context as MCPContext
|
|
9
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
10
10
|
from pydantic import Field
|
|
11
11
|
|
|
12
12
|
from hanzo_mcp.tools.common.base import BaseTool
|
hanzo_mcp/tools/mcp/mcp_stats.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
from typing import TypedDict, Unpack, final, override
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
|
|
7
|
-
from fastmcp import Context as MCPContext
|
|
7
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
10
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
"""Unified MCP tool for managing MCP servers."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, TypedDict, Unpack, final, override, Optional, Dict, Any, List
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import os
|
|
8
|
+
import signal
|
|
9
|
+
|
|
10
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
14
|
+
from hanzo_mcp.tools.common.context import create_tool_context
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Parameter types
|
|
18
|
+
Action = Annotated[
|
|
19
|
+
str,
|
|
20
|
+
Field(
|
|
21
|
+
description="Action to perform: list, add, remove, enable, disable, restart, config",
|
|
22
|
+
default="list",
|
|
23
|
+
),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
Name = Annotated[
|
|
27
|
+
Optional[str],
|
|
28
|
+
Field(
|
|
29
|
+
description="MCP server name",
|
|
30
|
+
default=None,
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
Command = Annotated[
|
|
35
|
+
Optional[str],
|
|
36
|
+
Field(
|
|
37
|
+
description="Command to run the MCP server",
|
|
38
|
+
default=None,
|
|
39
|
+
),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
Args = Annotated[
|
|
43
|
+
Optional[List[str]],
|
|
44
|
+
Field(
|
|
45
|
+
description="Arguments for the MCP server command",
|
|
46
|
+
default=None,
|
|
47
|
+
),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
Env = Annotated[
|
|
51
|
+
Optional[Dict[str, str]],
|
|
52
|
+
Field(
|
|
53
|
+
description="Environment variables for the MCP server",
|
|
54
|
+
default=None,
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
ConfigKey = Annotated[
|
|
59
|
+
Optional[str],
|
|
60
|
+
Field(
|
|
61
|
+
description="Configuration key to get/set",
|
|
62
|
+
default=None,
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
ConfigValue = Annotated[
|
|
67
|
+
Optional[Any],
|
|
68
|
+
Field(
|
|
69
|
+
description="Configuration value to set",
|
|
70
|
+
default=None,
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
AutoStart = Annotated[
|
|
75
|
+
bool,
|
|
76
|
+
Field(
|
|
77
|
+
description="Auto-start server when Hanzo MCP starts",
|
|
78
|
+
default=True,
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MCPParams(TypedDict, total=False):
|
|
84
|
+
"""Parameters for MCP tool."""
|
|
85
|
+
action: str
|
|
86
|
+
name: Optional[str]
|
|
87
|
+
command: Optional[str]
|
|
88
|
+
args: Optional[List[str]]
|
|
89
|
+
env: Optional[Dict[str, str]]
|
|
90
|
+
config_key: Optional[str]
|
|
91
|
+
config_value: Optional[Any]
|
|
92
|
+
auto_start: bool
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@final
|
|
96
|
+
class UnifiedMCPTool(BaseTool):
|
|
97
|
+
"""Unified tool for managing MCP servers."""
|
|
98
|
+
|
|
99
|
+
# Config file
|
|
100
|
+
CONFIG_FILE = Path.home() / ".hanzo" / "mcp" / "servers.json"
|
|
101
|
+
|
|
102
|
+
# Running servers tracking
|
|
103
|
+
_running_servers: Dict[str, subprocess.Popen] = {}
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
"""Initialize the MCP management tool."""
|
|
107
|
+
self.config = self._load_config()
|
|
108
|
+
|
|
109
|
+
# Auto-start servers if configured
|
|
110
|
+
self._auto_start_servers()
|
|
111
|
+
|
|
112
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
113
|
+
"""Load MCP server configuration."""
|
|
114
|
+
if self.CONFIG_FILE.exists():
|
|
115
|
+
try:
|
|
116
|
+
with open(self.CONFIG_FILE, 'r') as f:
|
|
117
|
+
return json.load(f)
|
|
118
|
+
except:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
# Default configuration with some examples
|
|
122
|
+
return {
|
|
123
|
+
"servers": {
|
|
124
|
+
# Example configurations (disabled by default)
|
|
125
|
+
"filesystem": {
|
|
126
|
+
"command": "npx",
|
|
127
|
+
"args": ["@modelcontextprotocol/server-filesystem", "/tmp"],
|
|
128
|
+
"env": {},
|
|
129
|
+
"enabled": False,
|
|
130
|
+
"auto_start": False,
|
|
131
|
+
"description": "MCP filesystem server for /tmp access"
|
|
132
|
+
},
|
|
133
|
+
"github": {
|
|
134
|
+
"command": "npx",
|
|
135
|
+
"args": ["@modelcontextprotocol/server-github"],
|
|
136
|
+
"env": {"GITHUB_TOKEN": "${GITHUB_TOKEN}"},
|
|
137
|
+
"enabled": False,
|
|
138
|
+
"auto_start": False,
|
|
139
|
+
"description": "GitHub API access via MCP"
|
|
140
|
+
},
|
|
141
|
+
"postgres": {
|
|
142
|
+
"command": "npx",
|
|
143
|
+
"args": ["@modelcontextprotocol/server-postgres", "postgresql://localhost/db"],
|
|
144
|
+
"env": {},
|
|
145
|
+
"enabled": False,
|
|
146
|
+
"auto_start": False,
|
|
147
|
+
"description": "PostgreSQL database access"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"global_env": {},
|
|
151
|
+
"log_dir": str(Path.home() / ".hanzo" / "mcp" / "logs")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
def _save_config(self):
|
|
155
|
+
"""Save configuration."""
|
|
156
|
+
self.CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
with open(self.CONFIG_FILE, 'w') as f:
|
|
158
|
+
json.dump(self.config, f, indent=2)
|
|
159
|
+
|
|
160
|
+
def _auto_start_servers(self):
|
|
161
|
+
"""Auto-start servers configured for auto-start."""
|
|
162
|
+
for name, server_config in self.config.get("servers", {}).items():
|
|
163
|
+
if server_config.get("enabled", False) and server_config.get("auto_start", False):
|
|
164
|
+
self._start_server(name, server_config)
|
|
165
|
+
|
|
166
|
+
def _start_server(self, name: str, config: Dict[str, Any]) -> bool:
|
|
167
|
+
"""Start an MCP server."""
|
|
168
|
+
if name in self._running_servers:
|
|
169
|
+
return False # Already running
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# Prepare environment
|
|
173
|
+
env = os.environ.copy()
|
|
174
|
+
env.update(self.config.get("global_env", {}))
|
|
175
|
+
|
|
176
|
+
# Process server-specific env vars
|
|
177
|
+
server_env = config.get("env", {})
|
|
178
|
+
for key, value in server_env.items():
|
|
179
|
+
# Replace ${VAR} with actual environment variable
|
|
180
|
+
if value.startswith("${") and value.endswith("}"):
|
|
181
|
+
var_name = value[2:-1]
|
|
182
|
+
if var_name in os.environ:
|
|
183
|
+
value = os.environ[var_name]
|
|
184
|
+
env[key] = value
|
|
185
|
+
|
|
186
|
+
# Prepare command
|
|
187
|
+
cmd = [config["command"]] + config.get("args", [])
|
|
188
|
+
|
|
189
|
+
# Create log directory
|
|
190
|
+
log_dir = Path(self.config.get("log_dir", str(Path.home() / ".hanzo" / "mcp" / "logs")))
|
|
191
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
|
|
193
|
+
# Start process
|
|
194
|
+
log_file = log_dir / f"{name}.log"
|
|
195
|
+
with open(log_file, 'a') as log:
|
|
196
|
+
process = subprocess.Popen(
|
|
197
|
+
cmd,
|
|
198
|
+
env=env,
|
|
199
|
+
stdout=log,
|
|
200
|
+
stderr=subprocess.STDOUT,
|
|
201
|
+
preexec_fn=os.setsid if os.name != 'nt' else None
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self._running_servers[name] = process
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def _stop_server(self, name: str) -> bool:
|
|
211
|
+
"""Stop an MCP server."""
|
|
212
|
+
if name not in self._running_servers:
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
process = self._running_servers[name]
|
|
216
|
+
try:
|
|
217
|
+
if os.name == 'nt':
|
|
218
|
+
process.terminate()
|
|
219
|
+
else:
|
|
220
|
+
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
221
|
+
|
|
222
|
+
process.wait(timeout=5)
|
|
223
|
+
except:
|
|
224
|
+
# Force kill if needed
|
|
225
|
+
try:
|
|
226
|
+
if os.name == 'nt':
|
|
227
|
+
process.kill()
|
|
228
|
+
else:
|
|
229
|
+
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
|
230
|
+
except:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
del self._running_servers[name]
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
@override
|
|
238
|
+
def name(self) -> str:
|
|
239
|
+
"""Get the tool name."""
|
|
240
|
+
return "mcp"
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
@override
|
|
244
|
+
def description(self) -> str:
|
|
245
|
+
"""Get the tool description."""
|
|
246
|
+
servers = self.config.get("servers", {})
|
|
247
|
+
enabled = sum(1 for s in servers.values() if s.get("enabled", False))
|
|
248
|
+
running = len(self._running_servers)
|
|
249
|
+
|
|
250
|
+
return f"""Manage MCP servers. Actions: list (default), add, remove, enable, disable, restart, config.
|
|
251
|
+
|
|
252
|
+
Usage:
|
|
253
|
+
mcp
|
|
254
|
+
mcp --action add --name github --command npx --args '["@modelcontextprotocol/server-github"]'
|
|
255
|
+
mcp --action enable --name github
|
|
256
|
+
|
|
257
|
+
Status: {enabled} enabled, {running} running"""
|
|
258
|
+
|
|
259
|
+
@override
|
|
260
|
+
async def call(
|
|
261
|
+
self,
|
|
262
|
+
ctx: MCPContext,
|
|
263
|
+
**params: Unpack[MCPParams],
|
|
264
|
+
) -> str:
|
|
265
|
+
"""Execute MCP management action."""
|
|
266
|
+
# Create tool context only if we have a proper MCP context
|
|
267
|
+
tool_ctx = None
|
|
268
|
+
try:
|
|
269
|
+
if hasattr(ctx, 'client') and ctx.client and hasattr(ctx.client, 'server'):
|
|
270
|
+
tool_ctx = create_tool_context(ctx)
|
|
271
|
+
if tool_ctx:
|
|
272
|
+
await tool_ctx.set_tool_info(self.name)
|
|
273
|
+
except:
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
# Extract action
|
|
277
|
+
action = params.get("action", "list")
|
|
278
|
+
|
|
279
|
+
# Route to appropriate handler
|
|
280
|
+
if action == "list":
|
|
281
|
+
return self._handle_list()
|
|
282
|
+
elif action == "add":
|
|
283
|
+
return self._handle_add(params)
|
|
284
|
+
elif action == "remove":
|
|
285
|
+
return self._handle_remove(params.get("name"))
|
|
286
|
+
elif action == "enable":
|
|
287
|
+
return self._handle_enable(params.get("name"))
|
|
288
|
+
elif action == "disable":
|
|
289
|
+
return self._handle_disable(params.get("name"))
|
|
290
|
+
elif action == "restart":
|
|
291
|
+
return self._handle_restart(params.get("name"))
|
|
292
|
+
elif action == "config":
|
|
293
|
+
return self._handle_config(params.get("config_key"), params.get("config_value"))
|
|
294
|
+
else:
|
|
295
|
+
return f"Error: Unknown action '{action}'. Valid actions: list, add, remove, enable, disable, restart, config"
|
|
296
|
+
|
|
297
|
+
def _handle_list(self) -> str:
|
|
298
|
+
"""List all MCP servers."""
|
|
299
|
+
servers = self.config.get("servers", {})
|
|
300
|
+
|
|
301
|
+
if not servers:
|
|
302
|
+
return "No MCP servers configured. Use 'mcp --action add' to add one."
|
|
303
|
+
|
|
304
|
+
output = ["=== MCP Servers ==="]
|
|
305
|
+
output.append(f"Total: {len(servers)} | Enabled: {sum(1 for s in servers.values() if s.get('enabled', False))} | Running: {len(self._running_servers)}")
|
|
306
|
+
output.append("")
|
|
307
|
+
|
|
308
|
+
for name, config in sorted(servers.items()):
|
|
309
|
+
status_parts = []
|
|
310
|
+
|
|
311
|
+
# Check if enabled
|
|
312
|
+
if config.get("enabled", False):
|
|
313
|
+
status_parts.append("✅ Enabled")
|
|
314
|
+
else:
|
|
315
|
+
status_parts.append("❌ Disabled")
|
|
316
|
+
|
|
317
|
+
# Check if running
|
|
318
|
+
if name in self._running_servers:
|
|
319
|
+
process = self._running_servers[name]
|
|
320
|
+
if process.poll() is None:
|
|
321
|
+
status_parts.append("🟢 Running")
|
|
322
|
+
else:
|
|
323
|
+
status_parts.append("🔴 Stopped")
|
|
324
|
+
del self._running_servers[name]
|
|
325
|
+
else:
|
|
326
|
+
status_parts.append("⚫ Not running")
|
|
327
|
+
|
|
328
|
+
# Auto-start status
|
|
329
|
+
if config.get("auto_start", False):
|
|
330
|
+
status_parts.append("🚀 Auto-start")
|
|
331
|
+
|
|
332
|
+
status = " | ".join(status_parts)
|
|
333
|
+
|
|
334
|
+
output.append(f"{name}: {status}")
|
|
335
|
+
if config.get("description"):
|
|
336
|
+
output.append(f" Description: {config['description']}")
|
|
337
|
+
output.append(f" Command: {config['command']} {' '.join(config.get('args', []))}")
|
|
338
|
+
|
|
339
|
+
if config.get("env"):
|
|
340
|
+
env_str = ", ".join([f"{k}={v}" for k, v in config['env'].items()])
|
|
341
|
+
output.append(f" Environment: {env_str}")
|
|
342
|
+
|
|
343
|
+
output.append("\nUse 'mcp --action enable --name <server>' to enable a server")
|
|
344
|
+
output.append("Use 'mcp --action add' to add a new server")
|
|
345
|
+
|
|
346
|
+
return "\n".join(output)
|
|
347
|
+
|
|
348
|
+
def _handle_add(self, params: Dict[str, Any]) -> str:
|
|
349
|
+
"""Add a new MCP server."""
|
|
350
|
+
name = params.get("name")
|
|
351
|
+
command = params.get("command")
|
|
352
|
+
|
|
353
|
+
if not name:
|
|
354
|
+
return "Error: name is required for add action"
|
|
355
|
+
if not command:
|
|
356
|
+
return "Error: command is required for add action"
|
|
357
|
+
|
|
358
|
+
servers = self.config.get("servers", {})
|
|
359
|
+
if name in servers:
|
|
360
|
+
return f"Error: Server '{name}' already exists. Use a different name or remove it first."
|
|
361
|
+
|
|
362
|
+
# Create server config
|
|
363
|
+
server_config = {
|
|
364
|
+
"command": command,
|
|
365
|
+
"args": params.get("args", []),
|
|
366
|
+
"env": params.get("env", {}),
|
|
367
|
+
"enabled": False,
|
|
368
|
+
"auto_start": params.get("auto_start", True),
|
|
369
|
+
"description": params.get("description", "")
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
servers[name] = server_config
|
|
373
|
+
self.config["servers"] = servers
|
|
374
|
+
self._save_config()
|
|
375
|
+
|
|
376
|
+
return f"Successfully added MCP server '{name}'. Use 'mcp --action enable --name {name}' to enable it."
|
|
377
|
+
|
|
378
|
+
def _handle_remove(self, name: Optional[str]) -> str:
|
|
379
|
+
"""Remove an MCP server."""
|
|
380
|
+
if not name:
|
|
381
|
+
return "Error: name is required for remove action"
|
|
382
|
+
|
|
383
|
+
servers = self.config.get("servers", {})
|
|
384
|
+
if name not in servers:
|
|
385
|
+
return f"Error: Server '{name}' not found"
|
|
386
|
+
|
|
387
|
+
# Stop if running
|
|
388
|
+
if name in self._running_servers:
|
|
389
|
+
self._stop_server(name)
|
|
390
|
+
|
|
391
|
+
del servers[name]
|
|
392
|
+
self.config["servers"] = servers
|
|
393
|
+
self._save_config()
|
|
394
|
+
|
|
395
|
+
return f"Successfully removed MCP server '{name}'"
|
|
396
|
+
|
|
397
|
+
def _handle_enable(self, name: Optional[str]) -> str:
|
|
398
|
+
"""Enable an MCP server."""
|
|
399
|
+
if not name:
|
|
400
|
+
return "Error: name is required for enable action"
|
|
401
|
+
|
|
402
|
+
servers = self.config.get("servers", {})
|
|
403
|
+
if name not in servers:
|
|
404
|
+
return f"Error: Server '{name}' not found"
|
|
405
|
+
|
|
406
|
+
servers[name]["enabled"] = True
|
|
407
|
+
self.config["servers"] = servers
|
|
408
|
+
self._save_config()
|
|
409
|
+
|
|
410
|
+
# Start if auto-start is enabled
|
|
411
|
+
if servers[name].get("auto_start", False):
|
|
412
|
+
if self._start_server(name, servers[name]):
|
|
413
|
+
return f"Successfully enabled and started MCP server '{name}'"
|
|
414
|
+
else:
|
|
415
|
+
return f"Enabled MCP server '{name}' but failed to start it. Check the configuration."
|
|
416
|
+
|
|
417
|
+
return f"Successfully enabled MCP server '{name}'"
|
|
418
|
+
|
|
419
|
+
def _handle_disable(self, name: Optional[str]) -> str:
|
|
420
|
+
"""Disable an MCP server."""
|
|
421
|
+
if not name:
|
|
422
|
+
return "Error: name is required for disable action"
|
|
423
|
+
|
|
424
|
+
servers = self.config.get("servers", {})
|
|
425
|
+
if name not in servers:
|
|
426
|
+
return f"Error: Server '{name}' not found"
|
|
427
|
+
|
|
428
|
+
# Stop if running
|
|
429
|
+
if name in self._running_servers:
|
|
430
|
+
self._stop_server(name)
|
|
431
|
+
|
|
432
|
+
servers[name]["enabled"] = False
|
|
433
|
+
self.config["servers"] = servers
|
|
434
|
+
self._save_config()
|
|
435
|
+
|
|
436
|
+
return f"Successfully disabled MCP server '{name}'"
|
|
437
|
+
|
|
438
|
+
def _handle_restart(self, name: Optional[str]) -> str:
|
|
439
|
+
"""Restart an MCP server."""
|
|
440
|
+
if not name:
|
|
441
|
+
return "Error: name is required for restart action"
|
|
442
|
+
|
|
443
|
+
servers = self.config.get("servers", {})
|
|
444
|
+
if name not in servers:
|
|
445
|
+
return f"Error: Server '{name}' not found"
|
|
446
|
+
|
|
447
|
+
if not servers[name].get("enabled", False):
|
|
448
|
+
return f"Error: Server '{name}' is not enabled"
|
|
449
|
+
|
|
450
|
+
# Stop if running
|
|
451
|
+
if name in self._running_servers:
|
|
452
|
+
self._stop_server(name)
|
|
453
|
+
|
|
454
|
+
# Start again
|
|
455
|
+
if self._start_server(name, servers[name]):
|
|
456
|
+
return f"Successfully restarted MCP server '{name}'"
|
|
457
|
+
else:
|
|
458
|
+
return f"Failed to restart MCP server '{name}'. Check the configuration."
|
|
459
|
+
|
|
460
|
+
def _handle_config(self, key: Optional[str], value: Optional[Any]) -> str:
|
|
461
|
+
"""Get or set configuration values."""
|
|
462
|
+
if not key:
|
|
463
|
+
# Show all config
|
|
464
|
+
return json.dumps(self.config, indent=2)
|
|
465
|
+
|
|
466
|
+
# Parse nested keys (e.g., "servers.github.auto_start")
|
|
467
|
+
keys = key.split('.')
|
|
468
|
+
|
|
469
|
+
if value is None:
|
|
470
|
+
# Get value
|
|
471
|
+
current = self.config
|
|
472
|
+
for k in keys:
|
|
473
|
+
if isinstance(current, dict) and k in current:
|
|
474
|
+
current = current[k]
|
|
475
|
+
else:
|
|
476
|
+
return f"Configuration key '{key}' not found"
|
|
477
|
+
|
|
478
|
+
return json.dumps(current, indent=2) if isinstance(current, (dict, list)) else str(current)
|
|
479
|
+
else:
|
|
480
|
+
# Set value
|
|
481
|
+
# Navigate to parent
|
|
482
|
+
current = self.config
|
|
483
|
+
for k in keys[:-1]:
|
|
484
|
+
if k not in current:
|
|
485
|
+
current[k] = {}
|
|
486
|
+
current = current[k]
|
|
487
|
+
|
|
488
|
+
# Parse value if it looks like JSON
|
|
489
|
+
if isinstance(value, str) and value.startswith('{') or value.startswith('['):
|
|
490
|
+
try:
|
|
491
|
+
value = json.loads(value)
|
|
492
|
+
except:
|
|
493
|
+
pass
|
|
494
|
+
|
|
495
|
+
# Set the value
|
|
496
|
+
current[keys[-1]] = value
|
|
497
|
+
self._save_config()
|
|
498
|
+
|
|
499
|
+
return f"Successfully set {key} = {json.dumps(value) if isinstance(value, (dict, list)) else value}"
|
|
500
|
+
|
|
501
|
+
def register(self, mcp_server) -> None:
|
|
502
|
+
"""Register this tool with the MCP server."""
|
|
503
|
+
pass
|
|
@@ -3,22 +3,17 @@
|
|
|
3
3
|
This package provides tools for executing shell commands and scripts.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
from fastmcp import FastMCP
|
|
6
|
+
from mcp.server import FastMCP
|
|
9
7
|
|
|
10
8
|
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
11
9
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from hanzo_mcp.tools.shell.
|
|
15
|
-
from hanzo_mcp.tools.shell.
|
|
16
|
-
from hanzo_mcp.tools.shell.
|
|
17
|
-
from hanzo_mcp.tools.shell.
|
|
18
|
-
from hanzo_mcp.tools.shell.
|
|
19
|
-
from hanzo_mcp.tools.shell.uvx_background import UvxBackgroundTool
|
|
20
|
-
from hanzo_mcp.tools.shell.npx import NpxTool
|
|
21
|
-
from hanzo_mcp.tools.shell.npx_background import NpxBackgroundTool
|
|
10
|
+
|
|
11
|
+
# Import unified tools
|
|
12
|
+
from hanzo_mcp.tools.shell.bash_unified import bash_tool
|
|
13
|
+
from hanzo_mcp.tools.shell.npx_unified import npx_tool
|
|
14
|
+
from hanzo_mcp.tools.shell.uvx_unified import uvx_tool
|
|
15
|
+
from hanzo_mcp.tools.shell.process_unified import process_tool
|
|
16
|
+
from hanzo_mcp.tools.shell.open import open_tool
|
|
22
17
|
|
|
23
18
|
# Export all tool classes
|
|
24
19
|
__all__ = [
|
|
@@ -38,35 +33,18 @@ def get_shell_tools(
|
|
|
38
33
|
Returns:
|
|
39
34
|
List of shell tool instances
|
|
40
35
|
"""
|
|
41
|
-
tools
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Use tmux-based implementation for interactive sessions
|
|
46
|
-
from hanzo_mcp.tools.shell.run_command import RunCommandTool
|
|
47
|
-
|
|
48
|
-
command_executor = BashSessionExecutor(permission_manager)
|
|
49
|
-
tools.append(RunCommandTool(permission_manager, command_executor))
|
|
50
|
-
else:
|
|
51
|
-
# Use Windows-compatible implementation
|
|
52
|
-
from hanzo_mcp.tools.shell.run_command_windows import RunCommandTool
|
|
53
|
-
|
|
54
|
-
command_executor = CommandExecutor(permission_manager)
|
|
55
|
-
tools.append(RunCommandTool(permission_manager, command_executor))
|
|
56
|
-
|
|
57
|
-
# Add other shell tools
|
|
58
|
-
tools.extend([
|
|
59
|
-
RunBackgroundTool(permission_manager),
|
|
60
|
-
ProcessesTool(),
|
|
61
|
-
PkillTool(),
|
|
62
|
-
LogsTool(),
|
|
63
|
-
UvxTool(permission_manager),
|
|
64
|
-
UvxBackgroundTool(permission_manager),
|
|
65
|
-
NpxTool(permission_manager),
|
|
66
|
-
NpxBackgroundTool(permission_manager),
|
|
67
|
-
])
|
|
36
|
+
# Set permission manager for tools that need it
|
|
37
|
+
bash_tool.permission_manager = permission_manager
|
|
38
|
+
npx_tool.permission_manager = permission_manager
|
|
39
|
+
uvx_tool.permission_manager = permission_manager
|
|
68
40
|
|
|
69
|
-
return
|
|
41
|
+
return [
|
|
42
|
+
bash_tool,
|
|
43
|
+
npx_tool,
|
|
44
|
+
uvx_tool,
|
|
45
|
+
process_tool,
|
|
46
|
+
open_tool,
|
|
47
|
+
]
|
|
70
48
|
|
|
71
49
|
|
|
72
50
|
def register_shell_tools(
|
|
@@ -84,4 +62,4 @@ def register_shell_tools(
|
|
|
84
62
|
"""
|
|
85
63
|
tools = get_shell_tools(permission_manager)
|
|
86
64
|
ToolRegistry.register_tools(mcp_server, tools)
|
|
87
|
-
return tools
|
|
65
|
+
return tools
|
hanzo_mcp/tools/shell/base.py
CHANGED
|
@@ -8,7 +8,7 @@ from abc import ABC, abstractmethod
|
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from typing import Any, Self, final
|
|
10
10
|
|
|
11
|
-
from fastmcp import Context as MCPContext
|
|
11
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
12
|
|
|
13
13
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
14
14
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|