agentfield 0.1.22rc2__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.
- agentfield/__init__.py +66 -0
- agentfield/agent.py +3569 -0
- agentfield/agent_ai.py +1125 -0
- agentfield/agent_cli.py +386 -0
- agentfield/agent_field_handler.py +494 -0
- agentfield/agent_mcp.py +534 -0
- agentfield/agent_registry.py +29 -0
- agentfield/agent_server.py +1185 -0
- agentfield/agent_utils.py +269 -0
- agentfield/agent_workflow.py +323 -0
- agentfield/async_config.py +278 -0
- agentfield/async_execution_manager.py +1227 -0
- agentfield/client.py +1447 -0
- agentfield/connection_manager.py +280 -0
- agentfield/decorators.py +527 -0
- agentfield/did_manager.py +337 -0
- agentfield/dynamic_skills.py +304 -0
- agentfield/execution_context.py +255 -0
- agentfield/execution_state.py +453 -0
- agentfield/http_connection_manager.py +429 -0
- agentfield/litellm_adapters.py +140 -0
- agentfield/logger.py +249 -0
- agentfield/mcp_client.py +204 -0
- agentfield/mcp_manager.py +340 -0
- agentfield/mcp_stdio_bridge.py +550 -0
- agentfield/memory.py +723 -0
- agentfield/memory_events.py +489 -0
- agentfield/multimodal.py +173 -0
- agentfield/multimodal_response.py +403 -0
- agentfield/pydantic_utils.py +227 -0
- agentfield/rate_limiter.py +280 -0
- agentfield/result_cache.py +441 -0
- agentfield/router.py +190 -0
- agentfield/status.py +70 -0
- agentfield/types.py +710 -0
- agentfield/utils.py +26 -0
- agentfield/vc_generator.py +464 -0
- agentfield/vision.py +198 -0
- agentfield-0.1.22rc2.dist-info/METADATA +102 -0
- agentfield-0.1.22rc2.dist-info/RECORD +42 -0
- agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
- agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
agentfield/agent_mcp.py
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from agentfield.agent_utils import AgentUtils
|
|
6
|
+
from agentfield.dynamic_skills import DynamicMCPSkillManager
|
|
7
|
+
from agentfield.execution_context import ExecutionContext
|
|
8
|
+
from agentfield.logger import log_debug, log_error, log_info, log_warn
|
|
9
|
+
from agentfield.mcp_client import MCPClientRegistry
|
|
10
|
+
from agentfield.mcp_manager import MCPManager
|
|
11
|
+
from agentfield.types import AgentStatus, MCPServerHealth
|
|
12
|
+
from fastapi import Request
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentMCP:
|
|
16
|
+
"""
|
|
17
|
+
MCP Management handler for Agent class.
|
|
18
|
+
|
|
19
|
+
This class encapsulates all MCP-related functionality including:
|
|
20
|
+
- Agent directory detection
|
|
21
|
+
- MCP server lifecycle management
|
|
22
|
+
- MCP skill registration
|
|
23
|
+
- Health monitoring
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, agent_instance):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the MCP handler with a reference to the agent instance.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
agent_instance: The Agent instance this handler belongs to
|
|
32
|
+
"""
|
|
33
|
+
self.agent = agent_instance
|
|
34
|
+
|
|
35
|
+
def _detect_agent_directory(self) -> str:
|
|
36
|
+
"""Detect the correct agent directory for MCP config discovery"""
|
|
37
|
+
import os
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
current_dir = Path(os.getcwd())
|
|
41
|
+
|
|
42
|
+
# Check if packages/mcp exists in current directory
|
|
43
|
+
if (current_dir / "packages" / "mcp").exists():
|
|
44
|
+
return str(current_dir)
|
|
45
|
+
|
|
46
|
+
# Look for agent directories in current directory
|
|
47
|
+
for item in current_dir.iterdir():
|
|
48
|
+
if item.is_dir() and (item / "packages" / "mcp").exists():
|
|
49
|
+
if self.agent.dev_mode:
|
|
50
|
+
log_debug(f"Found agent directory: {item}")
|
|
51
|
+
return str(item)
|
|
52
|
+
|
|
53
|
+
# Look in parent directories (up to 3 levels)
|
|
54
|
+
for i in range(3):
|
|
55
|
+
parent = current_dir.parents[i] if i < len(current_dir.parents) else None
|
|
56
|
+
if parent and (parent / "packages" / "mcp").exists():
|
|
57
|
+
if self.agent.dev_mode:
|
|
58
|
+
log_debug(f"Found agent directory in parent: {parent}")
|
|
59
|
+
return str(parent)
|
|
60
|
+
|
|
61
|
+
# Fallback to current directory
|
|
62
|
+
if self.agent.dev_mode:
|
|
63
|
+
log_warn(
|
|
64
|
+
f"No packages/mcp directory found, using current directory: {current_dir}"
|
|
65
|
+
)
|
|
66
|
+
return str(current_dir)
|
|
67
|
+
|
|
68
|
+
async def initialize_mcp(self):
|
|
69
|
+
"""
|
|
70
|
+
Initialize MCP management components.
|
|
71
|
+
|
|
72
|
+
This method combines the MCP initialization logic that was previously
|
|
73
|
+
scattered in the Agent.__init__ method.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
agent_dir = self._detect_agent_directory()
|
|
77
|
+
self.agent.mcp_manager = MCPManager(agent_dir, self.agent.dev_mode)
|
|
78
|
+
self.agent.mcp_client_registry = MCPClientRegistry(self.agent.dev_mode)
|
|
79
|
+
|
|
80
|
+
if self.agent.dev_mode:
|
|
81
|
+
log_info(f"Initialized MCP Manager in {agent_dir}")
|
|
82
|
+
|
|
83
|
+
# Initialize Dynamic Skill Manager when both MCP components are available
|
|
84
|
+
if self.agent.mcp_manager and self.agent.mcp_client_registry:
|
|
85
|
+
self.agent.dynamic_skill_manager = DynamicMCPSkillManager(
|
|
86
|
+
self.agent, self.agent.dev_mode
|
|
87
|
+
)
|
|
88
|
+
if self.agent.dev_mode:
|
|
89
|
+
log_info("Dynamic MCP skill manager initialized")
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
if self.agent.dev_mode:
|
|
93
|
+
log_error(f"Failed to initialize MCP Manager: {e}")
|
|
94
|
+
self.agent.mcp_manager = None
|
|
95
|
+
self.agent.mcp_client_registry = None
|
|
96
|
+
self.agent.dynamic_skill_manager = None
|
|
97
|
+
|
|
98
|
+
async def _start_mcp_servers(self) -> None:
|
|
99
|
+
"""Start all configured MCP servers using SimpleMCPManager."""
|
|
100
|
+
if not self.agent.mcp_manager:
|
|
101
|
+
if self.agent.dev_mode:
|
|
102
|
+
log_info("No MCP Manager available - skipping server startup")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
if self.agent.dev_mode:
|
|
107
|
+
log_info("Starting MCP servers...")
|
|
108
|
+
|
|
109
|
+
# Start all servers
|
|
110
|
+
started_servers = await self.agent.mcp_manager.start_all_servers()
|
|
111
|
+
|
|
112
|
+
if started_servers:
|
|
113
|
+
successful = sum(1 for success in started_servers.values() if success)
|
|
114
|
+
if self.agent.dev_mode:
|
|
115
|
+
log_info(f"Started {successful}/{len(started_servers)} MCP servers")
|
|
116
|
+
elif self.agent.dev_mode:
|
|
117
|
+
log_info("No MCP servers configured to start")
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
if self.agent.dev_mode:
|
|
121
|
+
log_error(f"MCP server startup error: {e}")
|
|
122
|
+
|
|
123
|
+
def _cleanup_mcp_servers(self) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Stop all MCP servers during agent shutdown.
|
|
126
|
+
|
|
127
|
+
This method is called during graceful shutdown to ensure all
|
|
128
|
+
MCP server processes are properly terminated.
|
|
129
|
+
"""
|
|
130
|
+
if not self.agent.mcp_manager:
|
|
131
|
+
if self.agent.dev_mode:
|
|
132
|
+
log_info("No MCP Manager available - skipping cleanup")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
async def async_cleanup():
|
|
136
|
+
try:
|
|
137
|
+
if self.agent.dev_mode:
|
|
138
|
+
log_info("Stopping MCP servers...")
|
|
139
|
+
|
|
140
|
+
# Check if mcp_manager is still available
|
|
141
|
+
if not self.agent.mcp_manager:
|
|
142
|
+
if self.agent.dev_mode:
|
|
143
|
+
log_info("MCP Manager not available during cleanup")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Get current server status before stopping
|
|
147
|
+
all_status = self.agent.mcp_manager.get_all_status()
|
|
148
|
+
|
|
149
|
+
if all_status:
|
|
150
|
+
running_servers = [
|
|
151
|
+
alias
|
|
152
|
+
for alias, health in all_status.items()
|
|
153
|
+
if health.get("status") == "running"
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
if running_servers:
|
|
157
|
+
# Stop all running servers
|
|
158
|
+
for alias in running_servers:
|
|
159
|
+
try:
|
|
160
|
+
if (
|
|
161
|
+
self.agent.mcp_manager
|
|
162
|
+
): # Double-check before each call
|
|
163
|
+
await self.agent.mcp_manager.stop_server(alias)
|
|
164
|
+
if self.agent.dev_mode:
|
|
165
|
+
health = all_status.get(alias, {})
|
|
166
|
+
pid = health.get("pid") or "N/A"
|
|
167
|
+
log_info(
|
|
168
|
+
f"Stopped MCP server: {alias} (PID: {pid})"
|
|
169
|
+
)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
if self.agent.dev_mode:
|
|
172
|
+
log_error(f"Failed to stop MCP server {alias}: {e}")
|
|
173
|
+
|
|
174
|
+
if self.agent.dev_mode:
|
|
175
|
+
log_info(f"Stopped {len(running_servers)} MCP servers")
|
|
176
|
+
elif self.agent.dev_mode:
|
|
177
|
+
log_info("No running MCP servers to stop")
|
|
178
|
+
except Exception as e:
|
|
179
|
+
if self.agent.dev_mode:
|
|
180
|
+
log_error(f"Error during MCP server cleanup: {e}")
|
|
181
|
+
# Continue with shutdown even if cleanup fails
|
|
182
|
+
|
|
183
|
+
# Run the async cleanup properly
|
|
184
|
+
try:
|
|
185
|
+
# Check if we're already in an event loop
|
|
186
|
+
try:
|
|
187
|
+
loop = asyncio.get_running_loop()
|
|
188
|
+
# If we're in a loop, create a task and store reference to prevent warning
|
|
189
|
+
task = loop.create_task(async_cleanup())
|
|
190
|
+
|
|
191
|
+
# Add a done callback to handle any exceptions and suppress warnings
|
|
192
|
+
def handle_task_completion(t):
|
|
193
|
+
try:
|
|
194
|
+
if t.exception() is not None and self.agent.dev_mode:
|
|
195
|
+
log_error(f"MCP cleanup task failed: {t.exception()}")
|
|
196
|
+
except Exception:
|
|
197
|
+
# Suppress any callback exceptions to prevent warnings
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
task.add_done_callback(handle_task_completion)
|
|
201
|
+
# Store task reference to prevent garbage collection warning
|
|
202
|
+
if not hasattr(self, "_cleanup_tasks"):
|
|
203
|
+
self._cleanup_tasks = []
|
|
204
|
+
self._cleanup_tasks.append(task)
|
|
205
|
+
except RuntimeError:
|
|
206
|
+
# No event loop running, we can use asyncio.run()
|
|
207
|
+
try:
|
|
208
|
+
asyncio.run(async_cleanup())
|
|
209
|
+
except Exception as cleanup_error:
|
|
210
|
+
if self.agent.dev_mode:
|
|
211
|
+
log_error(f"MCP cleanup failed: {cleanup_error}")
|
|
212
|
+
except Exception as e:
|
|
213
|
+
if self.agent.dev_mode:
|
|
214
|
+
log_error(f"Failed to run MCP cleanup: {e}")
|
|
215
|
+
|
|
216
|
+
def _register_mcp_server_skills(self) -> None:
|
|
217
|
+
"""
|
|
218
|
+
DEPRECATED: This method is replaced by DynamicMCPSkillManager.
|
|
219
|
+
The static file-based approach is broken after SimpleMCPManager refactor.
|
|
220
|
+
"""
|
|
221
|
+
if self.agent.dev_mode:
|
|
222
|
+
log_warn("DEPRECATED: _register_mcp_server_skills() is no longer used")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
def _register_mcp_tool_as_skill(
|
|
226
|
+
self, server_alias: str, tool: Dict[str, Any]
|
|
227
|
+
) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Register an MCP tool as a proper FastAPI skill endpoint.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
server_alias: The alias of the MCP server
|
|
233
|
+
tool: Tool definition from mcp.json
|
|
234
|
+
"""
|
|
235
|
+
tool_name = tool.get("name", "")
|
|
236
|
+
if not tool_name:
|
|
237
|
+
if self.agent.dev_mode:
|
|
238
|
+
log_warn(f"Skipping tool with missing name: {tool}")
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
skill_name = f"{server_alias}_{tool_name}"
|
|
242
|
+
endpoint_path = f"/skills/{skill_name}"
|
|
243
|
+
|
|
244
|
+
# Create a simple input schema - use dict for flexibility
|
|
245
|
+
from pydantic import BaseModel
|
|
246
|
+
|
|
247
|
+
class InputSchema(BaseModel):
|
|
248
|
+
"""Dynamic input schema for MCP tool"""
|
|
249
|
+
|
|
250
|
+
args: dict = {}
|
|
251
|
+
|
|
252
|
+
class Config:
|
|
253
|
+
extra = "allow" # Allow additional fields
|
|
254
|
+
|
|
255
|
+
# Create the MCP skill function
|
|
256
|
+
async def mcp_skill_function(**kwargs):
|
|
257
|
+
"""Dynamically created MCP skill function"""
|
|
258
|
+
if self.agent.dev_mode:
|
|
259
|
+
log_debug(
|
|
260
|
+
f"MCP skill called: {server_alias}.{tool_name} with args: {kwargs}"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Get process-aware MCP client (reuses existing running processes)
|
|
265
|
+
if not self.agent.mcp_client_registry:
|
|
266
|
+
raise Exception("MCPClientRegistry not initialized")
|
|
267
|
+
mcp_client = self.agent.mcp_client_registry.get_client(server_alias)
|
|
268
|
+
if not mcp_client:
|
|
269
|
+
raise Exception(f"MCP client for {server_alias} not found")
|
|
270
|
+
|
|
271
|
+
# Call the MCP tool using existing process
|
|
272
|
+
result = await mcp_client.call_tool(tool_name, kwargs)
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
"status": "success",
|
|
276
|
+
"result": result,
|
|
277
|
+
"server": server_alias,
|
|
278
|
+
"tool": tool_name,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
if self.agent.dev_mode:
|
|
283
|
+
log_error(f"MCP skill error: {e}")
|
|
284
|
+
return {
|
|
285
|
+
"status": "error",
|
|
286
|
+
"error": str(e),
|
|
287
|
+
"server": server_alias,
|
|
288
|
+
"tool": tool_name,
|
|
289
|
+
"args": kwargs,
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# Create FastAPI endpoint
|
|
293
|
+
@self.agent.post(endpoint_path, response_model=dict)
|
|
294
|
+
async def mcp_skill_endpoint(input_data: InputSchema, request: Request):
|
|
295
|
+
from agentfield.execution_context import ExecutionContext
|
|
296
|
+
|
|
297
|
+
# Extract execution context from request headers
|
|
298
|
+
execution_context = ExecutionContext.from_request(
|
|
299
|
+
request, self.agent.node_id
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Store current context for use in app.call()
|
|
303
|
+
self.agent._current_execution_context = execution_context
|
|
304
|
+
|
|
305
|
+
# Convert input to function arguments
|
|
306
|
+
kwargs = input_data.args
|
|
307
|
+
|
|
308
|
+
# Call the MCP skill function
|
|
309
|
+
result = await mcp_skill_function(**kwargs)
|
|
310
|
+
|
|
311
|
+
return result
|
|
312
|
+
|
|
313
|
+
# Register skill metadata
|
|
314
|
+
self.agent.skills.append(
|
|
315
|
+
{
|
|
316
|
+
"id": skill_name,
|
|
317
|
+
"input_schema": InputSchema.model_json_schema(),
|
|
318
|
+
"tags": ["mcp", server_alias],
|
|
319
|
+
"description": tool.get("description", f"MCP tool: {tool_name}"),
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def _create_and_register_mcp_skill(
|
|
324
|
+
self, server_alias: str, tool: Dict[str, Any]
|
|
325
|
+
) -> None:
|
|
326
|
+
"""
|
|
327
|
+
Create and register a single MCP tool as a AgentField skill.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
server_alias: The alias of the MCP server
|
|
331
|
+
tool: Tool definition from mcp.json
|
|
332
|
+
"""
|
|
333
|
+
tool_name = tool.get("name", "")
|
|
334
|
+
if not tool_name:
|
|
335
|
+
raise ValueError("Tool missing 'name' field")
|
|
336
|
+
|
|
337
|
+
# Generate skill function name: server_alias + tool_name
|
|
338
|
+
skill_name = AgentUtils.generate_skill_name(server_alias, tool_name)
|
|
339
|
+
|
|
340
|
+
# Create the skill function dynamically
|
|
341
|
+
async def mcp_skill_function(
|
|
342
|
+
execution_context: Optional[ExecutionContext] = None, **kwargs
|
|
343
|
+
) -> Any:
|
|
344
|
+
"""
|
|
345
|
+
Auto-generated MCP skill function.
|
|
346
|
+
|
|
347
|
+
This function calls the corresponding MCP tool and returns the result.
|
|
348
|
+
"""
|
|
349
|
+
try:
|
|
350
|
+
# Get MCP client
|
|
351
|
+
if not self.agent.mcp_client_registry:
|
|
352
|
+
raise Exception("MCPClientRegistry not initialized")
|
|
353
|
+
client = self.agent.mcp_client_registry.get_client(server_alias)
|
|
354
|
+
if not client:
|
|
355
|
+
raise Exception(f"MCP client for {server_alias} not found")
|
|
356
|
+
|
|
357
|
+
# Call the MCP tool
|
|
358
|
+
result = await client.call_tool(tool_name, kwargs)
|
|
359
|
+
return result
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
# Re-raise with helpful context
|
|
363
|
+
raise Exception(
|
|
364
|
+
f"MCP tool '{server_alias}.{tool_name}' failed: {str(e)}"
|
|
365
|
+
) from e
|
|
366
|
+
|
|
367
|
+
# Set function metadata
|
|
368
|
+
mcp_skill_function.__name__ = skill_name
|
|
369
|
+
mcp_skill_function.__doc__ = f"""
|
|
370
|
+
{tool.get("description", f"MCP tool: {tool_name}")}
|
|
371
|
+
|
|
372
|
+
This is an auto-generated skill that wraps the '{tool_name}' tool from the '{server_alias}' MCP server.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
execution_context (ExecutionContext, optional): AgentField execution context for workflow tracking
|
|
376
|
+
**kwargs: Arguments to pass to the MCP tool
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Any: The result from the MCP tool execution
|
|
380
|
+
|
|
381
|
+
Raises:
|
|
382
|
+
Exception: If the MCP server is unavailable or the tool execution fails
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
# Create input schema from tool's input schema
|
|
386
|
+
input_schema = AgentUtils.create_input_schema_from_mcp_tool(skill_name, tool)
|
|
387
|
+
|
|
388
|
+
# Create FastAPI endpoint
|
|
389
|
+
endpoint_path = f"/skills/{skill_name}"
|
|
390
|
+
|
|
391
|
+
@self.agent.post(endpoint_path, response_model=dict)
|
|
392
|
+
async def mcp_skill_endpoint(input_data: Dict[str, Any], request: Request):
|
|
393
|
+
# Extract execution context from request headers
|
|
394
|
+
execution_context = ExecutionContext.from_request(
|
|
395
|
+
request, self.agent.node_id
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Store current context for use in app.call()
|
|
399
|
+
self.agent._current_execution_context = execution_context
|
|
400
|
+
|
|
401
|
+
# Convert input to function arguments
|
|
402
|
+
kwargs = input_data
|
|
403
|
+
|
|
404
|
+
# Call the MCP skill function
|
|
405
|
+
result = await mcp_skill_function(
|
|
406
|
+
execution_context=execution_context, **kwargs
|
|
407
|
+
)
|
|
408
|
+
return result
|
|
409
|
+
|
|
410
|
+
# Register skill metadata
|
|
411
|
+
self.agent.skills.append(
|
|
412
|
+
{
|
|
413
|
+
"id": skill_name,
|
|
414
|
+
"input_schema": input_schema.model_json_schema(),
|
|
415
|
+
"tags": ["mcp", server_alias],
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
def _get_mcp_server_health(self) -> List[MCPServerHealth]:
|
|
420
|
+
"""
|
|
421
|
+
Get health information for all MCP servers.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
List of MCPServerHealth objects
|
|
425
|
+
"""
|
|
426
|
+
mcp_servers = []
|
|
427
|
+
|
|
428
|
+
if self.agent.mcp_manager:
|
|
429
|
+
try:
|
|
430
|
+
all_status = self.agent.mcp_manager.get_all_status()
|
|
431
|
+
|
|
432
|
+
for alias, server_info in all_status.items():
|
|
433
|
+
server_health = MCPServerHealth(
|
|
434
|
+
alias=alias,
|
|
435
|
+
status=server_info.get("status", "unknown"),
|
|
436
|
+
tool_count=0,
|
|
437
|
+
port=server_info.get("port"),
|
|
438
|
+
process_id=(
|
|
439
|
+
server_info.get("process", {}).get("pid")
|
|
440
|
+
if server_info.get("process")
|
|
441
|
+
else None
|
|
442
|
+
),
|
|
443
|
+
started_at=datetime.now().isoformat(),
|
|
444
|
+
last_health_check=datetime.now().isoformat(),
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
# Try to get tool count if server is running
|
|
448
|
+
if (
|
|
449
|
+
server_health.status == "running"
|
|
450
|
+
and self.agent.mcp_client_registry
|
|
451
|
+
):
|
|
452
|
+
try:
|
|
453
|
+
client = self.agent.mcp_client_registry.get_client(alias)
|
|
454
|
+
if client:
|
|
455
|
+
# This would need to be implemented properly
|
|
456
|
+
server_health.tool_count = 0 # Placeholder
|
|
457
|
+
except Exception:
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
mcp_servers.append(server_health)
|
|
461
|
+
|
|
462
|
+
except Exception as e:
|
|
463
|
+
if self.agent.dev_mode:
|
|
464
|
+
log_error(f"Error getting MCP server health: {e}")
|
|
465
|
+
|
|
466
|
+
return mcp_servers
|
|
467
|
+
|
|
468
|
+
async def _background_mcp_initialization(self) -> None:
|
|
469
|
+
"""
|
|
470
|
+
Initialize MCP servers in the background after registration.
|
|
471
|
+
"""
|
|
472
|
+
try:
|
|
473
|
+
if self.agent.dev_mode:
|
|
474
|
+
log_info("Background MCP initialization started")
|
|
475
|
+
|
|
476
|
+
# Start MCP servers
|
|
477
|
+
if self.agent.mcp_manager:
|
|
478
|
+
results = await self.agent.mcp_manager.start_all_servers()
|
|
479
|
+
|
|
480
|
+
# Register clients for successfully started servers
|
|
481
|
+
for alias, success in results.items():
|
|
482
|
+
if success and self.agent.mcp_client_registry:
|
|
483
|
+
server_status = self.agent.mcp_manager.get_server_status(alias)
|
|
484
|
+
if server_status and server_status.get("port"):
|
|
485
|
+
self.agent.mcp_client_registry.register_client(
|
|
486
|
+
alias, server_status["port"]
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
successful = sum(1 for success in results.values() if success)
|
|
490
|
+
total = len(results)
|
|
491
|
+
|
|
492
|
+
if self.agent.dev_mode:
|
|
493
|
+
log_info(
|
|
494
|
+
f"MCP initialization: {successful}/{total} servers started"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Update status based on MCP results
|
|
498
|
+
if successful == total and total > 0:
|
|
499
|
+
self.agent._current_status = AgentStatus.READY
|
|
500
|
+
elif successful > 0:
|
|
501
|
+
self.agent._current_status = AgentStatus.DEGRADED
|
|
502
|
+
else:
|
|
503
|
+
self.agent._current_status = (
|
|
504
|
+
AgentStatus.READY
|
|
505
|
+
) # Still ready even without MCP
|
|
506
|
+
else:
|
|
507
|
+
# No MCP manager, agent is ready
|
|
508
|
+
self.agent._current_status = AgentStatus.READY
|
|
509
|
+
if self.agent.dev_mode:
|
|
510
|
+
log_info("No MCP servers to initialize - agent ready")
|
|
511
|
+
|
|
512
|
+
# Register dynamic skills if available
|
|
513
|
+
if self.agent.dynamic_skill_manager:
|
|
514
|
+
if self.agent.dev_mode:
|
|
515
|
+
log_info("Registering MCP tools as skills...")
|
|
516
|
+
await (
|
|
517
|
+
self.agent.dynamic_skill_manager.discover_and_register_all_skills()
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
self.agent._mcp_initialization_complete = True
|
|
521
|
+
|
|
522
|
+
# Send status update heartbeat
|
|
523
|
+
await self.agent.agentfield_handler.send_enhanced_heartbeat()
|
|
524
|
+
|
|
525
|
+
if self.agent.dev_mode:
|
|
526
|
+
log_info(
|
|
527
|
+
f"Background initialization complete - Status: {self.agent._current_status.value}"
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
except Exception as e:
|
|
531
|
+
if self.agent.dev_mode:
|
|
532
|
+
log_error(f"Background MCP initialization error: {e}")
|
|
533
|
+
self.agent._current_status = AgentStatus.DEGRADED
|
|
534
|
+
await self.agent.agentfield_handler.send_enhanced_heartbeat()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent registry for tracking the current agent instance in thread-local storage.
|
|
3
|
+
This allows reasoners to automatically find their parent agent for workflow tracking.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
from typing import Optional, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .agent import Agent
|
|
11
|
+
|
|
12
|
+
# Thread-local storage for agent instances
|
|
13
|
+
_thread_local = threading.local()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def set_current_agent(agent_instance: "Agent"):
|
|
17
|
+
"""Register the current agent instance for this thread."""
|
|
18
|
+
_thread_local.current_agent = agent_instance
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_current_agent_instance() -> Optional["Agent"]:
|
|
22
|
+
"""Get the current agent instance for this thread."""
|
|
23
|
+
return getattr(_thread_local, "current_agent", None)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def clear_current_agent():
|
|
27
|
+
"""Clear the current agent instance."""
|
|
28
|
+
if hasattr(_thread_local, "current_agent"):
|
|
29
|
+
delattr(_thread_local, "current_agent")
|