hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.14__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 +2 -4
- hanzo_mcp/analytics/posthog_analytics.py +3 -9
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +6 -15
- 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 +1 -3
- 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 +13 -6
- hanzo_mcp/tools/__init__.py +10 -24
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +5 -15
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
- 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 +75 -74
- 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/swarm_tool.py +9 -29
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
- 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/permissions.py +38 -11
- hanzo_mcp/tools/common/personality.py +9 -34
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +6 -18
- 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/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_edit.py +2 -2
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/filesystem/__init__.py +2 -3
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/base.py +4 -12
- hanzo_mcp/tools/filesystem/batch_search.py +35 -115
- hanzo_mcp/tools/filesystem/content_replace.py +4 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
- hanzo_mcp/tools/filesystem/edit.py +6 -18
- hanzo_mcp/tools/filesystem/find.py +3 -9
- hanzo_mcp/tools/filesystem/find_files.py +2 -6
- hanzo_mcp/tools/filesystem/git_search.py +9 -24
- hanzo_mcp/tools/filesystem/grep.py +9 -27
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
- hanzo_mcp/tools/filesystem/read.py +8 -26
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
- hanzo_mcp/tools/filesystem/search_tool.py +18 -62
- hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
- hanzo_mcp/tools/filesystem/tree.py +1 -3
- hanzo_mcp/tools/filesystem/watch.py +1 -3
- hanzo_mcp/tools/filesystem/write.py +1 -3
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
- hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
- 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 +5 -17
- hanzo_mcp/tools/mcp/mcp_add.py +1 -3
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +10 -27
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +6 -18
- hanzo_mcp/tools/search/find_tool.py +10 -32
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +2 -2
- 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 +33 -86
- 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/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/todo/todo_read.py +3 -9
- hanzo_mcp/tools/todo/todo_write.py +6 -18
- hanzo_mcp/tools/vector/__init__.py +3 -9
- 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 +7 -27
- hanzo_mcp/tools/vector/mock_infinity.py +1 -3
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- 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.11.dist-info → hanzo_mcp-0.8.14.dist-info}/METADATA +2 -2
- hanzo_mcp-0.8.14.dist-info/RECORD +193 -0
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.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
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import atexit
|
|
5
5
|
import signal
|
|
6
|
+
import secrets
|
|
6
7
|
import logging
|
|
7
8
|
import warnings
|
|
8
9
|
import threading
|
|
9
10
|
from typing import Literal, cast, final
|
|
10
11
|
|
|
11
12
|
# Suppress litellm deprecation warnings about event loop
|
|
12
|
-
warnings.filterwarnings(
|
|
13
|
-
"ignore", message="There is no current event loop", category=DeprecationWarning
|
|
14
|
-
)
|
|
13
|
+
warnings.filterwarnings("ignore", message="There is no current event loop", category=DeprecationWarning)
|
|
15
14
|
|
|
16
15
|
try:
|
|
17
16
|
from fastmcp import FastMCP
|
|
@@ -55,6 +54,7 @@ class HanzoMCPServer:
|
|
|
55
54
|
port: int = 8888,
|
|
56
55
|
enabled_tools: dict[str, bool] | None = None,
|
|
57
56
|
disabled_tools: list[str] | None = None,
|
|
57
|
+
auth_token: str | None = None,
|
|
58
58
|
):
|
|
59
59
|
"""Initialize the Hanzo AI server.
|
|
60
60
|
|
|
@@ -82,6 +82,15 @@ class HanzoMCPServer:
|
|
|
82
82
|
# Use enhanced server for automatic context normalization
|
|
83
83
|
self.mcp = mcp_instance if mcp_instance is not None else EnhancedFastMCP(name)
|
|
84
84
|
|
|
85
|
+
# Initialize authentication token
|
|
86
|
+
self.auth_token = auth_token or os.environ.get('HANZO_MCP_TOKEN')
|
|
87
|
+
if not self.auth_token:
|
|
88
|
+
# Generate a secure random token if none provided
|
|
89
|
+
self.auth_token = secrets.token_urlsafe(32)
|
|
90
|
+
logger = logging.getLogger(__name__)
|
|
91
|
+
logger.warning(f"No auth token provided. Generated token: {self.auth_token}")
|
|
92
|
+
logger.warning("Set HANZO_MCP_TOKEN environment variable for persistent auth")
|
|
93
|
+
|
|
85
94
|
# Initialize permissions and command executor
|
|
86
95
|
self.permission_manager = PermissionManager()
|
|
87
96
|
|
|
@@ -176,9 +185,7 @@ class HanzoMCPServer:
|
|
|
176
185
|
signal.signal(signal.SIGINT, signal_handler)
|
|
177
186
|
|
|
178
187
|
# Start background cleanup thread for periodic cleanup
|
|
179
|
-
self._cleanup_thread = threading.Thread(
|
|
180
|
-
target=self._background_cleanup, daemon=True
|
|
181
|
-
)
|
|
188
|
+
self._cleanup_thread = threading.Thread(target=self._background_cleanup, daemon=True)
|
|
182
189
|
self._cleanup_thread.start()
|
|
183
190
|
|
|
184
191
|
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,
|
|
@@ -48,7 +49,6 @@ try: # pragma: no cover
|
|
|
48
49
|
from hanzo_mcp.tools.common.mode import activate_mode_from_env
|
|
49
50
|
from hanzo_mcp.tools.common.stats import StatsTool
|
|
50
51
|
from hanzo_mcp.tools.common.tool_list import ToolListTool
|
|
51
|
-
from hanzo_mcp.tools.common import register_thinking_tool, register_critic_tool, register_batch_tool
|
|
52
52
|
from hanzo_mcp.tools.config.mode_tool import mode_tool
|
|
53
53
|
from hanzo_mcp.tools.common.mode_loader import ModeLoader
|
|
54
54
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
@@ -68,14 +68,17 @@ except Exception:
|
|
|
68
68
|
# Define stub functions for required imports
|
|
69
69
|
def activate_mode_from_env():
|
|
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
75
|
return base_enabled_tools or {}
|
|
76
|
+
|
|
75
77
|
@staticmethod
|
|
76
78
|
def apply_environment_from_mode():
|
|
77
79
|
pass
|
|
78
80
|
|
|
81
|
+
|
|
79
82
|
# Try to import LSP tool
|
|
80
83
|
try:
|
|
81
84
|
from hanzo_mcp.tools.lsp import LSPTool, create_lsp_tool
|
|
@@ -134,9 +137,7 @@ def register_all_tools(
|
|
|
134
137
|
|
|
135
138
|
logger = logging.getLogger(__name__)
|
|
136
139
|
if plugins:
|
|
137
|
-
logger.info(
|
|
138
|
-
f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}"
|
|
139
|
-
)
|
|
140
|
+
logger.info(f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}")
|
|
140
141
|
except Exception as e:
|
|
141
142
|
import logging
|
|
142
143
|
|
|
@@ -149,9 +150,7 @@ def register_all_tools(
|
|
|
149
150
|
# First check for mode activation from environment
|
|
150
151
|
activate_mode_from_env()
|
|
151
152
|
|
|
152
|
-
tool_config = ModeLoader.get_enabled_tools_from_mode(
|
|
153
|
-
base_enabled_tools=enabled_tools, force_mode=force_mode
|
|
154
|
-
)
|
|
153
|
+
tool_config = ModeLoader.get_enabled_tools_from_mode(base_enabled_tools=enabled_tools, force_mode=force_mode)
|
|
155
154
|
# Apply mode environment variables
|
|
156
155
|
ModeLoader.apply_environment_from_mode()
|
|
157
156
|
else:
|
|
@@ -196,9 +195,7 @@ def register_all_tools(
|
|
|
196
195
|
search_paths = [str(path) for path in permission_manager.allowed_paths]
|
|
197
196
|
project_manager = ProjectVectorManager(
|
|
198
197
|
global_db_path=vector_config.get("data_path"),
|
|
199
|
-
embedding_model=vector_config.get(
|
|
200
|
-
"embedding_model", "text-embedding-3-small"
|
|
201
|
-
),
|
|
198
|
+
embedding_model=vector_config.get("embedding_model", "text-embedding-3-small"),
|
|
202
199
|
dimension=vector_config.get("dimension", 1536),
|
|
203
200
|
)
|
|
204
201
|
# Auto-detect projects from search paths
|
|
@@ -225,9 +222,7 @@ def register_all_tools(
|
|
|
225
222
|
}
|
|
226
223
|
|
|
227
224
|
if any(jupyter_enabled.values()):
|
|
228
|
-
jupyter_tools = register_jupyter_tools(
|
|
229
|
-
mcp_server, permission_manager, enabled_tools=jupyter_enabled
|
|
230
|
-
)
|
|
225
|
+
jupyter_tools = register_jupyter_tools(mcp_server, permission_manager, enabled_tools=jupyter_enabled)
|
|
231
226
|
for tool in jupyter_tools:
|
|
232
227
|
all_tools[tool.name] = tool
|
|
233
228
|
|
|
@@ -238,11 +233,7 @@ def register_all_tools(
|
|
|
238
233
|
all_tools[tool.name] = tool
|
|
239
234
|
|
|
240
235
|
# Register agent tools if enabled
|
|
241
|
-
agent_enabled = (
|
|
242
|
-
enable_agent_tool
|
|
243
|
-
or is_tool_enabled("agent", False)
|
|
244
|
-
or is_tool_enabled("dispatch_agent", False)
|
|
245
|
-
)
|
|
236
|
+
agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
|
|
246
237
|
swarm_enabled = is_tool_enabled("swarm", False)
|
|
247
238
|
|
|
248
239
|
if agent_enabled or swarm_enabled:
|
|
@@ -258,12 +249,7 @@ def register_all_tools(
|
|
|
258
249
|
)
|
|
259
250
|
# Filter based on what's enabled
|
|
260
251
|
for tool in agent_tools:
|
|
261
|
-
if
|
|
262
|
-
tool.name == "agent"
|
|
263
|
-
and agent_enabled
|
|
264
|
-
or tool.name == "swarm"
|
|
265
|
-
and swarm_enabled
|
|
266
|
-
):
|
|
252
|
+
if tool.name == "agent" and agent_enabled or tool.name == "swarm" and swarm_enabled:
|
|
267
253
|
all_tools[tool.name] = tool
|
|
268
254
|
elif tool.name in ["claude", "codex", "gemini", "grok", "code_auth"]:
|
|
269
255
|
# 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:
|
|
@@ -197,9 +197,7 @@ class MCPAgent(Agent):
|
|
|
197
197
|
adapter = MCPToolAdapter(mcp_tool, ctx)
|
|
198
198
|
self.register_tool(adapter)
|
|
199
199
|
|
|
200
|
-
async def run(
|
|
201
|
-
self, state: MCPAgentState, history: History, network: Network
|
|
202
|
-
) -> InferenceResult:
|
|
200
|
+
async def run(self, state: MCPAgentState, history: History, network: Network) -> InferenceResult:
|
|
203
201
|
"""Execute the agent."""
|
|
204
202
|
# Get current prompt
|
|
205
203
|
if state.current_prompt_index >= len(state.prompts):
|
|
@@ -324,12 +322,8 @@ Usage notes:
|
|
|
324
322
|
|
|
325
323
|
# Set up available tools
|
|
326
324
|
self.available_tools: list[BaseTool] = []
|
|
327
|
-
self.available_tools.extend(
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
self.available_tools.extend(
|
|
331
|
-
get_read_only_jupyter_tools(self.permission_manager)
|
|
332
|
-
)
|
|
325
|
+
self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
|
|
326
|
+
self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
|
|
333
327
|
|
|
334
328
|
# Add edit tools
|
|
335
329
|
self.available_tools.append(Edit(self.permission_manager))
|
|
@@ -341,9 +335,7 @@ Usage notes:
|
|
|
341
335
|
self.available_tools.append(ReviewTool())
|
|
342
336
|
self.available_tools.append(IChingTool())
|
|
343
337
|
|
|
344
|
-
self.available_tools.append(
|
|
345
|
-
BatchTool({t.name: t for t in self.available_tools})
|
|
346
|
-
)
|
|
338
|
+
self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
|
|
347
339
|
|
|
348
340
|
@override
|
|
349
341
|
async def call(
|
|
@@ -399,9 +391,7 @@ Usage notes:
|
|
|
399
391
|
times = (concurrency + len(prompt_list) - 1) // len(prompt_list)
|
|
400
392
|
prompt_list = (prompt_list * times)[:concurrency]
|
|
401
393
|
|
|
402
|
-
await tool_ctx.info(
|
|
403
|
-
f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK"
|
|
404
|
-
)
|
|
394
|
+
await tool_ctx.info(f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK")
|
|
405
395
|
|
|
406
396
|
# Determine model and agent type
|
|
407
397
|
model = params.get("model", self.model_override)
|
|
@@ -151,12 +151,8 @@ Usage notes:
|
|
|
151
151
|
self.max_iterations = max_iterations
|
|
152
152
|
self.max_tool_uses = max_tool_uses
|
|
153
153
|
self.available_tools: list[BaseTool] = []
|
|
154
|
-
self.available_tools.extend(
|
|
155
|
-
|
|
156
|
-
)
|
|
157
|
-
self.available_tools.extend(
|
|
158
|
-
get_read_only_jupyter_tools(self.permission_manager)
|
|
159
|
-
)
|
|
154
|
+
self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
|
|
155
|
+
self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
|
|
160
156
|
|
|
161
157
|
# Always add edit tools - agents should have edit access
|
|
162
158
|
self.available_tools.append(Edit(self.permission_manager))
|
|
@@ -174,9 +170,7 @@ Usage notes:
|
|
|
174
170
|
# Add I Ching tool for creative guidance
|
|
175
171
|
self.available_tools.append(IChingTool())
|
|
176
172
|
|
|
177
|
-
self.available_tools.append(
|
|
178
|
-
BatchTool({t.name: t for t in self.available_tools})
|
|
179
|
-
)
|
|
173
|
+
self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
|
|
180
174
|
|
|
181
175
|
# Initialize protocols
|
|
182
176
|
self.critic_protocol = CriticProtocol()
|
|
@@ -239,9 +233,7 @@ Example of correct usage:
|
|
|
239
233
|
absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
|
|
240
234
|
for prompt in prompt_list:
|
|
241
235
|
if not re.search(absolute_path_pattern, prompt):
|
|
242
|
-
await tool_ctx.error(
|
|
243
|
-
f"Prompt does not contain absolute path: {prompt[:50]}..."
|
|
244
|
-
)
|
|
236
|
+
await tool_ctx.error(f"Prompt does not contain absolute path: {prompt[:50]}...")
|
|
245
237
|
return """Error: All prompts must contain at least one absolute path.
|
|
246
238
|
|
|
247
239
|
IMPORTANT REMINDER FOR CLAUDE:
|
|
@@ -271,9 +263,7 @@ AGENT RESPONSE:
|
|
|
271
263
|
|
|
272
264
|
AGENT RESPONSES:
|
|
273
265
|
{result}"""
|
|
274
|
-
await tool_ctx.info(
|
|
275
|
-
f"Multi-agent execution completed in {execution_time:.2f}s"
|
|
276
|
-
)
|
|
266
|
+
await tool_ctx.info(f"Multi-agent execution completed in {execution_time:.2f}s")
|
|
277
267
|
return formatted_result
|
|
278
268
|
|
|
279
269
|
async def _execute_agent(self, prompt: str, tool_ctx: ToolContext) -> str:
|
|
@@ -310,9 +300,7 @@ AGENT RESPONSES:
|
|
|
310
300
|
|
|
311
301
|
# Execute agent
|
|
312
302
|
await tool_ctx.info(f"Executing agent task: {prompt[:50]}...")
|
|
313
|
-
result = await self._execute_agent_with_tools(
|
|
314
|
-
system_prompt, prompt, agent_tools, openai_tools, tool_ctx
|
|
315
|
-
)
|
|
303
|
+
result = await self._execute_agent_with_tools(system_prompt, prompt, agent_tools, openai_tools, tool_ctx)
|
|
316
304
|
except Exception as e:
|
|
317
305
|
# Log and return error result
|
|
318
306
|
error_message = f"Error executing agent: {str(e)}"
|
|
@@ -321,9 +309,7 @@ AGENT RESPONSES:
|
|
|
321
309
|
|
|
322
310
|
return result if result else "No results returned from agent"
|
|
323
311
|
|
|
324
|
-
async def _execute_multiple_agents(
|
|
325
|
-
self, prompts: list[str], tool_ctx: ToolContext
|
|
326
|
-
) -> str:
|
|
312
|
+
async def _execute_multiple_agents(self, prompts: list[str], tool_ctx: ToolContext) -> str:
|
|
327
313
|
"""Execute multiple agents concurrently.
|
|
328
314
|
|
|
329
315
|
Args:
|
|
@@ -352,9 +338,7 @@ AGENT RESPONSES:
|
|
|
352
338
|
tasks = []
|
|
353
339
|
for i, prompt in enumerate(prompts):
|
|
354
340
|
await tool_ctx.info(f"Creating agent task {i + 1}: {prompt[:50]}...")
|
|
355
|
-
task = self._execute_agent_with_tools(
|
|
356
|
-
system_prompt, prompt, agent_tools, openai_tools, tool_ctx
|
|
357
|
-
)
|
|
341
|
+
task = self._execute_agent_with_tools(system_prompt, prompt, agent_tools, openai_tools, tool_ctx)
|
|
358
342
|
tasks.append(task)
|
|
359
343
|
|
|
360
344
|
# Execute all agents concurrently
|
|
@@ -413,9 +397,7 @@ AGENT RESPONSES:
|
|
|
413
397
|
total_tool_use_count = 0
|
|
414
398
|
iteration_count = 0
|
|
415
399
|
max_tool_uses = self.max_tool_uses # Safety limit to prevent infinite loops
|
|
416
|
-
max_iterations =
|
|
417
|
-
self.max_iterations
|
|
418
|
-
) # Add a maximum number of iterations for safety
|
|
400
|
+
max_iterations = self.max_iterations # Add a maximum number of iterations for safety
|
|
419
401
|
|
|
420
402
|
# Execute until the agent completes or reaches the limit
|
|
421
403
|
while total_tool_use_count < max_tool_uses and iteration_count < max_iterations:
|
|
@@ -480,9 +462,7 @@ AGENT RESPONSES:
|
|
|
480
462
|
function_args = {}
|
|
481
463
|
|
|
482
464
|
# Find the matching tool
|
|
483
|
-
tool = next(
|
|
484
|
-
(t for t in available_tools if t.name == function_name), None
|
|
485
|
-
)
|
|
465
|
+
tool = next((t for t in available_tools if t.name == function_name), None)
|
|
486
466
|
if not tool:
|
|
487
467
|
tool_result = f"Error: Tool '{function_name}' not found"
|
|
488
468
|
# Special handling for clarification requests
|
|
@@ -505,9 +485,7 @@ AGENT RESPONSES:
|
|
|
505
485
|
options=options,
|
|
506
486
|
)
|
|
507
487
|
|
|
508
|
-
tool_result = self.format_clarification_in_output(
|
|
509
|
-
question, answer
|
|
510
|
-
)
|
|
488
|
+
tool_result = self.format_clarification_in_output(question, answer)
|
|
511
489
|
except Exception as e:
|
|
512
490
|
tool_result = f"Error processing clarification: {str(e)}"
|
|
513
491
|
# Special handling for critic requests
|
|
@@ -552,9 +530,7 @@ AGENT RESPONSES:
|
|
|
552
530
|
tool_result = f"Error processing review: {str(e)}"
|
|
553
531
|
else:
|
|
554
532
|
try:
|
|
555
|
-
tool_result = await tool.call(
|
|
556
|
-
ctx=tool_ctx.mcp_context, **function_args
|
|
557
|
-
)
|
|
533
|
+
tool_result = await tool.call(ctx=tool_ctx.mcp_context, **function_args)
|
|
558
534
|
except Exception as e:
|
|
559
535
|
tool_result = f"Error executing {function_name}: {str(e)}"
|
|
560
536
|
|
|
@@ -572,9 +548,7 @@ AGENT RESPONSES:
|
|
|
572
548
|
)
|
|
573
549
|
|
|
574
550
|
# Log progress
|
|
575
|
-
await tool_ctx.info(
|
|
576
|
-
f"Processed {len(message.tool_calls)} tool calls. Total: {total_tool_use_count}"
|
|
577
|
-
)
|
|
551
|
+
await tool_ctx.info(f"Processed {len(message.tool_calls)} tool calls. Total: {total_tool_use_count}")
|
|
578
552
|
|
|
579
553
|
except Exception as e:
|
|
580
554
|
await tool_ctx.error(f"Error in model call: {str(e)}")
|
|
@@ -602,8 +576,7 @@ AGENT RESPONSES:
|
|
|
602
576
|
)
|
|
603
577
|
|
|
604
578
|
return (
|
|
605
|
-
final_response.choices[0].message.content
|
|
606
|
-
or "Agent reached max iteration limit without a response."
|
|
579
|
+
final_response.choices[0].message.content or "Agent reached max iteration limit without a response."
|
|
607
580
|
) # pyright: ignore
|
|
608
581
|
except Exception as e:
|
|
609
582
|
await tool_ctx.error(f"Error in final model call: {str(e)}")
|
|
@@ -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:
|