fast-agent-mcp 0.3.10__py3-none-any.whl → 0.3.11__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/config.py +2 -2
- fast_agent/human_input/elicitation_handler.py +71 -54
- fast_agent/mcp/elicitation_factory.py +2 -2
- fast_agent/mcp/mcp_aggregator.py +9 -16
- fast_agent/mcp/sampling.py +21 -0
- fast_agent/ui/console_display.py +27 -22
- fast_agent/ui/enhanced_prompt.py +19 -3
- fast_agent/ui/mcp_display.py +28 -11
- fast_agent/ui/notification_tracker.py +167 -0
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.11.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.11.dist-info}/RECORD +14 -13
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.11.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.11.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.11.dist-info}/licenses/LICENSE +0 -0
fast_agent/config.py
CHANGED
|
@@ -43,8 +43,8 @@ class MCPSamplingSettings(BaseModel):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class MCPElicitationSettings(BaseModel):
|
|
46
|
-
mode: Literal["forms", "
|
|
47
|
-
"""Elicitation mode: 'forms' (default UI), '
|
|
46
|
+
mode: Literal["forms", "auto-cancel", "none"] = "none"
|
|
47
|
+
"""Elicitation mode: 'forms' (default UI), 'auto-cancel', 'none' (no capability)"""
|
|
48
48
|
|
|
49
49
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
50
50
|
|
|
@@ -29,64 +29,81 @@ async def elicitation_input_callback(
|
|
|
29
29
|
)
|
|
30
30
|
effective_server_name = server_name or "Unknown Server"
|
|
31
31
|
|
|
32
|
-
#
|
|
33
|
-
|
|
32
|
+
# Start tracking elicitation operation
|
|
33
|
+
try:
|
|
34
|
+
from fast_agent.ui import notification_tracker
|
|
35
|
+
notification_tracker.start_elicitation(effective_server_name)
|
|
36
|
+
except Exception:
|
|
37
|
+
# Don't let notification tracking break elicitation
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Check if elicitation is disabled for this server
|
|
42
|
+
if elicitation_state.is_disabled(effective_server_name):
|
|
43
|
+
return HumanInputResponse(
|
|
44
|
+
request_id=request.request_id,
|
|
45
|
+
response="__CANCELLED__",
|
|
46
|
+
metadata={"auto_cancelled": True, "reason": "Server elicitation disabled by user"},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Get the elicitation schema from metadata
|
|
50
|
+
schema: Optional[Dict[str, Any]] = None
|
|
51
|
+
if request.metadata and "requested_schema" in request.metadata:
|
|
52
|
+
schema = request.metadata["requested_schema"]
|
|
53
|
+
|
|
54
|
+
# Use the context manager to pause the progress display while getting input
|
|
55
|
+
with progress_display.paused():
|
|
56
|
+
try:
|
|
57
|
+
if schema:
|
|
58
|
+
form_action, form_data = await show_simple_elicitation_form(
|
|
59
|
+
schema=schema,
|
|
60
|
+
message=request.prompt,
|
|
61
|
+
agent_name=effective_agent_name,
|
|
62
|
+
server_name=effective_server_name,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if form_action == "accept" and form_data is not None:
|
|
66
|
+
# Convert form data to JSON string
|
|
67
|
+
import json
|
|
68
|
+
|
|
69
|
+
response = json.dumps(form_data)
|
|
70
|
+
elif form_action == "decline":
|
|
71
|
+
response = "__DECLINED__"
|
|
72
|
+
elif form_action == "disable":
|
|
73
|
+
response = "__DISABLE_SERVER__"
|
|
74
|
+
else: # cancel
|
|
75
|
+
response = "__CANCELLED__"
|
|
76
|
+
else:
|
|
77
|
+
# No schema, fall back to text input using prompt_toolkit only
|
|
78
|
+
from prompt_toolkit.shortcuts import input_dialog
|
|
79
|
+
|
|
80
|
+
response = await input_dialog(
|
|
81
|
+
title="Input Requested",
|
|
82
|
+
text=f"Agent: {effective_agent_name}\nServer: {effective_server_name}\n\n{request.prompt}",
|
|
83
|
+
style=ELICITATION_STYLE,
|
|
84
|
+
).run_async()
|
|
85
|
+
|
|
86
|
+
if response is None:
|
|
87
|
+
response = "__CANCELLED__"
|
|
88
|
+
|
|
89
|
+
except KeyboardInterrupt:
|
|
90
|
+
response = "__CANCELLED__"
|
|
91
|
+
except EOFError:
|
|
92
|
+
response = "__CANCELLED__"
|
|
93
|
+
|
|
34
94
|
return HumanInputResponse(
|
|
35
95
|
request_id=request.request_id,
|
|
36
|
-
response=
|
|
37
|
-
metadata={"
|
|
96
|
+
response=response.strip() if isinstance(response, str) else response,
|
|
97
|
+
metadata={"has_schema": schema is not None},
|
|
38
98
|
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
schema: Optional[Dict[str, Any]] = None
|
|
42
|
-
if request.metadata and "requested_schema" in request.metadata:
|
|
43
|
-
schema = request.metadata["requested_schema"]
|
|
44
|
-
|
|
45
|
-
# Use the context manager to pause the progress display while getting input
|
|
46
|
-
with progress_display.paused():
|
|
99
|
+
finally:
|
|
100
|
+
# End tracking elicitation operation
|
|
47
101
|
try:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
server_name=effective_server_name,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
if form_action == "accept" and form_data is not None:
|
|
57
|
-
# Convert form data to JSON string
|
|
58
|
-
import json
|
|
59
|
-
|
|
60
|
-
response = json.dumps(form_data)
|
|
61
|
-
elif form_action == "decline":
|
|
62
|
-
response = "__DECLINED__"
|
|
63
|
-
elif form_action == "disable":
|
|
64
|
-
response = "__DISABLE_SERVER__"
|
|
65
|
-
else: # cancel
|
|
66
|
-
response = "__CANCELLED__"
|
|
67
|
-
else:
|
|
68
|
-
# No schema, fall back to text input using prompt_toolkit only
|
|
69
|
-
from prompt_toolkit.shortcuts import input_dialog
|
|
70
|
-
|
|
71
|
-
response = await input_dialog(
|
|
72
|
-
title="Input Requested",
|
|
73
|
-
text=f"Agent: {effective_agent_name}\nServer: {effective_server_name}\n\n{request.prompt}",
|
|
74
|
-
style=ELICITATION_STYLE,
|
|
75
|
-
).run_async()
|
|
76
|
-
|
|
77
|
-
if response is None:
|
|
78
|
-
response = "__CANCELLED__"
|
|
79
|
-
|
|
80
|
-
except KeyboardInterrupt:
|
|
81
|
-
response = "__CANCELLED__"
|
|
82
|
-
except EOFError:
|
|
83
|
-
response = "__CANCELLED__"
|
|
84
|
-
|
|
85
|
-
return HumanInputResponse(
|
|
86
|
-
request_id=request.request_id,
|
|
87
|
-
response=response.strip() if isinstance(response, str) else response,
|
|
88
|
-
metadata={"has_schema": schema is not None},
|
|
89
|
-
)
|
|
102
|
+
from fast_agent.ui import notification_tracker
|
|
103
|
+
notification_tracker.end_elicitation(effective_server_name)
|
|
104
|
+
except Exception:
|
|
105
|
+
# Don't let notification tracking break elicitation
|
|
106
|
+
pass
|
|
90
107
|
|
|
91
108
|
|
|
92
109
|
# Register adapter with fast_agent tools so they can invoke this UI handler without importing types
|
|
@@ -53,7 +53,7 @@ def resolve_elicitation_handler(
|
|
|
53
53
|
if mode == "none":
|
|
54
54
|
logger.debug(f"Elicitation disabled by server config for agent {agent_config.name}")
|
|
55
55
|
return None # Don't advertise elicitation capability
|
|
56
|
-
elif mode == "
|
|
56
|
+
elif mode == "auto-cancel":
|
|
57
57
|
logger.debug(
|
|
58
58
|
f"Using auto-cancel elicitation handler (server config) for agent {agent_config.name}"
|
|
59
59
|
)
|
|
@@ -74,7 +74,7 @@ def resolve_elicitation_handler(
|
|
|
74
74
|
if mode == "none":
|
|
75
75
|
logger.debug(f"Elicitation disabled by global config for agent {agent_config.name}")
|
|
76
76
|
return None # Don't advertise elicitation capability
|
|
77
|
-
elif mode == "
|
|
77
|
+
elif mode == "auto-cancel":
|
|
78
78
|
logger.debug(
|
|
79
79
|
f"Using auto-cancel elicitation handler (global config) for agent {agent_config.name}"
|
|
80
80
|
)
|
fast_agent/mcp/mcp_aggregator.py
CHANGED
|
@@ -1362,22 +1362,15 @@ class MCPAggregator(ContextDependent):
|
|
|
1362
1362
|
|
|
1363
1363
|
async with self._refresh_lock:
|
|
1364
1364
|
try:
|
|
1365
|
-
# Fetch new tools from the server
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
async with gen_client(
|
|
1375
|
-
server_name,
|
|
1376
|
-
server_registry=self.context.server_registry,
|
|
1377
|
-
client_session_factory=self._create_session_factory(server_name),
|
|
1378
|
-
) as client:
|
|
1379
|
-
tools_result = await client.list_tools()
|
|
1380
|
-
new_tools = tools_result.tools or []
|
|
1365
|
+
# Fetch new tools from the server using _execute_on_server to properly record stats
|
|
1366
|
+
tools_result = await self._execute_on_server(
|
|
1367
|
+
server_name=server_name,
|
|
1368
|
+
operation_type="tools/list",
|
|
1369
|
+
operation_name="",
|
|
1370
|
+
method_name="list_tools",
|
|
1371
|
+
method_args={},
|
|
1372
|
+
)
|
|
1373
|
+
new_tools = tools_result.tools or []
|
|
1381
1374
|
|
|
1382
1375
|
# Update tool maps
|
|
1383
1376
|
async with self._tool_map_lock:
|
fast_agent/mcp/sampling.py
CHANGED
|
@@ -77,6 +77,19 @@ async def sample(mcp_ctx: ClientSession, params: CreateMessageRequestParams) ->
|
|
|
77
77
|
Returns:
|
|
78
78
|
A CreateMessageResult containing the LLM's response
|
|
79
79
|
"""
|
|
80
|
+
# Get server name for notification tracking
|
|
81
|
+
server_name = "unknown"
|
|
82
|
+
if hasattr(mcp_ctx, "session") and hasattr(mcp_ctx.session, "session_server_name"):
|
|
83
|
+
server_name = mcp_ctx.session.session_server_name or "unknown"
|
|
84
|
+
|
|
85
|
+
# Start tracking sampling operation
|
|
86
|
+
try:
|
|
87
|
+
from fast_agent.ui import notification_tracker
|
|
88
|
+
notification_tracker.start_sampling(server_name)
|
|
89
|
+
except Exception:
|
|
90
|
+
# Don't let notification tracking break sampling
|
|
91
|
+
pass
|
|
92
|
+
|
|
80
93
|
model: str | None = None
|
|
81
94
|
api_key: str | None = None
|
|
82
95
|
try:
|
|
@@ -157,6 +170,14 @@ async def sample(mcp_ctx: ClientSession, params: CreateMessageRequestParams) ->
|
|
|
157
170
|
return SamplingConverter.error_result(
|
|
158
171
|
error_message=f"Error in sampling: {str(e)}", model=model
|
|
159
172
|
)
|
|
173
|
+
finally:
|
|
174
|
+
# End tracking sampling operation
|
|
175
|
+
try:
|
|
176
|
+
from fast_agent.ui import notification_tracker
|
|
177
|
+
notification_tracker.end_sampling(server_name)
|
|
178
|
+
except Exception:
|
|
179
|
+
# Don't let notification tracking break sampling
|
|
180
|
+
pass
|
|
160
181
|
|
|
161
182
|
|
|
162
183
|
def sampling_agent_config(
|
fast_agent/ui/console_display.py
CHANGED
|
@@ -623,33 +623,38 @@ class ConsoleDisplay:
|
|
|
623
623
|
if not self.config or not self.config.logger.show_tools:
|
|
624
624
|
return
|
|
625
625
|
|
|
626
|
-
#
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
630
|
-
)
|
|
631
|
-
else:
|
|
632
|
-
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
626
|
+
# Check if prompt_toolkit is active
|
|
627
|
+
try:
|
|
628
|
+
from prompt_toolkit.application.current import get_app
|
|
633
629
|
|
|
634
|
-
|
|
635
|
-
|
|
630
|
+
app = get_app()
|
|
631
|
+
# We're in interactive mode - add to notification tracker
|
|
632
|
+
from fast_agent.ui import notification_tracker
|
|
636
633
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
console.console.print(message, style="dim", markup=self._markup)
|
|
634
|
+
notification_tracker.add_tool_update(updated_server)
|
|
635
|
+
app.invalidate() # Force toolbar redraw
|
|
640
636
|
|
|
641
|
-
#
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
637
|
+
except: # noqa: E722
|
|
638
|
+
# No active prompt_toolkit session - display with rich as before
|
|
639
|
+
# Combined separator and status line
|
|
640
|
+
if agent_name:
|
|
641
|
+
left = (
|
|
642
|
+
f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
643
|
+
)
|
|
644
|
+
else:
|
|
645
|
+
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
645
646
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
from prompt_toolkit.application.current import get_app
|
|
647
|
+
right = f"[dim]{updated_server}[/dim]"
|
|
648
|
+
self._create_combined_separator_status(left, right)
|
|
649
649
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
650
|
+
# Display update message
|
|
651
|
+
message = f"Updating tools for server {updated_server}"
|
|
652
|
+
console.console.print(message, style="dim", markup=self._markup)
|
|
653
|
+
|
|
654
|
+
# Bottom separator
|
|
655
|
+
console.console.print()
|
|
656
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
657
|
+
console.console.print()
|
|
653
658
|
|
|
654
659
|
def _create_combined_separator_status(self, left_content: str, right_info: str = "") -> None:
|
|
655
660
|
"""
|
fast_agent/ui/enhanced_prompt.py
CHANGED
|
@@ -717,17 +717,33 @@ async def get_enhanced_input(
|
|
|
717
717
|
# Version/app label in green (dynamic version)
|
|
718
718
|
version_segment = f"fast-agent {app_version}"
|
|
719
719
|
|
|
720
|
+
# Add notifications - prioritize active events over completed ones
|
|
721
|
+
from fast_agent.ui import notification_tracker
|
|
722
|
+
|
|
723
|
+
notification_segment = ""
|
|
724
|
+
|
|
725
|
+
# Check for active events first (highest priority)
|
|
726
|
+
active_status = notification_tracker.get_active_status()
|
|
727
|
+
if active_status:
|
|
728
|
+
event_type = active_status['type'].upper()
|
|
729
|
+
server = active_status['server']
|
|
730
|
+
notification_segment = f" | <style fg='ansired' bg='ansiblack'>◀ {event_type} ({server})</style>"
|
|
731
|
+
elif notification_tracker.get_count() > 0:
|
|
732
|
+
# Show completed events summary when no active events
|
|
733
|
+
summary = notification_tracker.get_summary()
|
|
734
|
+
notification_segment = f" | ◀ {notification_tracker.get_count()} updates ({summary})"
|
|
735
|
+
|
|
720
736
|
if middle:
|
|
721
737
|
return HTML(
|
|
722
738
|
f" <style fg='{toolbar_color}' bg='ansiblack'> {agent_name} </style> "
|
|
723
739
|
f" {middle} | <style fg='{mode_style}' bg='ansiblack'> {mode_text} </style> | "
|
|
724
|
-
f"{version_segment}"
|
|
740
|
+
f"{version_segment}{notification_segment}"
|
|
725
741
|
)
|
|
726
742
|
else:
|
|
727
743
|
return HTML(
|
|
728
744
|
f" <style fg='{toolbar_color}' bg='ansiblack'> {agent_name} </style> "
|
|
729
745
|
f"Mode: <style fg='{mode_style}' bg='ansiblack'> {mode_text} </style> | "
|
|
730
|
-
f"{version_segment}"
|
|
746
|
+
f"{version_segment}{notification_segment}"
|
|
731
747
|
)
|
|
732
748
|
|
|
733
749
|
# A more terminal-agnostic style that should work across themes
|
|
@@ -766,7 +782,7 @@ async def get_enhanced_input(
|
|
|
766
782
|
session.app.key_bindings = bindings
|
|
767
783
|
|
|
768
784
|
# Create formatted prompt text
|
|
769
|
-
prompt_text = f"<ansibrightblue>{agent_name}</ansibrightblue>
|
|
785
|
+
prompt_text = f"<ansibrightblue>{agent_name}</ansibrightblue> ❯ "
|
|
770
786
|
|
|
771
787
|
# Add default value display if requested
|
|
772
788
|
if show_default and default and default != "STOP":
|
fast_agent/ui/mcp_display.py
CHANGED
|
@@ -19,12 +19,12 @@ class Colours:
|
|
|
19
19
|
"""Color constants for MCP status display elements."""
|
|
20
20
|
|
|
21
21
|
# Timeline activity colors (Option A: Mixed Intensity)
|
|
22
|
-
ERROR = "bright_red"
|
|
23
|
-
DISABLED = "bright_blue"
|
|
24
|
-
RESPONSE = "blue"
|
|
25
|
-
REQUEST = "yellow"
|
|
26
|
-
NOTIFICATION = "cyan"
|
|
27
|
-
PING = "dim green"
|
|
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
28
|
IDLE = "white dim"
|
|
29
29
|
NONE = "dim"
|
|
30
30
|
|
|
@@ -195,7 +195,7 @@ def _format_capability_shorthand(
|
|
|
195
195
|
entries.append(("Ro", False, False))
|
|
196
196
|
|
|
197
197
|
mode = (status.elicitation_mode or "").lower()
|
|
198
|
-
if mode == "
|
|
198
|
+
if mode == "auto-cancel":
|
|
199
199
|
entries.append(("El", "red", False))
|
|
200
200
|
elif mode and mode != "none":
|
|
201
201
|
entries.append(("El", True, False))
|
|
@@ -405,7 +405,21 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
405
405
|
else:
|
|
406
406
|
display_arrow = arrow
|
|
407
407
|
line.append(display_arrow, style=arrow_style)
|
|
408
|
-
|
|
408
|
+
|
|
409
|
+
# Determine label style based on activity and special cases
|
|
410
|
+
if not channel:
|
|
411
|
+
# No channel = dim
|
|
412
|
+
label_style = Colours.TEXT_DIM
|
|
413
|
+
elif channel.last_status_code == 405 and "GET" in label:
|
|
414
|
+
# Special case: GET (SSE) with 405 = dim (hollow arrow already handled above)
|
|
415
|
+
label_style = Colours.TEXT_DIM
|
|
416
|
+
elif channel.request_count == 0 and channel.response_count == 0:
|
|
417
|
+
# No activity = dim
|
|
418
|
+
label_style = Colours.TEXT_DIM
|
|
419
|
+
else:
|
|
420
|
+
# Has activity = normal
|
|
421
|
+
label_style = Colours.TEXT_DEFAULT
|
|
422
|
+
line.append(f" {label:<13}", style=label_style)
|
|
409
423
|
|
|
410
424
|
# Always show timeline (dim black dots if no data)
|
|
411
425
|
line.append("10m ", style="dim")
|
|
@@ -415,6 +429,9 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
415
429
|
color = timeline_color_map.get(bucket_state, "dim")
|
|
416
430
|
if bucket_state in {"idle", "none"}:
|
|
417
431
|
symbol = "·"
|
|
432
|
+
elif is_stdio:
|
|
433
|
+
# For STDIO, all activity shows as filled circles since types are combined
|
|
434
|
+
symbol = "●"
|
|
418
435
|
elif bucket_state == "request":
|
|
419
436
|
symbol = "◆" # Diamond for requests - rare and important
|
|
420
437
|
else:
|
|
@@ -442,9 +459,9 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
442
459
|
# Show "-" for shut/disabled channels (405, off, disabled states)
|
|
443
460
|
channel_state = (channel.state or "open").lower()
|
|
444
461
|
is_shut = (
|
|
445
|
-
channel.last_status_code == 405
|
|
446
|
-
channel_state in {"off", "disabled"}
|
|
447
|
-
(channel_state == "error" and channel.last_status_code == 405)
|
|
462
|
+
channel.last_status_code == 405
|
|
463
|
+
or channel_state in {"off", "disabled"}
|
|
464
|
+
or (channel_state == "error" and channel.last_status_code == 405)
|
|
448
465
|
)
|
|
449
466
|
|
|
450
467
|
if is_shut:
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced notification tracker for prompt_toolkit toolbar display.
|
|
3
|
+
Tracks both active events (sampling/elicitation) and completed notifications.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
# Active events currently in progress
|
|
10
|
+
active_events: Dict[str, Dict[str, str]] = {}
|
|
11
|
+
|
|
12
|
+
# Completed notifications history
|
|
13
|
+
notifications: List[Dict[str, str]] = []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_tool_update(server_name: str) -> None:
|
|
17
|
+
"""Add a tool update notification.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
server_name: Name of the server that had tools updated
|
|
21
|
+
"""
|
|
22
|
+
notifications.append({
|
|
23
|
+
'type': 'tool_update',
|
|
24
|
+
'server': server_name
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def start_sampling(server_name: str) -> None:
|
|
29
|
+
"""Start tracking a sampling operation.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
server_name: Name of the server making the sampling request
|
|
33
|
+
"""
|
|
34
|
+
active_events['sampling'] = {
|
|
35
|
+
'server': server_name,
|
|
36
|
+
'start_time': datetime.now().isoformat()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Force prompt_toolkit to redraw if active
|
|
40
|
+
try:
|
|
41
|
+
from prompt_toolkit.application.current import get_app
|
|
42
|
+
get_app().invalidate()
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def end_sampling(server_name: str) -> None:
|
|
48
|
+
"""End tracking a sampling operation and add to completed notifications.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
server_name: Name of the server that made the sampling request
|
|
52
|
+
"""
|
|
53
|
+
if 'sampling' in active_events:
|
|
54
|
+
del active_events['sampling']
|
|
55
|
+
|
|
56
|
+
notifications.append({
|
|
57
|
+
'type': 'sampling',
|
|
58
|
+
'server': server_name
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
# Force prompt_toolkit to redraw if active
|
|
62
|
+
try:
|
|
63
|
+
from prompt_toolkit.application.current import get_app
|
|
64
|
+
get_app().invalidate()
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def start_elicitation(server_name: str) -> None:
|
|
70
|
+
"""Start tracking an elicitation operation.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
server_name: Name of the server making the elicitation request
|
|
74
|
+
"""
|
|
75
|
+
active_events['elicitation'] = {
|
|
76
|
+
'server': server_name,
|
|
77
|
+
'start_time': datetime.now().isoformat()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Force prompt_toolkit to redraw if active
|
|
81
|
+
try:
|
|
82
|
+
from prompt_toolkit.application.current import get_app
|
|
83
|
+
get_app().invalidate()
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def end_elicitation(server_name: str) -> None:
|
|
89
|
+
"""End tracking an elicitation operation and add to completed notifications.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
server_name: Name of the server that made the elicitation request
|
|
93
|
+
"""
|
|
94
|
+
if 'elicitation' in active_events:
|
|
95
|
+
del active_events['elicitation']
|
|
96
|
+
|
|
97
|
+
notifications.append({
|
|
98
|
+
'type': 'elicitation',
|
|
99
|
+
'server': server_name
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
# Force prompt_toolkit to redraw if active
|
|
103
|
+
try:
|
|
104
|
+
from prompt_toolkit.application.current import get_app
|
|
105
|
+
get_app().invalidate()
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_active_status() -> Optional[Dict[str, str]]:
|
|
111
|
+
"""Get currently active operation, if any.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dict with 'type' and 'server' keys, or None if nothing active
|
|
115
|
+
"""
|
|
116
|
+
if 'sampling' in active_events:
|
|
117
|
+
return {'type': 'sampling', 'server': active_events['sampling']['server']}
|
|
118
|
+
if 'elicitation' in active_events:
|
|
119
|
+
return {'type': 'elicitation', 'server': active_events['elicitation']['server']}
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def clear() -> None:
|
|
124
|
+
"""Clear all notifications and active events."""
|
|
125
|
+
notifications.clear()
|
|
126
|
+
active_events.clear()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_count() -> int:
|
|
130
|
+
"""Get the current completed notification count."""
|
|
131
|
+
return len(notifications)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_latest() -> Dict[str, str] | None:
|
|
135
|
+
"""Get the most recent completed notification."""
|
|
136
|
+
return notifications[-1] if notifications else None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_summary() -> str:
|
|
140
|
+
"""Get a summary of completed notifications by type.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
String like "3 tools, 1 sampling, 2 elicitations" or "1 tool update"
|
|
144
|
+
"""
|
|
145
|
+
if not notifications:
|
|
146
|
+
return ""
|
|
147
|
+
|
|
148
|
+
counts = {}
|
|
149
|
+
for notification in notifications:
|
|
150
|
+
event_type = notification['type']
|
|
151
|
+
if event_type == 'tool_update':
|
|
152
|
+
counts['tools'] = counts.get('tools', 0) + 1
|
|
153
|
+
else:
|
|
154
|
+
# For sampling/elicitation, use the type directly
|
|
155
|
+
counts[event_type] = counts.get(event_type, 0) + 1
|
|
156
|
+
|
|
157
|
+
# Build summary string
|
|
158
|
+
parts = []
|
|
159
|
+
for event_type, count in sorted(counts.items()):
|
|
160
|
+
if event_type == 'tools':
|
|
161
|
+
parts.append(f"{count} tool{'s' if count != 1 else ''}")
|
|
162
|
+
elif event_type == 'sampling':
|
|
163
|
+
parts.append(f"{count} sample{'s' if count != 1 else ''}")
|
|
164
|
+
else:
|
|
165
|
+
parts.append(f"{count} {event_type}{'s' if count != 1 else ''}")
|
|
166
|
+
|
|
167
|
+
return ", ".join(parts)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
fast_agent/__init__.py,sha256=ns6CPmjOL5y7cyV4XgFTfMGcfLnuBJwVTbcjJ5Co3x4,4152
|
|
2
|
-
fast_agent/config.py,sha256=
|
|
2
|
+
fast_agent/config.py,sha256=tKnAhGAADpuwU7DggScG0VESs7el4R1Y1T3MUPzD5_U,22509
|
|
3
3
|
fast_agent/constants.py,sha256=IoXL5m4L0iLlcRrKerMaK3ZPcS6KCJgK8b_bj1BAR60,345
|
|
4
4
|
fast_agent/context.py,sha256=nBelOqehSH91z3aG2nYhwETP-biRzz-iuA2fqmKdHP8,7700
|
|
5
5
|
fast_agent/context_dependent.py,sha256=KU1eydVBoIt4bYOZroqxDgE1AUexDaZi7hurE26QsF4,1584
|
|
@@ -54,7 +54,7 @@ fast_agent/core/logging/logger.py,sha256=L-hLfUGFCIABoNYDiUkNHWvFxL6j-6zn5Pc5E7a
|
|
|
54
54
|
fast_agent/core/logging/transport.py,sha256=i_WYXk5mqyfetT72bCYrbdrMWcuL1HJCyeQKfQg7U2w,16994
|
|
55
55
|
fast_agent/history/history_exporter.py,sha256=oqkw7qC5rrW73u20tkIqt8yBWPoVzCTC61x2Q2rOKGs,1404
|
|
56
56
|
fast_agent/human_input/__init__.py,sha256=4Jr_0JLJwdQ3iEUNd6Api9InldtnRMDv_WeZq_WEHpA,935
|
|
57
|
-
fast_agent/human_input/elicitation_handler.py,sha256=
|
|
57
|
+
fast_agent/human_input/elicitation_handler.py,sha256=diR8jWRCAuoqTDQHJ3C097EORcLFxpLjdz6EubVOnI8,4776
|
|
58
58
|
fast_agent/human_input/elicitation_state.py,sha256=L_vSTpw1-TSDumRYa89Me-twWRbUL2w7GNVhVJmn3KE,1152
|
|
59
59
|
fast_agent/human_input/form_fields.py,sha256=aE7HdR-wOPO_6HllNaJXtn3BzpPsC4TctUApbveRk8g,7644
|
|
60
60
|
fast_agent/human_input/simple_form.py,sha256=_flUll9z93VPjKqLS7gvz4L1_YJ3-KNx8_ZpUaaxhoQ,3508
|
|
@@ -97,14 +97,14 @@ fast_agent/llm/provider/openai/openai_multipart.py,sha256=uuoRMYWiznYksBODbknBuq
|
|
|
97
97
|
fast_agent/llm/provider/openai/openai_utils.py,sha256=RI9UgF7SGkZ02bAep7glvLy3erbismmZp7wXDsRoJPQ,2034
|
|
98
98
|
fast_agent/mcp/__init__.py,sha256=Q-86MBawKmB602VMut8U3uqUQDOrTAMx2HmHKnWT32Q,1371
|
|
99
99
|
fast_agent/mcp/common.py,sha256=MpSC0fLO21RcDz4VApah4C8_LisVGz7OXkR17Xw-9mY,431
|
|
100
|
-
fast_agent/mcp/elicitation_factory.py,sha256=
|
|
100
|
+
fast_agent/mcp/elicitation_factory.py,sha256=d4unxJyR0S-qH05yBZ_oQ08XLBToFtUzzFFYvv0wLrY,3172
|
|
101
101
|
fast_agent/mcp/elicitation_handlers.py,sha256=LWNKn850Ht9V1SGTfAZDptlsgzrycNhOPNsCcqzsuUY,6884
|
|
102
102
|
fast_agent/mcp/gen_client.py,sha256=Q0hhCVzC659GsvTLJIbhUBgGwsAJRL8b3ejTFokyjn4,3038
|
|
103
103
|
fast_agent/mcp/hf_auth.py,sha256=ndDvR7E9LCc5dBiMsStFXtvvX9lYrL-edCq_qJw4lDw,4476
|
|
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=HzNyKuUelAlp5JBkjMAaOB2JvdMgpm0ItEPGjidI9l0,67198
|
|
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
|
|
@@ -114,7 +114,7 @@ fast_agent/mcp/prompt_message_extended.py,sha256=BsiV2SsiZkDlvqzvjeSowq8Ojvowr9X
|
|
|
114
114
|
fast_agent/mcp/prompt_render.py,sha256=AqDaQqM6kqciV9X79S5rsRr3VjcQ_2JOiLaHqpRzsS4,2888
|
|
115
115
|
fast_agent/mcp/prompt_serialization.py,sha256=QMbY0aa_UlJ7bbxl_muOm2TYeYbBVTEeEMHFmEy99ss,20182
|
|
116
116
|
fast_agent/mcp/resource_utils.py,sha256=cu-l9aOy-NFs8tPihYRNjsB2QSuime8KGOGpUvihp84,6589
|
|
117
|
-
fast_agent/mcp/sampling.py,sha256=
|
|
117
|
+
fast_agent/mcp/sampling.py,sha256=6S9bpGCFGC5azIGE-zxODvKgBbBn1x6amL5mc4sMg_4,7491
|
|
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
120
|
fast_agent/mcp/transport_tracking.py,sha256=tsc2Ntf47KbKXs8DzRkqvG0U-FbpwU2VxemNfRbJBpo,24088
|
|
@@ -191,19 +191,20 @@ fast_agent/types/__init__.py,sha256=y-53m-C4drf4Rx8Bbnk_GAhko9LdNYCyRUWya8e0mos,
|
|
|
191
191
|
fast_agent/types/llm_stop_reason.py,sha256=bWe97OfhALUe8uQeAQOnTdPlYzJiabIfo8u38kPgj3Q,2293
|
|
192
192
|
fast_agent/ui/__init__.py,sha256=MXxTQjFdF7mI_3JHxBPd-aoZYLlxV_-51-Trqgv5-3w,1104
|
|
193
193
|
fast_agent/ui/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
|
194
|
-
fast_agent/ui/console_display.py,sha256=
|
|
194
|
+
fast_agent/ui/console_display.py,sha256=oZg-SMd_F0_1azsxWF5ug7cee_-ou2xOgGeW_y_6xrI,42034
|
|
195
195
|
fast_agent/ui/elicitation_form.py,sha256=t3UhBG44YmxTLu1RjCnHwW36eQQaroE45CiBGJB2czg,29410
|
|
196
196
|
fast_agent/ui/elicitation_style.py,sha256=-WqXgVjVs65oNwhCDw3E0A9cCyw95IOe6LYCJgjT6ok,3939
|
|
197
|
-
fast_agent/ui/enhanced_prompt.py,sha256=
|
|
197
|
+
fast_agent/ui/enhanced_prompt.py,sha256=nGtYqvabfwYqCNczqWEf36AjY4laetG8CI__ePKtwKs,43496
|
|
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=54NVlWL6KsvhLP2tZm3edC3RH7fiLOJeKOYQCJ55uks,27142
|
|
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
|
+
fast_agent/ui/notification_tracker.py,sha256=XU2_C_1PwKEReyL2L6sFfFOq-3CczMlBiIIspxUjx0E,4625
|
|
202
203
|
fast_agent/ui/progress_display.py,sha256=hajDob65PttiJ2mPS6FsCtnmTcnyvDWGn-UqQboXqkQ,361
|
|
203
204
|
fast_agent/ui/rich_progress.py,sha256=fMiTigtkll4gL4Ik5WYHx-t0a92jfUovj0b580rT6J0,7735
|
|
204
205
|
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.
|
|
206
|
+
fast_agent_mcp-0.3.11.dist-info/METADATA,sha256=hXINwYTIB7UUzPSbYOlPN0Mcq-YUKy1nWlfs0Gx5hcQ,31697
|
|
207
|
+
fast_agent_mcp-0.3.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
208
|
+
fast_agent_mcp-0.3.11.dist-info/entry_points.txt,sha256=i6Ujja9J-hRxttOKqTYdbYP_tyaS4gLHg53vupoCSsg,199
|
|
209
|
+
fast_agent_mcp-0.3.11.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
|
|
210
|
+
fast_agent_mcp-0.3.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|