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
hanzo_mcp/prompts/__init__.py
CHANGED
|
@@ -52,9 +52,7 @@ def create_project_system_prompt(project_path: str):
|
|
|
52
52
|
platform, _, os_version = get_os_info()
|
|
53
53
|
|
|
54
54
|
# Get directory structure
|
|
55
|
-
directory_structure = get_directory_structure(
|
|
56
|
-
working_directory, max_depth=3, include_filtered=False
|
|
57
|
-
)
|
|
55
|
+
directory_structure = get_directory_structure(working_directory, max_depth=3, include_filtered=False)
|
|
58
56
|
|
|
59
57
|
# Get git information
|
|
60
58
|
git_info = get_git_info(working_directory)
|
|
@@ -78,9 +76,7 @@ def create_project_system_prompt(project_path: str):
|
|
|
78
76
|
return project_system_prompt
|
|
79
77
|
|
|
80
78
|
|
|
81
|
-
def register_all_prompts(
|
|
82
|
-
mcp_server: FastMCP, projects: list[str] | None = None
|
|
83
|
-
) -> None:
|
|
79
|
+
def register_all_prompts(mcp_server: FastMCP, projects: list[str] | None = None) -> None:
|
|
84
80
|
@mcp_server.prompt(name="Compact current conversation")
|
|
85
81
|
def compact() -> str:
|
|
86
82
|
"""
|
|
@@ -49,13 +49,9 @@ def format_todo_list_concise(todos: list[dict[str, Any]]) -> str:
|
|
|
49
49
|
}.get(status, "[?]")
|
|
50
50
|
|
|
51
51
|
# Create priority indicator
|
|
52
|
-
priority_indicator = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
|
|
53
|
-
priority, "⚪"
|
|
54
|
-
)
|
|
52
|
+
priority_indicator = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(priority, "⚪")
|
|
55
53
|
|
|
56
|
-
formatted_lines.append(
|
|
57
|
-
f"{status_indicator} {priority_indicator} {content} (id: {todo_id})"
|
|
58
|
-
)
|
|
54
|
+
formatted_lines.append(f"{status_indicator} {priority_indicator} {content} (id: {todo_id})")
|
|
59
55
|
|
|
60
56
|
return "\n".join(formatted_lines)
|
|
61
57
|
|
|
@@ -105,6 +101,4 @@ def get_project_todo_reminder(session_id: str | None = None) -> str:
|
|
|
105
101
|
|
|
106
102
|
# Format the todo list and return the reminder with content
|
|
107
103
|
formatted_todos = format_todo_list_concise(todos)
|
|
108
|
-
return PROJECT_TODO_REMINDER.format(
|
|
109
|
-
session_id=session_id, todo_list=formatted_todos
|
|
110
|
-
)
|
|
104
|
+
return PROJECT_TODO_REMINDER.format(session_id=session_id, todo_list=formatted_todos)
|
|
@@ -596,9 +596,7 @@ def create_tool_category_prompt(category: str, tools: list[str]):
|
|
|
596
596
|
"batch": BATCH_TOOL_EXAMPLES,
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
-
base_prompt = tool_descriptions.get(
|
|
600
|
-
category, f"# {category.title()} Tools\n\nAvailable tools in this category:\n"
|
|
601
|
-
)
|
|
599
|
+
base_prompt = tool_descriptions.get(category, f"# {category.title()} Tools\n\nAvailable tools in this category:\n")
|
|
602
600
|
|
|
603
601
|
if category not in tool_descriptions:
|
|
604
602
|
base_prompt += "\n".join(f"- **{tool}**: [Tool description]" for tool in tools)
|
hanzo_mcp/prompts/utils.py
CHANGED
|
@@ -51,9 +51,7 @@ def get_os_info() -> tuple[str, str, str]:
|
|
|
51
51
|
return system, release, version
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def get_directory_structure(
|
|
55
|
-
path: str, max_depth: int = 3, include_filtered: bool = False
|
|
56
|
-
) -> str:
|
|
54
|
+
def get_directory_structure(path: str, max_depth: int = 3, include_filtered: bool = False) -> str:
|
|
57
55
|
"""Get a directory structure similar to directory_tree tool.
|
|
58
56
|
|
|
59
57
|
Args:
|
|
@@ -102,9 +100,7 @@ def get_directory_structure(
|
|
|
102
100
|
|
|
103
101
|
try:
|
|
104
102
|
# Sort entries: directories first, then files alphabetically
|
|
105
|
-
entries = sorted(
|
|
106
|
-
current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
|
|
107
|
-
)
|
|
103
|
+
entries = sorted(current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
|
|
108
104
|
|
|
109
105
|
for entry in entries:
|
|
110
106
|
if entry.is_dir():
|
|
@@ -147,9 +143,7 @@ def get_directory_structure(
|
|
|
147
143
|
# Format based on type
|
|
148
144
|
if item["type"] == "directory":
|
|
149
145
|
if "skipped" in item:
|
|
150
|
-
lines.append(
|
|
151
|
-
f"{indent}{item['name']}/ [skipped - {item['skipped']}]"
|
|
152
|
-
)
|
|
146
|
+
lines.append(f"{indent}{item['name']}/ [skipped - {item['skipped']}]")
|
|
153
147
|
else:
|
|
154
148
|
lines.append(f"{indent}{item['name']}/")
|
|
155
149
|
# Add children with increased indentation if present
|
|
@@ -228,9 +222,7 @@ def get_git_info(path: str) -> dict[str, str | None]:
|
|
|
228
222
|
change_type = item.change_type
|
|
229
223
|
status_lines.append(f"{change_type[0].upper()} {item.a_path}")
|
|
230
224
|
if len(staged_files) > 25:
|
|
231
|
-
status_lines.append(
|
|
232
|
-
f"... and {len(staged_files) - 25} more staged files"
|
|
233
|
-
)
|
|
225
|
+
status_lines.append(f"... and {len(staged_files) - 25} more staged files")
|
|
234
226
|
|
|
235
227
|
# Check for unstaged changes
|
|
236
228
|
unstaged_files = list(repo.index.diff(None))
|
|
@@ -238,9 +230,7 @@ def get_git_info(path: str) -> dict[str, str | None]:
|
|
|
238
230
|
for item in unstaged_files[:25]: # Limit to first 25
|
|
239
231
|
status_lines.append(f"M {item.a_path}")
|
|
240
232
|
if len(unstaged_files) > 25:
|
|
241
|
-
status_lines.append(
|
|
242
|
-
f"... and {len(unstaged_files) - 25} more modified files"
|
|
243
|
-
)
|
|
233
|
+
status_lines.append(f"... and {len(unstaged_files) - 25} more modified files")
|
|
244
234
|
|
|
245
235
|
# Check for untracked files
|
|
246
236
|
untracked_files = repo.untracked_files
|
|
@@ -248,13 +238,9 @@ def get_git_info(path: str) -> dict[str, str | None]:
|
|
|
248
238
|
for file in untracked_files[:25]: # Limit to first 25
|
|
249
239
|
status_lines.append(f"?? {file}")
|
|
250
240
|
if len(untracked_files) > 25:
|
|
251
|
-
status_lines.append(
|
|
252
|
-
f"... and {len(untracked_files) - 25} more untracked files"
|
|
253
|
-
)
|
|
241
|
+
status_lines.append(f"... and {len(untracked_files) - 25} more untracked files")
|
|
254
242
|
|
|
255
|
-
git_status = (
|
|
256
|
-
"\n".join(status_lines) if status_lines else "Working tree clean"
|
|
257
|
-
)
|
|
243
|
+
git_status = "\n".join(status_lines) if status_lines else "Working tree clean"
|
|
258
244
|
|
|
259
245
|
except Exception:
|
|
260
246
|
git_status = "Unable to get git status"
|
hanzo_mcp/server.py
CHANGED
|
@@ -9,15 +9,16 @@ import threading
|
|
|
9
9
|
from typing import Literal, cast, final
|
|
10
10
|
|
|
11
11
|
# Suppress litellm deprecation warnings about event loop
|
|
12
|
-
warnings.filterwarnings(
|
|
13
|
-
"ignore", message="There is no current event loop", category=DeprecationWarning
|
|
14
|
-
)
|
|
12
|
+
warnings.filterwarnings("ignore", message="There is no current event loop", category=DeprecationWarning)
|
|
15
13
|
|
|
16
14
|
try:
|
|
17
15
|
from fastmcp import FastMCP
|
|
18
16
|
except ImportError:
|
|
19
17
|
# Fallback for older MCP versions
|
|
20
|
-
|
|
18
|
+
try:
|
|
19
|
+
from mcp.server import FastMCP
|
|
20
|
+
except ImportError:
|
|
21
|
+
from mcp import FastMCP
|
|
21
22
|
|
|
22
23
|
# Import our enhanced server
|
|
23
24
|
from hanzo_mcp.tools import register_all_tools
|
|
@@ -173,9 +174,7 @@ class HanzoMCPServer:
|
|
|
173
174
|
signal.signal(signal.SIGINT, signal_handler)
|
|
174
175
|
|
|
175
176
|
# Start background cleanup thread for periodic cleanup
|
|
176
|
-
self._cleanup_thread = threading.Thread(
|
|
177
|
-
target=self._background_cleanup, daemon=True
|
|
178
|
-
)
|
|
177
|
+
self._cleanup_thread = threading.Thread(target=self._background_cleanup, daemon=True)
|
|
179
178
|
self._cleanup_thread.start()
|
|
180
179
|
|
|
181
180
|
self._cleanup_registered = True
|
hanzo_mcp/tools/__init__.py
CHANGED
|
@@ -35,6 +35,7 @@ try: # pragma: no cover
|
|
|
35
35
|
from hanzo_mcp.tools.todo import register_todo_tools
|
|
36
36
|
from hanzo_mcp.tools.agent import register_agent_tools
|
|
37
37
|
from hanzo_mcp.tools.shell import register_shell_tools
|
|
38
|
+
from hanzo_mcp.tools.common import register_batch_tool, register_critic_tool, register_thinking_tool
|
|
38
39
|
from hanzo_mcp.tools.editor import (
|
|
39
40
|
NeovimEditTool,
|
|
40
41
|
NeovimCommandTool,
|
|
@@ -64,19 +65,19 @@ try: # pragma: no cover
|
|
|
64
65
|
register_memory_tools = None # type: ignore
|
|
65
66
|
except Exception:
|
|
66
67
|
# Minimal surface to allow submodule imports elsewhere
|
|
67
|
-
# Define
|
|
68
|
+
# Define stub functions for required imports
|
|
68
69
|
def activate_mode_from_env():
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
pass
|
|
71
|
+
|
|
71
72
|
class ModeLoader:
|
|
72
73
|
@staticmethod
|
|
73
74
|
def get_enabled_tools_from_mode(base_enabled_tools=None, force_mode=None):
|
|
74
|
-
"""Fallback: Return base tools when imports fail."""
|
|
75
75
|
return base_enabled_tools or {}
|
|
76
|
+
|
|
76
77
|
@staticmethod
|
|
77
78
|
def apply_environment_from_mode():
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
pass
|
|
80
|
+
|
|
80
81
|
|
|
81
82
|
# Try to import LSP tool
|
|
82
83
|
try:
|
|
@@ -136,9 +137,7 @@ def register_all_tools(
|
|
|
136
137
|
|
|
137
138
|
logger = logging.getLogger(__name__)
|
|
138
139
|
if plugins:
|
|
139
|
-
logger.info(
|
|
140
|
-
f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}"
|
|
141
|
-
)
|
|
140
|
+
logger.info(f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}")
|
|
142
141
|
except Exception as e:
|
|
143
142
|
import logging
|
|
144
143
|
|
|
@@ -151,9 +150,7 @@ def register_all_tools(
|
|
|
151
150
|
# First check for mode activation from environment
|
|
152
151
|
activate_mode_from_env()
|
|
153
152
|
|
|
154
|
-
tool_config = ModeLoader.get_enabled_tools_from_mode(
|
|
155
|
-
base_enabled_tools=enabled_tools, force_mode=force_mode
|
|
156
|
-
)
|
|
153
|
+
tool_config = ModeLoader.get_enabled_tools_from_mode(base_enabled_tools=enabled_tools, force_mode=force_mode)
|
|
157
154
|
# Apply mode environment variables
|
|
158
155
|
ModeLoader.apply_environment_from_mode()
|
|
159
156
|
else:
|
|
@@ -198,9 +195,7 @@ def register_all_tools(
|
|
|
198
195
|
search_paths = [str(path) for path in permission_manager.allowed_paths]
|
|
199
196
|
project_manager = ProjectVectorManager(
|
|
200
197
|
global_db_path=vector_config.get("data_path"),
|
|
201
|
-
embedding_model=vector_config.get(
|
|
202
|
-
"embedding_model", "text-embedding-3-small"
|
|
203
|
-
),
|
|
198
|
+
embedding_model=vector_config.get("embedding_model", "text-embedding-3-small"),
|
|
204
199
|
dimension=vector_config.get("dimension", 1536),
|
|
205
200
|
)
|
|
206
201
|
# Auto-detect projects from search paths
|
|
@@ -227,24 +222,31 @@ def register_all_tools(
|
|
|
227
222
|
}
|
|
228
223
|
|
|
229
224
|
if any(jupyter_enabled.values()):
|
|
230
|
-
jupyter_tools = register_jupyter_tools(
|
|
231
|
-
mcp_server, permission_manager, enabled_tools=jupyter_enabled
|
|
232
|
-
)
|
|
225
|
+
jupyter_tools = register_jupyter_tools(mcp_server, permission_manager, enabled_tools=jupyter_enabled)
|
|
233
226
|
for tool in jupyter_tools:
|
|
234
227
|
all_tools[tool.name] = tool
|
|
235
228
|
|
|
236
|
-
# Register shell tools
|
|
237
|
-
|
|
238
|
-
|
|
229
|
+
# Register shell tools with individual configuration
|
|
230
|
+
shell_enabled = {
|
|
231
|
+
"run": is_tool_enabled("run", True),
|
|
232
|
+
"shell": is_tool_enabled("shell", True),
|
|
233
|
+
"bash": is_tool_enabled("bash", True),
|
|
234
|
+
"zsh": is_tool_enabled("zsh", True),
|
|
235
|
+
"npx": is_tool_enabled("npx", True),
|
|
236
|
+
"uvx": is_tool_enabled("uvx", True),
|
|
237
|
+
"process": is_tool_enabled("process", True),
|
|
238
|
+
"open": is_tool_enabled("open", True),
|
|
239
|
+
# Legacy name support
|
|
240
|
+
"run_command": is_tool_enabled("run_command", True),
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if any(shell_enabled.values()):
|
|
244
|
+
shell_tools = register_shell_tools(mcp_server, permission_manager, enabled_tools=shell_enabled)
|
|
239
245
|
for tool in shell_tools:
|
|
240
246
|
all_tools[tool.name] = tool
|
|
241
247
|
|
|
242
248
|
# Register agent tools if enabled
|
|
243
|
-
agent_enabled = (
|
|
244
|
-
enable_agent_tool
|
|
245
|
-
or is_tool_enabled("agent", False)
|
|
246
|
-
or is_tool_enabled("dispatch_agent", False)
|
|
247
|
-
)
|
|
249
|
+
agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
|
|
248
250
|
swarm_enabled = is_tool_enabled("swarm", False)
|
|
249
251
|
|
|
250
252
|
if agent_enabled or swarm_enabled:
|
|
@@ -260,12 +262,7 @@ def register_all_tools(
|
|
|
260
262
|
)
|
|
261
263
|
# Filter based on what's enabled
|
|
262
264
|
for tool in agent_tools:
|
|
263
|
-
if
|
|
264
|
-
tool.name == "agent"
|
|
265
|
-
and agent_enabled
|
|
266
|
-
or tool.name == "swarm"
|
|
267
|
-
and swarm_enabled
|
|
268
|
-
):
|
|
265
|
+
if tool.name == "agent" and agent_enabled or tool.name == "swarm" and swarm_enabled:
|
|
269
266
|
all_tools[tool.name] = tool
|
|
270
267
|
elif tool.name in ["claude", "codex", "gemini", "grok", "code_auth"]:
|
|
271
268
|
# CLI tools and auth are always included when agent tools are enabled
|
|
@@ -108,6 +108,7 @@ def register_agent_tools(
|
|
|
108
108
|
if memory_backend is not None:
|
|
109
109
|
p["memory_backend"] = memory_backend
|
|
110
110
|
return await tool_self.call(ctx, **p)
|
|
111
|
+
|
|
111
112
|
return tool_self
|
|
112
113
|
|
|
113
114
|
# Create auth management tool
|
|
@@ -127,6 +128,6 @@ def register_agent_tools(
|
|
|
127
128
|
|
|
128
129
|
# Register all CLI tools (includes claude, codex, gemini, grok, etc.)
|
|
129
130
|
cli_tools = register_cli_tools(mcp_server, permission_manager)
|
|
130
|
-
|
|
131
|
+
|
|
131
132
|
# Return list of registered tools
|
|
132
133
|
return [agent_tool, network_tool, code_auth_tool] + cli_tools
|
hanzo_mcp/tools/agent/agent.py
CHANGED
|
@@ -118,23 +118,17 @@ class AgentParams(TypedDict, total=False):
|
|
|
118
118
|
class RPCAgent:
|
|
119
119
|
"""Long-running RPC agent."""
|
|
120
120
|
|
|
121
|
-
def __init__(
|
|
122
|
-
self, agent_id: str, model: str, system_prompt: str, tools: List[BaseTool]
|
|
123
|
-
):
|
|
121
|
+
def __init__(self, agent_id: str, model: str, system_prompt: str, tools: List[BaseTool]):
|
|
124
122
|
self.agent_id = agent_id
|
|
125
123
|
self.model = model
|
|
126
124
|
self.system_prompt = system_prompt
|
|
127
125
|
self.tools = tools
|
|
128
|
-
self.messages: List[ChatCompletionMessageParam] = [
|
|
129
|
-
{"role": "system", "content": system_prompt}
|
|
130
|
-
]
|
|
126
|
+
self.messages: List[ChatCompletionMessageParam] = [{"role": "system", "content": system_prompt}]
|
|
131
127
|
self.created_at = time.time()
|
|
132
128
|
self.last_used = time.time()
|
|
133
129
|
self.call_count = 0
|
|
134
130
|
|
|
135
|
-
async def call_method(
|
|
136
|
-
self, method: str, args: Dict[str, Any], tool_ctx: ToolContext
|
|
137
|
-
) -> str:
|
|
131
|
+
async def call_method(self, method: str, args: Dict[str, Any], tool_ctx: ToolContext) -> str:
|
|
138
132
|
"""Call a method on the RPC agent."""
|
|
139
133
|
self.last_used = time.time()
|
|
140
134
|
self.call_count += 1
|
|
@@ -189,15 +183,9 @@ class AgentTool(BaseTool):
|
|
|
189
183
|
|
|
190
184
|
# Available tools
|
|
191
185
|
self.available_tools: list[BaseTool] = []
|
|
192
|
-
self.available_tools.extend(
|
|
193
|
-
|
|
194
|
-
)
|
|
195
|
-
self.available_tools.extend(
|
|
196
|
-
get_read_only_jupyter_tools(self.permission_manager)
|
|
197
|
-
)
|
|
198
|
-
self.available_tools.append(
|
|
199
|
-
BatchTool({t.name: t for t in self.available_tools})
|
|
200
|
-
)
|
|
186
|
+
self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
|
|
187
|
+
self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
|
|
188
|
+
self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
|
|
201
189
|
|
|
202
190
|
@property
|
|
203
191
|
@override
|
|
@@ -272,14 +260,10 @@ Modes:
|
|
|
272
260
|
|
|
273
261
|
if len(prompt_list) == 1:
|
|
274
262
|
await tool_ctx.info("Launching agent")
|
|
275
|
-
result = await self._execute_agent(
|
|
276
|
-
prompt_list[0], params.get("model"), tool_ctx
|
|
277
|
-
)
|
|
263
|
+
result = await self._execute_agent(prompt_list[0], params.get("model"), tool_ctx)
|
|
278
264
|
else:
|
|
279
265
|
await tool_ctx.info(f"Launching {len(prompt_list)} agents in parallel")
|
|
280
|
-
result = await self._execute_multiple_agents(
|
|
281
|
-
prompt_list, params.get("model"), tool_ctx
|
|
282
|
-
)
|
|
266
|
+
result = await self._execute_multiple_agents(prompt_list, params.get("model"), tool_ctx)
|
|
283
267
|
|
|
284
268
|
execution_time = time.time() - start_time
|
|
285
269
|
|
|
@@ -389,17 +373,13 @@ Use 'agent --action call --agent-id {agent_id} --method <method> --args <args>'
|
|
|
389
373
|
absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
|
|
390
374
|
return bool(re.search(absolute_path_pattern, prompt))
|
|
391
375
|
|
|
392
|
-
async def _execute_agent(
|
|
393
|
-
self, prompt: str, model: Optional[str], tool_ctx: ToolContext
|
|
394
|
-
) -> str:
|
|
376
|
+
async def _execute_agent(self, prompt: str, model: Optional[str], tool_ctx: ToolContext) -> str:
|
|
395
377
|
"""Execute a single agent (simplified - would use full logic from agent_tool.py)."""
|
|
396
378
|
# This would integrate the full agent execution logic from agent_tool.py
|
|
397
379
|
# For now, return a placeholder
|
|
398
380
|
return f"Executed agent with prompt: {prompt[:100]}..."
|
|
399
381
|
|
|
400
|
-
async def _execute_multiple_agents(
|
|
401
|
-
self, prompts: List[str], model: Optional[str], tool_ctx: ToolContext
|
|
402
|
-
) -> str:
|
|
382
|
+
async def _execute_multiple_agents(self, prompts: List[str], model: Optional[str], tool_ctx: ToolContext) -> str:
|
|
403
383
|
"""Execute multiple agents in parallel."""
|
|
404
384
|
tasks = []
|
|
405
385
|
for prompt in prompts:
|
|
@@ -49,8 +49,7 @@ except ImportError:
|
|
|
49
49
|
"""Stub State class when hanzo-agents is not available."""
|
|
50
50
|
|
|
51
51
|
def __init__(self):
|
|
52
|
-
|
|
53
|
-
self.data = {}
|
|
52
|
+
pass
|
|
54
53
|
|
|
55
54
|
def to_dict(self):
|
|
56
55
|
return {}
|
|
@@ -104,6 +103,23 @@ from hanzo_mcp.tools.agent.clarification_protocol import (
|
|
|
104
103
|
|
|
105
104
|
|
|
106
105
|
class AgentToolParams(TypedDict, total=False):
|
|
106
|
+
|
|
107
|
+
def __init__(self, *args, mode: str = "single", **kwargs):
|
|
108
|
+
"""Initialize agent tool with mode support.
|
|
109
|
+
|
|
110
|
+
Modes:
|
|
111
|
+
- single: Single agent execution (default)
|
|
112
|
+
- network: Network of agents (formerly swarm)
|
|
113
|
+
- dispatch: Dispatch to best agent (legacy compatibility)
|
|
114
|
+
"""
|
|
115
|
+
super().__init__(*args, **kwargs)
|
|
116
|
+
self.mode = mode
|
|
117
|
+
|
|
118
|
+
# Handle legacy names
|
|
119
|
+
if self.name == "dispatch_agent":
|
|
120
|
+
self.mode = "dispatch"
|
|
121
|
+
elif self.name in ["network", "swarm"]:
|
|
122
|
+
self.mode = "network"
|
|
107
123
|
"""Parameters for the AgentTool."""
|
|
108
124
|
|
|
109
125
|
prompts: str | list[str]
|
|
@@ -198,9 +214,7 @@ class MCPAgent(Agent):
|
|
|
198
214
|
adapter = MCPToolAdapter(mcp_tool, ctx)
|
|
199
215
|
self.register_tool(adapter)
|
|
200
216
|
|
|
201
|
-
async def run(
|
|
202
|
-
self, state: MCPAgentState, history: History, network: Network
|
|
203
|
-
) -> InferenceResult:
|
|
217
|
+
async def run(self, state: MCPAgentState, history: History, network: Network) -> InferenceResult:
|
|
204
218
|
"""Execute the agent."""
|
|
205
219
|
# Get current prompt
|
|
206
220
|
if state.current_prompt_index >= len(state.prompts):
|
|
@@ -325,12 +339,8 @@ Usage notes:
|
|
|
325
339
|
|
|
326
340
|
# Set up available tools
|
|
327
341
|
self.available_tools: list[BaseTool] = []
|
|
328
|
-
self.available_tools.extend(
|
|
329
|
-
|
|
330
|
-
)
|
|
331
|
-
self.available_tools.extend(
|
|
332
|
-
get_read_only_jupyter_tools(self.permission_manager)
|
|
333
|
-
)
|
|
342
|
+
self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
|
|
343
|
+
self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
|
|
334
344
|
|
|
335
345
|
# Add edit tools
|
|
336
346
|
self.available_tools.append(Edit(self.permission_manager))
|
|
@@ -342,9 +352,7 @@ Usage notes:
|
|
|
342
352
|
self.available_tools.append(ReviewTool())
|
|
343
353
|
self.available_tools.append(IChingTool())
|
|
344
354
|
|
|
345
|
-
self.available_tools.append(
|
|
346
|
-
BatchTool({t.name: t for t in self.available_tools})
|
|
347
|
-
)
|
|
355
|
+
self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
|
|
348
356
|
|
|
349
357
|
@override
|
|
350
358
|
async def call(
|
|
@@ -400,9 +408,7 @@ Usage notes:
|
|
|
400
408
|
times = (concurrency + len(prompt_list) - 1) // len(prompt_list)
|
|
401
409
|
prompt_list = (prompt_list * times)[:concurrency]
|
|
402
410
|
|
|
403
|
-
await tool_ctx.info(
|
|
404
|
-
f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK"
|
|
405
|
-
)
|
|
411
|
+
await tool_ctx.info(f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK")
|
|
406
412
|
|
|
407
413
|
# Determine model and agent type
|
|
408
414
|
model = params.get("model", self.model_override)
|
|
@@ -96,9 +96,7 @@ class ClaudeDesktopAuth:
|
|
|
96
96
|
except Exception:
|
|
97
97
|
return None
|
|
98
98
|
|
|
99
|
-
async def login_interactive(
|
|
100
|
-
self, account: Optional[str] = None, headless: bool = False
|
|
101
|
-
) -> Tuple[bool, str]:
|
|
99
|
+
async def login_interactive(self, account: Optional[str] = None, headless: bool = False) -> Tuple[bool, str]:
|
|
102
100
|
"""Login to Claude Desktop interactively.
|
|
103
101
|
|
|
104
102
|
Args:
|
|
@@ -192,9 +190,7 @@ class ClaudeDesktopAuth:
|
|
|
192
190
|
# For now, return a placeholder
|
|
193
191
|
return False, "Headless login not yet implemented"
|
|
194
192
|
|
|
195
|
-
async def _exchange_code_for_session(
|
|
196
|
-
self, code: str, account: Optional[str]
|
|
197
|
-
) -> bool:
|
|
193
|
+
async def _exchange_code_for_session(self, code: str, account: Optional[str]) -> bool:
|
|
198
194
|
"""Exchange auth code for session token."""
|
|
199
195
|
# This would make API calls to exchange the code
|
|
200
196
|
# For now, create a mock session
|
|
@@ -357,9 +353,7 @@ class ClaudeDesktopAuth:
|
|
|
357
353
|
# Generate agent-specific account
|
|
358
354
|
return f"agent_{agent_id}@claude.local"
|
|
359
355
|
|
|
360
|
-
async def ensure_agent_auth(
|
|
361
|
-
self, agent_id: str, force_new: bool = False
|
|
362
|
-
) -> Tuple[bool, str]:
|
|
356
|
+
async def ensure_agent_auth(self, agent_id: str, force_new: bool = False) -> Tuple[bool, str]:
|
|
363
357
|
"""Ensure an agent is authenticated with its own account.
|
|
364
358
|
|
|
365
359
|
Args:
|
|
@@ -97,9 +97,7 @@ class CLIAgentBase(BaseTool):
|
|
|
97
97
|
|
|
98
98
|
# Check if installed
|
|
99
99
|
if not self.is_installed():
|
|
100
|
-
error_msg = (
|
|
101
|
-
f"{self.provider_name} CLI ({self.command_name}) is not installed. "
|
|
102
|
-
)
|
|
100
|
+
error_msg = f"{self.provider_name} CLI ({self.command_name}) is not installed. "
|
|
103
101
|
error_msg += f"Please install it first: https://github.com/anthropics/{self.command_name}"
|
|
104
102
|
await tool_ctx.error(error_msg)
|
|
105
103
|
return f"Error: {error_msg}"
|
|
@@ -115,15 +113,11 @@ class CLIAgentBase(BaseTool):
|
|
|
115
113
|
cli_args = self.get_cli_args(prompt, **kwargs)
|
|
116
114
|
|
|
117
115
|
# Log command
|
|
118
|
-
await tool_ctx.info(
|
|
119
|
-
f"Executing {self.provider_name}: {self.command_name} {' '.join(cli_args[:3])}..."
|
|
120
|
-
)
|
|
116
|
+
await tool_ctx.info(f"Executing {self.provider_name}: {self.command_name} {' '.join(cli_args[:3])}...")
|
|
121
117
|
|
|
122
118
|
try:
|
|
123
119
|
# Create temp file for prompt if needed
|
|
124
|
-
with tempfile.NamedTemporaryFile(
|
|
125
|
-
mode="w", suffix=".txt", delete=False
|
|
126
|
-
) as f:
|
|
120
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
127
121
|
f.write(prompt)
|
|
128
122
|
prompt_file = f.name
|
|
129
123
|
|
|
@@ -131,12 +125,7 @@ class CLIAgentBase(BaseTool):
|
|
|
131
125
|
if "--prompt-file" in cli_args:
|
|
132
126
|
# Replace placeholder with actual file
|
|
133
127
|
cli_args = [
|
|
134
|
-
(
|
|
135
|
-
arg.replace("--prompt-file", prompt_file)
|
|
136
|
-
if arg == "--prompt-file"
|
|
137
|
-
else arg
|
|
138
|
-
)
|
|
139
|
-
for arg in cli_args
|
|
128
|
+
(arg.replace("--prompt-file", prompt_file) if arg == "--prompt-file" else arg) for arg in cli_args
|
|
140
129
|
]
|
|
141
130
|
|
|
142
131
|
# Execute command
|
|
@@ -151,13 +140,9 @@ class CLIAgentBase(BaseTool):
|
|
|
151
140
|
|
|
152
141
|
# Send prompt via stdin if not using file
|
|
153
142
|
if "--prompt-file" not in cli_args:
|
|
154
|
-
stdout, stderr = await asyncio.wait_for(
|
|
155
|
-
process.communicate(input=prompt.encode()), timeout=timeout
|
|
156
|
-
)
|
|
143
|
+
stdout, stderr = await asyncio.wait_for(process.communicate(input=prompt.encode()), timeout=timeout)
|
|
157
144
|
else:
|
|
158
|
-
stdout, stderr = await asyncio.wait_for(
|
|
159
|
-
process.communicate(), timeout=timeout
|
|
160
|
-
)
|
|
145
|
+
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
161
146
|
|
|
162
147
|
# Clean up temp file
|
|
163
148
|
try:
|
|
@@ -175,9 +160,7 @@ class CLIAgentBase(BaseTool):
|
|
|
175
160
|
return result
|
|
176
161
|
|
|
177
162
|
except asyncio.TimeoutError:
|
|
178
|
-
await tool_ctx.error(
|
|
179
|
-
f"{self.provider_name} timed out after {timeout} seconds"
|
|
180
|
-
)
|
|
163
|
+
await tool_ctx.error(f"{self.provider_name} timed out after {timeout} seconds")
|
|
181
164
|
return f"Error: Command timed out after {timeout} seconds"
|
|
182
165
|
except Exception as e:
|
|
183
166
|
await tool_ctx.error(f"{self.provider_name} error: {str(e)}")
|