hanzo-mcp 0.7.3__py3-none-any.whl → 0.7.6__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/cli.py +10 -0
- hanzo_mcp/prompts/__init__.py +43 -0
- hanzo_mcp/prompts/example_custom_prompt.py +40 -0
- hanzo_mcp/prompts/tool_explorer.py +603 -0
- hanzo_mcp/tools/__init__.py +52 -51
- hanzo_mcp/tools/agent/__init__.py +3 -16
- hanzo_mcp/tools/agent/agent_tool.py +365 -525
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +641 -0
- hanzo_mcp/tools/agent/network_tool.py +3 -5
- hanzo_mcp/tools/agent/swarm_tool.py +447 -349
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +535 -0
- hanzo_mcp/tools/agent/tool_adapter.py +21 -2
- hanzo_mcp/tools/common/forgiving_edit.py +24 -14
- hanzo_mcp/tools/common/permissions.py +8 -0
- hanzo_mcp/tools/filesystem/__init__.py +5 -5
- hanzo_mcp/tools/filesystem/{symbols.py → ast_tool.py} +8 -8
- hanzo_mcp/tools/filesystem/batch_search.py +2 -2
- hanzo_mcp/tools/filesystem/directory_tree.py +8 -1
- hanzo_mcp/tools/filesystem/find.py +1 -0
- hanzo_mcp/tools/filesystem/grep.py +11 -2
- hanzo_mcp/tools/filesystem/read.py +8 -1
- hanzo_mcp/tools/filesystem/search_tool.py +1 -1
- hanzo_mcp/tools/jupyter/__init__.py +5 -1
- hanzo_mcp/tools/jupyter/base.py +2 -2
- hanzo_mcp/tools/jupyter/jupyter.py +89 -18
- hanzo_mcp/tools/search/find_tool.py +49 -8
- hanzo_mcp/tools/shell/base_process.py +7 -1
- hanzo_mcp/tools/shell/streaming_command.py +34 -1
- {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.6.dist-info}/METADATA +7 -1
- {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.6.dist-info}/RECORD +33 -31
- hanzo_mcp/tools/agent/agent_tool_v2.py +0 -492
- hanzo_mcp/tools/agent/swarm_tool_v2.py +0 -654
- {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.6.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.6.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -1,124 +1,283 @@
|
|
|
1
|
-
"""Agent tool implementation
|
|
1
|
+
"""Agent tool implementation using hanzo-agents SDK.
|
|
2
2
|
|
|
3
|
-
This module implements the AgentTool that
|
|
4
|
-
|
|
3
|
+
This module implements the AgentTool that leverages the hanzo-agents SDK
|
|
4
|
+
for sophisticated agent orchestration and execution.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import json
|
|
9
|
+
import os
|
|
9
10
|
import re
|
|
10
11
|
import time
|
|
11
|
-
from
|
|
12
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
12
|
+
from typing import Annotated, TypedDict, Unpack, final, override, Optional, Dict, Any, List
|
|
13
13
|
|
|
14
|
-
# Import litellm with warnings suppressed
|
|
15
|
-
import warnings
|
|
16
|
-
with warnings.catch_warnings():
|
|
17
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
18
|
-
import litellm
|
|
19
14
|
from mcp.server.fastmcp import Context as MCPContext
|
|
20
15
|
from mcp.server import FastMCP
|
|
21
|
-
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionToolParam
|
|
22
16
|
from pydantic import Field
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
18
|
+
# Import hanzo-agents SDK
|
|
19
|
+
try:
|
|
20
|
+
from hanzo_agents import (
|
|
21
|
+
Agent, State, Network, Tool, History,
|
|
22
|
+
ModelRegistry, InferenceResult, ToolCall,
|
|
23
|
+
create_memory_kv, create_memory_vector,
|
|
24
|
+
sequential_router, state_based_router,
|
|
25
|
+
)
|
|
26
|
+
from hanzo_agents.core.cli_agent import (
|
|
27
|
+
ClaudeCodeAgent, OpenAICodexAgent,
|
|
28
|
+
GeminiAgent, GrokAgent
|
|
29
|
+
)
|
|
30
|
+
HANZO_AGENTS_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
HANZO_AGENTS_AVAILABLE = False
|
|
33
|
+
# Define stub classes when hanzo-agents is not available
|
|
34
|
+
class State:
|
|
35
|
+
"""Stub State class when hanzo-agents is not available."""
|
|
36
|
+
def __init__(self):
|
|
37
|
+
pass
|
|
38
|
+
def to_dict(self):
|
|
39
|
+
return {}
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_dict(cls, data):
|
|
42
|
+
return cls()
|
|
43
|
+
|
|
44
|
+
class Tool:
|
|
45
|
+
"""Stub Tool class when hanzo-agents is not available."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
class Agent:
|
|
49
|
+
"""Stub Agent class when hanzo-agents is not available."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
class Network:
|
|
53
|
+
"""Stub Network class when hanzo-agents is not available."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
class History:
|
|
57
|
+
"""Stub History class when hanzo-agents is not available."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
class InferenceResult:
|
|
61
|
+
"""Stub InferenceResult class when hanzo-agents is not available."""
|
|
62
|
+
def __init__(self, agent=None, content=None, metadata=None):
|
|
63
|
+
self.agent = agent
|
|
64
|
+
self.content = content
|
|
65
|
+
self.metadata = metadata or {}
|
|
66
|
+
|
|
41
67
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
42
|
-
from hanzo_mcp.tools.common.
|
|
43
|
-
from hanzo_mcp.tools.common.context import (
|
|
44
|
-
ToolContext,
|
|
45
|
-
create_tool_context,
|
|
46
|
-
)
|
|
68
|
+
from hanzo_mcp.tools.common.context import ToolContext, create_tool_context
|
|
47
69
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
48
70
|
from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools, Edit, MultiEdit
|
|
49
71
|
from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
),
|
|
57
|
-
]
|
|
72
|
+
from hanzo_mcp.tools.common.batch_tool import BatchTool
|
|
73
|
+
from hanzo_mcp.tools.agent.clarification_protocol import AgentClarificationMixin, ClarificationType
|
|
74
|
+
from hanzo_mcp.tools.agent.clarification_tool import ClarificationTool
|
|
75
|
+
from hanzo_mcp.tools.agent.critic_tool import CriticTool
|
|
76
|
+
from hanzo_mcp.tools.agent.review_tool import ReviewTool
|
|
77
|
+
from hanzo_mcp.tools.agent.iching_tool import IChingTool
|
|
58
78
|
|
|
59
79
|
|
|
60
80
|
class AgentToolParams(TypedDict, total=False):
|
|
61
|
-
"""Parameters for the AgentTool.
|
|
81
|
+
"""Parameters for the AgentTool."""
|
|
82
|
+
prompts: str | list[str]
|
|
83
|
+
model: Optional[str]
|
|
84
|
+
use_memory: Optional[bool]
|
|
85
|
+
memory_backend: Optional[str]
|
|
62
86
|
|
|
63
|
-
Attributes:
|
|
64
|
-
prompts: Task(s) for the agent to perform (must include absolute paths starting with /)
|
|
65
|
-
"""
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
class MCPAgentState(State):
|
|
89
|
+
"""State for MCP agents."""
|
|
90
|
+
|
|
91
|
+
def __init__(self, prompts: List[str], context: Dict[str, Any]):
|
|
92
|
+
"""Initialize agent state."""
|
|
93
|
+
super().__init__()
|
|
94
|
+
self.prompts = prompts
|
|
95
|
+
self.context = context
|
|
96
|
+
self.current_prompt_index = 0
|
|
97
|
+
self.results = []
|
|
98
|
+
|
|
99
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
100
|
+
"""Convert to dictionary."""
|
|
101
|
+
base_dict = super().to_dict()
|
|
102
|
+
base_dict.update({
|
|
103
|
+
"prompts": self.prompts,
|
|
104
|
+
"context": self.context,
|
|
105
|
+
"current_prompt_index": self.current_prompt_index,
|
|
106
|
+
"results": self.results
|
|
107
|
+
})
|
|
108
|
+
return base_dict
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_dict(cls, data: Dict[str, Any]) -> "MCPAgentState":
|
|
112
|
+
"""Create from dictionary."""
|
|
113
|
+
state = cls(
|
|
114
|
+
prompts=data.get("prompts", []),
|
|
115
|
+
context=data.get("context", {})
|
|
116
|
+
)
|
|
117
|
+
state.current_prompt_index = data.get("current_prompt_index", 0)
|
|
118
|
+
state.results = data.get("results", [])
|
|
119
|
+
for k, v in data.items():
|
|
120
|
+
if k not in ["prompts", "context", "current_prompt_index", "results"]:
|
|
121
|
+
state[k] = v
|
|
122
|
+
return state
|
|
68
123
|
|
|
69
124
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
125
|
+
class MCPToolAdapter(Tool):
|
|
126
|
+
"""Adapter to wrap MCP tools for hanzo-agents."""
|
|
127
|
+
|
|
128
|
+
def __init__(self, mcp_tool: BaseTool, ctx: MCPContext):
|
|
129
|
+
"""Initialize adapter."""
|
|
130
|
+
self.mcp_tool = mcp_tool
|
|
131
|
+
self.ctx = ctx
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def name(self) -> str:
|
|
135
|
+
"""Get tool name."""
|
|
136
|
+
return self.mcp_tool.name
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def description(self) -> str:
|
|
140
|
+
"""Get tool description."""
|
|
141
|
+
return self.mcp_tool.description
|
|
142
|
+
|
|
143
|
+
async def execute(self, **kwargs) -> str:
|
|
144
|
+
"""Execute the MCP tool."""
|
|
145
|
+
return await self.mcp_tool.call(self.ctx, **kwargs)
|
|
146
|
+
|
|
73
147
|
|
|
74
|
-
|
|
75
|
-
|
|
148
|
+
class MCPAgent(Agent):
|
|
149
|
+
"""Agent that executes MCP tasks."""
|
|
150
|
+
|
|
151
|
+
name = "mcp_agent"
|
|
152
|
+
description = "Agent for executing MCP tasks"
|
|
153
|
+
|
|
154
|
+
def __init__(self,
|
|
155
|
+
available_tools: List[BaseTool],
|
|
156
|
+
permission_manager: PermissionManager,
|
|
157
|
+
ctx: MCPContext,
|
|
158
|
+
model: str = "model://anthropic/claude-3-5-sonnet-20241022",
|
|
159
|
+
**kwargs):
|
|
160
|
+
"""Initialize MCP agent."""
|
|
161
|
+
super().__init__(model=model, **kwargs)
|
|
162
|
+
|
|
163
|
+
self.available_tools = available_tools
|
|
164
|
+
self.permission_manager = permission_manager
|
|
165
|
+
self.ctx = ctx
|
|
166
|
+
|
|
167
|
+
# Register MCP tools as agent tools
|
|
168
|
+
for mcp_tool in available_tools:
|
|
169
|
+
adapter = MCPToolAdapter(mcp_tool, ctx)
|
|
170
|
+
self.register_tool(adapter)
|
|
171
|
+
|
|
172
|
+
async def run(self, state: MCPAgentState, history: History, network: Network) -> InferenceResult:
|
|
173
|
+
"""Execute the agent."""
|
|
174
|
+
# Get current prompt
|
|
175
|
+
if state.current_prompt_index >= len(state.prompts):
|
|
176
|
+
return InferenceResult(
|
|
177
|
+
agent=self.name,
|
|
178
|
+
content="All prompts completed",
|
|
179
|
+
metadata={"completed": True}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
prompt = state.prompts[state.current_prompt_index]
|
|
183
|
+
|
|
184
|
+
# Execute with tools
|
|
185
|
+
messages = [
|
|
186
|
+
{"role": "system", "content": self._get_system_prompt()},
|
|
187
|
+
{"role": "user", "content": prompt}
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
# Add history context
|
|
191
|
+
for entry in history[-10:]:
|
|
192
|
+
if entry.role == "assistant":
|
|
193
|
+
messages.append({
|
|
194
|
+
"role": "assistant",
|
|
195
|
+
"content": entry.content
|
|
196
|
+
})
|
|
197
|
+
elif entry.role == "user":
|
|
198
|
+
messages.append({
|
|
199
|
+
"role": "user",
|
|
200
|
+
"content": entry.content
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
# Call model
|
|
204
|
+
from hanzo_agents import ModelRegistry
|
|
205
|
+
adapter = ModelRegistry.get_adapter(self.model)
|
|
206
|
+
response = await adapter.chat(messages)
|
|
207
|
+
|
|
208
|
+
# Update state
|
|
209
|
+
state.current_prompt_index += 1
|
|
210
|
+
state.results.append(response)
|
|
211
|
+
|
|
212
|
+
# Return result
|
|
213
|
+
return InferenceResult(
|
|
214
|
+
agent=self.name,
|
|
215
|
+
content=response,
|
|
216
|
+
metadata={
|
|
217
|
+
"prompt_index": state.current_prompt_index - 1,
|
|
218
|
+
"total_prompts": len(state.prompts)
|
|
219
|
+
}
|
|
220
|
+
)
|
|
76
221
|
|
|
77
|
-
|
|
78
|
-
|
|
222
|
+
def _get_system_prompt(self) -> str:
|
|
223
|
+
"""Get system prompt for the agent."""
|
|
224
|
+
tool_descriptions = []
|
|
225
|
+
for tool in self.tools.values():
|
|
226
|
+
tool_descriptions.append(f"- {tool.name}: {tool.description}")
|
|
227
|
+
|
|
228
|
+
return f"""You are an AI assistant with access to the following tools:
|
|
229
|
+
|
|
230
|
+
{chr(10).join(tool_descriptions)}
|
|
231
|
+
|
|
232
|
+
When you need to use a tool, respond with:
|
|
233
|
+
TOOL: tool_name(arg1="value1", arg2="value2")
|
|
234
|
+
|
|
235
|
+
Important guidelines:
|
|
236
|
+
- Always include absolute paths starting with / when working with files
|
|
237
|
+
- Be thorough in your searches and analysis
|
|
238
|
+
- Provide clear, actionable results
|
|
239
|
+
- Edit files when requested to make changes
|
|
240
|
+
"""
|
|
241
|
+
|
|
79
242
|
|
|
243
|
+
@final
|
|
244
|
+
class AgentTool(AgentClarificationMixin, BaseTool):
|
|
245
|
+
"""Tool for delegating tasks to sub-agents using hanzo-agents SDK."""
|
|
246
|
+
|
|
80
247
|
@property
|
|
81
248
|
@override
|
|
82
249
|
def name(self) -> str:
|
|
83
|
-
"""Get the tool name.
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
Tool name
|
|
87
|
-
"""
|
|
250
|
+
"""Get the tool name."""
|
|
88
251
|
return "agent"
|
|
89
|
-
|
|
252
|
+
|
|
90
253
|
@property
|
|
91
254
|
@override
|
|
92
255
|
def description(self) -> str:
|
|
93
|
-
"""Get the tool description.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"""
|
|
98
|
-
# TODO: Add glob when it is implemented
|
|
256
|
+
"""Get the tool description."""
|
|
257
|
+
if not HANZO_AGENTS_AVAILABLE:
|
|
258
|
+
return "Agent tool (hanzo-agents SDK not available - using fallback)"
|
|
259
|
+
|
|
99
260
|
at = [t.name for t in self.available_tools]
|
|
100
|
-
|
|
101
|
-
return f"""Launch a new agent that has access to the following tools: {at}. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries, use the Agent tool to perform the search for you.
|
|
261
|
+
return f"""Launch a new agent that has access to the following tools: {at}.
|
|
102
262
|
|
|
103
263
|
When to use the Agent tool:
|
|
104
|
-
- If you are searching for a keyword like
|
|
105
|
-
- When you need to perform edits across multiple files
|
|
264
|
+
- If you are searching for a keyword like "config" or "logger"
|
|
265
|
+
- When you need to perform edits across multiple files
|
|
106
266
|
- When you need to delegate complex file modification tasks
|
|
107
267
|
|
|
108
268
|
When NOT to use the Agent tool:
|
|
109
|
-
- If you want to read a specific file path
|
|
110
|
-
- If you are searching for a specific class definition
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- Other tasks that are not related to searching for a keyword or file
|
|
269
|
+
- If you want to read a specific file path
|
|
270
|
+
- If you are searching for a specific class definition
|
|
271
|
+
- Writing code and running bash commands
|
|
272
|
+
- Other tasks that are not related to searching
|
|
114
273
|
|
|
115
274
|
Usage notes:
|
|
116
|
-
1. Launch multiple agents concurrently whenever possible
|
|
117
|
-
2.
|
|
118
|
-
3. Each agent invocation is stateless
|
|
275
|
+
1. Launch multiple agents concurrently whenever possible
|
|
276
|
+
2. Agent results are not visible to the user - summarize them
|
|
277
|
+
3. Each agent invocation is stateless
|
|
119
278
|
4. The agent's outputs should generally be trusted
|
|
120
|
-
5. Clearly tell the agent whether you expect it to write code or just
|
|
121
|
-
|
|
279
|
+
5. Clearly tell the agent whether you expect it to write code or just do research"""
|
|
280
|
+
|
|
122
281
|
def __init__(
|
|
123
282
|
self,
|
|
124
283
|
permission_manager: PermissionManager,
|
|
@@ -129,26 +288,16 @@ Usage notes:
|
|
|
129
288
|
max_iterations: int = 10,
|
|
130
289
|
max_tool_uses: int = 30,
|
|
131
290
|
) -> None:
|
|
132
|
-
"""Initialize the agent tool.
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
|
|
136
|
-
permission_manager: Permission manager for access control
|
|
137
|
-
model: Optional model name override in LiteLLM format (e.g., "openai/gpt-4o")
|
|
138
|
-
api_key: Optional API key for the model provider
|
|
139
|
-
base_url: Optional base URL for the model provider API endpoint
|
|
140
|
-
max_tokens: Optional maximum tokens for model responses
|
|
141
|
-
max_iterations: Maximum number of iterations for agent (default: 10)
|
|
142
|
-
max_tool_uses: Maximum number of total tool uses for agent (default: 30)
|
|
143
|
-
"""
|
|
144
|
-
|
|
291
|
+
"""Initialize the agent tool."""
|
|
145
292
|
self.permission_manager = permission_manager
|
|
146
293
|
self.model_override = model
|
|
147
294
|
self.api_key_override = api_key
|
|
148
|
-
self.base_url_override = base_url
|
|
295
|
+
self.base_url_override = base_url
|
|
149
296
|
self.max_tokens_override = max_tokens
|
|
150
297
|
self.max_iterations = max_iterations
|
|
151
298
|
self.max_tool_uses = max_tool_uses
|
|
299
|
+
|
|
300
|
+
# Set up available tools
|
|
152
301
|
self.available_tools: list[BaseTool] = []
|
|
153
302
|
self.available_tools.extend(
|
|
154
303
|
get_read_only_filesystem_tools(self.permission_manager)
|
|
@@ -157,485 +306,176 @@ Usage notes:
|
|
|
157
306
|
get_read_only_jupyter_tools(self.permission_manager)
|
|
158
307
|
)
|
|
159
308
|
|
|
160
|
-
#
|
|
309
|
+
# Add edit tools
|
|
161
310
|
self.available_tools.append(Edit(self.permission_manager))
|
|
162
311
|
self.available_tools.append(MultiEdit(self.permission_manager))
|
|
163
312
|
|
|
164
|
-
# Add
|
|
313
|
+
# Add special tools
|
|
165
314
|
self.available_tools.append(ClarificationTool())
|
|
166
|
-
|
|
167
|
-
# Add critic tool for agents (devil's advocate)
|
|
168
315
|
self.available_tools.append(CriticTool())
|
|
169
|
-
|
|
170
|
-
# Add review tool for agents (balanced review)
|
|
171
316
|
self.available_tools.append(ReviewTool())
|
|
172
|
-
|
|
173
|
-
# Add I Ching tool for creative guidance
|
|
174
317
|
self.available_tools.append(IChingTool())
|
|
175
318
|
|
|
176
319
|
self.available_tools.append(
|
|
177
320
|
BatchTool({t.name: t for t in self.available_tools})
|
|
178
321
|
)
|
|
179
|
-
|
|
180
|
-
# Initialize protocols
|
|
181
|
-
self.critic_protocol = CriticProtocol()
|
|
182
|
-
self.review_protocol = ReviewProtocol()
|
|
183
|
-
|
|
322
|
+
|
|
184
323
|
@override
|
|
185
324
|
async def call(
|
|
186
325
|
self,
|
|
187
326
|
ctx: MCPContext,
|
|
188
327
|
**params: Unpack[AgentToolParams],
|
|
189
328
|
) -> str:
|
|
190
|
-
"""Execute the tool with the given parameters.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
ctx: MCP context
|
|
194
|
-
**params: Tool parameters
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
Tool execution result
|
|
198
|
-
"""
|
|
329
|
+
"""Execute the tool with the given parameters."""
|
|
199
330
|
start_time = time.time()
|
|
200
|
-
|
|
331
|
+
|
|
201
332
|
# Create tool context
|
|
202
333
|
tool_ctx = create_tool_context(ctx)
|
|
203
334
|
await tool_ctx.set_tool_info(self.name)
|
|
204
|
-
|
|
205
|
-
# Extract and validate parameters
|
|
206
|
-
prompts = params.get("prompts")
|
|
207
335
|
|
|
336
|
+
# Extract parameters
|
|
337
|
+
prompts = params.get("prompts")
|
|
208
338
|
if prompts is None:
|
|
209
339
|
await tool_ctx.error("No prompts provided")
|
|
210
|
-
return "
|
|
211
|
-
|
|
212
|
-
IMPORTANT REMINDER FOR CLAUDE:
|
|
213
|
-
The dispatch_agent tool requires prompts parameter. Please provide either:
|
|
214
|
-
- A single prompt as a string
|
|
215
|
-
- Multiple prompts as an array of strings
|
|
216
|
-
|
|
217
|
-
Each prompt must contain absolute paths starting with /.
|
|
218
|
-
Example of correct usage:
|
|
219
|
-
- prompts: "Search for all instances of the 'config' variable in /Users/bytedance/project/hanzo-mcp"
|
|
220
|
-
- prompts: ["Find files in /path/to/project", "Search code in /path/to/src"]"""
|
|
221
|
-
|
|
340
|
+
return "Error: At least one prompt must be provided."
|
|
341
|
+
|
|
222
342
|
# Handle both string and list inputs
|
|
223
343
|
if isinstance(prompts, str):
|
|
224
344
|
prompt_list = [prompts]
|
|
225
345
|
elif isinstance(prompts, list):
|
|
226
346
|
if not prompts:
|
|
227
347
|
await tool_ctx.error("Empty prompts list provided")
|
|
228
|
-
return "Error: At least one prompt must be provided
|
|
229
|
-
if not all(isinstance(p, str) for p in prompts):
|
|
230
|
-
await tool_ctx.error("All prompts must be strings")
|
|
231
|
-
return "Error: All prompts in the list must be strings."
|
|
348
|
+
return "Error: At least one prompt must be provided."
|
|
232
349
|
prompt_list = prompts
|
|
233
350
|
else:
|
|
234
351
|
await tool_ctx.error("Invalid prompts parameter type")
|
|
235
|
-
return "Error: Parameter 'prompts' must be a string or
|
|
236
|
-
|
|
237
|
-
# Validate absolute paths
|
|
352
|
+
return "Error: Parameter 'prompts' must be a string or list of strings."
|
|
353
|
+
|
|
354
|
+
# Validate absolute paths
|
|
238
355
|
absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
|
|
239
356
|
for prompt in prompt_list:
|
|
240
357
|
if not re.search(absolute_path_pattern, prompt):
|
|
241
|
-
await tool_ctx.error(f"Prompt
|
|
242
|
-
return "
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
358
|
+
await tool_ctx.error(f"Prompt missing absolute path: {prompt[:50]}...")
|
|
359
|
+
return "Error: All prompts must contain at least one absolute path."
|
|
360
|
+
|
|
361
|
+
# Require hanzo-agents SDK
|
|
362
|
+
if not HANZO_AGENTS_AVAILABLE:
|
|
363
|
+
await tool_ctx.error("hanzo-agents SDK is required but not available")
|
|
364
|
+
return "Error: hanzo-agents SDK is required for agent tool functionality. Please install it with: pip install hanzo-agents"
|
|
365
|
+
|
|
366
|
+
# Use hanzo-agents SDK
|
|
367
|
+
await tool_ctx.info(f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK")
|
|
368
|
+
|
|
369
|
+
# Determine model and agent type
|
|
370
|
+
model = params.get("model", self.model_override)
|
|
371
|
+
use_memory = params.get("use_memory", False)
|
|
372
|
+
memory_backend = params.get("memory_backend", "sqlite")
|
|
373
|
+
|
|
374
|
+
# Get appropriate agent class
|
|
375
|
+
agent_class = self._get_agent_class(model)
|
|
376
|
+
|
|
377
|
+
# Create state
|
|
378
|
+
state = MCPAgentState(
|
|
379
|
+
prompts=prompt_list,
|
|
380
|
+
context={
|
|
381
|
+
"permission_manager": self.permission_manager,
|
|
382
|
+
"api_key": self.api_key_override,
|
|
383
|
+
"base_url": self.base_url_override,
|
|
384
|
+
"max_tokens": self.max_tokens_override,
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Create memory if requested
|
|
389
|
+
memory_kv = None
|
|
390
|
+
memory_vector = None
|
|
391
|
+
if use_memory:
|
|
392
|
+
memory_kv = create_memory_kv(memory_backend)
|
|
393
|
+
memory_vector = create_memory_vector("simple")
|
|
394
|
+
|
|
395
|
+
# Create network
|
|
396
|
+
network = Network(
|
|
397
|
+
state=state,
|
|
398
|
+
agents=[agent_class],
|
|
399
|
+
router=sequential_router([agent_class] * len(prompt_list)),
|
|
400
|
+
memory_kv=memory_kv,
|
|
401
|
+
memory_vector=memory_vector,
|
|
402
|
+
max_steps=self.max_iterations * len(prompt_list),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Execute
|
|
406
|
+
try:
|
|
407
|
+
final_state = await network.run()
|
|
256
408
|
execution_time = time.time() - start_time
|
|
257
|
-
|
|
409
|
+
|
|
410
|
+
# Format results
|
|
411
|
+
results = final_state.results
|
|
412
|
+
if len(results) == 1:
|
|
413
|
+
formatted_result = f"""Agent execution completed in {execution_time:.2f} seconds.
|
|
258
414
|
|
|
259
415
|
AGENT RESPONSE:
|
|
260
|
-
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
formatted_result = f"""Multi-agent execution completed in {execution_time:.2f} seconds ({len(prompt_list)} agents).
|
|
416
|
+
{results[0]}"""
|
|
417
|
+
else:
|
|
418
|
+
formatted_results = []
|
|
419
|
+
for i, result in enumerate(results):
|
|
420
|
+
formatted_results.append(f"Agent {i+1} Result:\n{result}")
|
|
421
|
+
|
|
422
|
+
formatted_result = f"""Multi-agent execution completed in {execution_time:.2f} seconds ({len(results)} agents).
|
|
268
423
|
|
|
269
424
|
AGENT RESPONSES:
|
|
270
|
-
{
|
|
271
|
-
|
|
425
|
+
{chr(10).join(formatted_results)}"""
|
|
426
|
+
|
|
427
|
+
await tool_ctx.info(f"Execution completed in {execution_time:.2f}s")
|
|
272
428
|
return formatted_result
|
|
273
|
-
|
|
274
|
-
async def _execute_agent(self, prompt: str, tool_ctx: ToolContext) -> str:
|
|
275
|
-
"""Execute a single agent with the given prompt.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
prompt: The task prompt for the agent
|
|
279
|
-
tool_ctx: Tool context for logging
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
Agent execution result
|
|
283
|
-
"""
|
|
284
|
-
# Get available tools for the agent
|
|
285
|
-
agent_tools = get_allowed_agent_tools(
|
|
286
|
-
self.available_tools,
|
|
287
|
-
self.permission_manager,
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
# Convert tools to OpenAI format
|
|
291
|
-
openai_tools = convert_tools_to_openai_functions(agent_tools)
|
|
292
|
-
|
|
293
|
-
# Log execution start
|
|
294
|
-
await tool_ctx.info("Starting agent execution")
|
|
295
|
-
|
|
296
|
-
# Create a result container
|
|
297
|
-
result = ""
|
|
298
|
-
|
|
299
|
-
try:
|
|
300
|
-
# Create system prompt for this agent
|
|
301
|
-
system_prompt = get_system_prompt(
|
|
302
|
-
agent_tools,
|
|
303
|
-
self.permission_manager,
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
# Execute agent
|
|
307
|
-
await tool_ctx.info(f"Executing agent task: {prompt[:50]}...")
|
|
308
|
-
result = await self._execute_agent_with_tools(
|
|
309
|
-
system_prompt, prompt, agent_tools, openai_tools, tool_ctx
|
|
310
|
-
)
|
|
429
|
+
|
|
311
430
|
except Exception as e:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
agent_tools,
|
|
341
|
-
self.permission_manager,
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
# Create tasks for parallel execution
|
|
345
|
-
tasks = []
|
|
346
|
-
for i, prompt in enumerate(prompts):
|
|
347
|
-
await tool_ctx.info(f"Creating agent task {i+1}: {prompt[:50]}...")
|
|
348
|
-
task = self._execute_agent_with_tools(
|
|
349
|
-
system_prompt, prompt, agent_tools, openai_tools, tool_ctx
|
|
350
|
-
)
|
|
351
|
-
tasks.append(task)
|
|
352
|
-
|
|
353
|
-
# Execute all agents concurrently
|
|
354
|
-
await tool_ctx.info(f"Executing {len(tasks)} agents in parallel")
|
|
355
|
-
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
356
|
-
|
|
357
|
-
# Handle single agent case
|
|
358
|
-
if len(results) == 1:
|
|
359
|
-
if isinstance(results[0], Exception):
|
|
360
|
-
await tool_ctx.error(f"Agent execution failed: {str(results[0])}")
|
|
361
|
-
return f"Error: {str(results[0])}"
|
|
362
|
-
return results[0]
|
|
363
|
-
|
|
364
|
-
# Format results for multiple agents
|
|
365
|
-
formatted_results = []
|
|
366
|
-
for i, result in enumerate(results):
|
|
367
|
-
if isinstance(result, Exception):
|
|
368
|
-
formatted_results.append(f"Agent {i+1} Error:\n{str(result)}")
|
|
369
|
-
await tool_ctx.error(f"Agent {i+1} failed: {str(result)}")
|
|
370
|
-
else:
|
|
371
|
-
formatted_results.append(f"Agent {i+1} Result:\n{result}")
|
|
372
|
-
|
|
373
|
-
return "\n\n---\n\n".join(formatted_results)
|
|
374
|
-
|
|
375
|
-
async def _execute_agent_with_tools(
|
|
376
|
-
self,
|
|
377
|
-
system_prompt: str,
|
|
378
|
-
user_prompt: str,
|
|
379
|
-
available_tools: list[BaseTool],
|
|
380
|
-
openai_tools: list[ChatCompletionToolParam],
|
|
381
|
-
tool_ctx: ToolContext,
|
|
382
|
-
) -> str:
|
|
383
|
-
"""Execute agent with tool handling.
|
|
384
|
-
|
|
385
|
-
Args:
|
|
386
|
-
system_prompt: System prompt for the agent
|
|
387
|
-
user_prompt: User prompt for the agent
|
|
388
|
-
available_tools: List of available tools
|
|
389
|
-
openai_tools: List of tools in OpenAI format
|
|
390
|
-
tool_ctx: Tool context for logging
|
|
391
|
-
|
|
392
|
-
Returns:
|
|
393
|
-
Agent execution result
|
|
394
|
-
"""
|
|
395
|
-
# Get model parameters and name
|
|
396
|
-
model = get_default_model(self.model_override)
|
|
397
|
-
params = get_model_parameters(max_tokens=self.max_tokens_override)
|
|
398
|
-
|
|
399
|
-
# Initialize messages
|
|
400
|
-
messages: Iterable[ChatCompletionMessageParam] = []
|
|
401
|
-
messages.append({"role": "system", "content": system_prompt})
|
|
402
|
-
messages.append({"role": "user", "content": user_prompt})
|
|
403
|
-
|
|
404
|
-
# Track tool usage for metrics
|
|
405
|
-
tool_usage = {}
|
|
406
|
-
total_tool_use_count = 0
|
|
407
|
-
iteration_count = 0
|
|
408
|
-
max_tool_uses = self.max_tool_uses # Safety limit to prevent infinite loops
|
|
409
|
-
max_iterations = (
|
|
410
|
-
self.max_iterations
|
|
411
|
-
) # Add a maximum number of iterations for safety
|
|
412
|
-
|
|
413
|
-
# Execute until the agent completes or reaches the limit
|
|
414
|
-
while total_tool_use_count < max_tool_uses and iteration_count < max_iterations:
|
|
415
|
-
iteration_count += 1
|
|
416
|
-
await tool_ctx.info(f"Calling model (iteration {iteration_count})...")
|
|
417
|
-
|
|
418
|
-
try:
|
|
419
|
-
# Configure model parameters based on capabilities
|
|
420
|
-
completion_params = {
|
|
421
|
-
"model": model,
|
|
422
|
-
"messages": messages,
|
|
423
|
-
"tools": openai_tools,
|
|
424
|
-
"tool_choice": "auto",
|
|
425
|
-
"temperature": params["temperature"],
|
|
426
|
-
"timeout": params["timeout"],
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if self.api_key_override:
|
|
430
|
-
completion_params["api_key"] = self.api_key_override
|
|
431
|
-
|
|
432
|
-
# Add max_tokens if provided
|
|
433
|
-
if params.get("max_tokens"):
|
|
434
|
-
completion_params["max_tokens"] = params.get("max_tokens")
|
|
435
|
-
|
|
436
|
-
# Add base_url if provided
|
|
437
|
-
if self.base_url_override:
|
|
438
|
-
completion_params["base_url"] = self.base_url_override
|
|
439
|
-
|
|
440
|
-
# Make the model call
|
|
441
|
-
response = litellm.completion(
|
|
442
|
-
**completion_params # pyright: ignore
|
|
443
|
-
)
|
|
444
|
-
|
|
445
|
-
if len(response.choices) == 0: # pyright: ignore
|
|
446
|
-
raise ValueError("No response choices returned")
|
|
447
|
-
|
|
448
|
-
message = response.choices[0].message # pyright: ignore
|
|
449
|
-
|
|
450
|
-
# Add message to conversation history
|
|
451
|
-
messages.append(message) # pyright: ignore
|
|
452
|
-
|
|
453
|
-
# If no tool calls, we're done
|
|
454
|
-
if not message.tool_calls:
|
|
455
|
-
return message.content or "Agent completed with no response."
|
|
456
|
-
|
|
457
|
-
# Process tool calls
|
|
458
|
-
tool_call_count = len(message.tool_calls)
|
|
459
|
-
await tool_ctx.info(f"Processing {tool_call_count} tool calls")
|
|
460
|
-
|
|
461
|
-
for tool_call in message.tool_calls:
|
|
462
|
-
total_tool_use_count += 1
|
|
463
|
-
function_name = tool_call.function.name
|
|
464
|
-
|
|
465
|
-
# Track usage
|
|
466
|
-
tool_usage[function_name] = tool_usage.get(function_name, 0) + 1
|
|
467
|
-
|
|
468
|
-
# Log tool usage
|
|
469
|
-
await tool_ctx.info(f"Agent using tool: {function_name}")
|
|
470
|
-
|
|
471
|
-
# Parse the arguments
|
|
472
|
-
try:
|
|
473
|
-
function_args = json.loads(tool_call.function.arguments)
|
|
474
|
-
except json.JSONDecodeError:
|
|
475
|
-
function_args = {}
|
|
476
|
-
|
|
477
|
-
# Find the matching tool
|
|
478
|
-
tool = next(
|
|
479
|
-
(t for t in available_tools if t.name == function_name), None
|
|
480
|
-
)
|
|
481
|
-
if not tool:
|
|
482
|
-
tool_result = f"Error: Tool '{function_name}' not found"
|
|
483
|
-
# Special handling for clarification requests
|
|
484
|
-
elif function_name == "request_clarification":
|
|
485
|
-
try:
|
|
486
|
-
# Extract clarification parameters
|
|
487
|
-
request_type = function_args.get("type", "ADDITIONAL_INFO")
|
|
488
|
-
question = function_args.get("question", "")
|
|
489
|
-
context = function_args.get("context", {})
|
|
490
|
-
options = function_args.get("options", None)
|
|
491
|
-
|
|
492
|
-
# Convert string type to enum
|
|
493
|
-
clarification_type = ClarificationType[request_type]
|
|
494
|
-
|
|
495
|
-
# Request clarification
|
|
496
|
-
answer = await self.request_clarification(
|
|
497
|
-
request_type=clarification_type,
|
|
498
|
-
question=question,
|
|
499
|
-
context=context,
|
|
500
|
-
options=options
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
tool_result = self.format_clarification_in_output(question, answer)
|
|
504
|
-
except Exception as e:
|
|
505
|
-
tool_result = f"Error processing clarification: {str(e)}"
|
|
506
|
-
# Special handling for critic requests
|
|
507
|
-
elif function_name == "critic":
|
|
508
|
-
try:
|
|
509
|
-
# Extract critic parameters
|
|
510
|
-
review_type = function_args.get("review_type", "GENERAL")
|
|
511
|
-
work_description = function_args.get("work_description", "")
|
|
512
|
-
code_snippets = function_args.get("code_snippets", None)
|
|
513
|
-
file_paths = function_args.get("file_paths", None)
|
|
514
|
-
specific_concerns = function_args.get("specific_concerns", None)
|
|
515
|
-
|
|
516
|
-
# Request critical review
|
|
517
|
-
tool_result = self.critic_protocol.request_review(
|
|
518
|
-
review_type=review_type,
|
|
519
|
-
work_description=work_description,
|
|
520
|
-
code_snippets=code_snippets,
|
|
521
|
-
file_paths=file_paths,
|
|
522
|
-
specific_concerns=specific_concerns
|
|
523
|
-
)
|
|
524
|
-
except Exception as e:
|
|
525
|
-
tool_result = f"Error processing critic review: {str(e)}"
|
|
526
|
-
# Special handling for review requests
|
|
527
|
-
elif function_name == "review":
|
|
528
|
-
try:
|
|
529
|
-
# Extract review parameters
|
|
530
|
-
focus = function_args.get("focus", "GENERAL")
|
|
531
|
-
work_description = function_args.get("work_description", "")
|
|
532
|
-
code_snippets = function_args.get("code_snippets", None)
|
|
533
|
-
file_paths = function_args.get("file_paths", None)
|
|
534
|
-
context = function_args.get("context", None)
|
|
535
|
-
|
|
536
|
-
# Request balanced review
|
|
537
|
-
tool_result = self.review_protocol.request_review(
|
|
538
|
-
focus=focus,
|
|
539
|
-
work_description=work_description,
|
|
540
|
-
code_snippets=code_snippets,
|
|
541
|
-
file_paths=file_paths,
|
|
542
|
-
context=context
|
|
543
|
-
)
|
|
544
|
-
except Exception as e:
|
|
545
|
-
tool_result = f"Error processing review: {str(e)}"
|
|
546
|
-
else:
|
|
547
|
-
try:
|
|
548
|
-
tool_result = await tool.call(
|
|
549
|
-
ctx=tool_ctx.mcp_context, **function_args
|
|
550
|
-
)
|
|
551
|
-
except Exception as e:
|
|
552
|
-
tool_result = f"Error executing {function_name}: {str(e)}"
|
|
553
|
-
|
|
554
|
-
await tool_ctx.info(
|
|
555
|
-
f"tool {function_name} run with args {function_args} and return {tool_result[: min(100, len(tool_result))]}"
|
|
556
|
-
)
|
|
557
|
-
# Add the tool result to messages
|
|
558
|
-
messages.append(
|
|
559
|
-
{
|
|
560
|
-
"role": "tool",
|
|
561
|
-
"tool_call_id": tool_call.id,
|
|
562
|
-
"name": function_name,
|
|
563
|
-
"content": tool_result,
|
|
564
|
-
}
|
|
565
|
-
)
|
|
566
|
-
|
|
567
|
-
# Log progress
|
|
568
|
-
await tool_ctx.info(
|
|
569
|
-
f"Processed {len(message.tool_calls)} tool calls. Total: {total_tool_use_count}"
|
|
570
|
-
)
|
|
571
|
-
|
|
572
|
-
except Exception as e:
|
|
573
|
-
await tool_ctx.error(f"Error in model call: {str(e)}")
|
|
574
|
-
# Avoid trying to JSON serialize message objects
|
|
575
|
-
await tool_ctx.error(f"Message count: {len(messages)}")
|
|
576
|
-
return f"Error in agent execution: {str(e)}"
|
|
577
|
-
|
|
578
|
-
# If we've reached the limit, add a warning and get final response
|
|
579
|
-
if total_tool_use_count >= max_tool_uses or iteration_count >= max_iterations:
|
|
580
|
-
messages.append(
|
|
581
|
-
{
|
|
582
|
-
"role": "system",
|
|
583
|
-
"content": "You have reached the maximum iteration. Please provide your final response.",
|
|
584
|
-
}
|
|
431
|
+
await tool_ctx.error(f"Agent execution failed: {str(e)}")
|
|
432
|
+
return f"Error: {str(e)}"
|
|
433
|
+
|
|
434
|
+
def _get_agent_class(self, model: Optional[str]) -> type[Agent]:
|
|
435
|
+
"""Get appropriate agent class based on model."""
|
|
436
|
+
if not model:
|
|
437
|
+
model = "model://anthropic/claude-3-5-sonnet-20241022"
|
|
438
|
+
|
|
439
|
+
# Check for CLI agents
|
|
440
|
+
cli_agents = {
|
|
441
|
+
"claude_cli": ClaudeCodeAgent,
|
|
442
|
+
"codex_cli": OpenAICodexAgent,
|
|
443
|
+
"gemini_cli": GeminiAgent,
|
|
444
|
+
"grok_cli": GrokAgent,
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if model in cli_agents:
|
|
448
|
+
return cli_agents[model]
|
|
449
|
+
|
|
450
|
+
# Return generic MCP agent
|
|
451
|
+
return type("DynamicMCPAgent", (MCPAgent,), {
|
|
452
|
+
"model": model,
|
|
453
|
+
"__init__": lambda self: MCPAgent.__init__(
|
|
454
|
+
self,
|
|
455
|
+
available_tools=self.available_tools,
|
|
456
|
+
permission_manager=self.permission_manager,
|
|
457
|
+
ctx=self.ctx,
|
|
458
|
+
model=model
|
|
585
459
|
)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
# Make a final call to get the result
|
|
589
|
-
final_response = litellm.completion(
|
|
590
|
-
model=model,
|
|
591
|
-
messages=messages,
|
|
592
|
-
temperature=params["temperature"],
|
|
593
|
-
timeout=params["timeout"],
|
|
594
|
-
max_tokens=params.get("max_tokens"),
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
return (
|
|
598
|
-
final_response.choices[0].message.content
|
|
599
|
-
or "Agent reached max iteration limit without a response."
|
|
600
|
-
) # pyright: ignore
|
|
601
|
-
except Exception as e:
|
|
602
|
-
await tool_ctx.error(f"Error in final model call: {str(e)}")
|
|
603
|
-
return f"Error in final response: {str(e)}"
|
|
604
|
-
|
|
605
|
-
# Should not reach here but just in case
|
|
606
|
-
return "Agent execution completed after maximum iterations."
|
|
607
|
-
|
|
608
|
-
def _format_result(self, result: str, execution_time: float) -> str:
|
|
609
|
-
"""Format agent result with metrics.
|
|
610
|
-
|
|
611
|
-
Args:
|
|
612
|
-
result: Raw result from agent
|
|
613
|
-
execution_time: Execution time in seconds
|
|
614
|
-
|
|
615
|
-
Returns:
|
|
616
|
-
Formatted result with metrics
|
|
617
|
-
"""
|
|
618
|
-
return f"""Agent execution completed in {execution_time:.2f} seconds.
|
|
619
|
-
|
|
620
|
-
AGENT RESPONSE:
|
|
621
|
-
{result}
|
|
622
|
-
"""
|
|
623
|
-
|
|
460
|
+
})
|
|
461
|
+
|
|
624
462
|
@override
|
|
625
463
|
def register(self, mcp_server: FastMCP) -> None:
|
|
626
|
-
"""Register this agent tool with the MCP server.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
the tool's parameter schema and registers it with the MCP server.
|
|
630
|
-
|
|
631
|
-
Args:
|
|
632
|
-
mcp_server: The FastMCP server instance
|
|
633
|
-
"""
|
|
634
|
-
tool_self = self # Create a reference to self for use in the closure
|
|
635
|
-
|
|
464
|
+
"""Register this agent tool with the MCP server."""
|
|
465
|
+
tool_self = self
|
|
466
|
+
|
|
636
467
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
637
468
|
async def dispatch_agent(
|
|
638
469
|
prompts: str | list[str],
|
|
639
|
-
ctx: MCPContext
|
|
470
|
+
ctx: MCPContext,
|
|
471
|
+
model: Optional[str] = None,
|
|
472
|
+
use_memory: bool = False,
|
|
473
|
+
memory_backend: str = "sqlite"
|
|
640
474
|
) -> str:
|
|
641
|
-
return await tool_self.call(
|
|
475
|
+
return await tool_self.call(
|
|
476
|
+
ctx,
|
|
477
|
+
prompts=prompts,
|
|
478
|
+
model=model,
|
|
479
|
+
use_memory=use_memory,
|
|
480
|
+
memory_backend=memory_backend
|
|
481
|
+
)
|