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

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

Potentially problematic release.


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

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