hanzo-mcp 0.7.6__py3-none-any.whl → 0.8.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 (178) hide show
  1. hanzo_mcp/__init__.py +7 -1
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -4,25 +4,31 @@ This module implements the SwarmTool that enables both parallel execution of mul
4
4
  agent instances and hierarchical workflows with specialized roles.
5
5
  """
6
6
 
7
- import asyncio
8
- import json
9
7
  import os
10
- from typing import Annotated, Any, TypedDict, Unpack, final, override, Optional, Dict, List
11
- from pathlib import Path
8
+ import asyncio
9
+ from typing import (
10
+ Any,
11
+ Dict,
12
+ List,
13
+ Unpack,
14
+ Optional,
15
+ TypedDict,
16
+ final,
17
+ override,
18
+ )
12
19
 
13
20
  from mcp.server import FastMCP
14
21
  from mcp.server.fastmcp import Context as MCPContext
15
- from pydantic import Field
16
22
 
17
- from hanzo_mcp.tools.agent.agent_tool import AgentTool
18
23
  from hanzo_mcp.tools.common.base import BaseTool
19
24
  from hanzo_mcp.tools.common.context import create_tool_context
25
+ from hanzo_mcp.tools.agent.agent_tool import AgentTool
20
26
  from hanzo_mcp.tools.common.permissions import PermissionManager
21
27
 
22
28
 
23
29
  class AgentNode(TypedDict):
24
30
  """Node in the agent network.
25
-
31
+
26
32
  Attributes:
27
33
  id: Unique identifier for this agent
28
34
  query: The specific query/task for this agent
@@ -32,6 +38,7 @@ class AgentNode(TypedDict):
32
38
  receives_from: Optional list of agent IDs this agent receives input from
33
39
  file_path: Optional specific file for the agent to work on
34
40
  """
41
+
35
42
  id: str
36
43
  query: str
37
44
  model: Optional[str]
@@ -43,12 +50,13 @@ class AgentNode(TypedDict):
43
50
 
44
51
  class SwarmConfig(TypedDict):
45
52
  """Configuration for an agent network.
46
-
53
+
47
54
  Attributes:
48
55
  agents: Dictionary of agent configurations keyed by ID
49
56
  entry_point: ID of the first agent to execute (optional, defaults to finding roots)
50
57
  topology: Optional topology type (tree, dag, pipeline, star, mesh)
51
58
  """
59
+
52
60
  agents: Dict[str, AgentNode]
53
61
  entry_point: Optional[str]
54
62
  topology: Optional[str]
@@ -56,13 +64,14 @@ class SwarmConfig(TypedDict):
56
64
 
57
65
  class SwarmToolParams(TypedDict):
58
66
  """Parameters for the SwarmTool.
59
-
67
+
60
68
  Attributes:
61
69
  config: Agent network configuration
62
70
  query: Initial query to send to entry point agent(s)
63
71
  context: Optional context shared by all agents
64
72
  max_concurrent: Maximum number of concurrent agents (default: 10)
65
73
  """
74
+
66
75
  config: SwarmConfig
67
76
  query: str
68
77
  context: Optional[str]
@@ -72,17 +81,17 @@ class SwarmToolParams(TypedDict):
72
81
  @final
73
82
  class SwarmTool(BaseTool):
74
83
  """Tool for executing multiple agent tasks in parallel.
75
-
84
+
76
85
  The SwarmTool enables efficient parallel processing of multiple files or tasks
77
86
  by spawning independent agent instances for each task.
78
87
  """
79
-
88
+
80
89
  @property
81
90
  @override
82
91
  def name(self) -> str:
83
92
  """Get the tool name."""
84
93
  return "swarm"
85
-
94
+
86
95
  @property
87
96
  @override
88
97
  def description(self) -> str:
@@ -160,7 +169,7 @@ Models can be specified as:
160
169
  - Short: 'claude-3-5-sonnet', 'gpt-4o', 'gemini-1.5-pro'
161
170
  - CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
162
171
  """
163
-
172
+
164
173
  def __init__(
165
174
  self,
166
175
  permission_manager: PermissionManager,
@@ -172,7 +181,7 @@ Models can be specified as:
172
181
  agent_max_tool_uses: int = 30,
173
182
  ):
174
183
  """Initialize the swarm tool.
175
-
184
+
176
185
  Args:
177
186
  permission_manager: Permission manager for access control
178
187
  model: Optional model name override (defaults to Claude Sonnet)
@@ -185,13 +194,18 @@ Models can be specified as:
185
194
  self.permission_manager = permission_manager
186
195
  # Default to latest Claude Sonnet if no model specified
187
196
  from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
197
+
188
198
  self.model = model or f"anthropic/{get_latest_claude_model()}"
189
- self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("CLAUDE_API_KEY")
199
+ self.api_key = (
200
+ api_key
201
+ or os.environ.get("ANTHROPIC_API_KEY")
202
+ or os.environ.get("CLAUDE_API_KEY")
203
+ )
190
204
  self.base_url = base_url
191
205
  self.max_tokens = max_tokens
192
206
  self.agent_max_iterations = agent_max_iterations
193
207
  self.agent_max_tool_uses = agent_max_tool_uses
194
-
208
+
195
209
  @override
196
210
  async def call(
197
211
  self,
@@ -199,48 +213,50 @@ Models can be specified as:
199
213
  **params: Unpack[SwarmToolParams],
200
214
  ) -> str:
201
215
  """Execute the swarm tool.
202
-
216
+
203
217
  Args:
204
218
  ctx: MCP context
205
219
  **params: Tool parameters
206
-
220
+
207
221
  Returns:
208
222
  Combined results from all agents
209
223
  """
210
224
  tool_ctx = create_tool_context(ctx)
211
225
  await tool_ctx.set_tool_info(self.name)
212
-
226
+
213
227
  # Extract parameters
214
228
  agents = params.get("agents", [])
215
229
  manager_query = params.get("manager_query")
216
230
  reviewer_query = params.get("reviewer_query")
217
231
  common_context = params.get("common_context", "")
218
232
  max_concurrent = params.get("max_concurrent", 10)
219
-
233
+
220
234
  if not agents:
221
235
  await tool_ctx.error("No agents provided")
222
236
  return "Error: At least one agent must be provided."
223
-
237
+
224
238
  # Extract parameters
225
239
  config = params.get("config", {})
226
240
  initial_query = params.get("query", "")
227
241
  context = params.get("context", "")
228
-
242
+
229
243
  agents_config = config.get("agents", {})
230
244
  entry_point = config.get("entry_point")
231
-
232
- await tool_ctx.info(f"Starting swarm execution with {len(agents_config)} agents")
233
-
245
+
246
+ await tool_ctx.info(
247
+ f"Starting swarm execution with {len(agents_config)} agents"
248
+ )
249
+
234
250
  # Build agent network
235
251
  agent_instances = {}
236
252
  agent_results = {}
237
253
  execution_queue = asyncio.Queue()
238
254
  completed_agents = set()
239
-
255
+
240
256
  # Create agent instances
241
257
  for agent_id, agent_config in agents_config.items():
242
258
  model = agent_config.get("model", self.model)
243
-
259
+
244
260
  # Support CLI tools
245
261
  cli_tools = {
246
262
  "claude_cli": self._get_cli_tool("claude_cli"),
@@ -248,7 +264,7 @@ Models can be specified as:
248
264
  "gemini_cli": self._get_cli_tool("gemini_cli"),
249
265
  "grok_cli": self._get_cli_tool("grok_cli"),
250
266
  }
251
-
267
+
252
268
  if model in cli_tools:
253
269
  agent = cli_tools[model]
254
270
  else:
@@ -262,9 +278,9 @@ Models can be specified as:
262
278
  max_iterations=self.agent_max_iterations,
263
279
  max_tool_uses=self.agent_max_tool_uses,
264
280
  )
265
-
281
+
266
282
  agent_instances[agent_id] = agent
267
-
283
+
268
284
  # Find entry points (agents with no incoming connections)
269
285
  if entry_point:
270
286
  await execution_queue.put((entry_point, initial_query, {}))
@@ -276,83 +292,94 @@ Models can be specified as:
276
292
  # Check if any other agent connects to this one
277
293
  has_incoming = False
278
294
  for other_config in agents_config.values():
279
- if other_config.get("connections") and agent_id in other_config["connections"]:
295
+ if (
296
+ other_config.get("connections")
297
+ and agent_id in other_config["connections"]
298
+ ):
280
299
  has_incoming = True
281
300
  break
282
301
  if not has_incoming:
283
302
  roots.append(agent_id)
284
-
303
+
285
304
  if not roots:
286
305
  await tool_ctx.error("No entry point found in agent network")
287
306
  return "Error: Could not determine entry point for agent network"
288
-
307
+
289
308
  for root in roots:
290
309
  await execution_queue.put((root, initial_query, {}))
291
-
310
+
292
311
  # Execute agents in network order
293
- async def execute_agent(agent_id: str, query: str, inputs: Dict[str, str]) -> str:
312
+ async def execute_agent(
313
+ agent_id: str, query: str, inputs: Dict[str, str]
314
+ ) -> str:
294
315
  """Execute a single agent in the network."""
295
316
  async with semaphore:
296
317
  try:
297
318
  agent_config = agents_config[agent_id]
298
319
  agent = agent_instances[agent_id]
299
-
300
- await tool_ctx.info(f"Executing agent: {agent_id} ({agent_config.get('role', 'Agent')})")
301
-
320
+
321
+ await tool_ctx.info(
322
+ f"Executing agent: {agent_id} ({agent_config.get('role', 'Agent')})"
323
+ )
324
+
302
325
  # Build prompt with context and inputs
303
326
  prompt_parts = []
304
-
327
+
305
328
  # Add role context
306
329
  if agent_config.get("role"):
307
330
  prompt_parts.append(f"Your role: {agent_config['role']}")
308
-
331
+
309
332
  # Add shared context
310
333
  if context:
311
334
  prompt_parts.append(f"Context:\n{context}")
312
-
335
+
313
336
  # Add inputs from connected agents
314
337
  if inputs:
315
338
  prompt_parts.append("Input from previous agents:")
316
339
  for input_agent, input_result in inputs.items():
317
- prompt_parts.append(f"\n--- From {input_agent} ---\n{input_result}")
318
-
340
+ prompt_parts.append(
341
+ f"\n--- From {input_agent} ---\n{input_result}"
342
+ )
343
+
319
344
  # Add file context if specified
320
345
  if agent_config.get("file_path"):
321
- prompt_parts.append(f"\nFile to work on: {agent_config['file_path']}")
322
-
346
+ prompt_parts.append(
347
+ f"\nFile to work on: {agent_config['file_path']}"
348
+ )
349
+
323
350
  # Add the main query
324
351
  prompt_parts.append(f"\nTask: {agent_config['query']}")
325
-
352
+
326
353
  # Combine query with initial query if this is entry point
327
- if query and query != agent_config['query']:
354
+ if query and query != agent_config["query"]:
328
355
  prompt_parts.append(f"\nMain objective: {query}")
329
-
356
+
330
357
  full_prompt = "\n\n".join(prompt_parts)
331
-
358
+
332
359
  # Execute the agent
333
360
  result = await agent.call(ctx, prompts=full_prompt)
334
-
361
+
335
362
  await tool_ctx.info(f"Agent {agent_id} completed")
336
363
  return result
337
-
364
+
338
365
  except Exception as e:
339
366
  error_msg = f"Agent {agent_id} failed: {str(e)}"
340
367
  await tool_ctx.error(error_msg)
341
368
  return f"Error: {error_msg}"
342
-
369
+
343
370
  # Process agent network
344
371
  running_tasks = set()
345
-
372
+
346
373
  while not execution_queue.empty() or running_tasks:
347
374
  # Start new tasks up to concurrency limit
348
375
  while not execution_queue.empty() and len(running_tasks) < max_concurrent:
349
376
  agent_id, query, inputs = await execution_queue.get()
350
-
377
+
351
378
  if agent_id not in completed_agents:
352
379
  # Check if all dependencies are met
353
380
  agent_config = agents_config[agent_id]
354
381
  receives_from = agent_config.get("receives_from", [])
355
-
382
+
356
383
  # Collect inputs from dependencies
357
384
  ready = True
358
385
  for dep in receives_from:
@@ -363,38 +390,42 @@ Models can be specified as:
363
390
  break
364
391
  else:
365
392
  inputs[dep] = agent_results[dep]
366
-
393
+
367
394
  if ready:
368
395
  # Execute agent
369
- task = asyncio.create_task(execute_agent(agent_id, query, inputs))
396
+ task = asyncio.create_task(
397
+ execute_agent(agent_id, query, inputs)
398
+ )
370
399
  running_tasks.add(task)
371
-
400
+
372
401
  async def handle_completion(task, agent_id=agent_id):
373
402
  result = await task
374
403
  agent_results[agent_id] = result
375
404
  completed_agents.add(agent_id)
376
405
  running_tasks.discard(task)
377
-
406
+
378
407
  # Queue connected agents
379
408
  agent_config = agents_config[agent_id]
380
409
  connections = agent_config.get("connections", [])
381
410
  for next_agent in connections:
382
411
  if next_agent in agents_config:
383
- await execution_queue.put((next_agent, "", {agent_id: result}))
384
-
412
+ await execution_queue.put(
413
+ (next_agent, "", {agent_id: result})
414
+ )
415
+
385
416
  asyncio.create_task(handle_completion(task))
386
-
417
+
387
418
  # Wait a bit if we're at capacity
388
419
  if running_tasks:
389
420
  await asyncio.sleep(0.1)
390
-
421
+
391
422
  # Wait for all tasks to complete
392
423
  if running_tasks:
393
424
  await asyncio.gather(*running_tasks, return_exceptions=True)
394
-
425
+
395
426
  # Format results
396
427
  return self._format_network_results(agents_config, agent_results, entry_point)
397
-
428
+
398
429
  def _normalize_model(self, model: str) -> str:
399
430
  """Normalize model names to full format."""
400
431
  model_map = {
@@ -406,64 +437,75 @@ Models can be specified as:
406
437
  "gemini-1.5-flash": "google/gemini-1.5-flash",
407
438
  }
408
439
  return model_map.get(model, model)
409
-
440
+
410
441
  def _get_cli_tool(self, tool_name: str):
411
442
  """Get CLI tool instance."""
412
443
  # Import here to avoid circular imports
413
444
  if tool_name == "claude_cli":
414
445
  from hanzo_mcp.tools.agent.claude_cli_tool import ClaudeCLITool
446
+
415
447
  return ClaudeCLITool(self.permission_manager)
416
448
  elif tool_name == "codex_cli":
417
449
  from hanzo_mcp.tools.agent.codex_cli_tool import CodexCLITool
450
+
418
451
  return CodexCLITool(self.permission_manager)
419
452
  elif tool_name == "gemini_cli":
420
453
  from hanzo_mcp.tools.agent.gemini_cli_tool import GeminiCLITool
454
+
421
455
  return GeminiCLITool(self.permission_manager)
422
456
  elif tool_name == "grok_cli":
423
457
  from hanzo_mcp.tools.agent.grok_cli_tool import GrokCLITool
458
+
424
459
  return GrokCLITool(self.permission_manager)
425
460
  return None
426
-
461
+
427
462
  def _format_network_results(
428
463
  self,
429
464
  agents_config: Dict[str, Any],
430
465
  results: Dict[str, str],
431
- entry_point: Optional[str]
466
+ entry_point: Optional[str],
432
467
  ) -> str:
433
468
  """Format results from agent network execution."""
434
469
  output = ["Agent Network Execution Results"]
435
470
  output.append("=" * 80)
436
471
  output.append(f"Total agents: {len(agents_config)}")
437
472
  output.append(f"Completed: {len(results)}")
438
- output.append(f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}")
439
-
473
+ output.append(
474
+ f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}"
475
+ )
476
+
440
477
  if entry_point:
441
478
  output.append(f"Entry point: {entry_point}")
442
-
479
+
443
480
  output.append("\nExecution Flow:")
444
481
  output.append("-" * 40)
445
-
482
+
446
483
  # Show results in execution order
447
484
  def format_agent_tree(agent_id: str, level: int = 0) -> List[str]:
448
485
  lines = []
449
486
  indent = " " * level
450
-
487
+
451
488
  if agent_id in agents_config:
452
489
  config = agents_config[agent_id]
453
490
  role = config.get("role", "Agent")
454
491
  model = config.get("model", "default")
455
-
456
- status = "✅" if agent_id in results and not results[agent_id].startswith("Error:") else "❌"
492
+
493
+ status = (
494
+ "✅"
495
+ if agent_id in results
496
+ and not results[agent_id].startswith("Error:")
497
+ else "❌"
498
+ )
457
499
  lines.append(f"{indent}{status} {agent_id} ({role}) [{model}]")
458
-
500
+
459
501
  # Show connections
460
502
  connections = config.get("connections", [])
461
503
  for conn in connections:
462
504
  if conn in agents_config:
463
505
  lines.extend(format_agent_tree(conn, level + 1))
464
-
506
+
465
507
  return lines
466
-
508
+
467
509
  # Start from entry point or roots
468
510
  if entry_point:
469
511
  output.extend(format_agent_tree(entry_point))
@@ -478,39 +520,39 @@ Models can be specified as:
478
520
  break
479
521
  if not has_incoming:
480
522
  roots.append(agent_id)
481
-
523
+
482
524
  for root in roots:
483
525
  output.extend(format_agent_tree(root))
484
-
526
+
485
527
  # Detailed results
486
528
  output.append("\n\nDetailed Results:")
487
529
  output.append("=" * 80)
488
-
530
+
489
531
  for agent_id, result in results.items():
490
532
  config = agents_config.get(agent_id, {})
491
533
  role = config.get("role", "Agent")
492
-
534
+
493
535
  output.append(f"\n### {agent_id} ({role})")
494
536
  output.append("-" * 40)
495
-
537
+
496
538
  if result.startswith("Error:"):
497
539
  output.append(result)
498
540
  else:
499
541
  # Show first part of result
500
- lines = result.split('\n')
542
+ lines = result.split("\n")
501
543
  preview_lines = lines[:10]
502
544
  output.extend(preview_lines)
503
-
545
+
504
546
  if len(lines) > 10:
505
547
  output.append(f"... ({len(lines) - 10} more lines)")
506
-
548
+
507
549
  return "\n".join(output)
508
-
550
+
509
551
  @override
510
552
  def register(self, mcp_server: FastMCP) -> None:
511
553
  """Register this swarm tool with the MCP server."""
512
554
  tool_self = self
513
-
555
+
514
556
  @mcp_server.tool(name=self.name, description=self.description)
515
557
  async def swarm(
516
558
  ctx: MCPContext,
@@ -523,13 +565,13 @@ Models can be specified as:
523
565
  typed_config = SwarmConfig(
524
566
  agents=config.get("agents", {}),
525
567
  entry_point=config.get("entry_point"),
526
- topology=config.get("topology")
568
+ topology=config.get("topology"),
527
569
  )
528
-
570
+
529
571
  return await tool_self.call(
530
572
  ctx,
531
573
  config=typed_config,
532
574
  query=query,
533
575
  context=context,
534
- max_concurrent=max_concurrent
535
- )
576
+ max_concurrent=max_concurrent,
577
+ )
@@ -5,14 +5,14 @@ formats, making MCP tools available to the OpenAI API, and processing tool input
5
5
  and outputs for agent execution.
6
6
  """
7
7
 
8
+ # Import litellm with warnings suppressed
9
+ import warnings
10
+
8
11
  from openai.types import FunctionParameters
9
12
  from openai.types.chat import ChatCompletionToolParam
10
13
 
11
- # Import litellm with warnings suppressed
12
- import warnings
13
14
  with warnings.catch_warnings():
14
15
  warnings.simplefilter("ignore", DeprecationWarning)
15
- import litellm
16
16
 
17
17
  from hanzo_mcp.tools.common.base import BaseTool
18
18
 
@@ -80,20 +80,30 @@ def supports_parallel_function_calling(model: str) -> bool:
80
80
  # based on known models that support parallel function calling
81
81
  parallel_capable_models = {
82
82
  # OpenAI models that support parallel function calling
83
- "gpt-4-turbo", "gpt-4-turbo-preview", "gpt-4-turbo-2024-04-09",
84
- "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06",
85
- "gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-1106",
83
+ "gpt-4-turbo",
84
+ "gpt-4-turbo-preview",
85
+ "gpt-4-turbo-2024-04-09",
86
+ "gpt-4o",
87
+ "gpt-4o-mini",
88
+ "gpt-4o-2024-05-13",
89
+ "gpt-4o-2024-08-06",
90
+ "gpt-3.5-turbo",
91
+ "gpt-3.5-turbo-0125",
92
+ "gpt-3.5-turbo-1106",
86
93
  # Anthropic models with tool support
87
- "claude-3-opus", "claude-3-sonnet", "claude-3-haiku",
88
- "claude-3-5-sonnet", "claude-3-5-sonnet-20241022",
94
+ "claude-3-opus",
95
+ "claude-3-sonnet",
96
+ "claude-3-haiku",
97
+ "claude-3-5-sonnet",
98
+ "claude-3-5-sonnet-20241022",
89
99
  }
90
-
100
+
91
101
  # Extract model name without provider prefix
92
102
  model_name = model.split("/")[-1] if "/" in model else model
93
-
103
+
94
104
  # Check if the base model name matches any known parallel-capable models
95
105
  for capable_model in parallel_capable_models:
96
106
  if model_name.startswith(capable_model):
97
107
  return True
98
-
108
+
99
109
  return False
@@ -4,8 +4,8 @@ from mcp.server import FastMCP
4
4
 
5
5
  from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
6
6
  from hanzo_mcp.tools.common.batch_tool import BatchTool
7
- from hanzo_mcp.tools.common.thinking_tool import ThinkingTool
8
7
  from hanzo_mcp.tools.common.critic_tool import CriticTool
8
+ from hanzo_mcp.tools.common.thinking_tool import ThinkingTool
9
9
 
10
10
 
11
11
  def register_thinking_tool(
@@ -7,19 +7,17 @@ behavior and provide a foundation for tool registration and management.
7
7
 
8
8
  import functools
9
9
  from abc import ABC, abstractmethod
10
- from collections.abc import Awaitable
11
10
  from typing import Any, Callable, final
11
+ from collections.abc import Awaitable
12
12
 
13
13
  from mcp.server import FastMCP
14
14
  from mcp.server.fastmcp import Context as MCPContext
15
15
 
16
-
17
- from hanzo_mcp.tools.common.permissions import PermissionManager
18
16
  from hanzo_mcp.tools.common.validation import (
19
17
  ValidationResult,
20
18
  validate_path_parameter,
21
19
  )
22
- from hanzo_mcp.tools.common.truncate import truncate_response
20
+ from hanzo_mcp.tools.common.permissions import PermissionManager
23
21
 
24
22
 
25
23
  def handle_connection_errors(
@@ -174,7 +172,7 @@ class ToolRegistry:
174
172
  # Check if tool is enabled before registering
175
173
  # Import here to avoid circular imports
176
174
  from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
177
-
175
+
178
176
  if ToolEnableTool.is_tool_enabled(tool.name):
179
177
  # Use the tool's register method which handles all the details
180
178
  tool.register(mcp_server)