hanzo-mcp 0.8.8__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

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