hanzo-mcp 0.5.2__py3-none-any.whl → 0.6.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 +1 -1
- hanzo_mcp/cli.py +32 -0
- hanzo_mcp/dev_server.py +246 -0
- hanzo_mcp/prompts/__init__.py +1 -1
- hanzo_mcp/prompts/project_system.py +43 -7
- hanzo_mcp/server.py +5 -1
- hanzo_mcp/tools/__init__.py +66 -35
- hanzo_mcp/tools/agent/__init__.py +1 -1
- hanzo_mcp/tools/agent/agent.py +401 -0
- hanzo_mcp/tools/agent/agent_tool.py +3 -4
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +2 -2
- hanzo_mcp/tools/common/batch_tool.py +3 -5
- hanzo_mcp/tools/common/config_tool.py +1 -1
- hanzo_mcp/tools/common/context.py +1 -1
- hanzo_mcp/tools/common/palette.py +344 -0
- hanzo_mcp/tools/common/palette_loader.py +108 -0
- hanzo_mcp/tools/common/stats.py +1 -1
- hanzo_mcp/tools/common/thinking_tool.py +3 -5
- hanzo_mcp/tools/common/tool_disable.py +1 -1
- hanzo_mcp/tools/common/tool_enable.py +1 -1
- hanzo_mcp/tools/common/tool_list.py +49 -52
- hanzo_mcp/tools/config/__init__.py +10 -0
- hanzo_mcp/tools/config/config_tool.py +212 -0
- hanzo_mcp/tools/config/index_config.py +176 -0
- hanzo_mcp/tools/config/palette_tool.py +166 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/database/graph.py +482 -0
- hanzo_mcp/tools/database/graph_add.py +1 -1
- hanzo_mcp/tools/database/graph_query.py +1 -1
- hanzo_mcp/tools/database/graph_remove.py +1 -1
- hanzo_mcp/tools/database/graph_search.py +1 -1
- hanzo_mcp/tools/database/graph_stats.py +1 -1
- hanzo_mcp/tools/database/sql.py +411 -0
- hanzo_mcp/tools/database/sql_query.py +1 -1
- hanzo_mcp/tools/database/sql_search.py +1 -1
- hanzo_mcp/tools/database/sql_stats.py +1 -1
- hanzo_mcp/tools/editor/neovim_command.py +1 -1
- hanzo_mcp/tools/editor/neovim_edit.py +1 -1
- hanzo_mcp/tools/editor/neovim_session.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +42 -13
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +4 -4
- hanzo_mcp/tools/filesystem/content_replace.py +3 -5
- hanzo_mcp/tools/filesystem/diff.py +193 -0
- hanzo_mcp/tools/filesystem/directory_tree.py +3 -5
- hanzo_mcp/tools/filesystem/edit.py +3 -5
- hanzo_mcp/tools/filesystem/find.py +443 -0
- hanzo_mcp/tools/filesystem/find_files.py +1 -1
- hanzo_mcp/tools/filesystem/git_search.py +1 -1
- hanzo_mcp/tools/filesystem/grep.py +2 -2
- hanzo_mcp/tools/filesystem/multi_edit.py +3 -5
- hanzo_mcp/tools/filesystem/read.py +17 -5
- hanzo_mcp/tools/filesystem/{grep_ast_tool.py → symbols.py} +17 -27
- hanzo_mcp/tools/filesystem/symbols_unified.py +376 -0
- hanzo_mcp/tools/filesystem/tree.py +268 -0
- hanzo_mcp/tools/filesystem/unified_search.py +711 -0
- hanzo_mcp/tools/filesystem/unix_aliases.py +99 -0
- hanzo_mcp/tools/filesystem/watch.py +174 -0
- hanzo_mcp/tools/filesystem/write.py +3 -5
- hanzo_mcp/tools/jupyter/__init__.py +9 -12
- hanzo_mcp/tools/jupyter/base.py +1 -1
- hanzo_mcp/tools/jupyter/jupyter.py +326 -0
- hanzo_mcp/tools/jupyter/notebook_edit.py +3 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +3 -5
- hanzo_mcp/tools/llm/__init__.py +4 -0
- hanzo_mcp/tools/llm/consensus_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_manage.py +1 -1
- hanzo_mcp/tools/llm/llm_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_unified.py +851 -0
- hanzo_mcp/tools/llm/provider_tools.py +1 -1
- hanzo_mcp/tools/mcp/__init__.py +4 -0
- hanzo_mcp/tools/mcp/mcp_add.py +1 -1
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -1
- hanzo_mcp/tools/mcp/mcp_unified.py +503 -0
- hanzo_mcp/tools/shell/__init__.py +20 -42
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +303 -0
- hanzo_mcp/tools/shell/bash_unified.py +134 -0
- hanzo_mcp/tools/shell/logs.py +1 -1
- hanzo_mcp/tools/shell/npx.py +1 -1
- hanzo_mcp/tools/shell/npx_background.py +1 -1
- hanzo_mcp/tools/shell/npx_unified.py +101 -0
- hanzo_mcp/tools/shell/open.py +107 -0
- hanzo_mcp/tools/shell/pkill.py +1 -1
- hanzo_mcp/tools/shell/process_unified.py +131 -0
- hanzo_mcp/tools/shell/processes.py +1 -1
- hanzo_mcp/tools/shell/run_background.py +1 -1
- hanzo_mcp/tools/shell/run_command.py +3 -4
- hanzo_mcp/tools/shell/run_command_windows.py +3 -4
- hanzo_mcp/tools/shell/uvx.py +1 -1
- hanzo_mcp/tools/shell/uvx_background.py +1 -1
- hanzo_mcp/tools/shell/uvx_unified.py +101 -0
- hanzo_mcp/tools/todo/__init__.py +1 -1
- hanzo_mcp/tools/todo/base.py +1 -1
- hanzo_mcp/tools/todo/todo.py +265 -0
- hanzo_mcp/tools/todo/todo_read.py +3 -5
- hanzo_mcp/tools/todo/todo_write.py +3 -5
- hanzo_mcp/tools/vector/__init__.py +1 -1
- hanzo_mcp/tools/vector/index_tool.py +1 -1
- hanzo_mcp/tools/vector/project_manager.py +27 -5
- hanzo_mcp/tools/vector/vector.py +311 -0
- hanzo_mcp/tools/vector/vector_index.py +1 -1
- hanzo_mcp/tools/vector/vector_search.py +1 -1
- hanzo_mcp-0.6.1.dist-info/METADATA +336 -0
- hanzo_mcp-0.6.1.dist-info/RECORD +134 -0
- hanzo_mcp-0.6.1.dist-info/entry_points.txt +3 -0
- hanzo_mcp-0.5.2.dist-info/METADATA +0 -276
- hanzo_mcp-0.5.2.dist-info/RECORD +0 -106
- hanzo_mcp-0.5.2.dist-info/entry_points.txt +0 -2
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""Unified agent tool implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the AgentTool for delegating tasks to sub-agents,
|
|
4
|
+
supporting both one-off and long-running RPC modes, including A2A communication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Annotated, TypedDict, Unpack, final, override, Optional, Dict, Any, List
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
|
|
15
|
+
import litellm
|
|
16
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
17
|
+
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionToolParam
|
|
18
|
+
from pydantic import Field
|
|
19
|
+
|
|
20
|
+
from hanzo_mcp.tools.agent.prompt import (
|
|
21
|
+
get_allowed_agent_tools,
|
|
22
|
+
get_default_model,
|
|
23
|
+
get_model_parameters,
|
|
24
|
+
get_system_prompt,
|
|
25
|
+
)
|
|
26
|
+
from hanzo_mcp.tools.agent.tool_adapter import (
|
|
27
|
+
convert_tools_to_openai_functions,
|
|
28
|
+
)
|
|
29
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
30
|
+
from hanzo_mcp.tools.common.batch_tool import BatchTool
|
|
31
|
+
from hanzo_mcp.tools.common.context import (
|
|
32
|
+
ToolContext,
|
|
33
|
+
create_tool_context,
|
|
34
|
+
)
|
|
35
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
36
|
+
from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools
|
|
37
|
+
from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Parameter types
|
|
41
|
+
Action = Annotated[
|
|
42
|
+
str,
|
|
43
|
+
Field(
|
|
44
|
+
description="Action: run (default), start, call, stop, list",
|
|
45
|
+
default="run",
|
|
46
|
+
),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
Prompts = Annotated[
|
|
50
|
+
Optional[str | List[str]],
|
|
51
|
+
Field(
|
|
52
|
+
description="Task(s) for agent (must include absolute paths starting with /)",
|
|
53
|
+
default=None,
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
Mode = Annotated[
|
|
58
|
+
str,
|
|
59
|
+
Field(
|
|
60
|
+
description="Execution mode: oneoff (default) or rpc",
|
|
61
|
+
default="oneoff",
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
AgentId = Annotated[
|
|
66
|
+
Optional[str],
|
|
67
|
+
Field(
|
|
68
|
+
description="Agent ID for RPC mode",
|
|
69
|
+
default=None,
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
Method = Annotated[
|
|
74
|
+
Optional[str],
|
|
75
|
+
Field(
|
|
76
|
+
description="Method to call on RPC agent",
|
|
77
|
+
default=None,
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
Args = Annotated[
|
|
82
|
+
Optional[Dict[str, Any]],
|
|
83
|
+
Field(
|
|
84
|
+
description="Arguments for RPC method call",
|
|
85
|
+
default=None,
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
Model = Annotated[
|
|
90
|
+
Optional[str],
|
|
91
|
+
Field(
|
|
92
|
+
description="Model to use (e.g., lm-studio/local-model, openai/gpt-4o)",
|
|
93
|
+
default=None,
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class AgentParams(TypedDict, total=False):
|
|
99
|
+
"""Parameters for agent tool."""
|
|
100
|
+
action: str
|
|
101
|
+
prompts: Optional[str | List[str]]
|
|
102
|
+
mode: str
|
|
103
|
+
agent_id: Optional[str]
|
|
104
|
+
method: Optional[str]
|
|
105
|
+
args: Optional[Dict[str, Any]]
|
|
106
|
+
model: Optional[str]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class RPCAgent:
|
|
110
|
+
"""Long-running RPC agent."""
|
|
111
|
+
|
|
112
|
+
def __init__(self, agent_id: str, model: str, system_prompt: str, tools: List[BaseTool]):
|
|
113
|
+
self.agent_id = agent_id
|
|
114
|
+
self.model = model
|
|
115
|
+
self.system_prompt = system_prompt
|
|
116
|
+
self.tools = tools
|
|
117
|
+
self.messages: List[ChatCompletionMessageParam] = [
|
|
118
|
+
{"role": "system", "content": system_prompt}
|
|
119
|
+
]
|
|
120
|
+
self.created_at = time.time()
|
|
121
|
+
self.last_used = time.time()
|
|
122
|
+
self.call_count = 0
|
|
123
|
+
|
|
124
|
+
async def call_method(self, method: str, args: Dict[str, Any], tool_ctx: ToolContext) -> str:
|
|
125
|
+
"""Call a method on the RPC agent."""
|
|
126
|
+
self.last_used = time.time()
|
|
127
|
+
self.call_count += 1
|
|
128
|
+
|
|
129
|
+
# Build prompt based on method
|
|
130
|
+
if method == "search":
|
|
131
|
+
prompt = f"Search for: {args.get('query', 'unknown')}"
|
|
132
|
+
elif method == "analyze":
|
|
133
|
+
prompt = f"Analyze: {args.get('target', 'unknown')}"
|
|
134
|
+
elif method == "execute":
|
|
135
|
+
prompt = f"Execute: {args.get('command', 'unknown')}"
|
|
136
|
+
else:
|
|
137
|
+
# Generic method call
|
|
138
|
+
prompt = f"Method: {method}, Args: {json.dumps(args)}"
|
|
139
|
+
|
|
140
|
+
# Add to conversation
|
|
141
|
+
self.messages.append({"role": "user", "content": prompt})
|
|
142
|
+
|
|
143
|
+
# Get response
|
|
144
|
+
# (simplified - would integrate with full agent execution logic)
|
|
145
|
+
response = f"Executed {method} with args {args}"
|
|
146
|
+
self.messages.append({"role": "assistant", "content": response})
|
|
147
|
+
|
|
148
|
+
return response
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@final
|
|
152
|
+
class AgentTool(BaseTool):
|
|
153
|
+
"""Unified agent tool with one-off and RPC modes."""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
permission_manager: PermissionManager,
|
|
158
|
+
model: str | None = None,
|
|
159
|
+
api_key: str | None = None,
|
|
160
|
+
base_url: str | None = None,
|
|
161
|
+
max_tokens: int | None = None,
|
|
162
|
+
max_iterations: int = 10,
|
|
163
|
+
max_tool_uses: int = 30,
|
|
164
|
+
):
|
|
165
|
+
"""Initialize the agent tool."""
|
|
166
|
+
self.permission_manager = permission_manager
|
|
167
|
+
self.model_override = model
|
|
168
|
+
self.api_key_override = api_key
|
|
169
|
+
self.base_url_override = base_url
|
|
170
|
+
self.max_tokens_override = max_tokens
|
|
171
|
+
self.max_iterations = max_iterations
|
|
172
|
+
self.max_tool_uses = max_tool_uses
|
|
173
|
+
|
|
174
|
+
# RPC agent registry
|
|
175
|
+
self._rpc_agents: Dict[str, RPCAgent] = {}
|
|
176
|
+
|
|
177
|
+
# Available tools
|
|
178
|
+
self.available_tools: list[BaseTool] = []
|
|
179
|
+
self.available_tools.extend(
|
|
180
|
+
get_read_only_filesystem_tools(self.permission_manager)
|
|
181
|
+
)
|
|
182
|
+
self.available_tools.extend(
|
|
183
|
+
get_read_only_jupyter_tools(self.permission_manager)
|
|
184
|
+
)
|
|
185
|
+
self.available_tools.append(
|
|
186
|
+
BatchTool({t.name: t for t in self.available_tools})
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
@override
|
|
191
|
+
def name(self) -> str:
|
|
192
|
+
"""Get the tool name."""
|
|
193
|
+
return "agent"
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
@override
|
|
197
|
+
def description(self) -> str:
|
|
198
|
+
"""Get the tool description."""
|
|
199
|
+
tools = [t.name for t in self.available_tools]
|
|
200
|
+
|
|
201
|
+
return f"""AI agents with tools: {', '.join(tools)}. Actions: run (default), start, call, stop, list.
|
|
202
|
+
|
|
203
|
+
Usage:
|
|
204
|
+
agent "Search for config files in /project"
|
|
205
|
+
agent --action start --mode rpc --model lm-studio/local-model
|
|
206
|
+
agent --action call --agent-id abc123 --method search --args '{{"query": "database"}}'
|
|
207
|
+
agent --action list
|
|
208
|
+
|
|
209
|
+
Modes:
|
|
210
|
+
- oneoff: Single task execution (default)
|
|
211
|
+
- rpc: Long-running agent for multiple calls (A2A support)"""
|
|
212
|
+
|
|
213
|
+
@override
|
|
214
|
+
async def call(
|
|
215
|
+
self,
|
|
216
|
+
ctx: MCPContext,
|
|
217
|
+
**params: Unpack[AgentParams],
|
|
218
|
+
) -> str:
|
|
219
|
+
"""Execute agent operation."""
|
|
220
|
+
tool_ctx = create_tool_context(ctx)
|
|
221
|
+
await tool_ctx.set_tool_info(self.name)
|
|
222
|
+
|
|
223
|
+
# Extract action
|
|
224
|
+
action = params.get("action", "run")
|
|
225
|
+
|
|
226
|
+
# Route to appropriate handler
|
|
227
|
+
if action == "run":
|
|
228
|
+
return await self._handle_run(params, tool_ctx)
|
|
229
|
+
elif action == "start":
|
|
230
|
+
return await self._handle_start(params, tool_ctx)
|
|
231
|
+
elif action == "call":
|
|
232
|
+
return await self._handle_call(params, tool_ctx)
|
|
233
|
+
elif action == "stop":
|
|
234
|
+
return await self._handle_stop(params, tool_ctx)
|
|
235
|
+
elif action == "list":
|
|
236
|
+
return await self._handle_list(tool_ctx)
|
|
237
|
+
else:
|
|
238
|
+
return f"Error: Unknown action '{action}'. Valid actions: run, start, call, stop, list"
|
|
239
|
+
|
|
240
|
+
async def _handle_run(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
|
|
241
|
+
"""Handle one-off agent run (default action)."""
|
|
242
|
+
prompts = params.get("prompts")
|
|
243
|
+
if not prompts:
|
|
244
|
+
return "Error: prompts required for run action"
|
|
245
|
+
|
|
246
|
+
# Convert to list
|
|
247
|
+
if isinstance(prompts, str):
|
|
248
|
+
prompt_list = [prompts]
|
|
249
|
+
else:
|
|
250
|
+
prompt_list = prompts
|
|
251
|
+
|
|
252
|
+
# Validate prompts
|
|
253
|
+
for prompt in prompt_list:
|
|
254
|
+
if not self._validate_prompt(prompt):
|
|
255
|
+
return f"Error: Prompt must contain absolute paths starting with /: {prompt[:50]}..."
|
|
256
|
+
|
|
257
|
+
# Execute agents
|
|
258
|
+
start_time = time.time()
|
|
259
|
+
|
|
260
|
+
if len(prompt_list) == 1:
|
|
261
|
+
await tool_ctx.info("Launching agent")
|
|
262
|
+
result = await self._execute_agent(prompt_list[0], params.get("model"), tool_ctx)
|
|
263
|
+
else:
|
|
264
|
+
await tool_ctx.info(f"Launching {len(prompt_list)} agents in parallel")
|
|
265
|
+
result = await self._execute_multiple_agents(prompt_list, params.get("model"), tool_ctx)
|
|
266
|
+
|
|
267
|
+
execution_time = time.time() - start_time
|
|
268
|
+
|
|
269
|
+
return f"""Agent execution completed in {execution_time:.2f} seconds.
|
|
270
|
+
|
|
271
|
+
AGENT RESPONSE:
|
|
272
|
+
{result}"""
|
|
273
|
+
|
|
274
|
+
async def _handle_start(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
|
|
275
|
+
"""Start a new RPC agent."""
|
|
276
|
+
mode = params.get("mode", "oneoff")
|
|
277
|
+
if mode != "rpc":
|
|
278
|
+
return "Error: start action only valid for rpc mode"
|
|
279
|
+
|
|
280
|
+
# Generate agent ID
|
|
281
|
+
agent_id = str(uuid.uuid4())[:8]
|
|
282
|
+
|
|
283
|
+
# Get model
|
|
284
|
+
model = params.get("model") or get_default_model(self.model_override)
|
|
285
|
+
|
|
286
|
+
# Get available tools
|
|
287
|
+
agent_tools = get_allowed_agent_tools(
|
|
288
|
+
self.available_tools,
|
|
289
|
+
self.permission_manager,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Create system prompt
|
|
293
|
+
system_prompt = get_system_prompt(
|
|
294
|
+
agent_tools,
|
|
295
|
+
self.permission_manager,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Create RPC agent
|
|
299
|
+
agent = RPCAgent(agent_id, model, system_prompt, agent_tools)
|
|
300
|
+
self._rpc_agents[agent_id] = agent
|
|
301
|
+
|
|
302
|
+
await tool_ctx.info(f"Started RPC agent {agent_id} with model {model}")
|
|
303
|
+
|
|
304
|
+
return f"""Started RPC agent:
|
|
305
|
+
- ID: {agent_id}
|
|
306
|
+
- Model: {model}
|
|
307
|
+
- Tools: {len(agent_tools)}
|
|
308
|
+
|
|
309
|
+
Use 'agent --action call --agent-id {agent_id} --method <method> --args <args>' to interact."""
|
|
310
|
+
|
|
311
|
+
async def _handle_call(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
|
|
312
|
+
"""Call method on RPC agent."""
|
|
313
|
+
agent_id = params.get("agent_id")
|
|
314
|
+
if not agent_id:
|
|
315
|
+
return "Error: agent_id required for call action"
|
|
316
|
+
|
|
317
|
+
if agent_id not in self._rpc_agents:
|
|
318
|
+
return f"Error: Agent {agent_id} not found. Use 'agent --action list' to see active agents."
|
|
319
|
+
|
|
320
|
+
method = params.get("method")
|
|
321
|
+
if not method:
|
|
322
|
+
return "Error: method required for call action"
|
|
323
|
+
|
|
324
|
+
args = params.get("args", {})
|
|
325
|
+
|
|
326
|
+
# Call agent method
|
|
327
|
+
agent = self._rpc_agents[agent_id]
|
|
328
|
+
await tool_ctx.info(f"Calling {method} on agent {agent_id}")
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
result = await agent.call_method(method, args, tool_ctx)
|
|
332
|
+
return f"Agent {agent_id} response:\n{result}"
|
|
333
|
+
except Exception as e:
|
|
334
|
+
await tool_ctx.error(f"Error calling agent: {str(e)}")
|
|
335
|
+
return f"Error calling agent: {str(e)}"
|
|
336
|
+
|
|
337
|
+
async def _handle_stop(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
|
|
338
|
+
"""Stop an RPC agent."""
|
|
339
|
+
agent_id = params.get("agent_id")
|
|
340
|
+
if not agent_id:
|
|
341
|
+
return "Error: agent_id required for stop action"
|
|
342
|
+
|
|
343
|
+
if agent_id not in self._rpc_agents:
|
|
344
|
+
return f"Error: Agent {agent_id} not found"
|
|
345
|
+
|
|
346
|
+
agent = self._rpc_agents.pop(agent_id)
|
|
347
|
+
await tool_ctx.info(f"Stopped agent {agent_id}")
|
|
348
|
+
|
|
349
|
+
return f"""Stopped agent {agent_id}:
|
|
350
|
+
- Runtime: {time.time() - agent.created_at:.2f} seconds
|
|
351
|
+
- Calls: {agent.call_count}"""
|
|
352
|
+
|
|
353
|
+
async def _handle_list(self, tool_ctx: ToolContext) -> str:
|
|
354
|
+
"""List active RPC agents."""
|
|
355
|
+
if not self._rpc_agents:
|
|
356
|
+
return "No active RPC agents"
|
|
357
|
+
|
|
358
|
+
output = ["=== Active RPC Agents ==="]
|
|
359
|
+
for agent_id, agent in self._rpc_agents.items():
|
|
360
|
+
runtime = time.time() - agent.created_at
|
|
361
|
+
idle = time.time() - agent.last_used
|
|
362
|
+
output.append(f"\nAgent {agent_id}:")
|
|
363
|
+
output.append(f" Model: {agent.model}")
|
|
364
|
+
output.append(f" Runtime: {runtime:.2f}s")
|
|
365
|
+
output.append(f" Idle: {idle:.2f}s")
|
|
366
|
+
output.append(f" Calls: {agent.call_count}")
|
|
367
|
+
|
|
368
|
+
return "\n".join(output)
|
|
369
|
+
|
|
370
|
+
def _validate_prompt(self, prompt: str) -> bool:
|
|
371
|
+
"""Validate that prompt contains absolute paths."""
|
|
372
|
+
absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
|
|
373
|
+
return bool(re.search(absolute_path_pattern, prompt))
|
|
374
|
+
|
|
375
|
+
async def _execute_agent(self, prompt: str, model: Optional[str], tool_ctx: ToolContext) -> str:
|
|
376
|
+
"""Execute a single agent (simplified - would use full logic from agent_tool.py)."""
|
|
377
|
+
# This would integrate the full agent execution logic from agent_tool.py
|
|
378
|
+
# For now, return a placeholder
|
|
379
|
+
return f"Executed agent with prompt: {prompt[:100]}..."
|
|
380
|
+
|
|
381
|
+
async def _execute_multiple_agents(self, prompts: List[str], model: Optional[str], tool_ctx: ToolContext) -> str:
|
|
382
|
+
"""Execute multiple agents in parallel."""
|
|
383
|
+
tasks = []
|
|
384
|
+
for prompt in prompts:
|
|
385
|
+
task = self._execute_agent(prompt, model, tool_ctx)
|
|
386
|
+
tasks.append(task)
|
|
387
|
+
|
|
388
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
389
|
+
|
|
390
|
+
formatted_results = []
|
|
391
|
+
for i, result in enumerate(results):
|
|
392
|
+
if isinstance(result, Exception):
|
|
393
|
+
formatted_results.append(f"Agent {i+1} Error:\n{str(result)}")
|
|
394
|
+
else:
|
|
395
|
+
formatted_results.append(f"Agent {i+1} Result:\n{result}")
|
|
396
|
+
|
|
397
|
+
return "\n\n---\n\n".join(formatted_results)
|
|
398
|
+
|
|
399
|
+
def register(self, mcp_server) -> None:
|
|
400
|
+
"""Register this tool with the MCP server."""
|
|
401
|
+
pass
|
|
@@ -12,9 +12,8 @@ from collections.abc import Iterable
|
|
|
12
12
|
from typing import Annotated, TypedDict, Unpack, final, override
|
|
13
13
|
|
|
14
14
|
import litellm
|
|
15
|
-
from fastmcp import Context as MCPContext
|
|
16
|
-
from
|
|
17
|
-
from fastmcp.server.dependencies import get_context
|
|
15
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
16
|
+
from mcp.server import FastMCP
|
|
18
17
|
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionToolParam
|
|
19
18
|
from pydantic import Field
|
|
20
19
|
|
|
@@ -535,6 +534,6 @@ AGENT RESPONSE:
|
|
|
535
534
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
536
535
|
async def dispatch_agent(
|
|
537
536
|
prompts: str | list[str],
|
|
537
|
+
ctx: MCPContext
|
|
538
538
|
) -> str:
|
|
539
|
-
ctx = get_context()
|
|
540
539
|
return await tool_self.call(ctx, prompts=prompts)
|
hanzo_mcp/tools/common/base.py
CHANGED
|
@@ -10,8 +10,8 @@ from abc import ABC, abstractmethod
|
|
|
10
10
|
from collections.abc import Awaitable
|
|
11
11
|
from typing import Any, Callable, final
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from fastmcp import Context as MCPContext
|
|
13
|
+
from mcp.server import FastMCP
|
|
14
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
@@ -7,9 +7,8 @@ parallel or serial depending on their characteristics.
|
|
|
7
7
|
import asyncio
|
|
8
8
|
from typing import Annotated, Any, TypedDict, Unpack, final, override
|
|
9
9
|
|
|
10
|
-
from fastmcp import Context as MCPContext
|
|
11
|
-
from
|
|
12
|
-
from fastmcp.server.dependencies import get_context
|
|
10
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
+
from mcp.server import FastMCP
|
|
13
12
|
from pydantic import Field
|
|
14
13
|
|
|
15
14
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
@@ -320,11 +319,10 @@ Not available: think,write,edit,multi_edit,notebook_edit
|
|
|
320
319
|
|
|
321
320
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
322
321
|
async def batch(
|
|
323
|
-
ctx: MCPContext,
|
|
324
322
|
description: Description,
|
|
325
323
|
invocations: Invocations,
|
|
324
|
+
ctx: MCPContext
|
|
326
325
|
) -> str:
|
|
327
|
-
ctx = get_context()
|
|
328
326
|
return await tool_self.call(
|
|
329
327
|
ctx, description=description, invocations=invocations
|
|
330
328
|
)
|
|
@@ -4,7 +4,7 @@ from typing import Dict, List, Optional, TypedDict, Unpack, Any, final
|
|
|
4
4
|
import json
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from fastmcp import Context as MCPContext
|
|
7
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
10
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
@@ -11,7 +11,7 @@ from collections.abc import Iterable
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any, ClassVar, final
|
|
13
13
|
|
|
14
|
-
from fastmcp import Context as MCPContext
|
|
14
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
15
15
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
16
16
|
|
|
17
17
|
|