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