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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from .logger import get_logger
|
|
9
|
+
from .mcp_stdio_bridge import StdioMCPBridge
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class MCPServerConfig:
|
|
16
|
+
alias: str
|
|
17
|
+
run_command: str
|
|
18
|
+
working_dir: str
|
|
19
|
+
environment: Dict[str, str]
|
|
20
|
+
health_check: Optional[str] = None
|
|
21
|
+
port: Optional[int] = None
|
|
22
|
+
transport: str = "http"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class MCPServerProcess:
|
|
27
|
+
config: MCPServerConfig
|
|
28
|
+
process: Optional[subprocess.Popen] = None
|
|
29
|
+
port: Optional[int] = None
|
|
30
|
+
status: str = "stopped" # stopped, starting, running, failed
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MCPManager:
|
|
34
|
+
def __init__(self, agent_directory: str, dev_mode: bool = False):
|
|
35
|
+
self.agent_directory = agent_directory
|
|
36
|
+
self.dev_mode = dev_mode
|
|
37
|
+
self.servers: Dict[str, MCPServerProcess] = {}
|
|
38
|
+
self.stdio_bridges: Dict[str, StdioMCPBridge] = {}
|
|
39
|
+
self.port_range_start = 8100 # Start assigning ports from 8100
|
|
40
|
+
self.used_ports = set()
|
|
41
|
+
|
|
42
|
+
def discover_mcp_servers(self) -> List[MCPServerConfig]:
|
|
43
|
+
"""Discover MCP servers from packages/mcp/ directory"""
|
|
44
|
+
mcp_dir = os.path.join(self.agent_directory, "packages", "mcp")
|
|
45
|
+
servers = []
|
|
46
|
+
|
|
47
|
+
if not os.path.exists(mcp_dir):
|
|
48
|
+
if self.dev_mode:
|
|
49
|
+
logger.debug(f"No MCP directory found at {mcp_dir}")
|
|
50
|
+
return servers
|
|
51
|
+
|
|
52
|
+
for item in os.listdir(mcp_dir):
|
|
53
|
+
server_dir = os.path.join(mcp_dir, item)
|
|
54
|
+
config_file = os.path.join(server_dir, "config.json")
|
|
55
|
+
|
|
56
|
+
if os.path.isdir(server_dir) and os.path.exists(config_file):
|
|
57
|
+
try:
|
|
58
|
+
with open(config_file, "r") as f:
|
|
59
|
+
config_data = json.load(f)
|
|
60
|
+
|
|
61
|
+
config = MCPServerConfig(
|
|
62
|
+
alias=config_data.get("alias", item),
|
|
63
|
+
run_command=config_data.get("run", ""),
|
|
64
|
+
working_dir=server_dir,
|
|
65
|
+
environment=config_data.get("environment", {}),
|
|
66
|
+
health_check=config_data.get("health_check"),
|
|
67
|
+
transport=config_data.get("transport", "http"),
|
|
68
|
+
)
|
|
69
|
+
servers.append(config)
|
|
70
|
+
|
|
71
|
+
if self.dev_mode:
|
|
72
|
+
logger.debug(f"Discovered MCP server: {config.alias}")
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
if self.dev_mode:
|
|
76
|
+
logger.warning(f"Failed to load config for {item}: {e}")
|
|
77
|
+
|
|
78
|
+
return servers
|
|
79
|
+
|
|
80
|
+
def _get_next_available_port(self) -> int:
|
|
81
|
+
"""Get next available port for MCP server"""
|
|
82
|
+
import socket
|
|
83
|
+
|
|
84
|
+
for port in range(self.port_range_start, self.port_range_start + 1000):
|
|
85
|
+
if port not in self.used_ports:
|
|
86
|
+
# Test if port is actually available
|
|
87
|
+
try:
|
|
88
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
89
|
+
s.bind(("localhost", port))
|
|
90
|
+
self.used_ports.add(port)
|
|
91
|
+
return port
|
|
92
|
+
except OSError:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
raise RuntimeError("No available ports for MCP servers")
|
|
96
|
+
|
|
97
|
+
def _detect_transport(self, config: MCPServerConfig) -> str:
|
|
98
|
+
"""Detect transport type from config"""
|
|
99
|
+
return config.transport
|
|
100
|
+
|
|
101
|
+
async def _start_stdio_server(self, config: MCPServerConfig) -> bool:
|
|
102
|
+
"""Start stdio MCP server using bridge"""
|
|
103
|
+
try:
|
|
104
|
+
# Assign port for the bridge
|
|
105
|
+
port = self._get_next_available_port()
|
|
106
|
+
config.port = port
|
|
107
|
+
|
|
108
|
+
if self.dev_mode:
|
|
109
|
+
logger.info(f"Starting stdio MCP server: {config.alias} on port {port}")
|
|
110
|
+
logger.debug(f"Command: {config.run_command}")
|
|
111
|
+
|
|
112
|
+
# Prepare server config for bridge
|
|
113
|
+
server_config = {
|
|
114
|
+
"run": config.run_command,
|
|
115
|
+
"working_dir": config.working_dir,
|
|
116
|
+
"environment": config.environment,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Create and start stdio bridge
|
|
120
|
+
bridge = StdioMCPBridge(
|
|
121
|
+
server_config=server_config, port=port, dev_mode=self.dev_mode
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
success = await bridge.start()
|
|
125
|
+
if success:
|
|
126
|
+
self.stdio_bridges[config.alias] = bridge
|
|
127
|
+
if self.dev_mode:
|
|
128
|
+
logger.info(f"Stdio MCP server {config.alias} started successfully")
|
|
129
|
+
return True
|
|
130
|
+
else:
|
|
131
|
+
if self.dev_mode:
|
|
132
|
+
logger.error(f"Stdio MCP server {config.alias} failed to start")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
if self.dev_mode:
|
|
137
|
+
logger.error(f"Error starting stdio MCP server {config.alias}: {e}")
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
async def _start_http_server(self, config: MCPServerConfig) -> bool:
|
|
141
|
+
"""Start HTTP MCP server (original implementation)"""
|
|
142
|
+
try:
|
|
143
|
+
# Assign port
|
|
144
|
+
port = self._get_next_available_port()
|
|
145
|
+
config.port = port
|
|
146
|
+
|
|
147
|
+
# Prepare command with port substitution
|
|
148
|
+
run_command = config.run_command.replace("{{port}}", str(port))
|
|
149
|
+
|
|
150
|
+
# Prepare environment
|
|
151
|
+
env = os.environ.copy()
|
|
152
|
+
env.update(config.environment)
|
|
153
|
+
|
|
154
|
+
if self.dev_mode:
|
|
155
|
+
logger.info(f"Starting HTTP MCP server: {config.alias} on port {port}")
|
|
156
|
+
logger.debug(f"Command: {run_command}")
|
|
157
|
+
|
|
158
|
+
# Start process
|
|
159
|
+
process = subprocess.Popen(
|
|
160
|
+
run_command.split(),
|
|
161
|
+
cwd=config.working_dir,
|
|
162
|
+
env=env,
|
|
163
|
+
stdout=subprocess.PIPE,
|
|
164
|
+
stderr=subprocess.PIPE,
|
|
165
|
+
text=True,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Create server process object
|
|
169
|
+
server_process = MCPServerProcess(
|
|
170
|
+
config=config, process=process, port=port, status="starting"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
self.servers[config.alias] = server_process
|
|
174
|
+
|
|
175
|
+
# Wait a moment for startup
|
|
176
|
+
await asyncio.sleep(2)
|
|
177
|
+
|
|
178
|
+
# Check if process is still running
|
|
179
|
+
if process.poll() is None:
|
|
180
|
+
server_process.status = "running"
|
|
181
|
+
if self.dev_mode:
|
|
182
|
+
logger.info(f"HTTP MCP server {config.alias} started successfully")
|
|
183
|
+
return True
|
|
184
|
+
else:
|
|
185
|
+
server_process.status = "failed"
|
|
186
|
+
if self.dev_mode:
|
|
187
|
+
logger.error(f"HTTP MCP server {config.alias} failed to start")
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
if self.dev_mode:
|
|
192
|
+
logger.error(f"Error starting HTTP MCP server {config.alias}: {e}")
|
|
193
|
+
if config.alias in self.servers:
|
|
194
|
+
self.servers[config.alias].status = "failed"
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
async def start_server(self, config: MCPServerConfig) -> bool:
|
|
198
|
+
"""Start individual MCP server"""
|
|
199
|
+
transport = self._detect_transport(config)
|
|
200
|
+
if transport == "stdio":
|
|
201
|
+
return await self._start_stdio_server(config)
|
|
202
|
+
else:
|
|
203
|
+
return await self._start_http_server(config)
|
|
204
|
+
|
|
205
|
+
async def start_all_servers(self) -> Dict[str, bool]:
|
|
206
|
+
"""Start all discovered MCP servers"""
|
|
207
|
+
configs = self.discover_mcp_servers()
|
|
208
|
+
results = {}
|
|
209
|
+
|
|
210
|
+
if self.dev_mode:
|
|
211
|
+
logger.info(f"Starting {len(configs)} MCP servers...")
|
|
212
|
+
|
|
213
|
+
for config in configs:
|
|
214
|
+
success = await self.start_server(config)
|
|
215
|
+
results[config.alias] = success
|
|
216
|
+
|
|
217
|
+
return results
|
|
218
|
+
|
|
219
|
+
def get_server_status(self, alias: str) -> Optional[Dict[str, Any]]:
|
|
220
|
+
"""Get status of specific MCP server"""
|
|
221
|
+
# Check stdio bridges first
|
|
222
|
+
if alias in self.stdio_bridges:
|
|
223
|
+
bridge = self.stdio_bridges[alias]
|
|
224
|
+
return {
|
|
225
|
+
"alias": alias,
|
|
226
|
+
"transport": "stdio",
|
|
227
|
+
"port": bridge.port,
|
|
228
|
+
"status": "running" if bridge.running else "stopped",
|
|
229
|
+
"initialized": bridge.initialized,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
# Check HTTP servers
|
|
233
|
+
if alias in self.servers:
|
|
234
|
+
server_process = self.servers[alias]
|
|
235
|
+
return {
|
|
236
|
+
"alias": alias,
|
|
237
|
+
"transport": "http",
|
|
238
|
+
"port": server_process.port,
|
|
239
|
+
"status": server_process.status,
|
|
240
|
+
"config": server_process.config,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def get_all_status(self) -> Dict[str, Dict[str, Any]]:
|
|
246
|
+
"""Get status of all MCP servers"""
|
|
247
|
+
all_status = {}
|
|
248
|
+
|
|
249
|
+
# Add stdio bridges
|
|
250
|
+
for alias, bridge in self.stdio_bridges.items():
|
|
251
|
+
all_status[alias] = {
|
|
252
|
+
"alias": alias,
|
|
253
|
+
"transport": "stdio",
|
|
254
|
+
"port": bridge.port,
|
|
255
|
+
"status": "running" if bridge.running else "stopped",
|
|
256
|
+
"initialized": bridge.initialized,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# Add HTTP servers
|
|
260
|
+
for alias, server_process in self.servers.items():
|
|
261
|
+
all_status[alias] = {
|
|
262
|
+
"alias": alias,
|
|
263
|
+
"transport": "http",
|
|
264
|
+
"port": server_process.port,
|
|
265
|
+
"status": server_process.status,
|
|
266
|
+
"config": server_process.config,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return all_status
|
|
270
|
+
|
|
271
|
+
async def stop_server(self, alias: str) -> bool:
|
|
272
|
+
"""Stop specific MCP server"""
|
|
273
|
+
# Check if it's a stdio bridge
|
|
274
|
+
if alias in self.stdio_bridges:
|
|
275
|
+
bridge = self.stdio_bridges[alias]
|
|
276
|
+
await bridge.stop()
|
|
277
|
+
if bridge.port:
|
|
278
|
+
self.used_ports.discard(bridge.port)
|
|
279
|
+
del self.stdio_bridges[alias]
|
|
280
|
+
if self.dev_mode:
|
|
281
|
+
logger.info(f"Stopped stdio MCP server: {alias}")
|
|
282
|
+
return True
|
|
283
|
+
|
|
284
|
+
# Check if it's an HTTP server
|
|
285
|
+
if alias in self.servers:
|
|
286
|
+
server_process = self.servers[alias]
|
|
287
|
+
if server_process.process and server_process.process.poll() is None:
|
|
288
|
+
server_process.process.terminate()
|
|
289
|
+
try:
|
|
290
|
+
server_process.process.wait(timeout=5)
|
|
291
|
+
except subprocess.TimeoutExpired:
|
|
292
|
+
server_process.process.kill()
|
|
293
|
+
|
|
294
|
+
server_process.status = "stopped"
|
|
295
|
+
if server_process.port:
|
|
296
|
+
self.used_ports.discard(server_process.port)
|
|
297
|
+
|
|
298
|
+
if self.dev_mode:
|
|
299
|
+
logger.info(f"Stopped HTTP MCP server: {alias}")
|
|
300
|
+
return True
|
|
301
|
+
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
async def start_server_by_alias(self, alias: str) -> bool:
|
|
305
|
+
"""Start MCP server by alias"""
|
|
306
|
+
# Find the config for this alias
|
|
307
|
+
configs = self.discover_mcp_servers()
|
|
308
|
+
for config in configs:
|
|
309
|
+
if config.alias == alias:
|
|
310
|
+
return await self.start_server(config)
|
|
311
|
+
|
|
312
|
+
if self.dev_mode:
|
|
313
|
+
logger.warning(f"No configuration found for MCP server: {alias}")
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
async def restart_server(self, alias: str) -> bool:
|
|
317
|
+
"""Restart MCP server by alias"""
|
|
318
|
+
# Stop first
|
|
319
|
+
stop_success = await self.stop_server(alias)
|
|
320
|
+
if self.dev_mode:
|
|
321
|
+
logger.info(f"Stopped '{alias}' for restart: {stop_success}")
|
|
322
|
+
|
|
323
|
+
# Wait a moment for cleanup
|
|
324
|
+
await asyncio.sleep(1)
|
|
325
|
+
|
|
326
|
+
# Start again
|
|
327
|
+
return await self.start_server_by_alias(alias)
|
|
328
|
+
|
|
329
|
+
async def shutdown_all(self) -> None:
|
|
330
|
+
"""Stop all MCP servers"""
|
|
331
|
+
if self.dev_mode:
|
|
332
|
+
logger.info("Shutting down all MCP servers...")
|
|
333
|
+
|
|
334
|
+
# Stop all stdio bridges
|
|
335
|
+
for alias in list(self.stdio_bridges.keys()):
|
|
336
|
+
await self.stop_server(alias)
|
|
337
|
+
|
|
338
|
+
# Stop all HTTP servers
|
|
339
|
+
for alias in list(self.servers.keys()):
|
|
340
|
+
await self.stop_server(alias)
|