fast-agent-mcp 0.3.9__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/mcp/mcp_aggregator.py +32 -28
- fast_agent/mcp/transport_tracking.py +3 -1
- fast_agent/ui/elicitation_style.py +7 -7
- fast_agent/ui/mcp_display.py +160 -88
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.10.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.10.dist-info}/RECORD +9 -9
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.10.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.10.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.10.dist-info}/licenses/LICENSE +0 -0
fast_agent/mcp/mcp_aggregator.py
CHANGED
|
@@ -334,6 +334,9 @@ class MCPAggregator(ContextDependent):
|
|
|
334
334
|
server_name, client_session_factory=self._create_session_factory(server_name)
|
|
335
335
|
)
|
|
336
336
|
|
|
337
|
+
# Record the initialize call that happened during connection setup
|
|
338
|
+
await self._record_server_call(server_name, "initialize", True)
|
|
339
|
+
|
|
337
340
|
logger.info(
|
|
338
341
|
f"MCP Servers initialized for agent '{self.agent_name}'",
|
|
339
342
|
data={
|
|
@@ -342,27 +345,39 @@ class MCPAggregator(ContextDependent):
|
|
|
342
345
|
},
|
|
343
346
|
)
|
|
344
347
|
|
|
345
|
-
async def fetch_tools(
|
|
348
|
+
async def fetch_tools(server_name: str) -> List[Tool]:
|
|
346
349
|
# Only fetch tools if the server supports them
|
|
347
350
|
if not await self.server_supports_feature(server_name, "tools"):
|
|
348
351
|
logger.debug(f"Server '{server_name}' does not support tools")
|
|
349
352
|
return []
|
|
350
353
|
|
|
351
354
|
try:
|
|
352
|
-
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
|
+
)
|
|
353
362
|
return result.tools or []
|
|
354
363
|
except Exception as e:
|
|
355
364
|
logger.error(f"Error loading tools from server '{server_name}'", data=e)
|
|
356
365
|
return []
|
|
357
366
|
|
|
358
|
-
async def fetch_prompts(
|
|
367
|
+
async def fetch_prompts(server_name: str) -> List[Prompt]:
|
|
359
368
|
# Only fetch prompts if the server supports them
|
|
360
369
|
if not await self.server_supports_feature(server_name, "prompts"):
|
|
361
370
|
logger.debug(f"Server '{server_name}' does not support prompts")
|
|
362
371
|
return []
|
|
363
372
|
|
|
364
373
|
try:
|
|
365
|
-
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
|
+
)
|
|
366
381
|
return getattr(result, "prompts", [])
|
|
367
382
|
except Exception as e:
|
|
368
383
|
logger.debug(f"Error loading prompts from server '{server_name}': {e}")
|
|
@@ -372,20 +387,9 @@ class MCPAggregator(ContextDependent):
|
|
|
372
387
|
tools: List[Tool] = []
|
|
373
388
|
prompts: List[Prompt] = []
|
|
374
389
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
)
|
|
379
|
-
tools = await fetch_tools(server_connection.session, server_name)
|
|
380
|
-
prompts = await fetch_prompts(server_connection.session, server_name)
|
|
381
|
-
else:
|
|
382
|
-
async with gen_client(
|
|
383
|
-
server_name,
|
|
384
|
-
server_registry=self.context.server_registry,
|
|
385
|
-
client_session_factory=self._create_session_factory(server_name),
|
|
386
|
-
) as client:
|
|
387
|
-
tools = await fetch_tools(client, server_name)
|
|
388
|
-
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)
|
|
389
393
|
|
|
390
394
|
return server_name, tools, prompts
|
|
391
395
|
|
|
@@ -978,7 +982,7 @@ class MCPAggregator(ContextDependent):
|
|
|
978
982
|
|
|
979
983
|
return await self._execute_on_server(
|
|
980
984
|
server_name=server_name,
|
|
981
|
-
operation_type="
|
|
985
|
+
operation_type="tools/call",
|
|
982
986
|
operation_name=local_tool_name,
|
|
983
987
|
method_name="call_tool",
|
|
984
988
|
method_args={
|
|
@@ -1071,7 +1075,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1071
1075
|
|
|
1072
1076
|
result = await self._execute_on_server(
|
|
1073
1077
|
server_name=server_name,
|
|
1074
|
-
operation_type="
|
|
1078
|
+
operation_type="prompts/get",
|
|
1075
1079
|
operation_name=local_prompt_name or "default",
|
|
1076
1080
|
method_name="get_prompt",
|
|
1077
1081
|
method_args=method_args,
|
|
@@ -1119,7 +1123,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1119
1123
|
|
|
1120
1124
|
result = await self._execute_on_server(
|
|
1121
1125
|
server_name=s_name,
|
|
1122
|
-
operation_type="
|
|
1126
|
+
operation_type="prompts/get",
|
|
1123
1127
|
operation_name=local_prompt_name,
|
|
1124
1128
|
method_name="get_prompt",
|
|
1125
1129
|
method_args=method_args,
|
|
@@ -1167,7 +1171,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1167
1171
|
|
|
1168
1172
|
result = await self._execute_on_server(
|
|
1169
1173
|
server_name=s_name,
|
|
1170
|
-
operation_type="
|
|
1174
|
+
operation_type="prompts/get",
|
|
1171
1175
|
operation_name=local_prompt_name,
|
|
1172
1176
|
method_name="get_prompt",
|
|
1173
1177
|
method_args=method_args,
|
|
@@ -1190,7 +1194,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1190
1194
|
try:
|
|
1191
1195
|
prompt_list_result = await self._execute_on_server(
|
|
1192
1196
|
server_name=s_name,
|
|
1193
|
-
operation_type="prompts
|
|
1197
|
+
operation_type="prompts/list",
|
|
1194
1198
|
operation_name="",
|
|
1195
1199
|
method_name="list_prompts",
|
|
1196
1200
|
error_factory=lambda _: None,
|
|
@@ -1264,7 +1268,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1264
1268
|
# Fetch from server
|
|
1265
1269
|
result = await self._execute_on_server(
|
|
1266
1270
|
server_name=server_name,
|
|
1267
|
-
operation_type="prompts
|
|
1271
|
+
operation_type="prompts/list",
|
|
1268
1272
|
operation_name="",
|
|
1269
1273
|
method_name="list_prompts",
|
|
1270
1274
|
error_factory=lambda _: None,
|
|
@@ -1303,7 +1307,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1303
1307
|
try:
|
|
1304
1308
|
result = await self._execute_on_server(
|
|
1305
1309
|
server_name=s_name,
|
|
1306
|
-
operation_type="prompts
|
|
1310
|
+
operation_type="prompts/list",
|
|
1307
1311
|
operation_name="",
|
|
1308
1312
|
method_name="list_prompts",
|
|
1309
1313
|
error_factory=lambda _: None,
|
|
@@ -1489,7 +1493,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1489
1493
|
# Use the _execute_on_server method to call read_resource on the server
|
|
1490
1494
|
result = await self._execute_on_server(
|
|
1491
1495
|
server_name=server_name,
|
|
1492
|
-
operation_type="
|
|
1496
|
+
operation_type="resources/read",
|
|
1493
1497
|
operation_name=resource_uri,
|
|
1494
1498
|
method_name="read_resource",
|
|
1495
1499
|
method_args={"uri": uri},
|
|
@@ -1540,7 +1544,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1540
1544
|
# Use the _execute_on_server method to call list_resources on the server
|
|
1541
1545
|
result = await self._execute_on_server(
|
|
1542
1546
|
server_name=s_name,
|
|
1543
|
-
operation_type="resources
|
|
1547
|
+
operation_type="resources/list",
|
|
1544
1548
|
operation_name="",
|
|
1545
1549
|
method_name="list_resources",
|
|
1546
1550
|
method_args={}, # Empty dictionary instead of None
|
|
@@ -1593,7 +1597,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1593
1597
|
# Use the _execute_on_server method to call list_tools on the server
|
|
1594
1598
|
result = await self._execute_on_server(
|
|
1595
1599
|
server_name=s_name,
|
|
1596
|
-
operation_type="tools
|
|
1600
|
+
operation_type="tools/list",
|
|
1597
1601
|
operation_name="",
|
|
1598
1602
|
method_name="list_tools",
|
|
1599
1603
|
method_args={},
|
|
@@ -243,7 +243,9 @@ class TransportChannelMetrics:
|
|
|
243
243
|
self._get_last_error = event.detail
|
|
244
244
|
self._get_last_event = "error"
|
|
245
245
|
self._get_last_event_at = now
|
|
246
|
-
|
|
246
|
+
# Record 405 as "disabled" in timeline, not "error"
|
|
247
|
+
timeline_state = "disabled" if event.status_code == 405 else "error"
|
|
248
|
+
self._record_history("get", timeline_state, now)
|
|
247
249
|
|
|
248
250
|
def _handle_resumption_event(self, event: ChannelEvent, now: datetime) -> None:
|
|
249
251
|
if event.event_type == "message" and event.message is not None:
|
|
@@ -7,16 +7,16 @@ ELICITATION_STYLE = Style.from_dict(
|
|
|
7
7
|
{
|
|
8
8
|
# Dialog structure - use ansidefault for true black, remove problematic shadow
|
|
9
9
|
"dialog": "bg:ansidefault", # True black dialog using ansidefault
|
|
10
|
-
"dialog.body": "bg:ansidefault fg:
|
|
10
|
+
"dialog.body": "bg:ansidefault fg:ansidefault", # True black dialog body with default text color
|
|
11
11
|
"dialog shadow": "bg:ansidefault", # Set shadow background to match application
|
|
12
12
|
"dialog.border": "bg:ansidefault", # True black border background
|
|
13
13
|
# Set application background to true black
|
|
14
14
|
"application": "bg:ansidefault", # True black application background
|
|
15
15
|
# Title styling with better contrast
|
|
16
|
-
"title": "fg:
|
|
16
|
+
"title": "fg:ansidefault bold", # Default color title for terminal compatibility
|
|
17
17
|
# Buttons - only define focused state to preserve focus highlighting
|
|
18
18
|
"button.focused": "bg:ansibrightgreen fg:ansiblack bold", # Bright green with black text for contrast
|
|
19
|
-
"button.arrow": "fg:
|
|
19
|
+
"button.arrow": "fg:ansidefault bold", # Default color arrows for terminal compatibility
|
|
20
20
|
# Form elements with consistent green/yellow theme
|
|
21
21
|
# Checkboxes - green when checked, yellow when focused
|
|
22
22
|
"checkbox": "fg:ansidefault", # Default color unchecked checkbox (dimmer)
|
|
@@ -34,15 +34,15 @@ ELICITATION_STYLE = Style.from_dict(
|
|
|
34
34
|
# Frame styling with ANSI colors - make borders visible
|
|
35
35
|
"frame.border": "fg:ansibrightblack", # Bright black borders for subtlety
|
|
36
36
|
"frame.label": "fg:ansigray", # Gray frame labels (less prominent)
|
|
37
|
-
# Labels and text - use
|
|
38
|
-
"label": "fg:
|
|
37
|
+
# Labels and text - use default color for terminal compatibility
|
|
38
|
+
"label": "fg:ansidefault", # Default color labels for terminal compatibility
|
|
39
39
|
"message": "fg:ansibrightcyan", # Bright cyan messages (no bold)
|
|
40
40
|
# Agent and server names - make them match
|
|
41
41
|
"agent-name": "fg:ansibrightblue bold",
|
|
42
42
|
"server-name": "fg:ansibrightblue bold", # Same color as agent
|
|
43
43
|
# Validation errors - better contrast
|
|
44
|
-
"validation-toolbar": "bg:ansibrightred fg:
|
|
45
|
-
"validation-toolbar.text": "bg:ansibrightred fg:
|
|
44
|
+
"validation-toolbar": "bg:ansibrightred fg:ansidefault bold",
|
|
45
|
+
"validation-toolbar.text": "bg:ansibrightred fg:ansidefault",
|
|
46
46
|
"validation.border": "fg:ansibrightred",
|
|
47
47
|
"validation-error": "fg:ansibrightred bold", # For status line errors
|
|
48
48
|
# Separator styling
|
fast_agent/ui/mcp_display.py
CHANGED
|
@@ -14,6 +14,67 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from fast_agent.mcp.transport_tracking import ChannelSnapshot
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# Centralized color configuration
|
|
18
|
+
class Colours:
|
|
19
|
+
"""Color constants for MCP status display elements."""
|
|
20
|
+
|
|
21
|
+
# Timeline activity colors (Option A: Mixed Intensity)
|
|
22
|
+
ERROR = "bright_red" # Keep error bright
|
|
23
|
+
DISABLED = "bright_blue" # Keep disabled bright
|
|
24
|
+
RESPONSE = "blue" # Normal blue instead of bright
|
|
25
|
+
REQUEST = "yellow" # Normal yellow instead of bright
|
|
26
|
+
NOTIFICATION = "cyan" # Normal cyan instead of bright
|
|
27
|
+
PING = "dim green" # Keep ping dim
|
|
28
|
+
IDLE = "white dim"
|
|
29
|
+
NONE = "dim"
|
|
30
|
+
|
|
31
|
+
# Channel arrow states
|
|
32
|
+
ARROW_ERROR = "bright_red"
|
|
33
|
+
ARROW_DISABLED = "bright_yellow" # For explicitly disabled/off
|
|
34
|
+
ARROW_METHOD_NOT_ALLOWED = "cyan" # For 405 method not allowed (notification color)
|
|
35
|
+
ARROW_OFF = "black dim"
|
|
36
|
+
ARROW_IDLE = "bright_cyan" # Connected but no activity
|
|
37
|
+
ARROW_ACTIVE = "bright_green" # Connected with activity
|
|
38
|
+
|
|
39
|
+
# Capability token states
|
|
40
|
+
TOKEN_ERROR = "bright_red"
|
|
41
|
+
TOKEN_WARNING = "bright_cyan"
|
|
42
|
+
TOKEN_DISABLED = "dim"
|
|
43
|
+
TOKEN_HIGHLIGHTED = "bright_yellow"
|
|
44
|
+
TOKEN_ENABLED = "bright_green"
|
|
45
|
+
|
|
46
|
+
# Text elements
|
|
47
|
+
TEXT_DIM = "dim"
|
|
48
|
+
TEXT_DEFAULT = "default" # Use terminal's default text color
|
|
49
|
+
TEXT_BRIGHT = "bright_white"
|
|
50
|
+
TEXT_ERROR = "bright_red"
|
|
51
|
+
TEXT_WARNING = "bright_yellow"
|
|
52
|
+
TEXT_SUCCESS = "bright_green"
|
|
53
|
+
TEXT_INFO = "bright_blue"
|
|
54
|
+
TEXT_CYAN = "cyan"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Color mappings for different contexts
|
|
58
|
+
TIMELINE_COLORS = {
|
|
59
|
+
"error": Colours.ERROR,
|
|
60
|
+
"disabled": Colours.DISABLED,
|
|
61
|
+
"response": Colours.RESPONSE,
|
|
62
|
+
"request": Colours.REQUEST,
|
|
63
|
+
"notification": Colours.NOTIFICATION,
|
|
64
|
+
"ping": Colours.PING,
|
|
65
|
+
"none": Colours.IDLE,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
TIMELINE_COLORS_STDIO = {
|
|
69
|
+
"error": Colours.ERROR,
|
|
70
|
+
"request": Colours.TOKEN_ENABLED, # All activity shows as bright green
|
|
71
|
+
"response": Colours.TOKEN_ENABLED,
|
|
72
|
+
"notification": Colours.TOKEN_ENABLED,
|
|
73
|
+
"ping": Colours.PING,
|
|
74
|
+
"none": Colours.IDLE,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
17
78
|
def _format_compact_duration(seconds: float | None) -> str | None:
|
|
18
79
|
if seconds is None:
|
|
19
80
|
return None
|
|
@@ -58,7 +119,7 @@ def _format_session_id(session_id: str | None) -> Text:
|
|
|
58
119
|
|
|
59
120
|
|
|
60
121
|
def _build_aligned_field(
|
|
61
|
-
label: str, value: Text | str, *, label_width: int = 9, value_style: str =
|
|
122
|
+
label: str, value: Text | str, *, label_width: int = 9, value_style: str = Colours.TEXT_DEFAULT
|
|
62
123
|
) -> Text:
|
|
63
124
|
field = Text()
|
|
64
125
|
field.append(f"{label:<{label_width}}: ", style="dim")
|
|
@@ -153,14 +214,14 @@ def _format_capability_shorthand(
|
|
|
153
214
|
|
|
154
215
|
def token_style(supported, highlighted) -> str:
|
|
155
216
|
if supported == "red":
|
|
156
|
-
return
|
|
217
|
+
return Colours.TOKEN_ERROR
|
|
157
218
|
if supported == "blue":
|
|
158
|
-
return
|
|
219
|
+
return Colours.TOKEN_WARNING
|
|
159
220
|
if not supported:
|
|
160
|
-
return
|
|
221
|
+
return Colours.TOKEN_DISABLED
|
|
161
222
|
if highlighted:
|
|
162
|
-
return
|
|
163
|
-
return
|
|
223
|
+
return Colours.TOKEN_HIGHLIGHTED
|
|
224
|
+
return Colours.TOKEN_ENABLED
|
|
164
225
|
|
|
165
226
|
tokens = [
|
|
166
227
|
(label, token_style(supported, highlighted)) for label, supported, highlighted in entries
|
|
@@ -198,19 +259,16 @@ def _format_label(label: str, width: int = 10) -> str:
|
|
|
198
259
|
|
|
199
260
|
def _build_inline_timeline(buckets: Iterable[str]) -> str:
|
|
200
261
|
"""Build a compact timeline string for inline display."""
|
|
201
|
-
color_map = {
|
|
202
|
-
"error": "bright_red",
|
|
203
|
-
"disabled": "bright_blue",
|
|
204
|
-
"response": "bright_blue",
|
|
205
|
-
"request": "bright_yellow",
|
|
206
|
-
"notification": "bright_cyan",
|
|
207
|
-
"ping": "bright_green",
|
|
208
|
-
"none": "dim",
|
|
209
|
-
}
|
|
210
262
|
timeline = " [dim]10m[/dim] "
|
|
211
263
|
for state in buckets:
|
|
212
|
-
color =
|
|
213
|
-
|
|
264
|
+
color = TIMELINE_COLORS.get(state, Colours.NONE)
|
|
265
|
+
if state in {"idle", "none"}:
|
|
266
|
+
symbol = "·"
|
|
267
|
+
elif state == "request":
|
|
268
|
+
symbol = "◆" # Diamond for requests - rare and important
|
|
269
|
+
else:
|
|
270
|
+
symbol = "●" # Circle for other activity
|
|
271
|
+
timeline += f"[bold {color}]{symbol}[/bold {color}]"
|
|
214
272
|
timeline += " [dim]now[/dim]"
|
|
215
273
|
return timeline
|
|
216
274
|
|
|
@@ -298,45 +356,25 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
298
356
|
# Collect any errors to show at bottom
|
|
299
357
|
errors = []
|
|
300
358
|
|
|
301
|
-
#
|
|
302
|
-
if is_stdio
|
|
303
|
-
# Simplified color map for stdio: bright green for activity, dim for idle
|
|
304
|
-
timeline_color_map = {
|
|
305
|
-
"error": "bright_red", # Keep error as red
|
|
306
|
-
"request": "bright_green", # All activity shows as bright green
|
|
307
|
-
"response": "bright_green", # (not used in stdio but just in case)
|
|
308
|
-
"notification": "bright_green", # (not used in stdio but just in case)
|
|
309
|
-
"ping": "bright_green", # (not used in stdio but just in case)
|
|
310
|
-
"none": "white dim",
|
|
311
|
-
}
|
|
312
|
-
else:
|
|
313
|
-
# Full color map for HTTP channels
|
|
314
|
-
timeline_color_map = {
|
|
315
|
-
"error": "bright_red",
|
|
316
|
-
"disabled": "bright_blue",
|
|
317
|
-
"response": "bright_blue",
|
|
318
|
-
"request": "bright_yellow",
|
|
319
|
-
"notification": "bright_cyan",
|
|
320
|
-
"ping": "bright_green",
|
|
321
|
-
"none": "white dim",
|
|
322
|
-
}
|
|
359
|
+
# Get appropriate timeline color map
|
|
360
|
+
timeline_color_map = TIMELINE_COLORS_STDIO if is_stdio else TIMELINE_COLORS
|
|
323
361
|
|
|
324
362
|
for label, arrow, channel in entries:
|
|
325
363
|
line = Text(indent)
|
|
326
364
|
line.append("│ ", style="dim")
|
|
327
365
|
|
|
328
366
|
# Determine arrow color based on state
|
|
329
|
-
arrow_style =
|
|
367
|
+
arrow_style = Colours.ARROW_OFF # default no channel
|
|
330
368
|
if channel:
|
|
331
369
|
state = (channel.state or "open").lower()
|
|
332
370
|
|
|
333
|
-
# Check for 405 status code (method not allowed =
|
|
371
|
+
# Check for 405 status code (method not allowed = not an error, just unsupported)
|
|
334
372
|
if channel.last_status_code == 405:
|
|
335
|
-
arrow_style =
|
|
336
|
-
# Don't add 405 to errors list - it's
|
|
373
|
+
arrow_style = Colours.ARROW_METHOD_NOT_ALLOWED
|
|
374
|
+
# Don't add 405 to errors list - it's not an error, just method not supported
|
|
337
375
|
# Error state (non-405 errors)
|
|
338
376
|
elif state == "error":
|
|
339
|
-
arrow_style =
|
|
377
|
+
arrow_style = Colours.ARROW_ERROR
|
|
340
378
|
if channel.last_error and channel.last_status_code != 405:
|
|
341
379
|
error_msg = channel.last_error
|
|
342
380
|
if channel.last_status_code:
|
|
@@ -347,20 +385,27 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
347
385
|
errors.append((label.split()[0], error_msg))
|
|
348
386
|
# Explicitly disabled or off
|
|
349
387
|
elif state in {"off", "disabled"}:
|
|
350
|
-
arrow_style =
|
|
388
|
+
arrow_style = Colours.ARROW_OFF
|
|
351
389
|
# No activity (idle)
|
|
352
390
|
elif channel.request_count == 0 and channel.response_count == 0:
|
|
353
|
-
arrow_style =
|
|
391
|
+
arrow_style = Colours.ARROW_IDLE
|
|
354
392
|
# Active/connected with activity
|
|
355
393
|
elif state in {"open", "connected"}:
|
|
356
|
-
arrow_style =
|
|
394
|
+
arrow_style = Colours.ARROW_ACTIVE
|
|
357
395
|
# Fallback for other states
|
|
358
396
|
else:
|
|
359
|
-
arrow_style =
|
|
397
|
+
arrow_style = Colours.ARROW_IDLE
|
|
360
398
|
|
|
361
399
|
# Arrow and label with better spacing
|
|
362
|
-
|
|
363
|
-
|
|
400
|
+
# Use hollow arrow for 405 Method Not Allowed
|
|
401
|
+
if channel and channel.last_status_code == 405:
|
|
402
|
+
# Convert solid arrows to hollow for 405
|
|
403
|
+
hollow_arrows = {"◀": "◁", "▶": "▷", "⇄": "⇄"} # bidirectional stays same
|
|
404
|
+
display_arrow = hollow_arrows.get(arrow, arrow)
|
|
405
|
+
else:
|
|
406
|
+
display_arrow = arrow
|
|
407
|
+
line.append(display_arrow, style=arrow_style)
|
|
408
|
+
line.append(f" {label:<13}", style=Colours.TEXT_DEFAULT)
|
|
364
409
|
|
|
365
410
|
# Always show timeline (dim black dots if no data)
|
|
366
411
|
line.append("10m ", style="dim")
|
|
@@ -368,11 +413,17 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
368
413
|
# Show actual activity
|
|
369
414
|
for bucket_state in channel.activity_buckets:
|
|
370
415
|
color = timeline_color_map.get(bucket_state, "dim")
|
|
371
|
-
|
|
416
|
+
if bucket_state in {"idle", "none"}:
|
|
417
|
+
symbol = "·"
|
|
418
|
+
elif bucket_state == "request":
|
|
419
|
+
symbol = "◆" # Diamond for requests - rare and important
|
|
420
|
+
else:
|
|
421
|
+
symbol = "●" # Circle for other activity
|
|
422
|
+
line.append(symbol, style=f"bold {color}")
|
|
372
423
|
else:
|
|
373
|
-
# Show dim
|
|
424
|
+
# Show dim dots for no activity
|
|
374
425
|
for _ in range(20):
|
|
375
|
-
line.append("
|
|
426
|
+
line.append("·", style="black dim")
|
|
376
427
|
line.append(" now", style="dim")
|
|
377
428
|
|
|
378
429
|
# Metrics - different layouts for stdio vs HTTP
|
|
@@ -380,26 +431,41 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
380
431
|
# Simplified activity column for stdio
|
|
381
432
|
if channel and channel.message_count > 0:
|
|
382
433
|
activity = str(channel.message_count).rjust(8)
|
|
383
|
-
activity_style =
|
|
434
|
+
activity_style = Colours.TEXT_DEFAULT
|
|
384
435
|
else:
|
|
385
436
|
activity = "-".rjust(8)
|
|
386
|
-
activity_style =
|
|
437
|
+
activity_style = Colours.TEXT_DIM
|
|
387
438
|
line.append(f" {activity}", style=activity_style)
|
|
388
439
|
else:
|
|
389
440
|
# Original HTTP columns
|
|
390
441
|
if channel:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
442
|
+
# Show "-" for shut/disabled channels (405, off, disabled states)
|
|
443
|
+
channel_state = (channel.state or "open").lower()
|
|
444
|
+
is_shut = (
|
|
445
|
+
channel.last_status_code == 405 or
|
|
446
|
+
channel_state in {"off", "disabled"} or
|
|
447
|
+
(channel_state == "error" and channel.last_status_code == 405)
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if is_shut:
|
|
451
|
+
req = "-".rjust(5)
|
|
452
|
+
resp = "-".rjust(5)
|
|
453
|
+
notif = "-".rjust(5)
|
|
454
|
+
ping = "-".rjust(5)
|
|
455
|
+
metrics_style = Colours.TEXT_DIM
|
|
456
|
+
else:
|
|
457
|
+
req = str(channel.request_count).rjust(5)
|
|
458
|
+
resp = str(channel.response_count).rjust(5)
|
|
459
|
+
notif = str(channel.notification_count).rjust(5)
|
|
460
|
+
ping = str(channel.ping_count if channel.ping_count else "-").rjust(5)
|
|
461
|
+
metrics_style = Colours.TEXT_DEFAULT
|
|
395
462
|
else:
|
|
396
463
|
req = "-".rjust(5)
|
|
397
464
|
resp = "-".rjust(5)
|
|
398
465
|
notif = "-".rjust(5)
|
|
399
466
|
ping = "-".rjust(5)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
)
|
|
467
|
+
metrics_style = Colours.TEXT_DIM
|
|
468
|
+
line.append(f" {req} {resp} {notif} {ping}", style=metrics_style)
|
|
403
469
|
|
|
404
470
|
console.console.print(line)
|
|
405
471
|
|
|
@@ -416,13 +482,13 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
416
482
|
|
|
417
483
|
for channel_type, error_msg in errors:
|
|
418
484
|
error_line = Text(indent)
|
|
419
|
-
error_line.append("│ ", style=
|
|
420
|
-
error_line.append("⚠ ", style=
|
|
421
|
-
error_line.append(f"{channel_type}: ", style=
|
|
485
|
+
error_line.append("│ ", style=Colours.TEXT_DIM)
|
|
486
|
+
error_line.append("⚠ ", style=Colours.TEXT_WARNING)
|
|
487
|
+
error_line.append(f"{channel_type}: ", style=Colours.TEXT_DEFAULT)
|
|
422
488
|
# Truncate long error messages
|
|
423
489
|
if len(error_msg) > 60:
|
|
424
490
|
error_msg = error_msg[:57] + "..."
|
|
425
|
-
error_line.append(error_msg, style=
|
|
491
|
+
error_line.append(error_msg, style=Colours.TEXT_ERROR)
|
|
426
492
|
console.console.print(error_line)
|
|
427
493
|
|
|
428
494
|
# Legend if any timelines shown
|
|
@@ -444,24 +510,30 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
444
510
|
if is_stdio:
|
|
445
511
|
# Simplified legend for stdio: just activity vs idle
|
|
446
512
|
legend_map = [
|
|
447
|
-
("activity", "
|
|
448
|
-
("idle",
|
|
513
|
+
("activity", f"bold {Colours.TOKEN_ENABLED}"),
|
|
514
|
+
("idle", Colours.IDLE),
|
|
449
515
|
]
|
|
450
516
|
else:
|
|
451
517
|
# Full legend for HTTP channels
|
|
452
518
|
legend_map = [
|
|
453
|
-
("error", "
|
|
454
|
-
("response", "
|
|
455
|
-
("request", "
|
|
456
|
-
("notification", "
|
|
457
|
-
("ping",
|
|
458
|
-
("idle",
|
|
519
|
+
("error", f"bold {Colours.ERROR}"),
|
|
520
|
+
("response", f"bold {Colours.RESPONSE}"),
|
|
521
|
+
("request", f"bold {Colours.REQUEST}"),
|
|
522
|
+
("notification", f"bold {Colours.NOTIFICATION}"),
|
|
523
|
+
("ping", Colours.PING),
|
|
524
|
+
("idle", Colours.IDLE),
|
|
459
525
|
]
|
|
460
526
|
|
|
461
527
|
for i, (name, color) in enumerate(legend_map):
|
|
462
528
|
if i > 0:
|
|
463
529
|
footer.append(" ", style="dim")
|
|
464
|
-
|
|
530
|
+
if name == "idle":
|
|
531
|
+
symbol = "·"
|
|
532
|
+
elif name == "request":
|
|
533
|
+
symbol = "◆" # Diamond for requests
|
|
534
|
+
else:
|
|
535
|
+
symbol = "●"
|
|
536
|
+
footer.append(symbol, style=f"{color}")
|
|
465
537
|
footer.append(f" {name}", style="dim")
|
|
466
538
|
|
|
467
539
|
console.console.print(footer)
|
|
@@ -526,10 +598,10 @@ async def render_mcp_status(agent, indent: str = "") -> None:
|
|
|
526
598
|
version_display = version_display[:9] + "..."
|
|
527
599
|
|
|
528
600
|
header_label = Text(indent)
|
|
529
|
-
header_label.append("▎", style=
|
|
530
|
-
header_label.append("●", style="dim
|
|
531
|
-
header_label.append(f" [{index:2}] ", style=
|
|
532
|
-
header_label.append(server, style="
|
|
601
|
+
header_label.append("▎", style=Colours.TEXT_CYAN)
|
|
602
|
+
header_label.append("●", style=f"dim {Colours.TEXT_CYAN}")
|
|
603
|
+
header_label.append(f" [{index:2}] ", style=Colours.TEXT_CYAN)
|
|
604
|
+
header_label.append(server, style=f"{Colours.TEXT_INFO} bold")
|
|
533
605
|
render_header(header_label)
|
|
534
606
|
|
|
535
607
|
# First line: name and version
|
|
@@ -571,22 +643,22 @@ async def render_mcp_status(agent, indent: str = "") -> None:
|
|
|
571
643
|
|
|
572
644
|
duration = _format_compact_duration(status.staleness_seconds)
|
|
573
645
|
if duration:
|
|
574
|
-
last_text = Text("last activity: ", style=
|
|
575
|
-
last_text.append(duration, style=
|
|
576
|
-
last_text.append(" ago", style=
|
|
646
|
+
last_text = Text("last activity: ", style=Colours.TEXT_DIM)
|
|
647
|
+
last_text.append(duration, style=Colours.TEXT_DEFAULT)
|
|
648
|
+
last_text.append(" ago", style=Colours.TEXT_DIM)
|
|
577
649
|
state_segments.append(last_text)
|
|
578
650
|
|
|
579
651
|
if status.error_message and status.is_connected is False:
|
|
580
|
-
state_segments.append(Text(status.error_message, style=
|
|
652
|
+
state_segments.append(Text(status.error_message, style=Colours.TEXT_ERROR))
|
|
581
653
|
|
|
582
654
|
instr_available = bool(status.instructions_available)
|
|
583
655
|
if instr_available and status.instructions_enabled is False:
|
|
584
|
-
state_segments.append(Text("instructions disabled", style=
|
|
656
|
+
state_segments.append(Text("instructions disabled", style=Colours.TEXT_ERROR))
|
|
585
657
|
elif instr_available and not template_expected:
|
|
586
|
-
state_segments.append(Text("template missing", style=
|
|
658
|
+
state_segments.append(Text("template missing", style=Colours.TEXT_WARNING))
|
|
587
659
|
|
|
588
660
|
if status.spoofing_enabled:
|
|
589
|
-
state_segments.append(Text("client spoof", style=
|
|
661
|
+
state_segments.append(Text("client spoof", style=Colours.TEXT_WARNING))
|
|
590
662
|
|
|
591
663
|
# Main status line (without transport and connected)
|
|
592
664
|
if state_segments:
|
|
@@ -601,8 +673,8 @@ async def render_mcp_status(agent, indent: str = "") -> None:
|
|
|
601
673
|
calls = _summarise_call_counts(status.call_counts)
|
|
602
674
|
if calls:
|
|
603
675
|
calls_line = Text(indent + " ")
|
|
604
|
-
calls_line.append("mcp calls: ", style=
|
|
605
|
-
calls_line.append(calls, style=
|
|
676
|
+
calls_line.append("mcp calls: ", style=Colours.TEXT_DIM)
|
|
677
|
+
calls_line.append(calls, style=Colours.TEXT_DEFAULT)
|
|
606
678
|
console.console.print(calls_line)
|
|
607
679
|
_render_channel_summary(status, indent, total_width)
|
|
608
680
|
|
|
@@ -104,7 +104,7 @@ fast_agent/mcp/hf_auth.py,sha256=ndDvR7E9LCc5dBiMsStFXtvvX9lYrL-edCq_qJw4lDw,447
|
|
|
104
104
|
fast_agent/mcp/interfaces.py,sha256=xCWONGXe4uQSmmBlMZRD3mflPegTJnz2caVNihEl3ok,2411
|
|
105
105
|
fast_agent/mcp/logger_textio.py,sha256=4YLVXlXghdGm1s_qp1VoAWEX_eWufBfD2iD7l08yoak,3170
|
|
106
106
|
fast_agent/mcp/mcp_agent_client_session.py,sha256=5_9cW3CSzYspRzb8lOJTFx9E8Qk7ieCBASBbUtNrmiY,15578
|
|
107
|
-
fast_agent/mcp/mcp_aggregator.py,sha256=
|
|
107
|
+
fast_agent/mcp/mcp_aggregator.py,sha256=hKpxLFMiNUjvGLep0TGkZ1w7ZOZVGypVoqqTsn4fUa0,67649
|
|
108
108
|
fast_agent/mcp/mcp_connection_manager.py,sha256=nbjcyWGK3LQhuFUABV1Mxf_v3rWFSMXNsDHV1h3qDUI,23803
|
|
109
109
|
fast_agent/mcp/mcp_content.py,sha256=F9bgJ57EO9sgWg1m-eTNM6xd9js79mHKf4e9O8K8jrI,8829
|
|
110
110
|
fast_agent/mcp/mime_utils.py,sha256=D6YXNdZJ351BjacSW5o0sVF_hrWuRHD6UyWS4TDlLZI,2915
|
|
@@ -117,7 +117,7 @@ fast_agent/mcp/resource_utils.py,sha256=cu-l9aOy-NFs8tPihYRNjsB2QSuime8KGOGpUvih
|
|
|
117
117
|
fast_agent/mcp/sampling.py,sha256=3EhEguls5GpVMr_SYrVQcYSRXlryGmqidn-zbFaeDMk,6711
|
|
118
118
|
fast_agent/mcp/stdio_tracking_simple.py,sha256=T6kCIb6YjwqKtXHz_6HvlLLYiSCbuggt2xCXSihVnIg,1918
|
|
119
119
|
fast_agent/mcp/streamable_http_tracking.py,sha256=bcNNReokho6WMjWEH13F33bUSkjJ2F5l3qnegkDqdMA,11465
|
|
120
|
-
fast_agent/mcp/transport_tracking.py,sha256=
|
|
120
|
+
fast_agent/mcp/transport_tracking.py,sha256=tsc2Ntf47KbKXs8DzRkqvG0U-FbpwU2VxemNfRbJBpo,24088
|
|
121
121
|
fast_agent/mcp/ui_agent.py,sha256=OBGEuFpOPPK7EthPRwzxmtzu1SDIeZy-vHwdRsyDNQk,1424
|
|
122
122
|
fast_agent/mcp/ui_mixin.py,sha256=iOlSNJVPwiMUun0clCiWyot59Qgy8R7ZvUgH2afRnQA,7662
|
|
123
123
|
fast_agent/mcp/helpers/__init__.py,sha256=o6-HuX6bEVFnfT_wgclFOVb1NxtOsJEOnHX8L2IqDdw,857
|
|
@@ -193,17 +193,17 @@ fast_agent/ui/__init__.py,sha256=MXxTQjFdF7mI_3JHxBPd-aoZYLlxV_-51-Trqgv5-3w,110
|
|
|
193
193
|
fast_agent/ui/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
|
194
194
|
fast_agent/ui/console_display.py,sha256=8KBhiv1RRvi-WcvddaFgxkgoCDRdq81uJR_QFnLM-M0,41744
|
|
195
195
|
fast_agent/ui/elicitation_form.py,sha256=t3UhBG44YmxTLu1RjCnHwW36eQQaroE45CiBGJB2czg,29410
|
|
196
|
-
fast_agent/ui/elicitation_style.py,sha256
|
|
196
|
+
fast_agent/ui/elicitation_style.py,sha256=-WqXgVjVs65oNwhCDw3E0A9cCyw95IOe6LYCJgjT6ok,3939
|
|
197
197
|
fast_agent/ui/enhanced_prompt.py,sha256=aFQfKijKzE1y_erDaufdYl74u3wXOXWJLc0LwxfRlyI,42647
|
|
198
198
|
fast_agent/ui/interactive_prompt.py,sha256=Y-tYaU_5jqjDLRRgP3rzoMAXmJxYHVIVPsd8gWkcQso,44823
|
|
199
|
-
fast_agent/ui/mcp_display.py,sha256=
|
|
199
|
+
fast_agent/ui/mcp_display.py,sha256=afBNB2jg5BFpLMxB3m0klQXwScpJxDXGafSC_RTPwA0,26418
|
|
200
200
|
fast_agent/ui/mcp_ui_utils.py,sha256=hV7z-yHX86BgdH6CMmN5qyOUjyiegQXLJOa5n5A1vQs,8476
|
|
201
201
|
fast_agent/ui/mermaid_utils.py,sha256=MpcRyVCPMTwU1XeIxnyFg0fQLjcyXZduWRF8NhEqvXE,5332
|
|
202
202
|
fast_agent/ui/progress_display.py,sha256=hajDob65PttiJ2mPS6FsCtnmTcnyvDWGn-UqQboXqkQ,361
|
|
203
203
|
fast_agent/ui/rich_progress.py,sha256=fMiTigtkll4gL4Ik5WYHx-t0a92jfUovj0b580rT6J0,7735
|
|
204
204
|
fast_agent/ui/usage_display.py,sha256=ltJpn_sDzo8PDNSXWx-QdEUbQWUnhmajCItNt5mA5rM,7285
|
|
205
|
-
fast_agent_mcp-0.3.
|
|
206
|
-
fast_agent_mcp-0.3.
|
|
207
|
-
fast_agent_mcp-0.3.
|
|
208
|
-
fast_agent_mcp-0.3.
|
|
209
|
-
fast_agent_mcp-0.3.
|
|
205
|
+
fast_agent_mcp-0.3.10.dist-info/METADATA,sha256=t08x_3p6tpwIIEwfN_NKkUAP3AwrIF4s169OlSuDp38,31697
|
|
206
|
+
fast_agent_mcp-0.3.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
207
|
+
fast_agent_mcp-0.3.10.dist-info/entry_points.txt,sha256=i6Ujja9J-hRxttOKqTYdbYP_tyaS4gLHg53vupoCSsg,199
|
|
208
|
+
fast_agent_mcp-0.3.10.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
|
|
209
|
+
fast_agent_mcp-0.3.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|