hanzo-mcp 0.8.11__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.

Files changed (166) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +2 -6
  17. hanzo_mcp/tools/__init__.py +26 -27
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +22 -15
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  22. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  23. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  24. hanzo_mcp/tools/agent/code_auth.py +1 -3
  25. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  26. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  27. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  28. hanzo_mcp/tools/agent/network_tool.py +7 -18
  29. hanzo_mcp/tools/agent/prompt.py +1 -5
  30. hanzo_mcp/tools/agent/review_tool.py +10 -25
  31. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  32. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  33. hanzo_mcp/tools/common/batch_tool.py +15 -45
  34. hanzo_mcp/tools/common/config_tool.py +9 -28
  35. hanzo_mcp/tools/common/context.py +1 -3
  36. hanzo_mcp/tools/common/critic_tool.py +1 -3
  37. hanzo_mcp/tools/common/decorators.py +2 -6
  38. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  39. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  40. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  41. hanzo_mcp/tools/common/mode.py +1 -5
  42. hanzo_mcp/tools/common/paginated_base.py +3 -11
  43. hanzo_mcp/tools/common/paginated_response.py +10 -30
  44. hanzo_mcp/tools/common/pagination.py +3 -9
  45. hanzo_mcp/tools/common/path_utils.py +34 -0
  46. hanzo_mcp/tools/common/permissions.py +14 -13
  47. hanzo_mcp/tools/common/personality.py +983 -701
  48. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  49. hanzo_mcp/tools/common/stats.py +6 -18
  50. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  51. hanzo_mcp/tools/common/tool_disable.py +2 -6
  52. hanzo_mcp/tools/common/tool_list.py +2 -6
  53. hanzo_mcp/tools/common/validation.py +1 -3
  54. hanzo_mcp/tools/compiler/__init__.py +8 -0
  55. hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  72. hanzo_mcp/tools/environment/__init__.py +8 -0
  73. hanzo_mcp/tools/environment/environment_detector.py +594 -0
  74. hanzo_mcp/tools/filesystem/__init__.py +28 -26
  75. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  76. hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
  77. hanzo_mcp/tools/filesystem/base.py +20 -12
  78. hanzo_mcp/tools/filesystem/content_replace.py +7 -12
  79. hanzo_mcp/tools/filesystem/diff.py +2 -10
  80. hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
  81. hanzo_mcp/tools/filesystem/edit.py +10 -18
  82. hanzo_mcp/tools/filesystem/find.py +312 -179
  83. hanzo_mcp/tools/filesystem/git_search.py +12 -24
  84. hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
  85. hanzo_mcp/tools/filesystem/read.py +14 -30
  86. hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
  87. hanzo_mcp/tools/filesystem/search.py +1160 -0
  88. hanzo_mcp/tools/filesystem/watch.py +2 -4
  89. hanzo_mcp/tools/filesystem/write.py +7 -10
  90. hanzo_mcp/tools/framework/__init__.py +8 -0
  91. hanzo_mcp/tools/framework/framework_modes.py +714 -0
  92. hanzo_mcp/tools/jupyter/base.py +6 -20
  93. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  94. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  95. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  96. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  97. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  98. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  99. hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
  100. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  101. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  102. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  103. hanzo_mcp/tools/memory/__init__.py +10 -27
  104. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  105. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  106. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  107. hanzo_mcp/tools/search/find_tool.py +12 -34
  108. hanzo_mcp/tools/search/unified_search.py +24 -78
  109. hanzo_mcp/tools/shell/__init__.py +16 -4
  110. hanzo_mcp/tools/shell/auto_background.py +2 -6
  111. hanzo_mcp/tools/shell/base.py +1 -5
  112. hanzo_mcp/tools/shell/base_process.py +5 -7
  113. hanzo_mcp/tools/shell/bash_session.py +7 -24
  114. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  115. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  116. hanzo_mcp/tools/shell/command_executor.py +26 -79
  117. hanzo_mcp/tools/shell/logs.py +4 -16
  118. hanzo_mcp/tools/shell/npx.py +2 -8
  119. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  120. hanzo_mcp/tools/shell/pkill.py +4 -12
  121. hanzo_mcp/tools/shell/process_tool.py +2 -8
  122. hanzo_mcp/tools/shell/processes.py +5 -17
  123. hanzo_mcp/tools/shell/run_background.py +1 -3
  124. hanzo_mcp/tools/shell/run_command.py +1 -3
  125. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  126. hanzo_mcp/tools/shell/run_tool.py +56 -0
  127. hanzo_mcp/tools/shell/session_manager.py +2 -6
  128. hanzo_mcp/tools/shell/session_storage.py +2 -6
  129. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  130. hanzo_mcp/tools/shell/uvx.py +4 -14
  131. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  132. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  133. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  134. hanzo_mcp/tools/todo/todo.py +1 -3
  135. hanzo_mcp/tools/vector/__init__.py +97 -50
  136. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  137. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  138. hanzo_mcp/tools/vector/index_tool.py +3 -9
  139. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  140. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  141. hanzo_mcp/tools/vector/node_tool.py +538 -0
  142. hanzo_mcp/tools/vector/project_manager.py +4 -12
  143. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  144. hanzo_mcp/tools/vector/vector.py +2 -6
  145. hanzo_mcp/tools/vector/vector_index.py +8 -8
  146. hanzo_mcp/tools/vector/vector_search.py +7 -21
  147. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  148. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  149. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  150. hanzo_mcp/tools/agent/swarm_tool.py +0 -718
  151. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  152. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  153. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  154. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  155. hanzo_mcp/tools/filesystem/grep.py +0 -467
  156. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  157. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  158. hanzo_mcp/tools/filesystem/tree.py +0 -270
  159. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  160. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  161. hanzo_mcp/tools/todo/todo_read.py +0 -143
  162. hanzo_mcp/tools/todo/todo_write.py +0 -374
  163. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  164. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  165. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  166. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -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,9 +9,7 @@ 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
@@ -176,9 +174,7 @@ class HanzoMCPServer:
176
174
  signal.signal(signal.SIGINT, signal_handler)
177
175
 
178
176
  # Start background cleanup thread for periodic cleanup
179
- self._cleanup_thread = threading.Thread(
180
- target=self._background_cleanup, daemon=True
181
- )
177
+ self._cleanup_thread = threading.Thread(target=self._background_cleanup, daemon=True)
182
178
  self._cleanup_thread.start()
183
179
 
184
180
  self._cleanup_registered = True
@@ -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,24 +222,31 @@ 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
 
234
- # Register shell tools if enabled
235
- if is_tool_enabled("run_command", True):
236
- shell_tools = register_shell_tools(mcp_server, permission_manager)
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)
237
245
  for tool in shell_tools:
238
246
  all_tools[tool.name] = tool
239
247
 
240
248
  # 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
- )
249
+ agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
246
250
  swarm_enabled = is_tool_enabled("swarm", False)
247
251
 
248
252
  if agent_enabled or swarm_enabled:
@@ -258,12 +262,7 @@ def register_all_tools(
258
262
  )
259
263
  # Filter based on what's enabled
260
264
  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
- ):
265
+ if tool.name == "agent" and agent_enabled or tool.name == "swarm" and swarm_enabled:
267
266
  all_tools[tool.name] = tool
268
267
  elif tool.name in ["claude", "codex", "gemini", "grok", "code_auth"]:
269
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
@@ -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
- get_read_only_filesystem_tools(self.permission_manager)
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:
@@ -103,6 +103,23 @@ from hanzo_mcp.tools.agent.clarification_protocol import (
103
103
 
104
104
 
105
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"
106
123
  """Parameters for the AgentTool."""
107
124
 
108
125
  prompts: str | list[str]
@@ -197,9 +214,7 @@ class MCPAgent(Agent):
197
214
  adapter = MCPToolAdapter(mcp_tool, ctx)
198
215
  self.register_tool(adapter)
199
216
 
200
- async def run(
201
- self, state: MCPAgentState, history: History, network: Network
202
- ) -> InferenceResult:
217
+ async def run(self, state: MCPAgentState, history: History, network: Network) -> InferenceResult:
203
218
  """Execute the agent."""
204
219
  # Get current prompt
205
220
  if state.current_prompt_index >= len(state.prompts):
@@ -324,12 +339,8 @@ Usage notes:
324
339
 
325
340
  # Set up available tools
326
341
  self.available_tools: list[BaseTool] = []
327
- self.available_tools.extend(
328
- get_read_only_filesystem_tools(self.permission_manager)
329
- )
330
- self.available_tools.extend(
331
- get_read_only_jupyter_tools(self.permission_manager)
332
- )
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))
333
344
 
334
345
  # Add edit tools
335
346
  self.available_tools.append(Edit(self.permission_manager))
@@ -341,9 +352,7 @@ Usage notes:
341
352
  self.available_tools.append(ReviewTool())
342
353
  self.available_tools.append(IChingTool())
343
354
 
344
- self.available_tools.append(
345
- BatchTool({t.name: t for t in self.available_tools})
346
- )
355
+ self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
347
356
 
348
357
  @override
349
358
  async def call(
@@ -399,9 +408,7 @@ Usage notes:
399
408
  times = (concurrency + len(prompt_list) - 1) // len(prompt_list)
400
409
  prompt_list = (prompt_list * times)[:concurrency]
401
410
 
402
- await tool_ctx.info(
403
- f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK"
404
- )
411
+ await tool_ctx.info(f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK")
405
412
 
406
413
  # Determine model and agent type
407
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)}")