fast-agent-mcp 0.3.5__py3-none-any.whl → 0.3.6__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 +15 -1
- fast_agent/agents/mcp_agent.py +73 -1
- fast_agent/agents/tool_agent.py +10 -0
- fast_agent/agents/workflow/router_agent.py +10 -2
- fast_agent/cli/commands/auth.py +52 -29
- fast_agent/cli/commands/check_config.py +26 -5
- fast_agent/cli/commands/go.py +8 -2
- fast_agent/cli/commands/setup.py +4 -7
- fast_agent/config.py +4 -1
- fast_agent/mcp/mcp_aggregator.py +30 -0
- fast_agent/mcp/mcp_connection_manager.py +23 -4
- fast_agent/mcp/oauth_client.py +32 -4
- fast_agent/resources/setup/.gitignore +6 -0
- fast_agent/resources/setup/agent.py +8 -1
- fast_agent/resources/setup/pyproject.toml.tmpl +6 -0
- fast_agent/ui/console_display.py +48 -31
- fast_agent/ui/enhanced_prompt.py +8 -0
- fast_agent/ui/interactive_prompt.py +54 -0
- {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.6.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.6.dist-info}/RECORD +23 -23
- {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.6.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.6.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.6.dist-info}/licenses/LICENSE +0 -0
fast_agent/agents/llm_agent.py
CHANGED
|
@@ -137,10 +137,24 @@ class LlmAgent(LlmDecorator):
|
|
|
137
137
|
display_name = name if name is not None else self.name
|
|
138
138
|
display_model = model if model is not None else (self.llm.model_name if self._llm else None)
|
|
139
139
|
|
|
140
|
+
# Convert highlight_items to highlight_index
|
|
141
|
+
highlight_index = None
|
|
142
|
+
if highlight_items and bottom_items:
|
|
143
|
+
if isinstance(highlight_items, str):
|
|
144
|
+
try:
|
|
145
|
+
highlight_index = bottom_items.index(highlight_items)
|
|
146
|
+
except ValueError:
|
|
147
|
+
pass
|
|
148
|
+
elif isinstance(highlight_items, list) and len(highlight_items) > 0:
|
|
149
|
+
try:
|
|
150
|
+
highlight_index = bottom_items.index(highlight_items[0])
|
|
151
|
+
except ValueError:
|
|
152
|
+
pass
|
|
153
|
+
|
|
140
154
|
await self.display.show_assistant_message(
|
|
141
155
|
message_text,
|
|
142
156
|
bottom_items=bottom_items,
|
|
143
|
-
|
|
157
|
+
highlight_index=highlight_index,
|
|
144
158
|
max_item_length=max_item_length,
|
|
145
159
|
name=display_name,
|
|
146
160
|
model=display_model,
|
fast_agent/agents/mcp_agent.py
CHANGED
|
@@ -156,6 +156,9 @@ class McpAgent(ABC, ToolAgent):
|
|
|
156
156
|
"""
|
|
157
157
|
await self.__aenter__()
|
|
158
158
|
|
|
159
|
+
# Apply template substitution to the instruction with server instructions
|
|
160
|
+
await self._apply_instruction_templates()
|
|
161
|
+
|
|
159
162
|
async def shutdown(self) -> None:
|
|
160
163
|
"""
|
|
161
164
|
Shutdown the agent and close all MCP server connections.
|
|
@@ -174,6 +177,67 @@ class McpAgent(ABC, ToolAgent):
|
|
|
174
177
|
self._initialized = value
|
|
175
178
|
self._aggregator.initialized = value
|
|
176
179
|
|
|
180
|
+
async def _apply_instruction_templates(self) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Apply template substitution to the instruction, including server instructions.
|
|
183
|
+
This is called during initialization after servers are connected.
|
|
184
|
+
"""
|
|
185
|
+
if not self.instruction:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
# Gather server instructions if the template includes {{serverInstructions}}
|
|
189
|
+
if "{{serverInstructions}}" in self.instruction:
|
|
190
|
+
try:
|
|
191
|
+
instructions_data = await self._aggregator.get_server_instructions()
|
|
192
|
+
server_instructions = self._format_server_instructions(instructions_data)
|
|
193
|
+
except Exception as e:
|
|
194
|
+
self.logger.warning(f"Failed to get server instructions: {e}")
|
|
195
|
+
server_instructions = ""
|
|
196
|
+
|
|
197
|
+
# Replace the template variable
|
|
198
|
+
self.instruction = self.instruction.replace("{{serverInstructions}}", server_instructions)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# Update default request params to match
|
|
202
|
+
if self._default_request_params:
|
|
203
|
+
self._default_request_params.systemPrompt = self.instruction
|
|
204
|
+
|
|
205
|
+
self.logger.debug(f"Applied instruction templates for agent {self._name}")
|
|
206
|
+
|
|
207
|
+
def _format_server_instructions(self, instructions_data: Dict[str, tuple[str | None, List[str]]]) -> str:
|
|
208
|
+
"""
|
|
209
|
+
Format server instructions with XML tags and tool lists.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
instructions_data: Dict mapping server name to (instructions, tool_names)
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Formatted string with server instructions
|
|
216
|
+
"""
|
|
217
|
+
if not instructions_data:
|
|
218
|
+
return ""
|
|
219
|
+
|
|
220
|
+
formatted_parts = []
|
|
221
|
+
for server_name, (instructions, tool_names) in instructions_data.items():
|
|
222
|
+
# Skip servers with no instructions
|
|
223
|
+
if instructions is None:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
# Format tool names with server prefix
|
|
227
|
+
prefixed_tools = [f"{server_name}-{tool}" for tool in tool_names]
|
|
228
|
+
tools_list = ", ".join(prefixed_tools) if prefixed_tools else "No tools available"
|
|
229
|
+
|
|
230
|
+
formatted_parts.append(
|
|
231
|
+
f"<mcp-server name=\"{server_name}\">\n"
|
|
232
|
+
f"<tools>{tools_list}</tools>\n"
|
|
233
|
+
f"<instructions>\n{instructions}\n</instructions>\n"
|
|
234
|
+
f"</mcp-server>"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if formatted_parts:
|
|
238
|
+
return "\n\n".join(formatted_parts)
|
|
239
|
+
return ""
|
|
240
|
+
|
|
177
241
|
async def __call__(
|
|
178
242
|
self,
|
|
179
243
|
message: Union[
|
|
@@ -549,12 +613,20 @@ class McpAgent(ABC, ToolAgent):
|
|
|
549
613
|
namespaced_tool = self._aggregator._namespaced_tool_map.get(tool_name)
|
|
550
614
|
display_tool_name = namespaced_tool.tool.name if namespaced_tool else tool_name
|
|
551
615
|
|
|
616
|
+
# Find the index of the current tool in available_tools for highlighting
|
|
617
|
+
highlight_index = None
|
|
618
|
+
try:
|
|
619
|
+
highlight_index = available_tools.index(display_tool_name)
|
|
620
|
+
except ValueError:
|
|
621
|
+
# Tool not found in list, no highlighting
|
|
622
|
+
pass
|
|
623
|
+
|
|
552
624
|
self.display.show_tool_call(
|
|
553
625
|
name=self._name,
|
|
554
626
|
tool_args=tool_args,
|
|
555
627
|
bottom_items=available_tools,
|
|
556
628
|
tool_name=display_tool_name,
|
|
557
|
-
|
|
629
|
+
highlight_index=highlight_index,
|
|
558
630
|
max_item_length=12,
|
|
559
631
|
)
|
|
560
632
|
|
fast_agent/agents/tool_agent.py
CHANGED
|
@@ -128,11 +128,21 @@ class ToolAgent(LlmAgent):
|
|
|
128
128
|
for correlation_id, tool_request in request.tool_calls.items():
|
|
129
129
|
tool_name = tool_request.params.name
|
|
130
130
|
tool_args = tool_request.params.arguments or {}
|
|
131
|
+
|
|
132
|
+
# Find the index of the current tool in available_tools for highlighting
|
|
133
|
+
highlight_index = None
|
|
134
|
+
try:
|
|
135
|
+
highlight_index = available_tools.index(tool_name)
|
|
136
|
+
except ValueError:
|
|
137
|
+
# Tool not found in list, no highlighting
|
|
138
|
+
pass
|
|
139
|
+
|
|
131
140
|
self.display.show_tool_call(
|
|
132
141
|
name=self.name,
|
|
133
142
|
tool_args=tool_args,
|
|
134
143
|
bottom_items=available_tools,
|
|
135
144
|
tool_name=tool_name,
|
|
145
|
+
highlight_index=highlight_index,
|
|
136
146
|
max_item_length=12,
|
|
137
147
|
)
|
|
138
148
|
|
|
@@ -300,10 +300,18 @@ class RouterAgent(LlmAgent):
|
|
|
300
300
|
if response.reasoning:
|
|
301
301
|
routing_message += f" ({response.reasoning})"
|
|
302
302
|
|
|
303
|
+
# Convert highlight_items to highlight_index
|
|
304
|
+
agent_keys = list(self.agent_map.keys())
|
|
305
|
+
highlight_index = None
|
|
306
|
+
try:
|
|
307
|
+
highlight_index = agent_keys.index(response.agent)
|
|
308
|
+
except ValueError:
|
|
309
|
+
pass
|
|
310
|
+
|
|
303
311
|
await self.display.show_assistant_message(
|
|
304
312
|
routing_message,
|
|
305
|
-
bottom_items=
|
|
306
|
-
|
|
313
|
+
bottom_items=agent_keys,
|
|
314
|
+
highlight_index=highlight_index,
|
|
307
315
|
name=self.name,
|
|
308
316
|
)
|
|
309
317
|
|
fast_agent/cli/commands/auth.py
CHANGED
|
@@ -22,14 +22,28 @@ from fast_agent.ui.console import console
|
|
|
22
22
|
app = typer.Typer(help="Manage OAuth authentication state for MCP servers")
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def
|
|
25
|
+
def _get_keyring_status() -> tuple[str, bool]:
|
|
26
|
+
"""Return (backend_name, usable) where usable=False for the fail backend or missing keyring."""
|
|
26
27
|
try:
|
|
27
28
|
import keyring
|
|
28
29
|
|
|
29
30
|
kr = keyring.get_keyring()
|
|
30
|
-
|
|
31
|
+
name = getattr(kr, "name", kr.__class__.__name__)
|
|
32
|
+
try:
|
|
33
|
+
from keyring.backends.fail import Keyring as FailKeyring # type: ignore
|
|
34
|
+
|
|
35
|
+
return name, not isinstance(kr, FailKeyring)
|
|
36
|
+
except Exception:
|
|
37
|
+
# If fail backend marker cannot be imported, assume usable
|
|
38
|
+
return name, True
|
|
31
39
|
except Exception:
|
|
32
|
-
return "unavailable"
|
|
40
|
+
return "unavailable", False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_keyring_backend_name() -> str:
|
|
44
|
+
# Backwards-compat helper; prefer _get_keyring_status in new code
|
|
45
|
+
name, _ = _get_keyring_status()
|
|
46
|
+
return name
|
|
33
47
|
|
|
34
48
|
|
|
35
49
|
def _keyring_get_password(service: str, username: str) -> str | None:
|
|
@@ -106,7 +120,7 @@ def status(
|
|
|
106
120
|
) -> None:
|
|
107
121
|
"""Show keyring backend and token status for configured MCP servers."""
|
|
108
122
|
settings = get_settings(config_path)
|
|
109
|
-
backend =
|
|
123
|
+
backend, backend_usable = _get_keyring_status()
|
|
110
124
|
|
|
111
125
|
# Single-target view if target provided
|
|
112
126
|
if target:
|
|
@@ -123,12 +137,15 @@ def status(
|
|
|
123
137
|
|
|
124
138
|
# Direct presence check
|
|
125
139
|
present = False
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
if backend_usable:
|
|
141
|
+
try:
|
|
142
|
+
import keyring
|
|
128
143
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
present = (
|
|
145
|
+
keyring.get_password("fast-agent-mcp", f"oauth:tokens:{identity}") is not None
|
|
146
|
+
)
|
|
147
|
+
except Exception:
|
|
148
|
+
present = False
|
|
132
149
|
|
|
133
150
|
table = Table(show_header=True, box=None)
|
|
134
151
|
table.add_column("Identity", header_style="bold")
|
|
@@ -139,7 +156,10 @@ def status(
|
|
|
139
156
|
token_disp = "[bold green]✓[/bold green]" if present else "[dim]✗[/dim]"
|
|
140
157
|
table.add_row(identity, token_disp, servers_for_id)
|
|
141
158
|
|
|
142
|
-
|
|
159
|
+
if backend_usable and backend != "unavailable":
|
|
160
|
+
console.print(f"Keyring backend: [green]{backend}[/green]")
|
|
161
|
+
else:
|
|
162
|
+
console.print("Keyring backend: [red]not available[/red]")
|
|
143
163
|
console.print(table)
|
|
144
164
|
console.print(
|
|
145
165
|
"\n[dim]Run 'fast-agent auth clear --identity "
|
|
@@ -148,7 +168,10 @@ def status(
|
|
|
148
168
|
return
|
|
149
169
|
|
|
150
170
|
# Full status view
|
|
151
|
-
|
|
171
|
+
if backend_usable and backend != "unavailable":
|
|
172
|
+
console.print(f"Keyring backend: [green]{backend}[/green]")
|
|
173
|
+
else:
|
|
174
|
+
console.print("Keyring backend: [red]not available[/red]")
|
|
152
175
|
|
|
153
176
|
tokens = list_keyring_tokens()
|
|
154
177
|
token_table = Table(show_header=True, box=None)
|
|
@@ -181,25 +204,25 @@ def status(
|
|
|
181
204
|
)
|
|
182
205
|
# Direct presence check for each identity so status works even without index
|
|
183
206
|
has_token = False
|
|
207
|
+
token_disp = "[dim]✗[/dim]"
|
|
184
208
|
if persist == "keyring" and row["oauth"]:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
209
|
+
if backend_usable:
|
|
210
|
+
try:
|
|
211
|
+
import keyring
|
|
212
|
+
|
|
213
|
+
has_token = (
|
|
214
|
+
keyring.get_password(
|
|
215
|
+
"fast-agent-mcp", f"oauth:tokens:{row['identity']}"
|
|
216
|
+
)
|
|
217
|
+
is not None
|
|
218
|
+
)
|
|
219
|
+
except Exception:
|
|
220
|
+
has_token = False
|
|
221
|
+
token_disp = "[bold green]✓[/bold green]" if has_token else "[dim]✗[/dim]"
|
|
222
|
+
else:
|
|
223
|
+
token_disp = "[red]not available[/red]"
|
|
224
|
+
elif persist == "memory" and row["oauth"]:
|
|
225
|
+
token_disp = "[yellow]memory[/yellow]"
|
|
203
226
|
map_table.add_row(
|
|
204
227
|
row["name"],
|
|
205
228
|
row["transport"].upper(),
|
|
@@ -305,14 +305,25 @@ def show_check_summary() -> None:
|
|
|
305
305
|
env_table.add_column("Value")
|
|
306
306
|
|
|
307
307
|
# Determine keyring backend early so it can appear in the top section
|
|
308
|
+
# Also detect whether the backend is actually usable (not the fail backend)
|
|
309
|
+
keyring_usable = False
|
|
308
310
|
try:
|
|
309
311
|
import keyring # type: ignore
|
|
310
312
|
|
|
311
313
|
keyring_backend = keyring.get_keyring()
|
|
312
314
|
keyring_name = getattr(keyring_backend, "name", keyring_backend.__class__.__name__)
|
|
315
|
+
try:
|
|
316
|
+
# Detect the "fail" backend explicitly; it's present but unusable
|
|
317
|
+
from keyring.backends.fail import Keyring as FailKeyring # type: ignore
|
|
318
|
+
|
|
319
|
+
keyring_usable = not isinstance(keyring_backend, FailKeyring)
|
|
320
|
+
except Exception:
|
|
321
|
+
# If we can't import the fail backend marker, assume usable
|
|
322
|
+
keyring_usable = True
|
|
313
323
|
except Exception:
|
|
314
324
|
keyring = None # type: ignore
|
|
315
325
|
keyring_name = "unavailable"
|
|
326
|
+
keyring_usable = False
|
|
316
327
|
|
|
317
328
|
# Python info (highlight version and path in green)
|
|
318
329
|
env_table.add_row(
|
|
@@ -345,11 +356,14 @@ def show_check_summary() -> None:
|
|
|
345
356
|
)
|
|
346
357
|
else: # parsed successfully
|
|
347
358
|
env_table.add_row("Config File", f"[green]Found[/green] ({config_path})")
|
|
348
|
-
default_model_value = config_summary.get("default_model", "
|
|
359
|
+
default_model_value = config_summary.get("default_model", "gpt-5-mini.low (system default)")
|
|
349
360
|
env_table.add_row("Default Model", f"[green]{default_model_value}[/green]")
|
|
350
361
|
|
|
351
362
|
# Keyring backend (always shown in application-level settings)
|
|
352
|
-
|
|
363
|
+
if keyring_usable and keyring_name != "unavailable":
|
|
364
|
+
env_table.add_row("Keyring Backend", f"[green]{keyring_name}[/green]")
|
|
365
|
+
else:
|
|
366
|
+
env_table.add_row("Keyring Backend", "[red]not available[/red]")
|
|
353
367
|
|
|
354
368
|
console.print(env_table)
|
|
355
369
|
|
|
@@ -514,7 +528,9 @@ def show_check_summary() -> None:
|
|
|
514
528
|
try:
|
|
515
529
|
cfg = MCPServerSettings(
|
|
516
530
|
name=name,
|
|
517
|
-
transport="sse"
|
|
531
|
+
transport="sse"
|
|
532
|
+
if transport == "SSE"
|
|
533
|
+
else ("stdio" if transport == "STDIO" else "http"),
|
|
518
534
|
url=(server.get("url") or None),
|
|
519
535
|
auth=server.get("auth") if isinstance(server.get("auth"), dict) else None,
|
|
520
536
|
)
|
|
@@ -532,11 +548,16 @@ def show_check_summary() -> None:
|
|
|
532
548
|
persist = "keyring"
|
|
533
549
|
if cfg.auth is not None and hasattr(cfg.auth, "persist"):
|
|
534
550
|
persist = getattr(cfg.auth, "persist") or "keyring"
|
|
535
|
-
if keyring and persist == "keyring" and oauth_enabled:
|
|
551
|
+
if keyring and keyring_usable and persist == "keyring" and oauth_enabled:
|
|
536
552
|
identity = compute_server_identity(cfg)
|
|
537
553
|
tkey = f"oauth:tokens:{identity}"
|
|
538
|
-
|
|
554
|
+
try:
|
|
555
|
+
has = keyring.get_password("fast-agent-mcp", tkey) is not None
|
|
556
|
+
except Exception:
|
|
557
|
+
has = False
|
|
539
558
|
token_status = "[bold green]✓[/bold green]" if has else "[dim]✗[/dim]"
|
|
559
|
+
elif persist == "keyring" and not keyring_usable and oauth_enabled:
|
|
560
|
+
token_status = "[red]not available[/red]"
|
|
540
561
|
elif persist == "memory" and oauth_enabled:
|
|
541
562
|
token_status = "[yellow]memory[/yellow]"
|
|
542
563
|
|
fast_agent/cli/commands/go.py
CHANGED
|
@@ -18,10 +18,16 @@ app = typer.Typer(
|
|
|
18
18
|
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
+
default_instruction = """You are a helpful AI Agent.
|
|
22
|
+
|
|
23
|
+
{{serverInstructions}}
|
|
24
|
+
|
|
25
|
+
The current date is {{currentDate}}."""
|
|
26
|
+
|
|
21
27
|
|
|
22
28
|
async def _run_agent(
|
|
23
29
|
name: str = "fast-agent cli",
|
|
24
|
-
instruction: str =
|
|
30
|
+
instruction: str = default_instruction,
|
|
25
31
|
config_path: Optional[str] = None,
|
|
26
32
|
server_list: Optional[List[str]] = None,
|
|
27
33
|
model: Optional[str] = None,
|
|
@@ -352,7 +358,7 @@ def go(
|
|
|
352
358
|
stdio_commands.append(stdio)
|
|
353
359
|
|
|
354
360
|
# Resolve instruction from file/URL or use default
|
|
355
|
-
resolved_instruction =
|
|
361
|
+
resolved_instruction = default_instruction # Default
|
|
356
362
|
agent_name = "agent"
|
|
357
363
|
|
|
358
364
|
if instruction:
|
fast_agent/cli/commands/setup.py
CHANGED
|
@@ -26,9 +26,7 @@ def load_template_text(filename: str) -> str:
|
|
|
26
26
|
res_name = "pyproject.toml.tmpl"
|
|
27
27
|
else:
|
|
28
28
|
res_name = filename
|
|
29
|
-
resource_path = (
|
|
30
|
-
files("fast_agent").joinpath("resources").joinpath("setup").joinpath(res_name)
|
|
31
|
-
)
|
|
29
|
+
resource_path = files("fast_agent").joinpath("resources").joinpath("setup").joinpath(res_name)
|
|
32
30
|
if resource_path.is_file():
|
|
33
31
|
return resource_path.read_text()
|
|
34
32
|
|
|
@@ -137,9 +135,8 @@ def init(
|
|
|
137
135
|
# Always use latest fast-agent-mcp (no version pin)
|
|
138
136
|
fast_agent_dep = '"fast-agent-mcp"'
|
|
139
137
|
|
|
140
|
-
return (
|
|
141
|
-
|
|
142
|
-
.replace("{{fast_agent_dep}}", fast_agent_dep)
|
|
138
|
+
return template_text.replace("{{python_requires}}", py_req).replace(
|
|
139
|
+
"{{fast_agent_dep}}", fast_agent_dep
|
|
143
140
|
)
|
|
144
141
|
|
|
145
142
|
pyproject_template = load_template_text("pyproject.toml")
|
|
@@ -169,7 +166,7 @@ def init(
|
|
|
169
166
|
"2. Keep fastagent.secrets.yaml secure and never commit it to version control"
|
|
170
167
|
)
|
|
171
168
|
console.print(
|
|
172
|
-
"3. Update fastagent.config.yaml to set a default model (currently system default is '
|
|
169
|
+
"3. Update fastagent.config.yaml to set a default model (currently system default is 'gpt-5-mini.low')"
|
|
173
170
|
)
|
|
174
171
|
console.print("\nTo get started, run:")
|
|
175
172
|
console.print(" uv run agent.py")
|
fast_agent/config.py
CHANGED
|
@@ -37,7 +37,7 @@ class MCPServerAuthSettings(BaseModel):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class MCPSamplingSettings(BaseModel):
|
|
40
|
-
model: str = "
|
|
40
|
+
model: str = "gpt-5-mini.low"
|
|
41
41
|
|
|
42
42
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
43
43
|
|
|
@@ -122,6 +122,9 @@ class MCPServerSettings(BaseModel):
|
|
|
122
122
|
cwd: str | None = None
|
|
123
123
|
"""Working directory for the executed server command."""
|
|
124
124
|
|
|
125
|
+
include_instructions: bool = True
|
|
126
|
+
"""Whether to include this server's instructions in the system prompt (default: True)."""
|
|
127
|
+
|
|
125
128
|
implementation: Implementation | None = None
|
|
126
129
|
|
|
127
130
|
@model_validator(mode="before")
|
fast_agent/mcp/mcp_aggregator.py
CHANGED
|
@@ -461,6 +461,36 @@ class MCPAggregator(ContextDependent):
|
|
|
461
461
|
for server_name in self.server_names:
|
|
462
462
|
await self._refresh_server_tools(server_name)
|
|
463
463
|
|
|
464
|
+
async def get_server_instructions(self) -> Dict[str, tuple[str, List[str]]]:
|
|
465
|
+
"""
|
|
466
|
+
Get instructions from all connected servers along with their tool names.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Dict mapping server name to tuple of (instructions, list of tool names)
|
|
470
|
+
"""
|
|
471
|
+
instructions = {}
|
|
472
|
+
|
|
473
|
+
if self.connection_persistence and hasattr(self, '_persistent_connection_manager'):
|
|
474
|
+
# Get instructions from persistent connections
|
|
475
|
+
for server_name in self.server_names:
|
|
476
|
+
try:
|
|
477
|
+
server_conn = await self._persistent_connection_manager.get_server(
|
|
478
|
+
server_name, client_session_factory=self._create_session_factory(server_name)
|
|
479
|
+
)
|
|
480
|
+
# Always include server, even if no instructions
|
|
481
|
+
# Get tool names for this server
|
|
482
|
+
tool_names = [
|
|
483
|
+
namespaced_tool.tool.name
|
|
484
|
+
for namespaced_tool_name, namespaced_tool in self._namespaced_tool_map.items()
|
|
485
|
+
if namespaced_tool.server_name == server_name
|
|
486
|
+
]
|
|
487
|
+
# Include server even if instructions is None
|
|
488
|
+
instructions[server_name] = (server_conn.server_instructions, tool_names)
|
|
489
|
+
except Exception as e:
|
|
490
|
+
logger.debug(f"Failed to get instructions from server {server_name}: {e}")
|
|
491
|
+
|
|
492
|
+
return instructions
|
|
493
|
+
|
|
464
494
|
async def _execute_on_server(
|
|
465
495
|
self,
|
|
466
496
|
server_name: str,
|
|
@@ -105,6 +105,9 @@ class ServerConnection:
|
|
|
105
105
|
self._error_occurred = False
|
|
106
106
|
self._error_message = None
|
|
107
107
|
|
|
108
|
+
# Server instructions from initialization
|
|
109
|
+
self.server_instructions: str | None = None
|
|
110
|
+
|
|
108
111
|
def is_healthy(self) -> bool:
|
|
109
112
|
"""Check if the server connection is healthy and ready to use."""
|
|
110
113
|
return self.session is not None and not self._error_occurred
|
|
@@ -131,10 +134,20 @@ class ServerConnection:
|
|
|
131
134
|
Initializes the server connection and session.
|
|
132
135
|
Must be called within an async context.
|
|
133
136
|
"""
|
|
134
|
-
|
|
137
|
+
assert self.session, "Session must be created before initialization"
|
|
135
138
|
result = await self.session.initialize()
|
|
136
139
|
|
|
137
140
|
self.server_capabilities = result.capabilities
|
|
141
|
+
|
|
142
|
+
# Store instructions if provided by the server and enabled in config
|
|
143
|
+
if self.server_config.include_instructions:
|
|
144
|
+
self.server_instructions = getattr(result, 'instructions', None)
|
|
145
|
+
if self.server_instructions:
|
|
146
|
+
logger.debug(f"{self.server_name}: Received server instructions", data={"instructions": self.server_instructions})
|
|
147
|
+
else:
|
|
148
|
+
self.server_instructions = None
|
|
149
|
+
logger.debug(f"{self.server_name}: Server instructions disabled by configuration")
|
|
150
|
+
|
|
138
151
|
# If there's an init hook, run it
|
|
139
152
|
|
|
140
153
|
# Now the session is ready for use
|
|
@@ -343,7 +356,9 @@ class MCPConnectionManager(ContextDependent):
|
|
|
343
356
|
def transport_context_factory():
|
|
344
357
|
if config.transport == "stdio":
|
|
345
358
|
if not config.command:
|
|
346
|
-
raise ValueError(
|
|
359
|
+
raise ValueError(
|
|
360
|
+
f"Server '{server_name}' uses stdio transport but no command is specified"
|
|
361
|
+
)
|
|
347
362
|
server_params = StdioServerParameters(
|
|
348
363
|
command=config.command,
|
|
349
364
|
args=config.args if config.args is not None else [],
|
|
@@ -357,7 +372,9 @@ class MCPConnectionManager(ContextDependent):
|
|
|
357
372
|
return _add_none_to_context(stdio_client(server_params, errlog=error_handler))
|
|
358
373
|
elif config.transport == "sse":
|
|
359
374
|
if not config.url:
|
|
360
|
-
raise ValueError(
|
|
375
|
+
raise ValueError(
|
|
376
|
+
f"Server '{server_name}' uses sse transport but no url is specified"
|
|
377
|
+
)
|
|
361
378
|
# Suppress MCP library error spam
|
|
362
379
|
self._suppress_mcp_sse_errors()
|
|
363
380
|
oauth_auth = build_oauth_provider(config)
|
|
@@ -376,7 +393,9 @@ class MCPConnectionManager(ContextDependent):
|
|
|
376
393
|
)
|
|
377
394
|
elif config.transport == "http":
|
|
378
395
|
if not config.url:
|
|
379
|
-
raise ValueError(
|
|
396
|
+
raise ValueError(
|
|
397
|
+
f"Server '{server_name}' uses http transport but no url is specified"
|
|
398
|
+
)
|
|
380
399
|
oauth_auth = build_oauth_provider(config)
|
|
381
400
|
headers = dict(config.headers or {})
|
|
382
401
|
if oauth_auth is not None:
|
fast_agent/mcp/oauth_client.py
CHANGED
|
@@ -109,7 +109,7 @@ class _CallbackHandler(BaseHTTPRequestHandler):
|
|
|
109
109
|
self.send_response(404)
|
|
110
110
|
self.end_headers()
|
|
111
111
|
|
|
112
|
-
def log_message(self,
|
|
112
|
+
def log_message(self, format: str, *args: Any) -> None: # silence default logging
|
|
113
113
|
return
|
|
114
114
|
|
|
115
115
|
|
|
@@ -218,10 +218,33 @@ def keyring_has_token(server_config: MCPServerSettings) -> bool:
|
|
|
218
218
|
return False
|
|
219
219
|
|
|
220
220
|
|
|
221
|
-
async def _print_authorization_link(auth_url: str) -> None:
|
|
222
|
-
"""Emit a clickable authorization link using rich console markup.
|
|
221
|
+
async def _print_authorization_link(auth_url: str, warn_if_no_keyring: bool = False) -> None:
|
|
222
|
+
"""Emit a clickable authorization link using rich console markup.
|
|
223
|
+
|
|
224
|
+
If warn_if_no_keyring is True and the OS keyring backend is unavailable,
|
|
225
|
+
print a warning to indicate tokens won't be persisted.
|
|
226
|
+
"""
|
|
223
227
|
console.console.print("[bold]Open this link to authorize:[/bold]", markup=True)
|
|
224
228
|
console.console.print(f"[link={auth_url}]{auth_url}[/link]")
|
|
229
|
+
if warn_if_no_keyring:
|
|
230
|
+
try:
|
|
231
|
+
import keyring # type: ignore
|
|
232
|
+
|
|
233
|
+
backend = keyring.get_keyring()
|
|
234
|
+
try:
|
|
235
|
+
from keyring.backends.fail import Keyring as FailKeyring # type: ignore
|
|
236
|
+
|
|
237
|
+
if isinstance(backend, FailKeyring):
|
|
238
|
+
console.console.print(
|
|
239
|
+
"[yellow]Warning:[/yellow] Keyring backend not available — tokens will not be persisted."
|
|
240
|
+
)
|
|
241
|
+
except Exception:
|
|
242
|
+
# If we cannot detect the fail backend, do nothing
|
|
243
|
+
pass
|
|
244
|
+
except Exception:
|
|
245
|
+
console.console.print(
|
|
246
|
+
"[yellow]Warning:[/yellow] Keyring backend not available — tokens will not be persisted."
|
|
247
|
+
)
|
|
225
248
|
logger.info("OAuth authorization URL emitted to console")
|
|
226
249
|
|
|
227
250
|
|
|
@@ -283,6 +306,7 @@ class KeyringTokenStorage(TokenStorage):
|
|
|
283
306
|
|
|
284
307
|
# --- Keyring index helpers (to enable cross-platform token enumeration) ---
|
|
285
308
|
|
|
309
|
+
|
|
286
310
|
def _index_username() -> str:
|
|
287
311
|
return "oauth:index"
|
|
288
312
|
|
|
@@ -431,7 +455,11 @@ def build_oauth_provider(server_config: MCPServerSettings) -> OAuthClientProvide
|
|
|
431
455
|
|
|
432
456
|
# Local callback server handler
|
|
433
457
|
async def _redirect_handler(authorization_url: str) -> None:
|
|
434
|
-
|
|
458
|
+
# Warn if persisting to keyring but no backend is available
|
|
459
|
+
await _print_authorization_link(
|
|
460
|
+
authorization_url,
|
|
461
|
+
warn_if_no_keyring=(persist_mode == "keyring"),
|
|
462
|
+
)
|
|
435
463
|
|
|
436
464
|
async def _callback_handler() -> tuple[str, str | None]:
|
|
437
465
|
# Try local HTTP capture first
|
|
@@ -6,8 +6,15 @@ from fast_agent import FastAgent
|
|
|
6
6
|
fast = FastAgent("fast-agent example")
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
default_instruction = """You are a helpful AI Agent.
|
|
10
|
+
|
|
11
|
+
{{serverInstructions}}
|
|
12
|
+
|
|
13
|
+
The current date is {{currentDate}}."""
|
|
14
|
+
|
|
15
|
+
|
|
9
16
|
# Define the agent
|
|
10
|
-
@fast.agent(instruction=
|
|
17
|
+
@fast.agent(instruction=default_instruction)
|
|
11
18
|
async def main():
|
|
12
19
|
# use the --model command line switch or agent arguments to change model
|
|
13
20
|
async with fast.run() as agent:
|
fast_agent/ui/console_display.py
CHANGED
|
@@ -26,6 +26,7 @@ class MessageType(Enum):
|
|
|
26
26
|
|
|
27
27
|
USER = "user"
|
|
28
28
|
ASSISTANT = "assistant"
|
|
29
|
+
SYSTEM = "system"
|
|
29
30
|
TOOL_CALL = "tool_call"
|
|
30
31
|
TOOL_RESULT = "tool_result"
|
|
31
32
|
|
|
@@ -44,6 +45,12 @@ MESSAGE_CONFIGS = {
|
|
|
44
45
|
"arrow_style": "dim green",
|
|
45
46
|
"highlight_color": "bright_green",
|
|
46
47
|
},
|
|
48
|
+
MessageType.SYSTEM: {
|
|
49
|
+
"block_color": "yellow",
|
|
50
|
+
"arrow": "●",
|
|
51
|
+
"arrow_style": "dim yellow",
|
|
52
|
+
"highlight_color": "bright_yellow",
|
|
53
|
+
},
|
|
47
54
|
MessageType.TOOL_CALL: {
|
|
48
55
|
"block_color": "magenta",
|
|
49
56
|
"arrow": "◀",
|
|
@@ -144,7 +151,7 @@ class ConsoleDisplay:
|
|
|
144
151
|
name: str | None = None,
|
|
145
152
|
right_info: str = "",
|
|
146
153
|
bottom_metadata: List[str] | None = None,
|
|
147
|
-
|
|
154
|
+
highlight_index: int | None = None,
|
|
148
155
|
max_item_length: int | None = None,
|
|
149
156
|
is_error: bool = False,
|
|
150
157
|
truncate_content: bool = True,
|
|
@@ -159,7 +166,7 @@ class ConsoleDisplay:
|
|
|
159
166
|
name: Optional name to display (agent name, user name, etc.)
|
|
160
167
|
right_info: Information to display on the right side of the header
|
|
161
168
|
bottom_metadata: Optional list of items for bottom separator
|
|
162
|
-
|
|
169
|
+
highlight_index: Index of item to highlight in bottom metadata (0-based), or None
|
|
163
170
|
max_item_length: Optional max length for bottom metadata items (with ellipsis)
|
|
164
171
|
is_error: For tool results, whether this is an error (uses red color)
|
|
165
172
|
truncate_content: Whether to truncate long content
|
|
@@ -199,16 +206,6 @@ class ConsoleDisplay:
|
|
|
199
206
|
if max_item_length:
|
|
200
207
|
display_items = self._shorten_items(bottom_metadata, max_item_length)
|
|
201
208
|
|
|
202
|
-
# Normalize highlight_items
|
|
203
|
-
if highlight_items is None:
|
|
204
|
-
highlight_items = []
|
|
205
|
-
elif isinstance(highlight_items, str):
|
|
206
|
-
highlight_items = [highlight_items]
|
|
207
|
-
|
|
208
|
-
# Shorten highlight items to match if we shortened display items
|
|
209
|
-
if max_item_length:
|
|
210
|
-
highlight_items = self._shorten_items(highlight_items, max_item_length)
|
|
211
|
-
|
|
212
209
|
# Format the metadata with highlighting, clipped to available width
|
|
213
210
|
# Compute available width for the metadata segment (excluding the fixed prefix/suffix)
|
|
214
211
|
total_width = console.console.size.width
|
|
@@ -220,7 +217,7 @@ class ConsoleDisplay:
|
|
|
220
217
|
|
|
221
218
|
metadata_text = self._format_bottom_metadata(
|
|
222
219
|
display_items,
|
|
223
|
-
|
|
220
|
+
highlight_index,
|
|
224
221
|
config["highlight_color"],
|
|
225
222
|
max_width=available,
|
|
226
223
|
)
|
|
@@ -268,11 +265,11 @@ class ConsoleDisplay:
|
|
|
268
265
|
from fast_agent.mcp.helpers.content_helpers import get_text, is_text_content
|
|
269
266
|
|
|
270
267
|
# Determine the style based on message type
|
|
271
|
-
# USER and
|
|
268
|
+
# USER, ASSISTANT, and SYSTEM messages should display in normal style
|
|
272
269
|
# TOOL_CALL and TOOL_RESULT should be dimmed
|
|
273
270
|
if is_error:
|
|
274
271
|
style = "dim red"
|
|
275
|
-
elif message_type in [MessageType.USER, MessageType.ASSISTANT]:
|
|
272
|
+
elif message_type in [MessageType.USER, MessageType.ASSISTANT, MessageType.SYSTEM]:
|
|
276
273
|
style = None # No style means default/normal white
|
|
277
274
|
else:
|
|
278
275
|
style = "dim"
|
|
@@ -464,7 +461,7 @@ class ConsoleDisplay:
|
|
|
464
461
|
def _format_bottom_metadata(
|
|
465
462
|
self,
|
|
466
463
|
items: List[str],
|
|
467
|
-
|
|
464
|
+
highlight_index: int | None,
|
|
468
465
|
highlight_color: str,
|
|
469
466
|
max_width: int | None = None,
|
|
470
467
|
) -> Text:
|
|
@@ -473,8 +470,9 @@ class ConsoleDisplay:
|
|
|
473
470
|
|
|
474
471
|
Args:
|
|
475
472
|
items: List of items to display
|
|
476
|
-
|
|
473
|
+
highlight_index: Index of item to highlight (0-based), or None for no highlighting
|
|
477
474
|
highlight_color: Color to use for highlighting
|
|
475
|
+
max_width: Maximum width for the formatted text
|
|
478
476
|
|
|
479
477
|
Returns:
|
|
480
478
|
Formatted Text object with proper separators and highlighting
|
|
@@ -491,14 +489,7 @@ class ConsoleDisplay:
|
|
|
491
489
|
sep = Text(" | ", style="dim") if i > 0 else Text("")
|
|
492
490
|
|
|
493
491
|
# Prepare item text with potential highlighting
|
|
494
|
-
should_highlight =
|
|
495
|
-
if item in highlight_items:
|
|
496
|
-
should_highlight = True
|
|
497
|
-
else:
|
|
498
|
-
for highlight in highlight_items:
|
|
499
|
-
if item.startswith(highlight) or highlight.endswith(item):
|
|
500
|
-
should_highlight = True
|
|
501
|
-
break
|
|
492
|
+
should_highlight = highlight_index is not None and i == highlight_index
|
|
502
493
|
|
|
503
494
|
item_text = Text(item, style=(highlight_color if should_highlight else "dim"))
|
|
504
495
|
|
|
@@ -569,7 +560,7 @@ class ConsoleDisplay:
|
|
|
569
560
|
tool_name: str,
|
|
570
561
|
tool_args: Dict[str, Any] | None,
|
|
571
562
|
bottom_items: List[str] | None = None,
|
|
572
|
-
|
|
563
|
+
highlight_index: int | None = None,
|
|
573
564
|
max_item_length: int | None = None,
|
|
574
565
|
name: str | None = None,
|
|
575
566
|
) -> None:
|
|
@@ -579,7 +570,7 @@ class ConsoleDisplay:
|
|
|
579
570
|
tool_name: Name of the tool being called
|
|
580
571
|
tool_args: Arguments being passed to the tool
|
|
581
572
|
bottom_items: Optional list of items for bottom separator (e.g., available tools)
|
|
582
|
-
|
|
573
|
+
highlight_index: Index of item to highlight in the bottom separator (0-based), or None
|
|
583
574
|
max_item_length: Optional max length for bottom items (with ellipsis)
|
|
584
575
|
name: Optional agent name
|
|
585
576
|
"""
|
|
@@ -596,7 +587,7 @@ class ConsoleDisplay:
|
|
|
596
587
|
name=name,
|
|
597
588
|
right_info=right_info,
|
|
598
589
|
bottom_metadata=bottom_items,
|
|
599
|
-
|
|
590
|
+
highlight_index=highlight_index,
|
|
600
591
|
max_item_length=max_item_length,
|
|
601
592
|
truncate_content=True,
|
|
602
593
|
)
|
|
@@ -683,7 +674,7 @@ class ConsoleDisplay:
|
|
|
683
674
|
self,
|
|
684
675
|
message_text: Union[str, Text, "PromptMessageExtended"],
|
|
685
676
|
bottom_items: List[str] | None = None,
|
|
686
|
-
|
|
677
|
+
highlight_index: int | None = None,
|
|
687
678
|
max_item_length: int | None = None,
|
|
688
679
|
name: str | None = None,
|
|
689
680
|
model: str | None = None,
|
|
@@ -694,7 +685,7 @@ class ConsoleDisplay:
|
|
|
694
685
|
Args:
|
|
695
686
|
message_text: The message content to display (str, Text, or PromptMessageExtended)
|
|
696
687
|
bottom_items: Optional list of items for bottom separator (e.g., servers, destinations)
|
|
697
|
-
|
|
688
|
+
highlight_index: Index of item to highlight in the bottom separator (0-based), or None
|
|
698
689
|
max_item_length: Optional max length for bottom items (with ellipsis)
|
|
699
690
|
title: Title for the message (default "ASSISTANT")
|
|
700
691
|
name: Optional agent name
|
|
@@ -722,7 +713,7 @@ class ConsoleDisplay:
|
|
|
722
713
|
name=name,
|
|
723
714
|
right_info=right_info,
|
|
724
715
|
bottom_metadata=bottom_items,
|
|
725
|
-
|
|
716
|
+
highlight_index=highlight_index,
|
|
726
717
|
max_item_length=max_item_length,
|
|
727
718
|
truncate_content=False, # Assistant messages shouldn't be truncated
|
|
728
719
|
additional_message=additional_message,
|
|
@@ -816,6 +807,32 @@ class ConsoleDisplay:
|
|
|
816
807
|
truncate_content=False, # User messages typically shouldn't be truncated
|
|
817
808
|
)
|
|
818
809
|
|
|
810
|
+
def show_system_message(
|
|
811
|
+
self,
|
|
812
|
+
system_prompt: str,
|
|
813
|
+
agent_name: str | None = None,
|
|
814
|
+
server_count: int = 0,
|
|
815
|
+
) -> None:
|
|
816
|
+
"""Display the system prompt in a formatted panel."""
|
|
817
|
+
if not self.config or not self.config.logger.show_chat:
|
|
818
|
+
return
|
|
819
|
+
|
|
820
|
+
# Build right side info
|
|
821
|
+
right_parts = []
|
|
822
|
+
if server_count > 0:
|
|
823
|
+
server_word = "server" if server_count == 1 else "servers"
|
|
824
|
+
right_parts.append(f"{server_count} MCP {server_word}")
|
|
825
|
+
|
|
826
|
+
right_info = f"[dim]{' '.join(right_parts)}[/dim]" if right_parts else ""
|
|
827
|
+
|
|
828
|
+
self.display_message(
|
|
829
|
+
content=system_prompt,
|
|
830
|
+
message_type=MessageType.SYSTEM,
|
|
831
|
+
name=agent_name,
|
|
832
|
+
right_info=right_info,
|
|
833
|
+
truncate_content=False, # Don't truncate system prompts
|
|
834
|
+
)
|
|
835
|
+
|
|
819
836
|
async def show_prompt_loaded(
|
|
820
837
|
self,
|
|
821
838
|
prompt_name: str,
|
fast_agent/ui/enhanced_prompt.py
CHANGED
|
@@ -297,6 +297,7 @@ class AgentCompleter(Completer):
|
|
|
297
297
|
"tools": "List available MCP tools",
|
|
298
298
|
"prompt": "List and choose MCP prompts, or apply specific prompt (/prompt <name>)",
|
|
299
299
|
"agents": "List available agents",
|
|
300
|
+
"system": "Show the current system prompt",
|
|
300
301
|
"usage": "Show current usage statistics",
|
|
301
302
|
"markdown": "Show last assistant message without markdown formatting",
|
|
302
303
|
"save_history": "Save history; .json = MCP JSON, others = Markdown",
|
|
@@ -751,6 +752,8 @@ async def get_enhanced_input(
|
|
|
751
752
|
return "CLEAR"
|
|
752
753
|
elif cmd == "agents":
|
|
753
754
|
return "LIST_AGENTS"
|
|
755
|
+
elif cmd == "system":
|
|
756
|
+
return "SHOW_SYSTEM"
|
|
754
757
|
elif cmd == "usage":
|
|
755
758
|
return "SHOW_USAGE"
|
|
756
759
|
elif cmd == "markdown":
|
|
@@ -933,6 +936,7 @@ async def handle_special_commands(command, agent_app=None):
|
|
|
933
936
|
rich_print(" /help - Show this help")
|
|
934
937
|
rich_print(" /clear - Clear screen")
|
|
935
938
|
rich_print(" /agents - List available agents")
|
|
939
|
+
rich_print(" /system - Show the current system prompt")
|
|
936
940
|
rich_print(" /prompt <name> - Apply a specific prompt by name")
|
|
937
941
|
rich_print(" /usage - Show current usage statistics")
|
|
938
942
|
rich_print(" /markdown - Show last assistant message without markdown formatting")
|
|
@@ -972,6 +976,10 @@ async def handle_special_commands(command, agent_app=None):
|
|
|
972
976
|
# Return a dictionary to signal that usage should be shown
|
|
973
977
|
return {"show_usage": True}
|
|
974
978
|
|
|
979
|
+
elif command == "SHOW_SYSTEM":
|
|
980
|
+
# Return a dictionary to signal that system prompt should be shown
|
|
981
|
+
return {"show_system": True}
|
|
982
|
+
|
|
975
983
|
elif command == "MARKDOWN":
|
|
976
984
|
# Return a dictionary to signal that markdown display should be shown
|
|
977
985
|
return {"show_markdown": True}
|
|
@@ -197,6 +197,10 @@ class InteractivePrompt:
|
|
|
197
197
|
# Handle usage display
|
|
198
198
|
await self._show_usage(prompt_provider, agent)
|
|
199
199
|
continue
|
|
200
|
+
elif "show_system" in command_result:
|
|
201
|
+
# Handle system prompt display
|
|
202
|
+
await self._show_system(prompt_provider, agent)
|
|
203
|
+
continue
|
|
200
204
|
elif "show_markdown" in command_result:
|
|
201
205
|
# Handle markdown display
|
|
202
206
|
await self._show_markdown(prompt_provider, agent)
|
|
@@ -926,6 +930,56 @@ class InteractivePrompt:
|
|
|
926
930
|
except Exception as e:
|
|
927
931
|
rich_print(f"[red]Error showing usage: {e}[/red]")
|
|
928
932
|
|
|
933
|
+
async def _show_system(self, prompt_provider: PromptProvider, agent_name: str) -> None:
|
|
934
|
+
"""
|
|
935
|
+
Show the current system prompt for the agent.
|
|
936
|
+
|
|
937
|
+
Args:
|
|
938
|
+
prompt_provider: Provider that has access to agents
|
|
939
|
+
agent_name: Name of the current agent
|
|
940
|
+
"""
|
|
941
|
+
try:
|
|
942
|
+
# Get agent to display from
|
|
943
|
+
assert hasattr(prompt_provider, "_agent"), (
|
|
944
|
+
"Interactive prompt expects an AgentApp with _agent()"
|
|
945
|
+
)
|
|
946
|
+
agent = prompt_provider._agent(agent_name)
|
|
947
|
+
|
|
948
|
+
# Get the system prompt
|
|
949
|
+
system_prompt = getattr(agent, 'instruction', None)
|
|
950
|
+
if not system_prompt:
|
|
951
|
+
rich_print("[yellow]No system prompt available[/yellow]")
|
|
952
|
+
return
|
|
953
|
+
|
|
954
|
+
# Get server count for display
|
|
955
|
+
server_count = 0
|
|
956
|
+
if hasattr(agent, "_aggregator") and hasattr(agent._aggregator, "server_names"):
|
|
957
|
+
server_count = (
|
|
958
|
+
len(agent._aggregator.server_names) if agent._aggregator.server_names else 0
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
# Use the display utility to show the system prompt
|
|
962
|
+
if hasattr(agent, 'display') and agent.display:
|
|
963
|
+
agent.display.show_system_message(
|
|
964
|
+
system_prompt=system_prompt,
|
|
965
|
+
agent_name=agent_name,
|
|
966
|
+
server_count=server_count
|
|
967
|
+
)
|
|
968
|
+
else:
|
|
969
|
+
# Fallback to basic display
|
|
970
|
+
from fast_agent.ui.console_display import ConsoleDisplay
|
|
971
|
+
display = ConsoleDisplay(config=agent.context.config if hasattr(agent, 'context') else None)
|
|
972
|
+
display.show_system_message(
|
|
973
|
+
system_prompt=system_prompt,
|
|
974
|
+
agent_name=agent_name,
|
|
975
|
+
server_count=server_count
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
except Exception as e:
|
|
979
|
+
import traceback
|
|
980
|
+
rich_print(f"[red]Error showing system prompt: {e}[/red]")
|
|
981
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
982
|
+
|
|
929
983
|
async def _show_markdown(self, prompt_provider: PromptProvider, agent_name: str) -> None:
|
|
930
984
|
"""
|
|
931
985
|
Show the last assistant message without markdown formatting.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
fast_agent/__init__.py,sha256=zPSMngBIvbBYyu3jxZp-edxfOBWxCX7YlceMdlqvt9I,3795
|
|
2
|
-
fast_agent/config.py,sha256=
|
|
2
|
+
fast_agent/config.py,sha256=5glgvbPcPZFGG6UG_P8hByryPrteqrYg4pUCep4x6s8,22509
|
|
3
3
|
fast_agent/constants.py,sha256=d5EMSO2msRkjRFz8MgnnhpMnO3woqKP0EcQ4b_yI60w,235
|
|
4
4
|
fast_agent/context.py,sha256=nBelOqehSH91z3aG2nYhwETP-biRzz-iuA2fqmKdHP8,7700
|
|
5
5
|
fast_agent/context_dependent.py,sha256=KU1eydVBoIt4bYOZroqxDgE1AUexDaZi7hurE26QsF4,1584
|
|
@@ -9,28 +9,28 @@ fast_agent/mcp_server_registry.py,sha256=TDCNpQIehsh1PK4y7AWp_rkQMcwYx7FaouUbK3k
|
|
|
9
9
|
fast_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
fast_agent/agents/__init__.py,sha256=WfgtR9MgmJUJI7rb-CUH_10s7308LjxYIYzJRIBZ_9Y,2644
|
|
11
11
|
fast_agent/agents/agent_types.py,sha256=ugolD3lC2KxSfBYjjRf9HjNATZaFx1o4Fhjb15huSgk,1776
|
|
12
|
-
fast_agent/agents/llm_agent.py,sha256=
|
|
12
|
+
fast_agent/agents/llm_agent.py,sha256=deb6cOLKzebW3mZJ3uyingwIFIwVbTpVVvTV2HGel9g,8884
|
|
13
13
|
fast_agent/agents/llm_decorator.py,sha256=IhvMOZVjo1qT4GII0KP000VYTCprzBQBGZPbZwBIEd0,16485
|
|
14
|
-
fast_agent/agents/mcp_agent.py,sha256=
|
|
15
|
-
fast_agent/agents/tool_agent.py,sha256=
|
|
14
|
+
fast_agent/agents/mcp_agent.py,sha256=Zr3XxhUKxcGMSiawgQHLpajJtsc2CJYKUe9IUzNmYkQ,38115
|
|
15
|
+
fast_agent/agents/tool_agent.py,sha256=HAqNCftDlzjDuQGJjvsR4ZEmDX4aLG6YoaJ9ZGfXCBs,7082
|
|
16
16
|
fast_agent/agents/workflow/chain_agent.py,sha256=Pd8dOH_YdKu3LXsKa4fwqzY_B2qVuhzdfCUiKi5v17s,6293
|
|
17
17
|
fast_agent/agents/workflow/evaluator_optimizer.py,sha256=rhzazy8Aj-ydId6kmBC77TmtYZ5mirSe7eV6PPMWkBA,12040
|
|
18
18
|
fast_agent/agents/workflow/iterative_planner.py,sha256=CTtDpK-YGrFFZMQQmFeE-2I_9-cZv23pNwUoh8w5voA,20478
|
|
19
19
|
fast_agent/agents/workflow/orchestrator_models.py,sha256=VNnnVegnjimgiuL8ZhxkBPhg8tjbeJRGq2JAIl0FAbU,7216
|
|
20
20
|
fast_agent/agents/workflow/orchestrator_prompts.py,sha256=EXKEI174sshkZyPPEnWbwwNafzSPuA39MXL7iqG9cWc,9106
|
|
21
21
|
fast_agent/agents/workflow/parallel_agent.py,sha256=DlJXDURAfx-WBF297tKBLfH93gDFSAPUyEmJr7QNGyw,7476
|
|
22
|
-
fast_agent/agents/workflow/router_agent.py,sha256=
|
|
22
|
+
fast_agent/agents/workflow/router_agent.py,sha256=4jL7AM9FcOvsuf0BSWs-r17xa9vMml0BuF89PiFeVQs,11345
|
|
23
23
|
fast_agent/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
fast_agent/cli/__main__.py,sha256=iF9mNX1ja9Ea9T6gzNg9nfYAuei_vRowfHpUPiFbJVI,1260
|
|
25
25
|
fast_agent/cli/constants.py,sha256=flYDsml3_8wVcGg7T1t5mPFT9CC1M-XMjBigXjX6PuI,559
|
|
26
26
|
fast_agent/cli/main.py,sha256=tERbfQjure3kgKYqzZCNDSLyiVZGXCH2coZ8YBwvq64,4398
|
|
27
27
|
fast_agent/cli/terminal.py,sha256=tDN1fJ91Nc_wZJTNafkQuD7Z7gFscvo1PHh-t7Wl-5s,1066
|
|
28
|
-
fast_agent/cli/commands/auth.py,sha256=
|
|
29
|
-
fast_agent/cli/commands/check_config.py,sha256=
|
|
30
|
-
fast_agent/cli/commands/go.py,sha256=
|
|
28
|
+
fast_agent/cli/commands/auth.py,sha256=JlRT1iHDgF0fPmkbaoiLTm2cTO-qI9KZtrO3wppIVcg,14639
|
|
29
|
+
fast_agent/cli/commands/check_config.py,sha256=D0emHGl6yc9XRq-HjAwS4KfS7a9r5gkA55MyiIRptGQ,26638
|
|
30
|
+
fast_agent/cli/commands/go.py,sha256=yI-VAc2FVm5S5kCP_-i4cVCkwprz0zt_l6KyBam6V7U,15178
|
|
31
31
|
fast_agent/cli/commands/quickstart.py,sha256=CiSen7VxmKDy7apJZ8rAyC33-BIo0zsUt8cBnEtxg0Y,21241
|
|
32
32
|
fast_agent/cli/commands/server_helpers.py,sha256=Nuded8sZb4Rybwoq5LbXXUgwtJZg-OO04xhmPUp6e98,4073
|
|
33
|
-
fast_agent/cli/commands/setup.py,sha256=
|
|
33
|
+
fast_agent/cli/commands/setup.py,sha256=n5hVjXkKTmuiW8-0ezItVcMHJ92W6NlE2JOGCYiKw0A,6388
|
|
34
34
|
fast_agent/cli/commands/url_parser.py,sha256=v9KoprPBEEST5Fo7qXgbW50GC5vMpxFteKqAT6mFkdI,5991
|
|
35
35
|
fast_agent/core/__init__.py,sha256=n2ly7SBtFko9H2DPVh8cSqfw9chtiUaokxLmiDpaMyU,2233
|
|
36
36
|
fast_agent/core/agent_app.py,sha256=cdzNwpb--SUNYbhkUX6RolqVnxJ5WSchxw5I4gFrvpk,16836
|
|
@@ -104,11 +104,11 @@ 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=XRCliiYMxbCbkqS2VSrw7tAtnkLIP9HSRpSWaXW02MQ,13663
|
|
107
|
-
fast_agent/mcp/mcp_aggregator.py,sha256=
|
|
108
|
-
fast_agent/mcp/mcp_connection_manager.py,sha256=
|
|
107
|
+
fast_agent/mcp/mcp_aggregator.py,sha256=rrFzPV3keHJkc04Y_6x4S0ZNmhjwdp6jfE0dflHGpXY,54655
|
|
108
|
+
fast_agent/mcp/mcp_connection_manager.py,sha256=uhQqbOP2_LHDMEgrh1-yvGpueloYVbylvAj-pjYCboA,20247
|
|
109
109
|
fast_agent/mcp/mcp_content.py,sha256=F9bgJ57EO9sgWg1m-eTNM6xd9js79mHKf4e9O8K8jrI,8829
|
|
110
110
|
fast_agent/mcp/mime_utils.py,sha256=D6YXNdZJ351BjacSW5o0sVF_hrWuRHD6UyWS4TDlLZI,2915
|
|
111
|
-
fast_agent/mcp/oauth_client.py,sha256=
|
|
111
|
+
fast_agent/mcp/oauth_client.py,sha256=3shN3iwsJNXrk7nbcfUgrzNos3i2RuMuLXA80nR8r6Y,17104
|
|
112
112
|
fast_agent/mcp/prompt.py,sha256=U3jAIjGyGqCpS96zKf0idC7PI4YdFHq-3qjpCsEybxQ,5983
|
|
113
113
|
fast_agent/mcp/prompt_message_extended.py,sha256=-sw6FITvN0rTeIVWDUslNTPYyFEieToydOAf1-phQLs,4650
|
|
114
114
|
fast_agent/mcp/prompt_render.py,sha256=AqDaQqM6kqciV9X79S5rsRr3VjcQ_2JOiLaHqpRzsS4,2888
|
|
@@ -178,28 +178,28 @@ fast_agent/resources/examples/workflows/parallel.py,sha256=kROiRueTm0Wql4EWVjFSy
|
|
|
178
178
|
fast_agent/resources/examples/workflows/router.py,sha256=lxMxz6g0_XjYnwzEwKdl9l2hxRsD4PSsaIlhH5nLuQY,2075
|
|
179
179
|
fast_agent/resources/examples/workflows/short_story.md,sha256=XN9I2kzCcMmke3dE5F2lyRH5iFUZUQ8Sy-hS3rm_Wlc,1153
|
|
180
180
|
fast_agent/resources/examples/workflows/short_story.txt,sha256=X3y_1AyhLFN2AKzCKvucJtDgAFIJfnlbsbGZO5bBWu0,1187
|
|
181
|
-
fast_agent/resources/setup/.gitignore,sha256=
|
|
182
|
-
fast_agent/resources/setup/agent.py,sha256=
|
|
181
|
+
fast_agent/resources/setup/.gitignore,sha256=bksf0bkvBXtm3F5Nd4F9FB2LaO2RcTbHujYWjq5JKPo,305
|
|
182
|
+
fast_agent/resources/setup/agent.py,sha256=IlZecsc9vTtH2rj9BFF4M6BUgClg5fQ2HasYV1PPIHA,518
|
|
183
183
|
fast_agent/resources/setup/fastagent.config.yaml,sha256=GoXfrLXolMjl9oqam5lodzm40OmZDOkng99BSSP26pY,1446
|
|
184
184
|
fast_agent/resources/setup/fastagent.secrets.yaml.example,sha256=ht-i2_SpAyeXG2OnG_vOA1n7gRsGIInxp9g5Nio-jpI,1038
|
|
185
|
-
fast_agent/resources/setup/pyproject.toml.tmpl,sha256=
|
|
185
|
+
fast_agent/resources/setup/pyproject.toml.tmpl,sha256=SxyVPXbtD67yFOx9wIrzq6Yxo4W2PkSR1rrnPkmpjwA,478
|
|
186
186
|
fast_agent/tools/elicitation.py,sha256=8FaNvuN__LAM328VSJ5T4Bg3m8auHraqYvIYv6Eh4KU,13464
|
|
187
187
|
fast_agent/types/__init__.py,sha256=IpUWlzx3u_3h99F2IKJ8aPjyGZXguOwkOVJKGrst6hg,951
|
|
188
188
|
fast_agent/types/llm_stop_reason.py,sha256=bWe97OfhALUe8uQeAQOnTdPlYzJiabIfo8u38kPgj3Q,2293
|
|
189
189
|
fast_agent/ui/__init__.py,sha256=MXxTQjFdF7mI_3JHxBPd-aoZYLlxV_-51-Trqgv5-3w,1104
|
|
190
190
|
fast_agent/ui/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
|
191
|
-
fast_agent/ui/console_display.py,sha256=
|
|
191
|
+
fast_agent/ui/console_display.py,sha256=gzSehwK-6b5Y1amflnhocTWtuRtIjoS990qChR6O8oA,40922
|
|
192
192
|
fast_agent/ui/elicitation_form.py,sha256=t3UhBG44YmxTLu1RjCnHwW36eQQaroE45CiBGJB2czg,29410
|
|
193
193
|
fast_agent/ui/elicitation_style.py,sha256=rtZiJH4CwTdkDLSzDDvThlZyIyuRX0oVNzscKiHvry8,3835
|
|
194
|
-
fast_agent/ui/enhanced_prompt.py,sha256=
|
|
195
|
-
fast_agent/ui/interactive_prompt.py,sha256=
|
|
194
|
+
fast_agent/ui/enhanced_prompt.py,sha256=a7XYq8sef7PoIBW0o40ihzumTENp1nr2jFsG8s2R6D4,40427
|
|
195
|
+
fast_agent/ui/interactive_prompt.py,sha256=ZW-bCFtsJoD8InWHgJ2ibTK9OjhaA-39-RYIocDV9XY,45516
|
|
196
196
|
fast_agent/ui/mcp_ui_utils.py,sha256=hV7z-yHX86BgdH6CMmN5qyOUjyiegQXLJOa5n5A1vQs,8476
|
|
197
197
|
fast_agent/ui/mermaid_utils.py,sha256=MpcRyVCPMTwU1XeIxnyFg0fQLjcyXZduWRF8NhEqvXE,5332
|
|
198
198
|
fast_agent/ui/progress_display.py,sha256=hajDob65PttiJ2mPS6FsCtnmTcnyvDWGn-UqQboXqkQ,361
|
|
199
199
|
fast_agent/ui/rich_progress.py,sha256=jy6VuUOYFkWXdyvnRTSBPAmmNAP6TJDFw_lgbt_YYLo,7548
|
|
200
200
|
fast_agent/ui/usage_display.py,sha256=ltJpn_sDzo8PDNSXWx-QdEUbQWUnhmajCItNt5mA5rM,7285
|
|
201
|
-
fast_agent_mcp-0.3.
|
|
202
|
-
fast_agent_mcp-0.3.
|
|
203
|
-
fast_agent_mcp-0.3.
|
|
204
|
-
fast_agent_mcp-0.3.
|
|
205
|
-
fast_agent_mcp-0.3.
|
|
201
|
+
fast_agent_mcp-0.3.6.dist-info/METADATA,sha256=BqwJzI5karZB_iUusd69TKvh1Rg4Ja813Lb_E6biljM,31696
|
|
202
|
+
fast_agent_mcp-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
203
|
+
fast_agent_mcp-0.3.6.dist-info/entry_points.txt,sha256=i6Ujja9J-hRxttOKqTYdbYP_tyaS4gLHg53vupoCSsg,199
|
|
204
|
+
fast_agent_mcp-0.3.6.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
|
|
205
|
+
fast_agent_mcp-0.3.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|