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,577 +0,0 @@
1
- """Swarm tool implementation for parallel and hierarchical agent execution.
2
-
3
- This module implements the SwarmTool that enables both parallel execution of multiple
4
- agent instances and hierarchical workflows with specialized roles.
5
- """
6
-
7
- import os
8
- import asyncio
9
- from typing import (
10
- Any,
11
- Dict,
12
- List,
13
- Unpack,
14
- Optional,
15
- TypedDict,
16
- final,
17
- override,
18
- )
19
-
20
- from mcp.server import FastMCP
21
- from mcp.server.fastmcp import Context as MCPContext
22
-
23
- from hanzo_mcp.tools.common.base import BaseTool
24
- from hanzo_mcp.tools.common.context import create_tool_context
25
- from hanzo_mcp.tools.agent.agent_tool import AgentTool
26
- from hanzo_mcp.tools.common.permissions import PermissionManager
27
-
28
-
29
- class AgentNode(TypedDict):
30
- """Node in the agent network.
31
-
32
- Attributes:
33
- id: Unique identifier for this agent
34
- query: The specific query/task for this agent
35
- model: Optional model override (e.g., 'claude-3-5-sonnet', 'gpt-4o')
36
- role: Optional role description (e.g., 'architect', 'frontend', 'reviewer')
37
- connections: List of agent IDs this agent connects to (sends results to)
38
- receives_from: Optional list of agent IDs this agent receives input from
39
- file_path: Optional specific file for the agent to work on
40
- """
41
-
42
- id: str
43
- query: str
44
- model: Optional[str]
45
- role: Optional[str]
46
- connections: Optional[List[str]]
47
- receives_from: Optional[List[str]]
48
- file_path: Optional[str]
49
-
50
-
51
- class SwarmConfig(TypedDict):
52
- """Configuration for an agent network.
53
-
54
- Attributes:
55
- agents: Dictionary of agent configurations keyed by ID
56
- entry_point: ID of the first agent to execute (optional, defaults to finding roots)
57
- topology: Optional topology type (tree, dag, pipeline, star, mesh)
58
- """
59
-
60
- agents: Dict[str, AgentNode]
61
- entry_point: Optional[str]
62
- topology: Optional[str]
63
-
64
-
65
- class SwarmToolParams(TypedDict):
66
- """Parameters for the SwarmTool.
67
-
68
- Attributes:
69
- config: Agent network configuration
70
- query: Initial query to send to entry point agent(s)
71
- context: Optional context shared by all agents
72
- max_concurrent: Maximum number of concurrent agents (default: 10)
73
- """
74
-
75
- config: SwarmConfig
76
- query: str
77
- context: Optional[str]
78
- max_concurrent: Optional[int]
79
-
80
-
81
- @final
82
- class SwarmTool(BaseTool):
83
- """Tool for executing multiple agent tasks in parallel.
84
-
85
- The SwarmTool enables efficient parallel processing of multiple files or tasks
86
- by spawning independent agent instances for each task.
87
- """
88
-
89
- @property
90
- @override
91
- def name(self) -> str:
92
- """Get the tool name."""
93
- return "swarm"
94
-
95
- @property
96
- @override
97
- def description(self) -> str:
98
- """Get the tool description."""
99
- return """Execute a network of AI agents with flexible connection topologies.
100
-
101
- This tool enables sophisticated agent orchestration where agents can be connected
102
- in various network patterns. Each agent can pass results to connected agents,
103
- enabling complex workflows.
104
-
105
- Features:
106
- - Flexible agent networks (tree, DAG, pipeline, star, mesh)
107
- - Each agent can use different models (Claude, GPT-4, Gemini, etc.)
108
- - Agents automatically pass results to connected agents
109
- - Parallel execution with dependency management
110
- - Full editing capabilities for each agent
111
-
112
- Common Topologies:
113
-
114
- 1. Tree (Architect pattern):
115
- architect → [frontend, backend, database] → reviewer
116
-
117
- 2. Pipeline (Sequential processing):
118
- analyzer → planner → implementer → tester → reviewer
119
-
120
- 3. Star (Central coordinator):
121
- coordinator ← → [agent1, agent2, agent3, agent4]
122
-
123
- 4. DAG (Complex dependencies):
124
- Multiple agents with custom connections
125
-
126
- Usage Example:
127
-
128
- swarm(
129
- config={
130
- "agents": {
131
- "architect": {
132
- "id": "architect",
133
- "query": "Analyze codebase and create refactoring plan",
134
- "model": "claude-3-5-sonnet",
135
- "connections": ["frontend", "backend", "database"]
136
- },
137
- "frontend": {
138
- "id": "frontend",
139
- "query": "Refactor UI components based on architect's plan",
140
- "role": "Frontend Developer",
141
- "connections": ["reviewer"]
142
- },
143
- "backend": {
144
- "id": "backend",
145
- "query": "Refactor API endpoints based on architect's plan",
146
- "role": "Backend Developer",
147
- "connections": ["reviewer"]
148
- },
149
- "database": {
150
- "id": "database",
151
- "query": "Optimize database schema based on architect's plan",
152
- "role": "Database Expert",
153
- "connections": ["reviewer"]
154
- },
155
- "reviewer": {
156
- "id": "reviewer",
157
- "query": "Review all changes and ensure consistency",
158
- "model": "gpt-4o",
159
- "receives_from": ["frontend", "backend", "database"]
160
- }
161
- },
162
- "entry_point": "architect"
163
- },
164
- query="Refactor the authentication system for better security and performance"
165
- )
166
-
167
- Models can be specified as:
168
- - Full: 'anthropic/claude-3-5-sonnet-20241022'
169
- - Short: 'claude-3-5-sonnet', 'gpt-4o', 'gemini-1.5-pro'
170
- - CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
171
- """
172
-
173
- def __init__(
174
- self,
175
- permission_manager: PermissionManager,
176
- model: str | None = None,
177
- api_key: str | None = None,
178
- base_url: str | None = None,
179
- max_tokens: int | None = None,
180
- agent_max_iterations: int = 10,
181
- agent_max_tool_uses: int = 30,
182
- ):
183
- """Initialize the swarm tool.
184
-
185
- Args:
186
- permission_manager: Permission manager for access control
187
- model: Optional model name override (defaults to Claude Sonnet)
188
- api_key: Optional API key for the model provider
189
- base_url: Optional base URL for the model provider
190
- max_tokens: Optional maximum tokens for model responses
191
- agent_max_iterations: Max iterations per agent (default: 10)
192
- agent_max_tool_uses: Max tool uses per agent (default: 30)
193
- """
194
- self.permission_manager = permission_manager
195
- # Default to latest Claude Sonnet if no model specified
196
- from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
197
-
198
- self.model = model or f"anthropic/{get_latest_claude_model()}"
199
- self.api_key = (
200
- api_key
201
- or os.environ.get("ANTHROPIC_API_KEY")
202
- or os.environ.get("CLAUDE_API_KEY")
203
- )
204
- self.base_url = base_url
205
- self.max_tokens = max_tokens
206
- self.agent_max_iterations = agent_max_iterations
207
- self.agent_max_tool_uses = agent_max_tool_uses
208
-
209
- @override
210
- async def call(
211
- self,
212
- ctx: MCPContext,
213
- **params: Unpack[SwarmToolParams],
214
- ) -> str:
215
- """Execute the swarm tool.
216
-
217
- Args:
218
- ctx: MCP context
219
- **params: Tool parameters
220
-
221
- Returns:
222
- Combined results from all agents
223
- """
224
- tool_ctx = create_tool_context(ctx)
225
- await tool_ctx.set_tool_info(self.name)
226
-
227
- # Extract parameters
228
- agents = params.get("agents", [])
229
- manager_query = params.get("manager_query")
230
- reviewer_query = params.get("reviewer_query")
231
- common_context = params.get("common_context", "")
232
- max_concurrent = params.get("max_concurrent", 10)
233
-
234
- if not agents:
235
- await tool_ctx.error("No agents provided")
236
- return "Error: At least one agent must be provided."
237
-
238
- # Extract parameters
239
- config = params.get("config", {})
240
- initial_query = params.get("query", "")
241
- context = params.get("context", "")
242
-
243
- agents_config = config.get("agents", {})
244
- entry_point = config.get("entry_point")
245
-
246
- await tool_ctx.info(
247
- f"Starting swarm execution with {len(agents_config)} agents"
248
- )
249
-
250
- # Build agent network
251
- agent_instances = {}
252
- agent_results = {}
253
- execution_queue = asyncio.Queue()
254
- completed_agents = set()
255
-
256
- # Create agent instances
257
- for agent_id, agent_config in agents_config.items():
258
- model = agent_config.get("model", self.model)
259
-
260
- # Support CLI tools
261
- cli_tools = {
262
- "claude_cli": self._get_cli_tool("claude_cli"),
263
- "codex_cli": self._get_cli_tool("codex_cli"),
264
- "gemini_cli": self._get_cli_tool("gemini_cli"),
265
- "grok_cli": self._get_cli_tool("grok_cli"),
266
- }
267
-
268
- if model in cli_tools:
269
- agent = cli_tools[model]
270
- else:
271
- # Regular agent with model
272
- agent = AgentTool(
273
- permission_manager=self.permission_manager,
274
- model=self._normalize_model(model),
275
- api_key=self.api_key,
276
- base_url=self.base_url,
277
- max_tokens=self.max_tokens,
278
- max_iterations=self.agent_max_iterations,
279
- max_tool_uses=self.agent_max_tool_uses,
280
- )
281
-
282
- agent_instances[agent_id] = agent
283
-
284
- # Find entry points (agents with no incoming connections)
285
- if entry_point:
286
- await execution_queue.put((entry_point, initial_query, {}))
287
- else:
288
- # Find root agents (no receives_from)
289
- roots = []
290
- for agent_id, agent_config in agents_config.items():
291
- if not agent_config.get("receives_from"):
292
- # Check if any other agent connects to this one
293
- has_incoming = False
294
- for other_config in agents_config.values():
295
- if (
296
- other_config.get("connections")
297
- and agent_id in other_config["connections"]
298
- ):
299
- has_incoming = True
300
- break
301
- if not has_incoming:
302
- roots.append(agent_id)
303
-
304
- if not roots:
305
- await tool_ctx.error("No entry point found in agent network")
306
- return "Error: Could not determine entry point for agent network"
307
-
308
- for root in roots:
309
- await execution_queue.put((root, initial_query, {}))
310
-
311
- # Execute agents in network order
312
- async def execute_agent(
313
- agent_id: str, query: str, inputs: Dict[str, str]
314
- ) -> str:
315
- """Execute a single agent in the network."""
316
- async with semaphore:
317
- try:
318
- agent_config = agents_config[agent_id]
319
- agent = agent_instances[agent_id]
320
-
321
- await tool_ctx.info(
322
- f"Executing agent: {agent_id} ({agent_config.get('role', 'Agent')})"
323
- )
324
-
325
- # Build prompt with context and inputs
326
- prompt_parts = []
327
-
328
- # Add role context
329
- if agent_config.get("role"):
330
- prompt_parts.append(f"Your role: {agent_config['role']}")
331
-
332
- # Add shared context
333
- if context:
334
- prompt_parts.append(f"Context:\n{context}")
335
-
336
- # Add inputs from connected agents
337
- if inputs:
338
- prompt_parts.append("Input from previous agents:")
339
- for input_agent, input_result in inputs.items():
340
- prompt_parts.append(
341
- f"\n--- From {input_agent} ---\n{input_result}"
342
- )
343
-
344
- # Add file context if specified
345
- if agent_config.get("file_path"):
346
- prompt_parts.append(
347
- f"\nFile to work on: {agent_config['file_path']}"
348
- )
349
-
350
- # Add the main query
351
- prompt_parts.append(f"\nTask: {agent_config['query']}")
352
-
353
- # Combine query with initial query if this is entry point
354
- if query and query != agent_config["query"]:
355
- prompt_parts.append(f"\nMain objective: {query}")
356
-
357
- full_prompt = "\n\n".join(prompt_parts)
358
-
359
- # Execute the agent
360
- result = await agent.call(ctx, prompts=full_prompt)
361
-
362
- await tool_ctx.info(f"Agent {agent_id} completed")
363
- return result
364
-
365
- except Exception as e:
366
- error_msg = f"Agent {agent_id} failed: {str(e)}"
367
- await tool_ctx.error(error_msg)
368
- return f"Error: {error_msg}"
369
-
370
- # Process agent network
371
- running_tasks = set()
372
-
373
- while not execution_queue.empty() or running_tasks:
374
- # Start new tasks up to concurrency limit
375
- while not execution_queue.empty() and len(running_tasks) < max_concurrent:
376
- agent_id, query, inputs = await execution_queue.get()
377
-
378
- if agent_id not in completed_agents:
379
- # Check if all dependencies are met
380
- agent_config = agents_config[agent_id]
381
- receives_from = agent_config.get("receives_from", [])
382
-
383
- # Collect inputs from dependencies
384
- ready = True
385
- for dep in receives_from:
386
- if dep not in agent_results:
387
- ready = False
388
- # Re-queue for later
389
- await execution_queue.put((agent_id, query, inputs))
390
- break
391
- else:
392
- inputs[dep] = agent_results[dep]
393
-
394
- if ready:
395
- # Execute agent
396
- task = asyncio.create_task(
397
- execute_agent(agent_id, query, inputs)
398
- )
399
- running_tasks.add(task)
400
-
401
- async def handle_completion(task, agent_id=agent_id):
402
- result = await task
403
- agent_results[agent_id] = result
404
- completed_agents.add(agent_id)
405
- running_tasks.discard(task)
406
-
407
- # Queue connected agents
408
- agent_config = agents_config[agent_id]
409
- connections = agent_config.get("connections", [])
410
- for next_agent in connections:
411
- if next_agent in agents_config:
412
- await execution_queue.put(
413
- (next_agent, "", {agent_id: result})
414
- )
415
-
416
- asyncio.create_task(handle_completion(task))
417
-
418
- # Wait a bit if we're at capacity
419
- if running_tasks:
420
- await asyncio.sleep(0.1)
421
-
422
- # Wait for all tasks to complete
423
- if running_tasks:
424
- await asyncio.gather(*running_tasks, return_exceptions=True)
425
-
426
- # Format results
427
- return self._format_network_results(agents_config, agent_results, entry_point)
428
-
429
- def _normalize_model(self, model: str) -> str:
430
- """Normalize model names to full format."""
431
- model_map = {
432
- "claude-3-5-sonnet": "anthropic/claude-3-5-sonnet-20241022",
433
- "claude-3-opus": "anthropic/claude-3-opus-20240229",
434
- "gpt-4o": "openai/gpt-4o",
435
- "gpt-4": "openai/gpt-4",
436
- "gemini-1.5-pro": "google/gemini-1.5-pro",
437
- "gemini-1.5-flash": "google/gemini-1.5-flash",
438
- }
439
- return model_map.get(model, model)
440
-
441
- def _get_cli_tool(self, tool_name: str):
442
- """Get CLI tool instance."""
443
- # Import here to avoid circular imports
444
- if tool_name == "claude_cli":
445
- from hanzo_mcp.tools.agent.claude_cli_tool import ClaudeCLITool
446
-
447
- return ClaudeCLITool(self.permission_manager)
448
- elif tool_name == "codex_cli":
449
- from hanzo_mcp.tools.agent.codex_cli_tool import CodexCLITool
450
-
451
- return CodexCLITool(self.permission_manager)
452
- elif tool_name == "gemini_cli":
453
- from hanzo_mcp.tools.agent.gemini_cli_tool import GeminiCLITool
454
-
455
- return GeminiCLITool(self.permission_manager)
456
- elif tool_name == "grok_cli":
457
- from hanzo_mcp.tools.agent.grok_cli_tool import GrokCLITool
458
-
459
- return GrokCLITool(self.permission_manager)
460
- return None
461
-
462
- def _format_network_results(
463
- self,
464
- agents_config: Dict[str, Any],
465
- results: Dict[str, str],
466
- entry_point: Optional[str],
467
- ) -> str:
468
- """Format results from agent network execution."""
469
- output = ["Agent Network Execution Results"]
470
- output.append("=" * 80)
471
- output.append(f"Total agents: {len(agents_config)}")
472
- output.append(f"Completed: {len(results)}")
473
- output.append(
474
- f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}"
475
- )
476
-
477
- if entry_point:
478
- output.append(f"Entry point: {entry_point}")
479
-
480
- output.append("\nExecution Flow:")
481
- output.append("-" * 40)
482
-
483
- # Show results in execution order
484
- def format_agent_tree(agent_id: str, level: int = 0) -> List[str]:
485
- lines = []
486
- indent = " " * level
487
-
488
- if agent_id in agents_config:
489
- config = agents_config[agent_id]
490
- role = config.get("role", "Agent")
491
- model = config.get("model", "default")
492
-
493
- status = (
494
- "✅"
495
- if agent_id in results
496
- and not results[agent_id].startswith("Error:")
497
- else "❌"
498
- )
499
- lines.append(f"{indent}{status} {agent_id} ({role}) [{model}]")
500
-
501
- # Show connections
502
- connections = config.get("connections", [])
503
- for conn in connections:
504
- if conn in agents_config:
505
- lines.extend(format_agent_tree(conn, level + 1))
506
-
507
- return lines
508
-
509
- # Start from entry point or roots
510
- if entry_point:
511
- output.extend(format_agent_tree(entry_point))
512
- else:
513
- # Find roots
514
- roots = []
515
- for agent_id in agents_config:
516
- has_incoming = False
517
- for config in agents_config.values():
518
- if config.get("connections") and agent_id in config["connections"]:
519
- has_incoming = True
520
- break
521
- if not has_incoming:
522
- roots.append(agent_id)
523
-
524
- for root in roots:
525
- output.extend(format_agent_tree(root))
526
-
527
- # Detailed results
528
- output.append("\n\nDetailed Results:")
529
- output.append("=" * 80)
530
-
531
- for agent_id, result in results.items():
532
- config = agents_config.get(agent_id, {})
533
- role = config.get("role", "Agent")
534
-
535
- output.append(f"\n### {agent_id} ({role})")
536
- output.append("-" * 40)
537
-
538
- if result.startswith("Error:"):
539
- output.append(result)
540
- else:
541
- # Show first part of result
542
- lines = result.split("\n")
543
- preview_lines = lines[:10]
544
- output.extend(preview_lines)
545
-
546
- if len(lines) > 10:
547
- output.append(f"... ({len(lines) - 10} more lines)")
548
-
549
- return "\n".join(output)
550
-
551
- @override
552
- def register(self, mcp_server: FastMCP) -> None:
553
- """Register this swarm tool with the MCP server."""
554
- tool_self = self
555
-
556
- @mcp_server.tool(name=self.name, description=self.description)
557
- async def swarm(
558
- ctx: MCPContext,
559
- config: dict[str, Any],
560
- query: str,
561
- context: Optional[str] = None,
562
- max_concurrent: int = 10,
563
- ) -> str:
564
- # Convert to typed format
565
- typed_config = SwarmConfig(
566
- agents=config.get("agents", {}),
567
- entry_point=config.get("entry_point"),
568
- topology=config.get("topology"),
569
- )
570
-
571
- return await tool_self.call(
572
- ctx,
573
- config=typed_config,
574
- query=query,
575
- context=context,
576
- max_concurrent=max_concurrent,
577
- )