hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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 +6 -0
  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.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
@@ -4,49 +4,90 @@ This module implements the SwarmTool that leverages the hanzo-agents SDK
4
4
  for sophisticated multi-agent orchestration with flexible network topologies.
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
+ from typing import (
9
+ Any,
10
+ Dict,
11
+ List,
12
+ Unpack,
13
+ Optional,
14
+ TypedDict,
15
+ final,
16
+ override,
17
+ )
12
18
 
13
19
  from mcp.server import FastMCP
14
20
  from mcp.server.fastmcp import Context as MCPContext
15
- from pydantic import Field
16
21
 
17
22
  # Import hanzo-agents SDK with fallback
18
23
  try:
19
24
  from hanzo_agents import (
20
- Agent, State, Network, Tool, History,
21
- ModelRegistry, InferenceResult, ToolCall,
25
+ Tool,
26
+ Agent,
27
+ State,
22
28
  Router,
29
+ History,
30
+ Network,
31
+ ToolCall,
32
+ ModelRegistry,
33
+ InferenceResult,
23
34
  )
35
+
24
36
  HANZO_AGENTS_AVAILABLE = True
25
37
  except ImportError:
26
38
  # Define minimal stubs if hanzo-agents is not available
27
39
  HANZO_AGENTS_AVAILABLE = False
28
- class Agent: pass
29
- class State: pass
30
- class Network: pass
31
- class Tool: pass
32
- class History: pass
33
- class ModelRegistry: pass
34
- class InferenceResult: pass
35
- class ToolCall: pass
36
- class Router: pass
40
+
41
+ class Agent:
42
+ pass
43
+
44
+ class State:
45
+ pass
46
+
47
+ class Network:
48
+ pass
49
+
50
+ class Tool:
51
+ pass
52
+
53
+ class History:
54
+ pass
55
+
56
+ class ModelRegistry:
57
+ pass
58
+
59
+ class InferenceResult:
60
+ pass
61
+
62
+ class ToolCall:
63
+ pass
64
+
65
+ class Router:
66
+ pass
67
+
37
68
 
38
69
  # Import optional components with fallbacks
39
70
  try:
40
- from hanzo_agents import DeterministicRouter, LLMRouter, HybridRouter
71
+ from hanzo_agents import LLMRouter, HybridRouter, DeterministicRouter
41
72
  except ImportError:
42
73
  try:
43
74
  # Try core module import
44
- from hanzo_agents.core.router import DeterministicRouter, LLMRouter, HybridRouter
75
+ from hanzo_agents.core.router import (
76
+ LLMRouter,
77
+ HybridRouter,
78
+ DeterministicRouter,
79
+ )
45
80
  except ImportError:
46
81
  # Define stubs if not available
47
- class DeterministicRouter: pass
48
- class LLMRouter: pass
49
- class HybridRouter: pass
82
+ class DeterministicRouter:
83
+ pass
84
+
85
+ class LLMRouter:
86
+ pass
87
+
88
+ class HybridRouter:
89
+ pass
90
+
50
91
 
51
92
  try:
52
93
  from hanzo_agents import create_memory_kv, create_memory_vector
@@ -56,48 +97,69 @@ except ImportError:
56
97
  from hanzo_agents.core.memory import create_memory_kv, create_memory_vector
57
98
  except ImportError:
58
99
  # Define stubs if not available
59
- def create_memory_kv(*args, **kwargs): pass
60
- def create_memory_vector(*args, **kwargs): pass
100
+ def create_memory_kv(*args, **kwargs):
101
+ pass
102
+
103
+ def create_memory_vector(*args, **kwargs):
104
+ pass
105
+
61
106
 
62
107
  try:
63
108
  from hanzo_agents import sequential_router, conditional_router, state_based_router
64
109
  except ImportError:
65
110
  try:
66
111
  # Try core module import
67
- from hanzo_agents.core.router import sequential_router, conditional_router, state_based_router
112
+ from hanzo_agents.core.router import (
113
+ sequential_router,
114
+ conditional_router,
115
+ state_based_router,
116
+ )
68
117
  except ImportError:
69
118
  # Define stubs if not available
70
- def sequential_router(*args, **kwargs): pass
71
- def conditional_router(*args, **kwargs): pass
72
- def state_based_router(*args, **kwargs): pass
119
+ def sequential_router(*args, **kwargs):
120
+ pass
121
+
122
+ def conditional_router(*args, **kwargs):
123
+ pass
124
+
125
+ def state_based_router(*args, **kwargs):
126
+ pass
127
+
73
128
 
74
129
  try:
75
130
  from hanzo_agents.core.cli_agent import (
76
- ClaudeCodeAgent, OpenAICodexAgent,
77
- GeminiAgent, GrokAgent
131
+ GrokAgent,
132
+ GeminiAgent,
133
+ ClaudeCodeAgent,
134
+ OpenAICodexAgent,
78
135
  )
79
136
  except ImportError:
80
137
  # Define stub classes if not available
81
138
  class ClaudeCodeAgent(Agent):
82
139
  pass
140
+
83
141
  class OpenAICodexAgent(Agent):
84
142
  pass
143
+
85
144
  class GeminiAgent(Agent):
86
145
  pass
146
+
87
147
  class GrokAgent(Agent):
88
148
  pass
89
149
 
90
- from hanzo_mcp.tools.agent.agent_tool import MCPAgent, MCPToolAdapter
150
+
151
+ from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
152
+ from hanzo_mcp.tools.filesystem import Edit, MultiEdit, get_read_only_filesystem_tools
91
153
  from hanzo_mcp.tools.common.base import BaseTool
92
154
  from hanzo_mcp.tools.common.context import create_tool_context
93
- from hanzo_mcp.tools.common.permissions import PermissionManager
94
- from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools, Edit, MultiEdit
95
- from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
155
+ from hanzo_mcp.tools.agent.agent_tool import MCPAgent
96
156
  from hanzo_mcp.tools.common.batch_tool import BatchTool
157
+ from hanzo_mcp.tools.common.permissions import PermissionManager
97
158
 
98
159
 
99
160
  class AgentNode(TypedDict):
100
161
  """Node in the agent network."""
162
+
101
163
  id: str
102
164
  query: str
103
165
  model: Optional[str]
@@ -109,6 +171,7 @@ class AgentNode(TypedDict):
109
171
 
110
172
  class SwarmConfig(TypedDict):
111
173
  """Configuration for an agent network."""
174
+
112
175
  agents: Dict[str, AgentNode]
113
176
  entry_point: Optional[str]
114
177
  topology: Optional[str]
@@ -116,6 +179,7 @@ class SwarmConfig(TypedDict):
116
179
 
117
180
  class SwarmToolParams(TypedDict):
118
181
  """Parameters for the SwarmTool."""
182
+
119
183
  config: SwarmConfig
120
184
  query: str
121
185
  context: Optional[str]
@@ -126,11 +190,10 @@ class SwarmToolParams(TypedDict):
126
190
 
127
191
  class SwarmState(State):
128
192
  """State for swarm execution."""
129
-
130
- def __init__(self,
131
- config: SwarmConfig,
132
- initial_query: str,
133
- context: Optional[str] = None):
193
+
194
+ def __init__(
195
+ self, config: SwarmConfig, initial_query: str, context: Optional[str] = None
196
+ ):
134
197
  """Initialize swarm state."""
135
198
  super().__init__()
136
199
  self.config = config
@@ -140,28 +203,30 @@ class SwarmState(State):
140
203
  self.completed_agents = set()
141
204
  self.current_agent = None
142
205
  self.execution_order = []
143
-
206
+
144
207
  def to_dict(self) -> Dict[str, Any]:
145
208
  """Convert to dictionary."""
146
209
  base_dict = super().to_dict()
147
- base_dict.update({
148
- "config": self.config,
149
- "initial_query": self.initial_query,
150
- "context": self.context,
151
- "agent_results": self.agent_results,
152
- "completed_agents": list(self.completed_agents),
153
- "current_agent": self.current_agent,
154
- "execution_order": self.execution_order
155
- })
210
+ base_dict.update(
211
+ {
212
+ "config": self.config,
213
+ "initial_query": self.initial_query,
214
+ "context": self.context,
215
+ "agent_results": self.agent_results,
216
+ "completed_agents": list(self.completed_agents),
217
+ "current_agent": self.current_agent,
218
+ "execution_order": self.execution_order,
219
+ }
220
+ )
156
221
  return base_dict
157
-
222
+
158
223
  @classmethod
159
224
  def from_dict(cls, data: Dict[str, Any]) -> "SwarmState":
160
225
  """Create from dictionary."""
161
226
  state = cls(
162
227
  config=data.get("config", {}),
163
228
  initial_query=data.get("initial_query", ""),
164
- context=data.get("context")
229
+ context=data.get("context"),
165
230
  )
166
231
  state.agent_results = data.get("agent_results", {})
167
232
  state.completed_agents = set(data.get("completed_agents", []))
@@ -172,35 +237,37 @@ class SwarmState(State):
172
237
 
173
238
  class SwarmAgent(MCPAgent):
174
239
  """Agent that executes within a swarm network."""
175
-
176
- def __init__(self,
177
- agent_id: str,
178
- agent_config: AgentNode,
179
- available_tools: List[BaseTool],
180
- permission_manager: PermissionManager,
181
- ctx: MCPContext,
182
- **kwargs):
240
+
241
+ def __init__(
242
+ self,
243
+ agent_id: str,
244
+ agent_config: AgentNode,
245
+ available_tools: List[BaseTool],
246
+ permission_manager: PermissionManager,
247
+ ctx: MCPContext,
248
+ **kwargs,
249
+ ):
183
250
  """Initialize swarm agent."""
184
251
  # Set name and description from config
185
252
  self.name = agent_id
186
253
  self.description = agent_config.get("role", f"Agent {agent_id}")
187
254
  self.agent_config = agent_config
188
-
255
+
189
256
  # Initialize with specified model
190
257
  model = agent_config.get("model")
191
258
  if model:
192
259
  model = self._normalize_model(model)
193
260
  else:
194
261
  model = "model://anthropic/claude-3-5-sonnet-20241022"
195
-
262
+
196
263
  super().__init__(
197
264
  available_tools=available_tools,
198
265
  permission_manager=permission_manager,
199
266
  ctx=ctx,
200
267
  model=model,
201
- **kwargs
268
+ **kwargs,
202
269
  )
203
-
270
+
204
271
  def _normalize_model(self, model: str) -> str:
205
272
  """Normalize model names to full format."""
206
273
  model_map = {
@@ -211,35 +278,37 @@ class SwarmAgent(MCPAgent):
211
278
  "gemini-1.5-pro": "model://google/gemini-1.5-pro",
212
279
  "gemini-1.5-flash": "model://google/gemini-1.5-flash",
213
280
  }
214
-
281
+
215
282
  # Check if it's already a model:// URI
216
283
  if model.startswith("model://"):
217
284
  return model
218
-
285
+
219
286
  # Check mapping
220
287
  if model in model_map:
221
288
  return model_map[model]
222
-
289
+
223
290
  # Assume it's a provider/model format
224
291
  if "/" in model:
225
292
  return f"model://{model}"
226
-
293
+
227
294
  # Default to anthropic
228
295
  return f"model://anthropic/{model}"
229
-
230
- async def run(self, state: SwarmState, history: History, network: Network) -> InferenceResult:
296
+
297
+ async def run(
298
+ self, state: SwarmState, history: History, network: Network
299
+ ) -> InferenceResult:
231
300
  """Execute the swarm agent."""
232
301
  # Build prompt with context
233
302
  prompt_parts = []
234
-
303
+
235
304
  # Add role context
236
305
  if self.agent_config.get("role"):
237
306
  prompt_parts.append(f"Your role: {self.agent_config['role']}")
238
-
307
+
239
308
  # Add shared context
240
309
  if state.context:
241
310
  prompt_parts.append(f"Context:\n{state.context}")
242
-
311
+
243
312
  # Add inputs from connected agents
244
313
  receives_from = self.agent_config.get("receives_from", [])
245
314
  if receives_from:
@@ -247,41 +316,42 @@ class SwarmAgent(MCPAgent):
247
316
  for agent_id in receives_from:
248
317
  if agent_id in state.agent_results:
249
318
  inputs[agent_id] = state.agent_results[agent_id]
250
-
319
+
251
320
  if inputs:
252
321
  prompt_parts.append("Input from previous agents:")
253
322
  for input_agent, input_result in inputs.items():
254
323
  prompt_parts.append(f"\n--- From {input_agent} ---\n{input_result}")
255
-
324
+
256
325
  # Add file context if specified
257
326
  if self.agent_config.get("file_path"):
258
327
  prompt_parts.append(f"\nFile to work on: {self.agent_config['file_path']}")
259
-
328
+
260
329
  # Add the main query
261
330
  prompt_parts.append(f"\nTask: {self.agent_config['query']}")
262
-
331
+
263
332
  # Add initial query if this is entry point
264
333
  if state.current_agent == state.config.get("entry_point"):
265
334
  prompt_parts.append(f"\nMain objective: {state.initial_query}")
266
-
335
+
267
336
  full_prompt = "\n\n".join(prompt_parts)
268
-
337
+
269
338
  # Execute using base class
270
339
  messages = [
271
340
  {"role": "system", "content": self._get_system_prompt()},
272
- {"role": "user", "content": full_prompt}
341
+ {"role": "user", "content": full_prompt},
273
342
  ]
274
-
343
+
275
344
  # Call model
276
345
  from hanzo_agents import ModelRegistry
346
+
277
347
  adapter = ModelRegistry.get_adapter(self.model)
278
348
  response = await adapter.chat(messages)
279
-
349
+
280
350
  # Store result in state
281
351
  state.agent_results[self.name] = response
282
352
  state.completed_agents.add(self.name)
283
353
  state.execution_order.append(self.name)
284
-
354
+
285
355
  # Return result
286
356
  return InferenceResult(
287
357
  agent=self.name,
@@ -289,39 +359,39 @@ class SwarmAgent(MCPAgent):
289
359
  metadata={
290
360
  "agent_id": self.name,
291
361
  "role": self.agent_config.get("role"),
292
- "connections": self.agent_config.get("connections", [])
293
- }
362
+ "connections": self.agent_config.get("connections", []),
363
+ },
294
364
  )
295
365
 
296
366
 
297
367
  class SwarmRouter(DeterministicRouter):
298
368
  """Router for swarm agent orchestration."""
299
-
369
+
300
370
  def __init__(self, swarm_config: SwarmConfig):
301
371
  """Initialize swarm router."""
302
372
  self.swarm_config = swarm_config
303
373
  self.agents_config = swarm_config["agents"]
304
374
  self.entry_point = swarm_config.get("entry_point")
305
-
375
+
306
376
  # Build dependency graph
307
377
  self.dependencies = {}
308
378
  self.dependents = {}
309
-
379
+
310
380
  for agent_id, config in self.agents_config.items():
311
381
  # Dependencies (agents this one waits for)
312
382
  self.dependencies[agent_id] = set(config.get("receives_from", []))
313
-
383
+
314
384
  # Dependents (agents that wait for this one)
315
385
  connections = config.get("connections", [])
316
386
  for conn in connections:
317
387
  if conn not in self.dependents:
318
388
  self.dependents[conn] = set()
319
389
  self.dependents[conn].add(agent_id)
320
-
390
+
321
391
  def route(self, network, call_count, last_result, agent_stack):
322
392
  """Determine next agent to execute."""
323
393
  state = network.state
324
-
394
+
325
395
  # First call - start with entry point or roots
326
396
  if call_count == 0:
327
397
  if self.entry_point:
@@ -333,28 +403,30 @@ class SwarmRouter(DeterministicRouter):
333
403
  if roots:
334
404
  state.current_agent = roots[0]
335
405
  return self._get_agent_class(roots[0], agent_stack)
336
-
406
+
337
407
  # Find next agent to execute
338
408
  for agent_id in self.agents_config:
339
409
  if agent_id in state.completed_agents:
340
410
  continue
341
-
411
+
342
412
  # Check if all dependencies are met
343
413
  deps = self.dependencies.get(agent_id, set())
344
414
  if deps.issubset(state.completed_agents):
345
415
  state.current_agent = agent_id
346
416
  return self._get_agent_class(agent_id, agent_stack)
347
-
417
+
348
418
  # No more agents to execute
349
419
  return None
350
-
351
- def _get_agent_class(self, agent_id: str, agent_stack: List[type[Agent]]) -> type[Agent]:
420
+
421
+ def _get_agent_class(
422
+ self, agent_id: str, agent_stack: List[type[Agent]]
423
+ ) -> type[Agent]:
352
424
  """Get agent class for given agent ID."""
353
425
  # Find matching agent by name
354
426
  for agent_class in agent_stack:
355
427
  if hasattr(agent_class, "name") and agent_class.name == agent_id:
356
428
  return agent_class
357
-
429
+
358
430
  # Not found - this shouldn't happen
359
431
  return None
360
432
 
@@ -362,13 +434,13 @@ class SwarmRouter(DeterministicRouter):
362
434
  @final
363
435
  class SwarmTool(BaseTool):
364
436
  """Tool for executing agent networks using hanzo-agents SDK."""
365
-
437
+
366
438
  @property
367
439
  @override
368
440
  def name(self) -> str:
369
441
  """Get the tool name."""
370
442
  return "swarm"
371
-
443
+
372
444
  @property
373
445
  @override
374
446
  def description(self) -> str:
@@ -406,7 +478,7 @@ Models can be specified as:
406
478
  - CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
407
479
  - Model URIs: 'model://anthropic/claude-3-opus'
408
480
  """
409
-
481
+
410
482
  def __init__(
411
483
  self,
412
484
  permission_manager: PermissionManager,
@@ -421,13 +493,18 @@ Models can be specified as:
421
493
  self.permission_manager = permission_manager
422
494
  # Default to latest Claude Sonnet if no model specified
423
495
  from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
496
+
424
497
  self.model = model or f"anthropic/{get_latest_claude_model()}"
425
- self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("CLAUDE_API_KEY")
498
+ self.api_key = (
499
+ api_key
500
+ or os.environ.get("ANTHROPIC_API_KEY")
501
+ or os.environ.get("CLAUDE_API_KEY")
502
+ )
426
503
  self.base_url = base_url
427
504
  self.max_tokens = max_tokens
428
505
  self.agent_max_iterations = agent_max_iterations
429
506
  self.agent_max_tool_uses = agent_max_tool_uses
430
-
507
+
431
508
  # Set up available tools for agents
432
509
  self.available_tools: list[BaseTool] = []
433
510
  self.available_tools.extend(
@@ -436,16 +513,16 @@ Models can be specified as:
436
513
  self.available_tools.extend(
437
514
  get_read_only_jupyter_tools(self.permission_manager)
438
515
  )
439
-
516
+
440
517
  # Add edit tools
441
518
  self.available_tools.append(Edit(self.permission_manager))
442
519
  self.available_tools.append(MultiEdit(self.permission_manager))
443
-
520
+
444
521
  # Add batch tool
445
522
  self.available_tools.append(
446
523
  BatchTool({t.name: t for t in self.available_tools})
447
524
  )
448
-
525
+
449
526
  @override
450
527
  async def call(
451
528
  self,
@@ -455,7 +532,7 @@ Models can be specified as:
455
532
  """Execute the swarm tool."""
456
533
  tool_ctx = create_tool_context(ctx)
457
534
  await tool_ctx.set_tool_info(self.name)
458
-
535
+
459
536
  # Extract parameters
460
537
  config = params.get("config", {})
461
538
  initial_query = params.get("query", "")
@@ -463,70 +540,76 @@ Models can be specified as:
463
540
  max_concurrent = params.get("max_concurrent", 10)
464
541
  use_memory = params.get("use_memory", False)
465
542
  memory_backend = params.get("memory_backend", "sqlite")
466
-
543
+
467
544
  agents_config = config.get("agents", {})
468
-
545
+
469
546
  if not agents_config:
470
547
  await tool_ctx.error("No agents provided")
471
548
  return "Error: At least one agent must be provided."
472
-
549
+
473
550
  # hanzo-agents SDK is required (already imported above)
474
-
475
- await tool_ctx.info(f"Starting swarm execution with {len(agents_config)} agents using hanzo-agents SDK")
476
-
477
- # Create state
478
- state = SwarmState(
479
- config=config,
480
- initial_query=initial_query,
481
- context=context
551
+
552
+ await tool_ctx.info(
553
+ f"Starting swarm execution with {len(agents_config)} agents using hanzo-agents SDK"
482
554
  )
483
-
555
+
556
+ # Create state
557
+ state = SwarmState(config=config, initial_query=initial_query, context=context)
558
+
484
559
  # Create agent classes dynamically
485
560
  agent_classes = []
486
561
  for agent_id, agent_config in agents_config.items():
487
562
  # Check for CLI agents
488
563
  model = agent_config.get("model", self.model)
489
-
564
+
490
565
  cli_agents = {
491
566
  "claude_cli": ClaudeCodeAgent,
492
567
  "codex_cli": OpenAICodexAgent,
493
568
  "gemini_cli": GeminiAgent,
494
569
  "grok_cli": GrokAgent,
495
570
  }
496
-
571
+
497
572
  if model in cli_agents:
498
573
  # Use CLI agent
499
- agent_class = type(f"Swarm{agent_id}", (cli_agents[model],), {
500
- "name": agent_id,
501
- "description": agent_config.get("role", f"Agent {agent_id}"),
502
- "agent_config": agent_config
503
- })
574
+ agent_class = type(
575
+ f"Swarm{agent_id}",
576
+ (cli_agents[model],),
577
+ {
578
+ "name": agent_id,
579
+ "description": agent_config.get("role", f"Agent {agent_id}"),
580
+ "agent_config": agent_config,
581
+ },
582
+ )
504
583
  else:
505
584
  # Create dynamic SwarmAgent class
506
- agent_class = type(f"Swarm{agent_id}", (SwarmAgent,), {
507
- "name": agent_id,
508
- "__init__": lambda self, aid=agent_id, acfg=agent_config: SwarmAgent.__init__(
509
- self,
510
- agent_id=aid,
511
- agent_config=acfg,
512
- available_tools=self.available_tools,
513
- permission_manager=self.permission_manager,
514
- ctx=ctx
515
- )
516
- })
517
-
585
+ agent_class = type(
586
+ f"Swarm{agent_id}",
587
+ (SwarmAgent,),
588
+ {
589
+ "name": agent_id,
590
+ "__init__": lambda self, aid=agent_id, acfg=agent_config: SwarmAgent.__init__(
591
+ self,
592
+ agent_id=aid,
593
+ agent_config=acfg,
594
+ available_tools=self.available_tools,
595
+ permission_manager=self.permission_manager,
596
+ ctx=ctx,
597
+ ),
598
+ },
599
+ )
600
+
518
601
  agent_classes.append(agent_class)
519
-
602
+
520
603
  # Create memory if requested
521
604
  memory_kv = None
522
605
  memory_vector = None
523
606
  if use_memory:
524
607
  memory_kv = create_memory_kv(memory_backend)
525
608
  memory_vector = create_memory_vector("simple")
526
-
609
+
527
610
  # Create router
528
611
  router = SwarmRouter(config)
529
-
612
+
530
613
  # Create network
531
614
  network = Network(
532
615
  state=state,
@@ -536,75 +619,77 @@ Models can be specified as:
536
619
  memory_vector=memory_vector,
537
620
  max_steps=self.agent_max_iterations * len(agents_config),
538
621
  )
539
-
622
+
540
623
  # Execute
541
624
  try:
542
625
  final_state = await network.run()
543
-
626
+
544
627
  # Format results
545
628
  return self._format_network_results(
546
629
  agents_config,
547
630
  final_state.agent_results,
548
631
  final_state.execution_order,
549
- config.get("entry_point")
632
+ config.get("entry_point"),
550
633
  )
551
-
634
+
552
635
  except Exception as e:
553
636
  await tool_ctx.error(f"Swarm execution failed: {str(e)}")
554
637
  return f"Error: {str(e)}"
555
-
638
+
556
639
  def _format_network_results(
557
640
  self,
558
641
  agents_config: Dict[str, Any],
559
642
  results: Dict[str, str],
560
643
  execution_order: List[str],
561
- entry_point: Optional[str]
644
+ entry_point: Optional[str],
562
645
  ) -> str:
563
646
  """Format results from agent network execution."""
564
647
  output = ["Agent Network Execution Results (hanzo-agents SDK)"]
565
648
  output.append("=" * 80)
566
649
  output.append(f"Total agents: {len(agents_config)}")
567
650
  output.append(f"Completed: {len(results)}")
568
- output.append(f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}")
569
-
651
+ output.append(
652
+ f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}"
653
+ )
654
+
570
655
  if entry_point:
571
656
  output.append(f"Entry point: {entry_point}")
572
-
657
+
573
658
  output.append(f"\nExecution Order: {' → '.join(execution_order)}")
574
659
  output.append("-" * 40)
575
-
660
+
576
661
  # Detailed results
577
662
  output.append("\n\nDetailed Results:")
578
663
  output.append("=" * 80)
579
-
664
+
580
665
  for agent_id in execution_order:
581
666
  if agent_id in results:
582
667
  config = agents_config.get(agent_id, {})
583
668
  role = config.get("role", "Agent")
584
669
  model = config.get("model", "default")
585
-
670
+
586
671
  output.append(f"\n### {agent_id} ({role}) [{model}]")
587
672
  output.append("-" * 40)
588
-
673
+
589
674
  result = results[agent_id]
590
675
  if result.startswith("Error:"):
591
676
  output.append(result)
592
677
  else:
593
678
  # Show first part of result
594
- lines = result.split('\n')
679
+ lines = result.split("\n")
595
680
  preview_lines = lines[:10]
596
681
  output.extend(preview_lines)
597
-
682
+
598
683
  if len(lines) > 10:
599
684
  output.append(f"... ({len(lines) - 10} more lines)")
600
-
685
+
601
686
  return "\n".join(output)
602
-
687
+
603
688
  @override
604
689
  def register(self, mcp_server: FastMCP) -> None:
605
690
  """Register this swarm tool with the MCP server."""
606
691
  tool_self = self
607
-
692
+
608
693
  @mcp_server.tool(name=self.name, description=self.description)
609
694
  async def swarm(
610
695
  ctx: MCPContext,
@@ -613,15 +698,15 @@ Models can be specified as:
613
698
  context: Optional[str] = None,
614
699
  max_concurrent: int = 10,
615
700
  use_memory: bool = False,
616
- memory_backend: str = "sqlite"
701
+ memory_backend: str = "sqlite",
617
702
  ) -> str:
618
703
  # Convert to typed format
619
704
  typed_config = SwarmConfig(
620
705
  agents=config.get("agents", {}),
621
706
  entry_point=config.get("entry_point"),
622
- topology=config.get("topology")
707
+ topology=config.get("topology"),
623
708
  )
624
-
709
+
625
710
  return await tool_self.call(
626
711
  ctx,
627
712
  config=typed_config,
@@ -629,5 +714,5 @@ Models can be specified as:
629
714
  context=context,
630
715
  max_concurrent=max_concurrent,
631
716
  use_memory=use_memory,
632
- memory_backend=memory_backend
633
- )
717
+ memory_backend=memory_backend,
718
+ )