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.

@@ -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
- highlight_items=highlight_items,
157
+ highlight_index=highlight_index,
144
158
  max_item_length=max_item_length,
145
159
  name=display_name,
146
160
  model=display_model,
@@ -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
- highlight_items=tool_name,
629
+ highlight_index=highlight_index,
558
630
  max_item_length=12,
559
631
  )
560
632
 
@@ -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=list(self.agent_map.keys()),
306
- highlight_items=[response.agent],
313
+ bottom_items=agent_keys,
314
+ highlight_index=highlight_index,
307
315
  name=self.name,
308
316
  )
309
317
 
@@ -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 _get_keyring_backend_name() -> str:
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
- return getattr(kr, "name", kr.__class__.__name__)
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 = _get_keyring_backend_name()
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
- try:
127
- import keyring
140
+ if backend_usable:
141
+ try:
142
+ import keyring
128
143
 
129
- present = keyring.get_password("fast-agent-mcp", f"oauth:tokens:{identity}") is not None
130
- except Exception:
131
- present = False
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
- console.print(f"Keyring backend: [green]{backend}[/green]")
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
- console.print(f"Keyring backend: [green]{backend}[/green]")
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
- try:
186
- import keyring
187
-
188
- has_token = (
189
- keyring.get_password("fast-agent-mcp", f"oauth:tokens:{row['identity']}")
190
- is not None
191
- )
192
- except Exception:
193
- has_token = False
194
- token_disp = (
195
- "[bold green]✓[/bold green]"
196
- if has_token
197
- else (
198
- "[yellow]memory[/yellow]"
199
- if persist == "memory" and row["oauth"]
200
- else "[dim]✗[/dim]"
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", "haiku (system default)")
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
- env_table.add_row("Keyring Backend", f"[green]{keyring_name}[/green]")
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" if transport == "SSE" else ("stdio" if transport == "STDIO" else "http"),
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
- has = keyring.get_password("fast-agent-mcp", tkey) is not None
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
 
@@ -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 = "You are a helpful AI Agent.",
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 = "You are a helpful AI Agent." # Default
361
+ resolved_instruction = default_instruction # Default
356
362
  agent_name = "agent"
357
363
 
358
364
  if instruction:
@@ -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
- template_text.replace("{{python_requires}}", py_req)
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 'haiku')"
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 = "haiku"
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")
@@ -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(f"Server '{server_name}' uses stdio transport but no command is specified")
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(f"Server '{server_name}' uses sse transport but no url is specified")
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(f"Server '{server_name}' uses http transport but no url is specified")
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:
@@ -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, fmt: str, *args: Any) -> None: # silence default logging
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
- await _print_authorization_link(authorization_url)
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
@@ -22,3 +22,9 @@ fastagent.jsonl
22
22
  # Editors (optional)
23
23
  .idea/
24
24
  .vscode/
25
+
26
+ # Packaging metadata
27
+ *.dist-info/
28
+ *.egg-info/
29
+ dist/
30
+ build/
@@ -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="You are a helpful AI Agent")
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:
@@ -15,3 +15,9 @@ package = true
15
15
  [project.scripts]
16
16
  fast-agent-app = "agent:main"
17
17
 
18
+ [build-system]
19
+ requires = ["setuptools>=64", "wheel"]
20
+ build-backend = "setuptools.build_meta"
21
+
22
+ [tool.setuptools]
23
+ py-modules = ["agent"]
@@ -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
- highlight_items: str | List[str] | None = None,
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
- highlight_items: Item(s) to highlight in bottom metadata
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
- highlight_items,
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 ASSISTANT messages should display in normal style
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
- highlight_items: List[str],
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
- highlight_items: List of items to highlight
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 = False
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
- highlight_items: str | List[str] | None = None,
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
- highlight_items: Item(s) to highlight in the bottom separator
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
- highlight_items=tool_name,
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
- highlight_items: str | List[str] | None = None,
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
- highlight_items: Item(s) to highlight in the bottom separator
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
- highlight_items=highlight_items,
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,
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fast-agent-mcp
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Define, Prompt and Test MCP enabled Agents and Workflows
5
5
  Author-email: Shaun Smith <fastagent@llmindset.co.uk>
6
6
  License: Apache License
@@ -1,5 +1,5 @@
1
1
  fast_agent/__init__.py,sha256=zPSMngBIvbBYyu3jxZp-edxfOBWxCX7YlceMdlqvt9I,3795
2
- fast_agent/config.py,sha256=f_43QrqB83-Q4ltkGUSiHLyiDcyzWAI9bhLx2Ocxn5E,22367
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=iuy3kO5Q-f9bm3tjYLG2Wurd0gK2sLhTfTkSnfVWzSw,8311
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=BMSTcGd9_SdoyEGNGVAWXu8ubaE1TAeAMl-2c_wymQU,35240
15
- fast_agent/agents/tool_agent.py,sha256=owGcc1an9P8jb8ZfNYjCsLK6lv85IL7friWcSbZxMIo,6717
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=KWLOZMFI4YPn0fqzR001qLFbOe7DYxx-E2c0BgIgO-g,11081
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=qLCuXt27i3v3KVLRCAsy1iK9KnU05nJjpsQOXDaoSrA,13547
29
- fast_agent/cli/commands/check_config.py,sha256=23HahZ9ANyi1wtSN9Fp1ACHhpcAhjxIiSIT1bhwHYPQ,25628
30
- fast_agent/cli/commands/go.py,sha256=o9fT8161dDtLP2F0gWrndzitlPO7q7zvdeRoqs0PavE,15079
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=SEp2omVBNJcfueqqr_ru-ulewepJZ-vjO2mHf7tVqJg,6410
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=5J1QBGJGZMW7QlqKCWk77_THEzKwiTzKYsqaO_DrTTw,53180
108
- fast_agent/mcp/mcp_connection_manager.py,sha256=vL5JiqSf21sqz9CLX8Ljp-49lDbsRjuG1Rw3hz70fCI,19397
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=O9U0n_UA-ZNDG0IJSvK4sq_joZCrSIeUzTM1iEN8BlU,16003
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=UQKYvhK6samGg5JZg613XiuGUkUAz2qzE1WfFa8J0mQ,245
182
- fast_agent/resources/setup/agent.py,sha256=cRQfWFkWJxF1TkWASXf8nuHbIDUgyk5o23vyoroSBOY,407
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=b_dL9TBAzA4sPOkVZd3psMKFT-g_gef0n2NcANojEqs,343
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=x_0pmQgMAasfUfv_MwoGJyXSwT7y-BxPGWJWGblSSJQ,40367
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=w99sJkdAn2WVtIT9hLumhz1DQY-H9a0Qq80JSM0jRg8,40080
195
- fast_agent/ui/interactive_prompt.py,sha256=lxY26PNBNihtccO4iI7CxIag_9CxEcbajbIPdzqnEcM,43230
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.5.dist-info/METADATA,sha256=DuTTbjbnDeBMTx7zweiM7QU1T0kfdZ_Jqpf7E99Hx4k,31696
202
- fast_agent_mcp-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
203
- fast_agent_mcp-0.3.5.dist-info/entry_points.txt,sha256=i6Ujja9J-hRxttOKqTYdbYP_tyaS4gLHg53vupoCSsg,199
204
- fast_agent_mcp-0.3.5.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
205
- fast_agent_mcp-0.3.5.dist-info/RECORD,,
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,,