hanzo-mcp 0.8.8__py3-none-any.whl → 0.9.0__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 -3
- hanzo_mcp/analytics/posthog_analytics.py +4 -17
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +8 -17
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +2 -4
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +6 -7
- hanzo_mcp/tools/__init__.py +29 -32
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +23 -17
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +76 -75
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +7 -19
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
- hanzo_mcp/tools/mcp/mcp_add.py +3 -5
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +33 -40
- hanzo_mcp/tools/memory/conversation_memory.py +636 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +7 -19
- hanzo_mcp/tools/search/find_tool.py +12 -34
- hanzo_mcp/tools/search/unified_search.py +27 -81
- hanzo_mcp/tools/shell/__init__.py +16 -4
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/run_tool.py +56 -0
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/vector/__init__.py +97 -50
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +11 -30
- hanzo_mcp/tools/vector/mock_infinity.py +159 -0
- hanzo_mcp/tools/vector/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -723
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -20,9 +20,7 @@ from hanzo_mcp.tools.shell.command_executor import CommandExecutor
|
|
|
20
20
|
class RunCommandTool(ShellBaseTool):
|
|
21
21
|
"""Tool for executing shell commands."""
|
|
22
22
|
|
|
23
|
-
def __init__(
|
|
24
|
-
self, permission_manager: Any, command_executor: CommandExecutor
|
|
25
|
-
) -> None:
|
|
23
|
+
def __init__(self, permission_manager: Any, command_executor: CommandExecutor) -> None:
|
|
26
24
|
"""Initialize the run command tool.
|
|
27
25
|
|
|
28
26
|
Args:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Run tool for command execution with automatic backgrounding."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, override
|
|
4
|
+
from mcp.server import FastMCP
|
|
5
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
6
|
+
|
|
7
|
+
from hanzo_mcp.tools.shell.zsh_tool import ShellTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RunTool(ShellTool):
|
|
11
|
+
"""Tool for running commands with automatic backgrounding (alias of shell tool)."""
|
|
12
|
+
|
|
13
|
+
name = "run"
|
|
14
|
+
|
|
15
|
+
def register(self, server: FastMCP) -> None:
|
|
16
|
+
"""Register the tool with the MCP server."""
|
|
17
|
+
tool_self = self
|
|
18
|
+
|
|
19
|
+
@server.tool(name=self.name, description=self.description)
|
|
20
|
+
async def run(
|
|
21
|
+
ctx: MCPContext,
|
|
22
|
+
command: str,
|
|
23
|
+
cwd: Optional[str] = None,
|
|
24
|
+
env: Optional[dict[str, str]] = None,
|
|
25
|
+
timeout: Optional[int] = None,
|
|
26
|
+
) -> str:
|
|
27
|
+
return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@override
|
|
31
|
+
def description(self) -> str:
|
|
32
|
+
"""Get the tool description."""
|
|
33
|
+
return """Execute shell commands with automatic backgrounding for long-running processes.
|
|
34
|
+
|
|
35
|
+
Automatically selects the best available shell:
|
|
36
|
+
- Zsh if available (with .zshrc)
|
|
37
|
+
- User's preferred shell ($SHELL)
|
|
38
|
+
- Bash as fallback
|
|
39
|
+
|
|
40
|
+
Commands that run for more than 2 minutes will automatically continue in the background.
|
|
41
|
+
You can check their status and logs using the 'process' tool.
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
run "ls -la"
|
|
45
|
+
run "python server.py" # Auto-backgrounds after 2 minutes
|
|
46
|
+
run "git status && git diff"
|
|
47
|
+
run "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
def get_tool_name(self) -> str:
|
|
51
|
+
"""Get the tool name."""
|
|
52
|
+
return "run"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Create instance
|
|
56
|
+
run_tool = RunTool()
|
|
@@ -24,9 +24,7 @@ class SessionManager:
|
|
|
24
24
|
_instance: Self | None = None
|
|
25
25
|
_lock = threading.Lock()
|
|
26
26
|
|
|
27
|
-
def __new__(
|
|
28
|
-
cls, use_singleton: bool = True, session_storage: SessionStorage | None = None
|
|
29
|
-
) -> "SessionManager":
|
|
27
|
+
def __new__(cls, use_singleton: bool = True, session_storage: SessionStorage | None = None) -> "SessionManager":
|
|
30
28
|
"""Create SessionManager instance.
|
|
31
29
|
|
|
32
30
|
Args:
|
|
@@ -45,9 +43,7 @@ class SessionManager:
|
|
|
45
43
|
cls._instance._initialized = False
|
|
46
44
|
return cls._instance
|
|
47
45
|
|
|
48
|
-
def __init__(
|
|
49
|
-
self, use_singleton: bool = True, session_storage: SessionStorage | None = None
|
|
50
|
-
) -> None:
|
|
46
|
+
def __init__(self, use_singleton: bool = True, session_storage: SessionStorage | None = None) -> None:
|
|
51
47
|
"""Initialize the session manager.
|
|
52
48
|
|
|
53
49
|
Args:
|
|
@@ -152,9 +152,7 @@ class SessionStorageInstance:
|
|
|
152
152
|
Returns:
|
|
153
153
|
Number of sessions cleaned up
|
|
154
154
|
"""
|
|
155
|
-
max_age =
|
|
156
|
-
max_age_seconds if max_age_seconds is not None else self.default_ttl_seconds
|
|
157
|
-
)
|
|
155
|
+
max_age = max_age_seconds if max_age_seconds is not None else self.default_ttl_seconds
|
|
158
156
|
current_time = time.time()
|
|
159
157
|
expired_sessions: list[str] = []
|
|
160
158
|
|
|
@@ -201,9 +199,7 @@ class SessionStorageInstance:
|
|
|
201
199
|
return {
|
|
202
200
|
"total_sessions": len(self._sessions),
|
|
203
201
|
"max_sessions": self.max_sessions,
|
|
204
|
-
"utilization": (
|
|
205
|
-
len(self._sessions) / self.max_sessions if self.max_sessions > 0 else 0
|
|
206
|
-
),
|
|
202
|
+
"utilization": (len(self._sessions) / self.max_sessions if self.max_sessions > 0 else 0),
|
|
207
203
|
"default_ttl_seconds": self.default_ttl_seconds,
|
|
208
204
|
}
|
|
209
205
|
|
|
@@ -117,9 +117,7 @@ class StreamingCommandTool(BaseProcessTool):
|
|
|
117
117
|
try:
|
|
118
118
|
with open(meta_file, "r") as f:
|
|
119
119
|
meta = json.load(f)
|
|
120
|
-
last_accessed = datetime.fromisoformat(
|
|
121
|
-
meta.get("last_accessed", "")
|
|
122
|
-
)
|
|
120
|
+
last_accessed = datetime.fromisoformat(meta.get("last_accessed", ""))
|
|
123
121
|
if last_accessed < cutoff:
|
|
124
122
|
shutil.rmtree(session_dir)
|
|
125
123
|
except Exception:
|
|
@@ -240,9 +238,7 @@ class StreamingCommandTool(BaseProcessTool):
|
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
# Execute new command
|
|
243
|
-
return await self._execute_new_command(
|
|
244
|
-
command, working_dir, timeout, chunk_size
|
|
245
|
-
)
|
|
241
|
+
return await self._execute_new_command(command, working_dir, timeout, chunk_size)
|
|
246
242
|
|
|
247
243
|
async def _execute_new_command(
|
|
248
244
|
self,
|
|
@@ -296,18 +292,12 @@ class StreamingCommandTool(BaseProcessTool):
|
|
|
296
292
|
f.flush() # Ensure immediate write
|
|
297
293
|
|
|
298
294
|
# Start streaming tasks
|
|
299
|
-
stdout_task = asyncio.create_task(
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
stderr_task = asyncio.create_task(
|
|
303
|
-
stream_to_file(process.stderr, error_file)
|
|
304
|
-
)
|
|
295
|
+
stdout_task = asyncio.create_task(stream_to_file(process.stdout, output_file))
|
|
296
|
+
stderr_task = asyncio.create_task(stream_to_file(process.stderr, error_file))
|
|
305
297
|
|
|
306
298
|
# Wait for initial output or timeout
|
|
307
299
|
start_time = time.time()
|
|
308
|
-
initial_timeout = min(
|
|
309
|
-
timeout or 5, 5
|
|
310
|
-
) # Wait max 5 seconds for initial output
|
|
300
|
+
initial_timeout = min(timeout or 5, 5) # Wait max 5 seconds for initial output
|
|
311
301
|
|
|
312
302
|
while time.time() - start_time < initial_timeout:
|
|
313
303
|
if output_file.stat().st_size > 0 or error_file.stat().st_size > 0:
|
|
@@ -487,9 +477,7 @@ class StreamingCommandTool(BaseProcessTool):
|
|
|
487
477
|
"""Get list of recent commands for hints."""
|
|
488
478
|
commands = []
|
|
489
479
|
|
|
490
|
-
for cmd_dir in sorted(
|
|
491
|
-
self.commands_dir.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True
|
|
492
|
-
)[:limit]:
|
|
480
|
+
for cmd_dir in sorted(self.commands_dir.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True)[:limit]:
|
|
493
481
|
try:
|
|
494
482
|
with open(cmd_dir / "metadata.json", "r") as f:
|
|
495
483
|
meta = json.load(f)
|
|
@@ -502,11 +490,7 @@ class StreamingCommandTool(BaseProcessTool):
|
|
|
502
490
|
commands.append(
|
|
503
491
|
{
|
|
504
492
|
"id": meta["command_id"][:8],
|
|
505
|
-
"command": (
|
|
506
|
-
meta["command"][:50] + "..."
|
|
507
|
-
if len(meta["command"]) > 50
|
|
508
|
-
else meta["command"]
|
|
509
|
-
),
|
|
493
|
+
"command": (meta["command"][:50] + "..." if len(meta["command"]) > 50 else meta["command"]),
|
|
510
494
|
"status": meta.get("status", "unknown"),
|
|
511
495
|
"output_size": output_size,
|
|
512
496
|
"time": meta.get("start_time", ""),
|
hanzo_mcp/tools/shell/uvx.py
CHANGED
|
@@ -135,9 +135,7 @@ For long-running servers, use uvx_background instead.
|
|
|
135
135
|
|
|
136
136
|
try:
|
|
137
137
|
# Run installation
|
|
138
|
-
install_result = subprocess.run(
|
|
139
|
-
install_cmd, shell=True, capture_output=True, text=True, timeout=60
|
|
140
|
-
)
|
|
138
|
+
install_result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True, timeout=60)
|
|
141
139
|
|
|
142
140
|
if install_result.returncode == 0:
|
|
143
141
|
await tool_ctx.info("uvx installed successfully!")
|
|
@@ -146,9 +144,7 @@ For long-running servers, use uvx_background instead.
|
|
|
146
144
|
import os
|
|
147
145
|
|
|
148
146
|
home = os.path.expanduser("~")
|
|
149
|
-
os.environ["PATH"] = (
|
|
150
|
-
f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
151
|
-
)
|
|
147
|
+
os.environ["PATH"] = f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
152
148
|
|
|
153
149
|
# Check again
|
|
154
150
|
if not shutil.which("uvx"):
|
|
@@ -197,9 +193,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
197
193
|
|
|
198
194
|
try:
|
|
199
195
|
# Execute command
|
|
200
|
-
result = subprocess.run(
|
|
201
|
-
cmd, capture_output=True, text=True, timeout=timeout, check=True
|
|
202
|
-
)
|
|
196
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=True)
|
|
203
197
|
|
|
204
198
|
output = []
|
|
205
199
|
if result.stdout:
|
|
@@ -207,11 +201,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
207
201
|
if result.stderr:
|
|
208
202
|
output.append(f"\nSTDERR:\n{result.stderr}")
|
|
209
203
|
|
|
210
|
-
return (
|
|
211
|
-
"\n".join(output)
|
|
212
|
-
if output
|
|
213
|
-
else "Command completed successfully with no output."
|
|
214
|
-
)
|
|
204
|
+
return "\n".join(output) if output else "Command completed successfully with no output."
|
|
215
205
|
|
|
216
206
|
except subprocess.TimeoutExpired:
|
|
217
207
|
return f"Error: Command timed out after {timeout} seconds. Use uvx_background for long-running processes."
|
|
@@ -156,9 +156,7 @@ Use 'processes' to list running processes and 'pkill' to stop them.
|
|
|
156
156
|
|
|
157
157
|
try:
|
|
158
158
|
# Run installation
|
|
159
|
-
install_result = subprocess.run(
|
|
160
|
-
install_cmd, shell=True, capture_output=True, text=True, timeout=60
|
|
161
|
-
)
|
|
159
|
+
install_result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True, timeout=60)
|
|
162
160
|
|
|
163
161
|
if install_result.returncode == 0:
|
|
164
162
|
await tool_ctx.info("uvx installed successfully!")
|
|
@@ -167,9 +165,7 @@ Use 'processes' to list running processes and 'pkill' to stop them.
|
|
|
167
165
|
import os
|
|
168
166
|
|
|
169
167
|
home = os.path.expanduser("~")
|
|
170
|
-
os.environ["PATH"] = (
|
|
171
|
-
f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
172
|
-
)
|
|
168
|
+
os.environ["PATH"] = f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
173
169
|
|
|
174
170
|
# Check again
|
|
175
171
|
if not shutil.which("uvx"):
|
|
@@ -86,9 +86,7 @@ uvx jupyter lab --port 8888 # Auto-backgrounds if needed"""
|
|
|
86
86
|
cwd: Optional[str] = None,
|
|
87
87
|
python: Optional[str] = None,
|
|
88
88
|
) -> str:
|
|
89
|
-
return await tool_self.run(
|
|
90
|
-
ctx, package=package, args=args, cwd=cwd, python=python
|
|
91
|
-
)
|
|
89
|
+
return await tool_self.run(ctx, package=package, args=args, cwd=cwd, python=python)
|
|
92
90
|
|
|
93
91
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
94
92
|
"""Call the tool with arguments."""
|
|
@@ -29,9 +29,7 @@ class ZshTool(BaseScriptTool):
|
|
|
29
29
|
env: Optional[dict[str, str]] = None,
|
|
30
30
|
timeout: Optional[int] = None,
|
|
31
31
|
) -> str:
|
|
32
|
-
return await tool_self.run(
|
|
33
|
-
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
34
|
-
)
|
|
32
|
+
return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
|
|
35
33
|
|
|
36
34
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
37
35
|
"""Call the tool with arguments."""
|
|
@@ -79,12 +77,12 @@ zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
79
77
|
return path
|
|
80
78
|
# Fall back to bash if no zsh found
|
|
81
79
|
return "bash"
|
|
82
|
-
|
|
80
|
+
|
|
83
81
|
# On Unix-like systems, check for zsh
|
|
84
82
|
zsh_path = shutil.which("zsh")
|
|
85
83
|
if zsh_path:
|
|
86
84
|
return zsh_path
|
|
87
|
-
|
|
85
|
+
|
|
88
86
|
# Fall back to bash if zsh not found
|
|
89
87
|
return "bash"
|
|
90
88
|
|
|
@@ -124,14 +122,12 @@ zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
124
122
|
# Check if zsh is available
|
|
125
123
|
if not shutil.which("zsh") and platform.system() != "Windows":
|
|
126
124
|
return "Error: Zsh is not installed. Please install zsh first."
|
|
127
|
-
|
|
125
|
+
|
|
128
126
|
# Prepare working directory
|
|
129
127
|
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
|
|
130
128
|
|
|
131
129
|
# Use execute_sync which has auto-backgrounding
|
|
132
|
-
output = await self.execute_sync(
|
|
133
|
-
command, cwd=work_dir, env=env, timeout=timeout
|
|
134
|
-
)
|
|
130
|
+
output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
|
|
135
131
|
return output if output else "Command completed successfully (no output)"
|
|
136
132
|
|
|
137
133
|
|
|
@@ -152,12 +148,12 @@ class ShellTool(BaseScriptTool):
|
|
|
152
148
|
# Also check if .zshrc exists
|
|
153
149
|
if (Path.home() / ".zshrc").exists():
|
|
154
150
|
return "zsh"
|
|
155
|
-
|
|
151
|
+
|
|
156
152
|
# Check for user's preferred shell
|
|
157
153
|
user_shell = os.environ.get("SHELL", "")
|
|
158
154
|
if user_shell and Path(user_shell).exists():
|
|
159
155
|
return user_shell
|
|
160
|
-
|
|
156
|
+
|
|
161
157
|
# Default to bash
|
|
162
158
|
return "bash"
|
|
163
159
|
|
|
@@ -173,9 +169,7 @@ class ShellTool(BaseScriptTool):
|
|
|
173
169
|
env: Optional[dict[str, str]] = None,
|
|
174
170
|
timeout: Optional[int] = None,
|
|
175
171
|
) -> str:
|
|
176
|
-
return await tool_self.run(
|
|
177
|
-
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
178
|
-
)
|
|
172
|
+
return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
|
|
179
173
|
|
|
180
174
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
181
175
|
"""Call the tool with arguments."""
|
|
@@ -249,12 +243,10 @@ shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
249
243
|
|
|
250
244
|
# Add shell info to output if verbose
|
|
251
245
|
shell_name = os.path.basename(self._best_shell)
|
|
252
|
-
|
|
246
|
+
|
|
253
247
|
# Use execute_sync which has auto-backgrounding
|
|
254
|
-
output = await self.execute_sync(
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
248
|
+
output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
|
|
249
|
+
|
|
258
250
|
if output:
|
|
259
251
|
return output
|
|
260
252
|
else:
|
|
@@ -263,4 +255,4 @@ shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
263
255
|
|
|
264
256
|
# Create tool instances
|
|
265
257
|
zsh_tool = ZshTool()
|
|
266
|
-
shell_tool = ShellTool() # Smart shell that prefers zsh
|
|
258
|
+
shell_tool = ShellTool() # Smart shell that prefers zsh
|
hanzo_mcp/tools/todo/todo.py
CHANGED
|
@@ -161,9 +161,7 @@ todo --filter in_progress
|
|
|
161
161
|
priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
|
|
162
162
|
todo.get("priority", "medium"), "⚪"
|
|
163
163
|
)
|
|
164
|
-
output.append(
|
|
165
|
-
f"{priority_icon} [{todo['id'][:8]}] {todo['content']}"
|
|
166
|
-
)
|
|
164
|
+
output.append(f"{priority_icon} [{todo['id'][:8]}] {todo['content']}")
|
|
167
165
|
|
|
168
166
|
# Summary
|
|
169
167
|
output.append(
|
|
@@ -4,15 +4,26 @@ This package provides tools for working with local vector databases for semantic
|
|
|
4
4
|
document indexing, and retrieval-augmented generation (RAG) workflows.
|
|
5
5
|
|
|
6
6
|
Supported backends:
|
|
7
|
-
-
|
|
7
|
+
- LanceDB (primary) - High-performance embedded vector database
|
|
8
|
+
- Hanzo-node (optional) - Distributed vector processing node
|
|
9
|
+
- Infinity database (legacy) - High-performance local vector database
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
12
|
from mcp.server import FastMCP
|
|
11
13
|
|
|
12
|
-
from hanzo_mcp.tools.common.base import BaseTool
|
|
14
|
+
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
13
15
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
14
16
|
|
|
15
|
-
# Try to import vector
|
|
17
|
+
# Try to import unified vector tools first
|
|
18
|
+
try:
|
|
19
|
+
from .unified_vector import UnifiedVectorTool
|
|
20
|
+
from .node_tool import NodeTool
|
|
21
|
+
|
|
22
|
+
UNIFIED_TOOLS_AVAILABLE = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
UNIFIED_TOOLS_AVAILABLE = False
|
|
25
|
+
|
|
26
|
+
# Try to import legacy vector dependencies
|
|
16
27
|
try:
|
|
17
28
|
from .index_tool import IndexTool
|
|
18
29
|
from .vector_index import VectorIndexTool
|
|
@@ -20,35 +31,73 @@ try:
|
|
|
20
31
|
from .infinity_store import InfinityVectorStore
|
|
21
32
|
from .project_manager import ProjectVectorManager
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
LEGACY_VECTOR_AVAILABLE = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
LEGACY_VECTOR_AVAILABLE = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def register_vector_tools(
|
|
40
|
+
mcp_server: FastMCP,
|
|
41
|
+
permission_manager: PermissionManager | None = None,
|
|
42
|
+
vector_config: dict | None = None,
|
|
43
|
+
enabled_tools: dict[str, bool] | None = None,
|
|
44
|
+
search_paths: list[str] | None = None,
|
|
45
|
+
project_manager: "ProjectVectorManager | None" = None,
|
|
46
|
+
user_id: str = "default",
|
|
47
|
+
project_id: str = "default",
|
|
48
|
+
use_unified: bool = True,
|
|
49
|
+
) -> list[BaseTool]:
|
|
50
|
+
"""Register vector database tools with the MCP server.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
mcp_server: The FastMCP server instance
|
|
54
|
+
permission_manager: Permission manager for access control (optional for unified tools)
|
|
55
|
+
vector_config: Vector store configuration
|
|
56
|
+
enabled_tools: Dictionary of individual tool enable states
|
|
57
|
+
search_paths: Paths to search for projects (default: None, uses allowed paths)
|
|
58
|
+
project_manager: Optional existing project manager to reuse
|
|
59
|
+
user_id: User ID for unified tools
|
|
60
|
+
project_id: Project ID for unified tools
|
|
61
|
+
use_unified: Whether to use unified tools (default: True)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List of registered tools
|
|
65
|
+
"""
|
|
66
|
+
tools = []
|
|
67
|
+
|
|
68
|
+
# Prefer unified tools if available and enabled
|
|
69
|
+
if use_unified and UNIFIED_TOOLS_AVAILABLE:
|
|
70
|
+
tool_enabled = enabled_tools or {}
|
|
71
|
+
|
|
72
|
+
# Register unified vector tool (consolidates vector_search, vector_index, memory ops)
|
|
73
|
+
if tool_enabled.get("vector", True):
|
|
74
|
+
unified_vector = UnifiedVectorTool(user_id=user_id, project_id=project_id)
|
|
75
|
+
ToolRegistry.register_tool(mcp_server, unified_vector)
|
|
76
|
+
tools.append(unified_vector)
|
|
77
|
+
|
|
78
|
+
# Register node management tool
|
|
79
|
+
if tool_enabled.get("node", True):
|
|
80
|
+
node_tool = NodeTool()
|
|
81
|
+
ToolRegistry.register_tool(mcp_server, node_tool)
|
|
82
|
+
tools.append(node_tool)
|
|
83
|
+
|
|
84
|
+
import logging
|
|
85
|
+
logger = logging.getLogger(__name__)
|
|
86
|
+
logger.info(f"Registered {len(tools)} unified vector tools")
|
|
87
|
+
|
|
88
|
+
# Fall back to legacy tools if unified not available or disabled
|
|
89
|
+
elif not use_unified and LEGACY_VECTOR_AVAILABLE:
|
|
46
90
|
if not vector_config or not vector_config.get("enabled", False):
|
|
47
91
|
return []
|
|
48
92
|
|
|
93
|
+
if not permission_manager:
|
|
94
|
+
import logging
|
|
95
|
+
logger = logging.getLogger(__name__)
|
|
96
|
+
logger.warning("Permission manager required for legacy vector tools")
|
|
97
|
+
return []
|
|
98
|
+
|
|
49
99
|
# Check individual tool enablement
|
|
50
100
|
tool_enabled = enabled_tools or {}
|
|
51
|
-
tools = []
|
|
52
101
|
|
|
53
102
|
# Use provided project manager or create new one
|
|
54
103
|
if project_manager is None:
|
|
@@ -56,9 +105,7 @@ try:
|
|
|
56
105
|
store_config = vector_config.copy()
|
|
57
106
|
project_manager = ProjectVectorManager(
|
|
58
107
|
global_db_path=store_config.get("data_path"),
|
|
59
|
-
embedding_model=store_config.get(
|
|
60
|
-
"embedding_model", "text-embedding-3-small"
|
|
61
|
-
),
|
|
108
|
+
embedding_model=store_config.get("embedding_model", "text-embedding-3-small"),
|
|
62
109
|
dimension=store_config.get("dimension", 1536),
|
|
63
110
|
)
|
|
64
111
|
|
|
@@ -68,9 +115,7 @@ try:
|
|
|
68
115
|
import logging
|
|
69
116
|
|
|
70
117
|
logger = logging.getLogger(__name__)
|
|
71
|
-
logger.info(
|
|
72
|
-
f"Detected {len(detected_projects)} projects with LLM.md files"
|
|
73
|
-
)
|
|
118
|
+
logger.info(f"Detected {len(detected_projects)} projects with LLM.md files")
|
|
74
119
|
|
|
75
120
|
# Register individual tools if enabled
|
|
76
121
|
if tool_enabled.get("index", True):
|
|
@@ -83,32 +128,34 @@ try:
|
|
|
83
128
|
tools.append(VectorSearchTool(permission_manager, project_manager))
|
|
84
129
|
|
|
85
130
|
# Register with MCP server
|
|
86
|
-
from hanzo_mcp.tools.common.base import ToolRegistry
|
|
87
|
-
|
|
88
131
|
ToolRegistry.register_tools(mcp_server, tools)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
except ImportError:
|
|
93
|
-
VECTOR_AVAILABLE = False
|
|
94
|
-
|
|
95
|
-
def register_vector_tools(*args, **kwargs) -> list[BaseTool]:
|
|
96
|
-
"""Vector tools not available - missing dependencies."""
|
|
132
|
+
|
|
133
|
+
else:
|
|
97
134
|
import logging
|
|
98
|
-
|
|
99
135
|
logger = logging.getLogger(__name__)
|
|
100
|
-
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
|
|
136
|
+
if not UNIFIED_TOOLS_AVAILABLE and not LEGACY_VECTOR_AVAILABLE:
|
|
137
|
+
logger.warning("No vector tools available. Install hanzo-memory package or infinity-embedded.")
|
|
138
|
+
elif not use_unified:
|
|
139
|
+
logger.info("Unified vector tools disabled, legacy tools not available")
|
|
140
|
+
|
|
141
|
+
return tools
|
|
104
142
|
|
|
105
143
|
|
|
106
144
|
__all__ = [
|
|
107
145
|
"register_vector_tools",
|
|
108
|
-
"
|
|
146
|
+
"UNIFIED_TOOLS_AVAILABLE",
|
|
147
|
+
"LEGACY_VECTOR_AVAILABLE",
|
|
109
148
|
]
|
|
110
149
|
|
|
111
|
-
if
|
|
150
|
+
if UNIFIED_TOOLS_AVAILABLE:
|
|
151
|
+
__all__.extend(
|
|
152
|
+
[
|
|
153
|
+
"UnifiedVectorTool",
|
|
154
|
+
"NodeTool",
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if LEGACY_VECTOR_AVAILABLE:
|
|
112
159
|
__all__.extend(
|
|
113
160
|
[
|
|
114
161
|
"InfinityVectorStore",
|
|
@@ -130,9 +130,7 @@ class ASTAnalyzer:
|
|
|
130
130
|
return self._analyze_python_file(file_path, content, file_hash)
|
|
131
131
|
else:
|
|
132
132
|
# Generic analysis for other languages
|
|
133
|
-
return self._analyze_generic_file(
|
|
134
|
-
file_path, content, file_hash, language
|
|
135
|
-
)
|
|
133
|
+
return self._analyze_generic_file(file_path, content, file_hash, language)
|
|
136
134
|
|
|
137
135
|
except Exception as e:
|
|
138
136
|
import logging
|
|
@@ -177,9 +175,7 @@ class ASTAnalyzer:
|
|
|
177
175
|
|
|
178
176
|
return language_map.get(extension)
|
|
179
177
|
|
|
180
|
-
def _analyze_python_file(
|
|
181
|
-
self, file_path: str, content: str, file_hash: str
|
|
182
|
-
) -> FileAST:
|
|
178
|
+
def _analyze_python_file(self, file_path: str, content: str, file_hash: str) -> FileAST:
|
|
183
179
|
"""Analyze Python file using both AST and tree-sitter."""
|
|
184
180
|
symbols = []
|
|
185
181
|
ast_nodes = []
|
|
@@ -228,9 +224,7 @@ class ASTAnalyzer:
|
|
|
228
224
|
dependencies=dependencies,
|
|
229
225
|
)
|
|
230
226
|
|
|
231
|
-
def _analyze_generic_file(
|
|
232
|
-
self, file_path: str, content: str, file_hash: str, language: str
|
|
233
|
-
) -> FileAST:
|
|
227
|
+
def _analyze_generic_file(self, file_path: str, content: str, file_hash: str, language: str) -> FileAST:
|
|
234
228
|
"""Generic analysis for non-Python files."""
|
|
235
229
|
# For now, just basic line-based analysis
|
|
236
230
|
# Could be enhanced with language-specific parsers
|
|
@@ -248,11 +242,7 @@ class ASTAnalyzer:
|
|
|
248
242
|
|
|
249
243
|
# Basic function detection (works for many C-style languages)
|
|
250
244
|
if language in ["javascript", "typescript", "java", "cpp", "c"]:
|
|
251
|
-
if (
|
|
252
|
-
"function " in line
|
|
253
|
-
or line.startswith("def ")
|
|
254
|
-
or " function(" in line
|
|
255
|
-
):
|
|
245
|
+
if "function " in line or line.startswith("def ") or " function(" in line:
|
|
256
246
|
# Extract function name
|
|
257
247
|
parts = line.split()
|
|
258
248
|
for j, part in enumerate(parts):
|
|
@@ -399,9 +389,7 @@ class PythonSymbolExtractor(ast.NodeVisitor):
|
|
|
399
389
|
|
|
400
390
|
# Extract base classes
|
|
401
391
|
bases = [self._get_name(base) for base in node.bases]
|
|
402
|
-
signature = (
|
|
403
|
-
f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
|
|
404
|
-
)
|
|
392
|
+
signature = f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
|
|
405
393
|
|
|
406
394
|
symbol = Symbol(
|
|
407
395
|
name=node.name,
|
|
@@ -441,9 +429,7 @@ class PythonSymbolExtractor(ast.NodeVisitor):
|
|
|
441
429
|
|
|
442
430
|
for alias in node.names:
|
|
443
431
|
if alias.name != "*":
|
|
444
|
-
import_item =
|
|
445
|
-
f"{node.module}.{alias.name}" if node.module else alias.name
|
|
446
|
-
)
|
|
432
|
+
import_item = f"{node.module}.{alias.name}" if node.module else alias.name
|
|
447
433
|
self.imports.append(import_item)
|
|
448
434
|
|
|
449
435
|
def visit_Assign(self, node):
|