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