fast-agent-mcp 0.3.8__py3-none-any.whl → 0.3.10__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 fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/agents/llm_agent.py +24 -0
- fast_agent/agents/mcp_agent.py +7 -1
- fast_agent/core/direct_factory.py +20 -8
- fast_agent/llm/provider/anthropic/llm_anthropic.py +107 -62
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +4 -3
- fast_agent/llm/provider/google/google_converter.py +8 -41
- fast_agent/llm/provider/openai/llm_openai.py +3 -3
- fast_agent/mcp/mcp_agent_client_session.py +45 -2
- fast_agent/mcp/mcp_aggregator.py +314 -33
- fast_agent/mcp/mcp_connection_manager.py +86 -10
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/transport_tracking.py +600 -0
- fast_agent/resources/examples/data-analysis/analysis.py +7 -3
- fast_agent/ui/console_display.py +22 -1
- fast_agent/ui/elicitation_style.py +7 -7
- fast_agent/ui/enhanced_prompt.py +21 -1
- fast_agent/ui/interactive_prompt.py +5 -0
- fast_agent/ui/mcp_display.py +708 -0
- {fast_agent_mcp-0.3.8.dist-info → fast_agent_mcp-0.3.10.dist-info}/METADATA +5 -5
- {fast_agent_mcp-0.3.8.dist-info → fast_agent_mcp-0.3.10.dist-info}/RECORD +24 -20
- {fast_agent_mcp-0.3.8.dist-info → fast_agent_mcp-0.3.10.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.8.dist-info → fast_agent_mcp-0.3.10.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.8.dist-info → fast_agent_mcp-0.3.10.dist-info}/licenses/LICENSE +0 -0
fast_agent/mcp/mcp_aggregator.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
from asyncio import Lock, gather
|
|
2
|
+
from collections import Counter
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
2
5
|
from typing import (
|
|
3
6
|
TYPE_CHECKING,
|
|
4
7
|
Any,
|
|
@@ -17,11 +20,12 @@ from mcp.types import (
|
|
|
17
20
|
CallToolResult,
|
|
18
21
|
ListToolsResult,
|
|
19
22
|
Prompt,
|
|
23
|
+
ServerCapabilities,
|
|
20
24
|
TextContent,
|
|
21
25
|
Tool,
|
|
22
26
|
)
|
|
23
27
|
from opentelemetry import trace
|
|
24
|
-
from pydantic import AnyUrl, BaseModel, ConfigDict
|
|
28
|
+
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
|
|
25
29
|
|
|
26
30
|
from fast_agent.context_dependent import ContextDependent
|
|
27
31
|
from fast_agent.core.logging.logger import get_logger
|
|
@@ -30,6 +34,7 @@ from fast_agent.mcp.common import SEP, create_namespaced_name, is_namespaced_nam
|
|
|
30
34
|
from fast_agent.mcp.gen_client import gen_client
|
|
31
35
|
from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
|
|
32
36
|
from fast_agent.mcp.mcp_connection_manager import MCPConnectionManager
|
|
37
|
+
from fast_agent.mcp.transport_tracking import TransportSnapshot
|
|
33
38
|
|
|
34
39
|
if TYPE_CHECKING:
|
|
35
40
|
from fast_agent.context import Context
|
|
@@ -52,6 +57,49 @@ class NamespacedTool(BaseModel):
|
|
|
52
57
|
namespaced_tool_name: str
|
|
53
58
|
|
|
54
59
|
|
|
60
|
+
@dataclass
|
|
61
|
+
class ServerStats:
|
|
62
|
+
call_counts: Counter = field(default_factory=Counter)
|
|
63
|
+
last_call_at: datetime | None = None
|
|
64
|
+
last_error_at: datetime | None = None
|
|
65
|
+
|
|
66
|
+
def record(self, operation_type: str, success: bool) -> None:
|
|
67
|
+
self.call_counts[operation_type] += 1
|
|
68
|
+
now = datetime.now(timezone.utc)
|
|
69
|
+
self.last_call_at = now
|
|
70
|
+
if not success:
|
|
71
|
+
self.last_error_at = now
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ServerStatus(BaseModel):
|
|
75
|
+
server_name: str
|
|
76
|
+
implementation_name: str | None = None
|
|
77
|
+
implementation_version: str | None = None
|
|
78
|
+
server_capabilities: ServerCapabilities | None = None
|
|
79
|
+
client_capabilities: Mapping[str, Any] | None = None
|
|
80
|
+
client_info_name: str | None = None
|
|
81
|
+
client_info_version: str | None = None
|
|
82
|
+
transport: str | None = None
|
|
83
|
+
is_connected: bool | None = None
|
|
84
|
+
last_call_at: datetime | None = None
|
|
85
|
+
last_error_at: datetime | None = None
|
|
86
|
+
staleness_seconds: float | None = None
|
|
87
|
+
call_counts: Dict[str, int] = Field(default_factory=dict)
|
|
88
|
+
error_message: str | None = None
|
|
89
|
+
instructions_available: bool | None = None
|
|
90
|
+
instructions_enabled: bool | None = None
|
|
91
|
+
instructions_included: bool | None = None
|
|
92
|
+
roots_configured: bool | None = None
|
|
93
|
+
roots_count: int | None = None
|
|
94
|
+
elicitation_mode: str | None = None
|
|
95
|
+
sampling_mode: str | None = None
|
|
96
|
+
spoofing_enabled: bool | None = None
|
|
97
|
+
session_id: str | None = None
|
|
98
|
+
transport_channels: TransportSnapshot | None = None
|
|
99
|
+
|
|
100
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
101
|
+
|
|
102
|
+
|
|
55
103
|
class MCPAggregator(ContextDependent):
|
|
56
104
|
"""
|
|
57
105
|
Aggregates multiple MCP servers. When a developer calls, e.g. call_tool(...),
|
|
@@ -140,6 +188,10 @@ class MCPAggregator(ContextDependent):
|
|
|
140
188
|
# Lock for refreshing tools from a server
|
|
141
189
|
self._refresh_lock = Lock()
|
|
142
190
|
|
|
191
|
+
# Track runtime stats per server
|
|
192
|
+
self._server_stats: Dict[str, ServerStats] = {}
|
|
193
|
+
self._stats_lock = Lock()
|
|
194
|
+
|
|
143
195
|
def _create_progress_callback(self, server_name: str, tool_name: str) -> "ProgressFnT":
|
|
144
196
|
"""Create a progress callback function for tool execution."""
|
|
145
197
|
|
|
@@ -282,6 +334,9 @@ class MCPAggregator(ContextDependent):
|
|
|
282
334
|
server_name, client_session_factory=self._create_session_factory(server_name)
|
|
283
335
|
)
|
|
284
336
|
|
|
337
|
+
# Record the initialize call that happened during connection setup
|
|
338
|
+
await self._record_server_call(server_name, "initialize", True)
|
|
339
|
+
|
|
285
340
|
logger.info(
|
|
286
341
|
f"MCP Servers initialized for agent '{self.agent_name}'",
|
|
287
342
|
data={
|
|
@@ -290,27 +345,39 @@ class MCPAggregator(ContextDependent):
|
|
|
290
345
|
},
|
|
291
346
|
)
|
|
292
347
|
|
|
293
|
-
async def fetch_tools(
|
|
348
|
+
async def fetch_tools(server_name: str) -> List[Tool]:
|
|
294
349
|
# Only fetch tools if the server supports them
|
|
295
350
|
if not await self.server_supports_feature(server_name, "tools"):
|
|
296
351
|
logger.debug(f"Server '{server_name}' does not support tools")
|
|
297
352
|
return []
|
|
298
353
|
|
|
299
354
|
try:
|
|
300
|
-
result: ListToolsResult = await
|
|
355
|
+
result: ListToolsResult = await self._execute_on_server(
|
|
356
|
+
server_name=server_name,
|
|
357
|
+
operation_type="tools/list",
|
|
358
|
+
operation_name="",
|
|
359
|
+
method_name="list_tools",
|
|
360
|
+
method_args={},
|
|
361
|
+
)
|
|
301
362
|
return result.tools or []
|
|
302
363
|
except Exception as e:
|
|
303
364
|
logger.error(f"Error loading tools from server '{server_name}'", data=e)
|
|
304
365
|
return []
|
|
305
366
|
|
|
306
|
-
async def fetch_prompts(
|
|
367
|
+
async def fetch_prompts(server_name: str) -> List[Prompt]:
|
|
307
368
|
# Only fetch prompts if the server supports them
|
|
308
369
|
if not await self.server_supports_feature(server_name, "prompts"):
|
|
309
370
|
logger.debug(f"Server '{server_name}' does not support prompts")
|
|
310
371
|
return []
|
|
311
372
|
|
|
312
373
|
try:
|
|
313
|
-
result = await
|
|
374
|
+
result = await self._execute_on_server(
|
|
375
|
+
server_name=server_name,
|
|
376
|
+
operation_type="prompts/list",
|
|
377
|
+
operation_name="",
|
|
378
|
+
method_name="list_prompts",
|
|
379
|
+
method_args={},
|
|
380
|
+
)
|
|
314
381
|
return getattr(result, "prompts", [])
|
|
315
382
|
except Exception as e:
|
|
316
383
|
logger.debug(f"Error loading prompts from server '{server_name}': {e}")
|
|
@@ -320,20 +387,9 @@ class MCPAggregator(ContextDependent):
|
|
|
320
387
|
tools: List[Tool] = []
|
|
321
388
|
prompts: List[Prompt] = []
|
|
322
389
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
)
|
|
327
|
-
tools = await fetch_tools(server_connection.session, server_name)
|
|
328
|
-
prompts = await fetch_prompts(server_connection.session, server_name)
|
|
329
|
-
else:
|
|
330
|
-
async with gen_client(
|
|
331
|
-
server_name,
|
|
332
|
-
server_registry=self.context.server_registry,
|
|
333
|
-
client_session_factory=self._create_session_factory(server_name),
|
|
334
|
-
) as client:
|
|
335
|
-
tools = await fetch_tools(client, server_name)
|
|
336
|
-
prompts = await fetch_prompts(client, server_name)
|
|
390
|
+
# Use _execute_on_server for consistent tracking regardless of connection mode
|
|
391
|
+
tools = await fetch_tools(server_name)
|
|
392
|
+
prompts = await fetch_prompts(server_name)
|
|
337
393
|
|
|
338
394
|
return server_name, tools, prompts
|
|
339
395
|
|
|
@@ -461,6 +517,50 @@ class MCPAggregator(ContextDependent):
|
|
|
461
517
|
for server_name in self.server_names:
|
|
462
518
|
await self._refresh_server_tools(server_name)
|
|
463
519
|
|
|
520
|
+
async def _record_server_call(
|
|
521
|
+
self, server_name: str, operation_type: str, success: bool
|
|
522
|
+
) -> None:
|
|
523
|
+
async with self._stats_lock:
|
|
524
|
+
stats = self._server_stats.setdefault(server_name, ServerStats())
|
|
525
|
+
stats.record(operation_type, success)
|
|
526
|
+
|
|
527
|
+
# For stdio servers, also emit synthetic transport events to create activity timeline
|
|
528
|
+
await self._notify_stdio_transport_activity(server_name, operation_type, success)
|
|
529
|
+
|
|
530
|
+
async def _notify_stdio_transport_activity(
|
|
531
|
+
self, server_name: str, operation_type: str, success: bool
|
|
532
|
+
) -> None:
|
|
533
|
+
"""Notify transport metrics of activity for stdio servers to create activity timeline."""
|
|
534
|
+
if not self._persistent_connection_manager:
|
|
535
|
+
return
|
|
536
|
+
|
|
537
|
+
try:
|
|
538
|
+
# Get the server connection and check if it's stdio transport
|
|
539
|
+
server_conn = self._persistent_connection_manager.running_servers.get(server_name)
|
|
540
|
+
if not server_conn:
|
|
541
|
+
return
|
|
542
|
+
|
|
543
|
+
server_config = getattr(server_conn, "server_config", None)
|
|
544
|
+
if not server_config or server_config.transport != "stdio":
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
# Get transport metrics and emit synthetic message event
|
|
548
|
+
transport_metrics = getattr(server_conn, "transport_metrics", None)
|
|
549
|
+
if transport_metrics:
|
|
550
|
+
# Import here to avoid circular imports
|
|
551
|
+
from fast_agent.mcp.transport_tracking import ChannelEvent
|
|
552
|
+
|
|
553
|
+
# Create a synthetic message event to represent the MCP operation
|
|
554
|
+
event = ChannelEvent(
|
|
555
|
+
channel="stdio",
|
|
556
|
+
event_type="message",
|
|
557
|
+
detail=f"{operation_type} ({'success' if success else 'error'})"
|
|
558
|
+
)
|
|
559
|
+
transport_metrics.record_event(event)
|
|
560
|
+
except Exception:
|
|
561
|
+
# Don't let transport tracking errors break normal operation
|
|
562
|
+
logger.debug("Failed to notify stdio transport activity for %s", server_name, exc_info=True)
|
|
563
|
+
|
|
464
564
|
async def get_server_instructions(self) -> Dict[str, tuple[str, List[str]]]:
|
|
465
565
|
"""
|
|
466
566
|
Get instructions from all connected servers along with their tool names.
|
|
@@ -492,6 +592,174 @@ class MCPAggregator(ContextDependent):
|
|
|
492
592
|
|
|
493
593
|
return instructions
|
|
494
594
|
|
|
595
|
+
async def collect_server_status(self) -> Dict[str, ServerStatus]:
|
|
596
|
+
"""Return aggregated status information for each configured server."""
|
|
597
|
+
if not self.initialized:
|
|
598
|
+
await self.load_servers()
|
|
599
|
+
|
|
600
|
+
now = datetime.now(timezone.utc)
|
|
601
|
+
status_map: Dict[str, ServerStatus] = {}
|
|
602
|
+
|
|
603
|
+
for server_name in self.server_names:
|
|
604
|
+
stats = self._server_stats.get(server_name)
|
|
605
|
+
last_call = stats.last_call_at if stats else None
|
|
606
|
+
last_error = stats.last_error_at if stats else None
|
|
607
|
+
staleness = (now - last_call).total_seconds() if last_call else None
|
|
608
|
+
call_counts = dict(stats.call_counts) if stats else {}
|
|
609
|
+
|
|
610
|
+
implementation_name = None
|
|
611
|
+
implementation_version = None
|
|
612
|
+
capabilities: ServerCapabilities | None = None
|
|
613
|
+
client_capabilities: Mapping[str, Any] | None = None
|
|
614
|
+
client_info_name = None
|
|
615
|
+
client_info_version = None
|
|
616
|
+
is_connected = None
|
|
617
|
+
error_message = None
|
|
618
|
+
instructions_available = None
|
|
619
|
+
instructions_enabled = None
|
|
620
|
+
instructions_included = None
|
|
621
|
+
roots_configured = None
|
|
622
|
+
roots_count = None
|
|
623
|
+
elicitation_mode = None
|
|
624
|
+
sampling_mode = None
|
|
625
|
+
spoofing_enabled = None
|
|
626
|
+
server_cfg = None
|
|
627
|
+
session_id = None
|
|
628
|
+
server_conn = None
|
|
629
|
+
transport: str | None = None
|
|
630
|
+
transport_snapshot: TransportSnapshot | None = None
|
|
631
|
+
|
|
632
|
+
manager = getattr(self, "_persistent_connection_manager", None)
|
|
633
|
+
if self.connection_persistence and manager is not None:
|
|
634
|
+
try:
|
|
635
|
+
server_conn = await manager.get_server(
|
|
636
|
+
server_name,
|
|
637
|
+
client_session_factory=self._create_session_factory(server_name),
|
|
638
|
+
)
|
|
639
|
+
implementation = getattr(server_conn, "server_implementation", None)
|
|
640
|
+
if implementation:
|
|
641
|
+
implementation_name = getattr(implementation, "name", None)
|
|
642
|
+
implementation_version = getattr(implementation, "version", None)
|
|
643
|
+
capabilities = getattr(server_conn, "server_capabilities", None)
|
|
644
|
+
client_capabilities = getattr(server_conn, "client_capabilities", None)
|
|
645
|
+
session = server_conn.session
|
|
646
|
+
client_info = getattr(session, "client_info", None) if session else None
|
|
647
|
+
if client_info:
|
|
648
|
+
client_info_name = getattr(client_info, "name", None)
|
|
649
|
+
client_info_version = getattr(client_info, "version", None)
|
|
650
|
+
is_connected = server_conn.is_healthy()
|
|
651
|
+
error_message = getattr(server_conn, "_error_message", None)
|
|
652
|
+
instructions_available = getattr(
|
|
653
|
+
server_conn, "server_instructions_available", None
|
|
654
|
+
)
|
|
655
|
+
instructions_enabled = getattr(
|
|
656
|
+
server_conn, "server_instructions_enabled", None
|
|
657
|
+
)
|
|
658
|
+
instructions_included = bool(getattr(server_conn, "server_instructions", None))
|
|
659
|
+
server_cfg = getattr(server_conn, "server_config", None)
|
|
660
|
+
if session:
|
|
661
|
+
elicitation_mode = getattr(session, "effective_elicitation_mode", elicitation_mode)
|
|
662
|
+
session_id = getattr(server_conn, "session_id", None)
|
|
663
|
+
if not session_id and getattr(server_conn, "_get_session_id_cb", None):
|
|
664
|
+
try:
|
|
665
|
+
session_id = server_conn._get_session_id_cb() # type: ignore[attr-defined]
|
|
666
|
+
except Exception:
|
|
667
|
+
session_id = None
|
|
668
|
+
metrics = getattr(server_conn, "transport_metrics", None)
|
|
669
|
+
if metrics is not None:
|
|
670
|
+
try:
|
|
671
|
+
transport_snapshot = metrics.snapshot()
|
|
672
|
+
except Exception:
|
|
673
|
+
logger.debug(
|
|
674
|
+
"Failed to snapshot transport metrics for server '%s'",
|
|
675
|
+
server_name,
|
|
676
|
+
exc_info=True,
|
|
677
|
+
)
|
|
678
|
+
except Exception as exc:
|
|
679
|
+
logger.debug(
|
|
680
|
+
f"Failed to collect status for server '{server_name}'",
|
|
681
|
+
data={"error": str(exc)},
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if server_cfg is None and self.context and getattr(self.context, "server_registry", None):
|
|
685
|
+
try:
|
|
686
|
+
server_cfg = self.context.server_registry.get_server_config(server_name)
|
|
687
|
+
except Exception:
|
|
688
|
+
server_cfg = None
|
|
689
|
+
|
|
690
|
+
if server_cfg is not None:
|
|
691
|
+
instructions_enabled = (
|
|
692
|
+
instructions_enabled
|
|
693
|
+
if instructions_enabled is not None
|
|
694
|
+
else server_cfg.include_instructions
|
|
695
|
+
)
|
|
696
|
+
roots = getattr(server_cfg, "roots", None)
|
|
697
|
+
roots_configured = bool(roots)
|
|
698
|
+
roots_count = len(roots) if roots else 0
|
|
699
|
+
transport = getattr(server_cfg, "transport", transport)
|
|
700
|
+
elicitation = getattr(server_cfg, "elicitation", None)
|
|
701
|
+
elicitation_mode = (
|
|
702
|
+
getattr(elicitation, "mode", None)
|
|
703
|
+
if elicitation
|
|
704
|
+
else elicitation_mode
|
|
705
|
+
)
|
|
706
|
+
sampling_cfg = getattr(server_cfg, "sampling", None)
|
|
707
|
+
spoofing_enabled = bool(getattr(server_cfg, "implementation", None))
|
|
708
|
+
if implementation_name is None and getattr(server_cfg, "implementation", None):
|
|
709
|
+
implementation_name = server_cfg.implementation.name
|
|
710
|
+
implementation_version = getattr(server_cfg.implementation, "version", None)
|
|
711
|
+
if session_id is None:
|
|
712
|
+
if server_cfg.transport == "stdio":
|
|
713
|
+
session_id = "local"
|
|
714
|
+
elif server_conn and getattr(server_conn, "_get_session_id_cb", None):
|
|
715
|
+
try:
|
|
716
|
+
session_id = server_conn._get_session_id_cb() # type: ignore[attr-defined]
|
|
717
|
+
except Exception:
|
|
718
|
+
session_id = None
|
|
719
|
+
|
|
720
|
+
if sampling_cfg is not None:
|
|
721
|
+
sampling_mode = "configured"
|
|
722
|
+
else:
|
|
723
|
+
auto_sampling = True
|
|
724
|
+
if self.context and getattr(self.context, "config", None):
|
|
725
|
+
auto_sampling = getattr(self.context.config, "auto_sampling", True)
|
|
726
|
+
sampling_mode = "auto" if auto_sampling else "off"
|
|
727
|
+
else:
|
|
728
|
+
# Fall back to defaults when config missing
|
|
729
|
+
auto_sampling = True
|
|
730
|
+
if self.context and getattr(self.context, "config", None):
|
|
731
|
+
auto_sampling = getattr(self.context.config, "auto_sampling", True)
|
|
732
|
+
sampling_mode = sampling_mode or ("auto" if auto_sampling else "off")
|
|
733
|
+
|
|
734
|
+
status_map[server_name] = ServerStatus(
|
|
735
|
+
server_name=server_name,
|
|
736
|
+
implementation_name=implementation_name,
|
|
737
|
+
implementation_version=implementation_version,
|
|
738
|
+
server_capabilities=capabilities,
|
|
739
|
+
client_capabilities=client_capabilities,
|
|
740
|
+
client_info_name=client_info_name,
|
|
741
|
+
client_info_version=client_info_version,
|
|
742
|
+
transport=transport,
|
|
743
|
+
is_connected=is_connected,
|
|
744
|
+
last_call_at=last_call,
|
|
745
|
+
last_error_at=last_error,
|
|
746
|
+
staleness_seconds=staleness,
|
|
747
|
+
call_counts=call_counts,
|
|
748
|
+
error_message=error_message,
|
|
749
|
+
instructions_available=instructions_available,
|
|
750
|
+
instructions_enabled=instructions_enabled,
|
|
751
|
+
instructions_included=instructions_included,
|
|
752
|
+
roots_configured=roots_configured,
|
|
753
|
+
roots_count=roots_count,
|
|
754
|
+
elicitation_mode=elicitation_mode,
|
|
755
|
+
sampling_mode=sampling_mode,
|
|
756
|
+
spoofing_enabled=spoofing_enabled,
|
|
757
|
+
session_id=session_id,
|
|
758
|
+
transport_channels=transport_snapshot,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
return status_map
|
|
762
|
+
|
|
495
763
|
async def _execute_on_server(
|
|
496
764
|
self,
|
|
497
765
|
server_name: str,
|
|
@@ -554,13 +822,17 @@ class MCPAggregator(ContextDependent):
|
|
|
554
822
|
# Re-raise the original exception to propagate it
|
|
555
823
|
raise e
|
|
556
824
|
|
|
825
|
+
success_flag: bool | None = None
|
|
826
|
+
result: R | None = None
|
|
827
|
+
|
|
557
828
|
# Try initial execution
|
|
558
829
|
try:
|
|
559
830
|
if self.connection_persistence:
|
|
560
831
|
server_connection = await self._persistent_connection_manager.get_server(
|
|
561
832
|
server_name, client_session_factory=self._create_session_factory(server_name)
|
|
562
833
|
)
|
|
563
|
-
|
|
834
|
+
result = await try_execute(server_connection.session)
|
|
835
|
+
success_flag = True
|
|
564
836
|
else:
|
|
565
837
|
logger.debug(
|
|
566
838
|
f"Creating temporary connection to server: {server_name}",
|
|
@@ -582,7 +854,7 @@ class MCPAggregator(ContextDependent):
|
|
|
582
854
|
"agent_name": self.agent_name,
|
|
583
855
|
},
|
|
584
856
|
)
|
|
585
|
-
|
|
857
|
+
success_flag = True
|
|
586
858
|
except ConnectionError:
|
|
587
859
|
# Server offline - attempt reconnection
|
|
588
860
|
from fast_agent.ui import console
|
|
@@ -613,7 +885,7 @@ class MCPAggregator(ContextDependent):
|
|
|
613
885
|
|
|
614
886
|
# Success!
|
|
615
887
|
console.console.print(f"[dim green]MCP server {server_name} online[/dim green]")
|
|
616
|
-
|
|
888
|
+
success_flag = True
|
|
617
889
|
|
|
618
890
|
except Exception:
|
|
619
891
|
# Reconnection failed
|
|
@@ -621,10 +893,19 @@ class MCPAggregator(ContextDependent):
|
|
|
621
893
|
f"[dim red]MCP server {server_name} offline - failed to reconnect[/dim red]"
|
|
622
894
|
)
|
|
623
895
|
error_msg = f"MCP server {server_name} offline - failed to reconnect"
|
|
896
|
+
success_flag = False
|
|
624
897
|
if error_factory:
|
|
625
|
-
|
|
898
|
+
result = error_factory(error_msg)
|
|
626
899
|
else:
|
|
627
900
|
raise Exception(error_msg)
|
|
901
|
+
except Exception:
|
|
902
|
+
success_flag = False
|
|
903
|
+
raise
|
|
904
|
+
finally:
|
|
905
|
+
if success_flag is not None:
|
|
906
|
+
await self._record_server_call(server_name, operation_type, success_flag)
|
|
907
|
+
|
|
908
|
+
return result
|
|
628
909
|
|
|
629
910
|
async def _parse_resource_name(self, name: str, resource_type: str) -> tuple[str, str]:
|
|
630
911
|
"""
|
|
@@ -701,7 +982,7 @@ class MCPAggregator(ContextDependent):
|
|
|
701
982
|
|
|
702
983
|
return await self._execute_on_server(
|
|
703
984
|
server_name=server_name,
|
|
704
|
-
operation_type="
|
|
985
|
+
operation_type="tools/call",
|
|
705
986
|
operation_name=local_tool_name,
|
|
706
987
|
method_name="call_tool",
|
|
707
988
|
method_args={
|
|
@@ -794,7 +1075,7 @@ class MCPAggregator(ContextDependent):
|
|
|
794
1075
|
|
|
795
1076
|
result = await self._execute_on_server(
|
|
796
1077
|
server_name=server_name,
|
|
797
|
-
operation_type="
|
|
1078
|
+
operation_type="prompts/get",
|
|
798
1079
|
operation_name=local_prompt_name or "default",
|
|
799
1080
|
method_name="get_prompt",
|
|
800
1081
|
method_args=method_args,
|
|
@@ -842,7 +1123,7 @@ class MCPAggregator(ContextDependent):
|
|
|
842
1123
|
|
|
843
1124
|
result = await self._execute_on_server(
|
|
844
1125
|
server_name=s_name,
|
|
845
|
-
operation_type="
|
|
1126
|
+
operation_type="prompts/get",
|
|
846
1127
|
operation_name=local_prompt_name,
|
|
847
1128
|
method_name="get_prompt",
|
|
848
1129
|
method_args=method_args,
|
|
@@ -890,7 +1171,7 @@ class MCPAggregator(ContextDependent):
|
|
|
890
1171
|
|
|
891
1172
|
result = await self._execute_on_server(
|
|
892
1173
|
server_name=s_name,
|
|
893
|
-
operation_type="
|
|
1174
|
+
operation_type="prompts/get",
|
|
894
1175
|
operation_name=local_prompt_name,
|
|
895
1176
|
method_name="get_prompt",
|
|
896
1177
|
method_args=method_args,
|
|
@@ -913,7 +1194,7 @@ class MCPAggregator(ContextDependent):
|
|
|
913
1194
|
try:
|
|
914
1195
|
prompt_list_result = await self._execute_on_server(
|
|
915
1196
|
server_name=s_name,
|
|
916
|
-
operation_type="prompts
|
|
1197
|
+
operation_type="prompts/list",
|
|
917
1198
|
operation_name="",
|
|
918
1199
|
method_name="list_prompts",
|
|
919
1200
|
error_factory=lambda _: None,
|
|
@@ -987,7 +1268,7 @@ class MCPAggregator(ContextDependent):
|
|
|
987
1268
|
# Fetch from server
|
|
988
1269
|
result = await self._execute_on_server(
|
|
989
1270
|
server_name=server_name,
|
|
990
|
-
operation_type="prompts
|
|
1271
|
+
operation_type="prompts/list",
|
|
991
1272
|
operation_name="",
|
|
992
1273
|
method_name="list_prompts",
|
|
993
1274
|
error_factory=lambda _: None,
|
|
@@ -1026,7 +1307,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1026
1307
|
try:
|
|
1027
1308
|
result = await self._execute_on_server(
|
|
1028
1309
|
server_name=s_name,
|
|
1029
|
-
operation_type="prompts
|
|
1310
|
+
operation_type="prompts/list",
|
|
1030
1311
|
operation_name="",
|
|
1031
1312
|
method_name="list_prompts",
|
|
1032
1313
|
error_factory=lambda _: None,
|
|
@@ -1212,7 +1493,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1212
1493
|
# Use the _execute_on_server method to call read_resource on the server
|
|
1213
1494
|
result = await self._execute_on_server(
|
|
1214
1495
|
server_name=server_name,
|
|
1215
|
-
operation_type="
|
|
1496
|
+
operation_type="resources/read",
|
|
1216
1497
|
operation_name=resource_uri,
|
|
1217
1498
|
method_name="read_resource",
|
|
1218
1499
|
method_args={"uri": uri},
|
|
@@ -1263,7 +1544,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1263
1544
|
# Use the _execute_on_server method to call list_resources on the server
|
|
1264
1545
|
result = await self._execute_on_server(
|
|
1265
1546
|
server_name=s_name,
|
|
1266
|
-
operation_type="resources
|
|
1547
|
+
operation_type="resources/list",
|
|
1267
1548
|
operation_name="",
|
|
1268
1549
|
method_name="list_resources",
|
|
1269
1550
|
method_args={}, # Empty dictionary instead of None
|
|
@@ -1316,7 +1597,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1316
1597
|
# Use the _execute_on_server method to call list_tools on the server
|
|
1317
1598
|
result = await self._execute_on_server(
|
|
1318
1599
|
server_name=s_name,
|
|
1319
|
-
operation_type="tools
|
|
1600
|
+
operation_type="tools/list",
|
|
1320
1601
|
operation_name="",
|
|
1321
1602
|
method_name="list_tools",
|
|
1322
1603
|
method_args={},
|