fast-agent-mcp 0.3.12__py3-none-any.whl → 0.3.14__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.

Files changed (35) hide show
  1. fast_agent/agents/llm_agent.py +15 -34
  2. fast_agent/agents/llm_decorator.py +13 -2
  3. fast_agent/agents/mcp_agent.py +18 -2
  4. fast_agent/agents/tool_agent.py +8 -10
  5. fast_agent/cli/commands/check_config.py +45 -1
  6. fast_agent/config.py +63 -0
  7. fast_agent/constants.py +3 -0
  8. fast_agent/context.py +42 -9
  9. fast_agent/core/logging/listeners.py +1 -1
  10. fast_agent/event_progress.py +2 -3
  11. fast_agent/interfaces.py +9 -2
  12. fast_agent/llm/model_factory.py +4 -0
  13. fast_agent/llm/provider/google/google_converter.py +10 -3
  14. fast_agent/llm/provider_key_manager.py +1 -0
  15. fast_agent/llm/provider_types.py +1 -0
  16. fast_agent/llm/request_params.py +3 -1
  17. fast_agent/mcp/mcp_agent_client_session.py +13 -0
  18. fast_agent/mcp/mcp_aggregator.py +313 -40
  19. fast_agent/mcp/mcp_connection_manager.py +95 -22
  20. fast_agent/mcp/skybridge.py +45 -0
  21. fast_agent/mcp/sse_tracking.py +287 -0
  22. fast_agent/mcp/transport_tracking.py +37 -3
  23. fast_agent/mcp/types.py +24 -0
  24. fast_agent/resources/examples/workflows/router.py +1 -0
  25. fast_agent/resources/setup/fastagent.config.yaml +5 -0
  26. fast_agent/ui/console_display.py +347 -20
  27. fast_agent/ui/enhanced_prompt.py +107 -58
  28. fast_agent/ui/interactive_prompt.py +57 -34
  29. fast_agent/ui/mcp_display.py +159 -41
  30. fast_agent/ui/rich_progress.py +4 -1
  31. {fast_agent_mcp-0.3.12.dist-info → fast_agent_mcp-0.3.14.dist-info}/METADATA +16 -7
  32. {fast_agent_mcp-0.3.12.dist-info → fast_agent_mcp-0.3.14.dist-info}/RECORD +35 -32
  33. {fast_agent_mcp-0.3.12.dist-info → fast_agent_mcp-0.3.14.dist-info}/WHEEL +0 -0
  34. {fast_agent_mcp-0.3.12.dist-info → fast_agent_mcp-0.3.14.dist-info}/entry_points.txt +0 -0
  35. {fast_agent_mcp-0.3.12.dist-info → fast_agent_mcp-0.3.14.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,7 @@ import shlex
9
9
  import subprocess
10
10
  import tempfile
11
11
  from importlib.metadata import version
12
- from typing import TYPE_CHECKING, List, Optional
12
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
13
13
 
14
14
  from prompt_toolkit import PromptSession
15
15
  from prompt_toolkit.completion import Completer, Completion, WordCompleter
@@ -24,6 +24,7 @@ from fast_agent.agents.agent_types import AgentType
24
24
  from fast_agent.constants import FAST_AGENT_ERROR_CHANNEL, FAST_AGENT_REMOVED_METADATA_CHANNEL
25
25
  from fast_agent.core.exceptions import PromptExitError
26
26
  from fast_agent.llm.model_info import get_model_info
27
+ from fast_agent.mcp.types import McpAgentProtocol
27
28
  from fast_agent.ui.mcp_display import render_mcp_status
28
29
 
29
30
  if TYPE_CHECKING:
@@ -103,10 +104,9 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: "AgentApp
103
104
 
104
105
  # Get counts TODO -- add this to the type library or adjust the way aggregator/reporting works
105
106
  server_count = 0
106
- if hasattr(agent, "_aggregator") and hasattr(agent._aggregator, "server_names"):
107
- server_count = (
108
- len(agent._aggregator.server_names) if agent._aggregator.server_names else 0
109
- )
107
+ if isinstance(agent, McpAgentProtocol):
108
+ server_names = agent.aggregator.server_names
109
+ server_count = len(server_names) if server_names else 0
110
110
 
111
111
  tools_result = await agent.list_tools()
112
112
  tool_count = (
@@ -182,6 +182,17 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: "AgentApp
182
182
  rich_print(f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {content}")
183
183
  # await _render_mcp_status(agent)
184
184
 
185
+ # Display Skybridge status (if aggregator discovered any)
186
+ try:
187
+ aggregator = agent.aggregator if isinstance(agent, McpAgentProtocol) else None
188
+ display = getattr(agent, "display", None)
189
+ if aggregator and display and hasattr(display, "show_skybridge_summary"):
190
+ skybridge_configs = await aggregator.get_skybridge_configs()
191
+ display.show_skybridge_summary(agent_name, skybridge_configs)
192
+ except Exception:
193
+ # Ignore Skybridge rendering issues to avoid interfering with startup
194
+ pass
195
+
185
196
  # Mark as shown
186
197
  _agent_info_shown.add(agent_name)
187
198
 
@@ -648,60 +659,96 @@ async def get_enhanced_input(
648
659
  model_display = None
649
660
  tdv_segment = None
650
661
  turn_count = 0
651
- try:
652
- if agent_provider:
662
+ agent = None
663
+ if agent_provider:
664
+ try:
653
665
  agent = agent_provider._agent(agent_name)
666
+ except Exception as exc:
667
+ print(f"[toolbar debug] unable to resolve agent '{agent_name}': {exc}")
654
668
 
655
- # Get turn count from message history
656
- for message in agent.message_history:
657
- if message.role == "user":
658
- turn_count += 1
659
-
660
- # Get model name from LLM
661
- if agent.llm and agent.llm.model_name:
662
- model_name = agent.llm.model_name
663
- # Truncate model name to max 25 characters with ellipsis
664
- max_len = 25
665
- if len(model_name) > max_len:
666
- # Keep total length at max_len including ellipsis
667
- model_display = model_name[: max_len - 1] + "…"
668
- else:
669
- model_display = model_name
670
-
671
- # Build TDV capability segment based on model database
672
- info = get_model_info(agent)
673
- # Default to text-only if info resolution fails for any reason
674
- t, d, v = (True, False, False)
675
- if info:
676
- t, d, v = info.tdv_flags
677
-
678
- # Check for alert flags in user messages
679
- alert_flags: set[str] = set()
680
- error_seen = False
681
- for message in agent.message_history:
682
- if message.channels:
683
- if message.channels.get(FAST_AGENT_ERROR_CHANNEL):
684
- error_seen = True
685
- if message.role == "user" and message.channels:
686
- meta_blocks = message.channels.get(FAST_AGENT_REMOVED_METADATA_CHANNEL, [])
687
- alert_flags.update(_extract_alert_flags_from_meta(meta_blocks))
688
-
689
- if error_seen and not alert_flags:
690
- alert_flags.add("T")
691
-
692
- def _style_flag(letter: str, supported: bool) -> str:
693
- # Enabled uses the same color as NORMAL mode (ansigreen), disabled is dim
694
- if letter in alert_flags:
695
- return f"<style fg='ansired' bg='ansiblack'>{letter}</style>"
696
-
697
- enabled_color = "ansigreen"
698
- if supported:
699
- return f"<style fg='{enabled_color}' bg='ansiblack'>{letter}</style>"
700
- return f"<style fg='ansiblack' bg='ansiwhite'>{letter}</style>"
701
-
702
- tdv_segment = f"{_style_flag('T', t)}{_style_flag('D', d)}{_style_flag('V', v)}"
703
- except Exception:
704
- # If anything goes wrong determining the model, omit it gracefully
669
+ if agent:
670
+ for message in agent.message_history:
671
+ if message.role == "user":
672
+ turn_count += 1
673
+
674
+ # Resolve LLM reference safely (avoid assertion when unattached)
675
+ llm = None
676
+ try:
677
+ llm = agent.llm
678
+ except AssertionError:
679
+ llm = getattr(agent, "_llm", None)
680
+ except Exception as exc:
681
+ print(f"[toolbar debug] agent.llm access failed for '{agent_name}': {exc}")
682
+
683
+ model_name = None
684
+ if llm:
685
+ model_name = getattr(llm, "model_name", None)
686
+ if not model_name:
687
+ model_name = getattr(getattr(llm, "default_request_params", None), "model", None)
688
+
689
+ if not model_name:
690
+ model_name = getattr(agent.config, "model", None)
691
+ if not model_name and getattr(agent.config, "default_request_params", None):
692
+ model_name = getattr(agent.config.default_request_params, "model", None)
693
+ if not model_name:
694
+ context = getattr(agent, "context", None) or getattr(agent_provider, "context", None)
695
+ config_obj = getattr(context, "config", None) if context else None
696
+ model_name = getattr(config_obj, "default_model", None)
697
+
698
+ if model_name:
699
+ max_len = 25
700
+ model_display = model_name[: max_len - 1] + "…" if len(model_name) > max_len else model_name
701
+ else:
702
+ print(f"[toolbar debug] no model resolved for agent '{agent_name}'")
703
+ model_display = "unknown"
704
+
705
+ # Build TDV capability segment based on model database
706
+ info = None
707
+ if llm:
708
+ try:
709
+ info = get_model_info(llm)
710
+ except TypeError:
711
+ info = None
712
+ if not info and model_name:
713
+ try:
714
+ info = get_model_info(model_name)
715
+ except TypeError:
716
+ info = None
717
+ except Exception as exc:
718
+ print(f"[toolbar debug] get_model_info failed for '{agent_name}': {exc}")
719
+ info = None
720
+
721
+ # Default to text-only if info resolution fails for any reason
722
+ t, d, v = (True, False, False)
723
+ if info:
724
+ t, d, v = info.tdv_flags
725
+
726
+ # Check for alert flags in user messages
727
+ alert_flags: set[str] = set()
728
+ error_seen = False
729
+ for message in agent.message_history:
730
+ if message.channels:
731
+ if message.channels.get(FAST_AGENT_ERROR_CHANNEL):
732
+ error_seen = True
733
+ if message.role == "user" and message.channels:
734
+ meta_blocks = message.channels.get(FAST_AGENT_REMOVED_METADATA_CHANNEL, [])
735
+ alert_flags.update(_extract_alert_flags_from_meta(meta_blocks))
736
+
737
+ if error_seen and not alert_flags:
738
+ alert_flags.add("T")
739
+
740
+ def _style_flag(letter: str, supported: bool) -> str:
741
+ # Enabled uses the same color as NORMAL mode (ansigreen), disabled is dim
742
+ if letter in alert_flags:
743
+ return f"<style fg='ansired' bg='ansiblack'>{letter}</style>"
744
+
745
+ enabled_color = "ansigreen"
746
+ if supported:
747
+ return f"<style fg='{enabled_color}' bg='ansiblack'>{letter}</style>"
748
+ return f"<style fg='ansiblack' bg='ansiwhite'>{letter}</style>"
749
+
750
+ tdv_segment = f"{_style_flag('T', t)}{_style_flag('D', d)}{_style_flag('V', v)}"
751
+ else:
705
752
  model_display = None
706
753
  tdv_segment = None
707
754
 
@@ -1022,7 +1069,9 @@ async def get_argument_input(
1022
1069
  prompt_session.app.exit()
1023
1070
 
1024
1071
 
1025
- async def handle_special_commands(command, agent_app=None):
1072
+ async def handle_special_commands(
1073
+ command: Any, agent_app: "AgentApp | None" = None
1074
+ ) -> bool | Dict[str, Any]:
1026
1075
  """
1027
1076
  Handle special input commands.
1028
1077
 
@@ -14,7 +14,7 @@ Usage:
14
14
  )
15
15
  """
16
16
 
17
- from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional, Union
17
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union, cast
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from fast_agent.core.agent_app import AgentApp
@@ -25,6 +25,7 @@ from rich import print as rich_print
25
25
  from fast_agent.agents.agent_types import AgentType
26
26
  from fast_agent.history.history_exporter import HistoryExporter
27
27
  from fast_agent.mcp.mcp_aggregator import SEP
28
+ from fast_agent.mcp.types import McpAgentProtocol
28
29
  from fast_agent.types import PromptMessageExtended
29
30
  from fast_agent.ui.enhanced_prompt import (
30
31
  _display_agent_info_helper,
@@ -114,8 +115,9 @@ class InteractivePrompt:
114
115
 
115
116
  # Check if we should switch agents
116
117
  if isinstance(command_result, dict):
117
- if "switch_agent" in command_result:
118
- new_agent = command_result["switch_agent"]
118
+ command_dict: Dict[str, Any] = command_result
119
+ if "switch_agent" in command_dict:
120
+ new_agent = command_dict["switch_agent"]
119
121
  if new_agent in available_agents_set:
120
122
  agent = new_agent
121
123
  # Display new agent info immediately when switching
@@ -126,14 +128,14 @@ class InteractivePrompt:
126
128
  rich_print(f"[red]Agent '{new_agent}' not found[/red]")
127
129
  continue
128
130
  # Keep the existing list_prompts handler for backward compatibility
129
- elif "list_prompts" in command_result:
131
+ elif "list_prompts" in command_dict:
130
132
  # Use the prompt_provider directly
131
133
  await self._list_prompts(prompt_provider, agent)
132
134
  continue
133
- elif "select_prompt" in command_result:
135
+ elif "select_prompt" in command_dict:
134
136
  # Handle prompt selection, using both list_prompts and apply_prompt
135
- prompt_name = command_result.get("prompt_name")
136
- prompt_index = command_result.get("prompt_index")
137
+ prompt_name = command_dict.get("prompt_name")
138
+ prompt_index = command_dict.get("prompt_index")
137
139
 
138
140
  # If a specific index was provided (from /prompt <number>)
139
141
  if prompt_index is not None:
@@ -163,16 +165,20 @@ class InteractivePrompt:
163
165
  # Use the name-based selection
164
166
  await self._select_prompt(prompt_provider, agent, prompt_name)
165
167
  continue
166
- elif "list_tools" in command_result:
168
+ elif "list_tools" in command_dict:
167
169
  # Handle tools list display
168
170
  await self._list_tools(prompt_provider, agent)
169
171
  continue
170
- elif "show_usage" in command_result:
172
+ elif "show_usage" in command_dict:
171
173
  # Handle usage display
172
174
  await self._show_usage(prompt_provider, agent)
173
175
  continue
174
- elif "show_history" in command_result:
175
- target_agent = command_result.get("show_history", {}).get("agent") or agent
176
+ elif "show_history" in command_dict:
177
+ history_info = command_dict.get("show_history")
178
+ history_agent = (
179
+ history_info.get("agent") if isinstance(history_info, dict) else None
180
+ )
181
+ target_agent = history_agent or agent
176
182
  try:
177
183
  agent_obj = prompt_provider._agent(target_agent)
178
184
  except Exception:
@@ -183,8 +189,12 @@ class InteractivePrompt:
183
189
  usage = getattr(agent_obj, "usage_accumulator", None)
184
190
  display_history_overview(target_agent, history, usage)
185
191
  continue
186
- elif "clear_history" in command_result:
187
- target_agent = command_result.get("clear_history", {}).get("agent") or agent
192
+ elif "clear_history" in command_dict:
193
+ clear_info = command_dict.get("clear_history")
194
+ clear_agent = (
195
+ clear_info.get("agent") if isinstance(clear_info, dict) else None
196
+ )
197
+ target_agent = clear_agent or agent
188
198
  try:
189
199
  agent_obj = prompt_provider._agent(target_agent)
190
200
  except Exception:
@@ -194,7 +204,9 @@ class InteractivePrompt:
194
204
  if hasattr(agent_obj, "clear"):
195
205
  try:
196
206
  agent_obj.clear()
197
- rich_print(f"[green]History cleared for agent '{target_agent}'.[/green]")
207
+ rich_print(
208
+ f"[green]History cleared for agent '{target_agent}'.[/green]"
209
+ )
198
210
  except Exception as exc:
199
211
  rich_print(
200
212
  f"[red]Failed to clear history for '{target_agent}': {exc}[/red]"
@@ -204,21 +216,21 @@ class InteractivePrompt:
204
216
  f"[yellow]Agent '{target_agent}' does not support clearing history.[/yellow]"
205
217
  )
206
218
  continue
207
- elif "show_system" in command_result:
219
+ elif "show_system" in command_dict:
208
220
  # Handle system prompt display
209
221
  await self._show_system(prompt_provider, agent)
210
222
  continue
211
- elif "show_markdown" in command_result:
223
+ elif "show_markdown" in command_dict:
212
224
  # Handle markdown display
213
225
  await self._show_markdown(prompt_provider, agent)
214
226
  continue
215
- elif "show_mcp_status" in command_result:
227
+ elif "show_mcp_status" in command_dict:
216
228
  rich_print()
217
229
  await show_mcp_status(agent, prompt_provider)
218
230
  continue
219
- elif "save_history" in command_result:
231
+ elif "save_history" in command_dict:
220
232
  # Save history for the current agent
221
- filename = command_result.get("filename")
233
+ filename = command_dict.get("filename")
222
234
  try:
223
235
  agent_obj = prompt_provider._agent(agent)
224
236
 
@@ -353,15 +365,16 @@ class InteractivePrompt:
353
365
  )
354
366
  else:
355
367
  # Handle Prompt objects from mcp.types
368
+ prompt_obj = cast("Prompt", prompt)
356
369
  all_prompts.append(
357
370
  {
358
371
  "server": server_name,
359
- "name": prompt.name,
360
- "namespaced_name": f"{server_name}{SEP}{prompt.name}",
361
- "title": prompt.title or None,
362
- "description": prompt.description or "No description",
363
- "arg_count": len(prompt.arguments or []),
364
- "arguments": prompt.arguments or [],
372
+ "name": prompt_obj.name,
373
+ "namespaced_name": f"{server_name}{SEP}{prompt_obj.name}",
374
+ "title": prompt_obj.title or None,
375
+ "description": prompt_obj.description or "No description",
376
+ "arg_count": len(prompt_obj.arguments or []),
377
+ "arguments": prompt_obj.arguments or [],
365
378
  }
366
379
  )
367
380
 
@@ -856,6 +869,10 @@ class InteractivePrompt:
856
869
  if tool.title and tool.title.strip():
857
870
  tool_line.append(f" {tool.title}", style="default")
858
871
 
872
+ meta = getattr(tool, "meta", {}) or {}
873
+ if meta.get("openai/skybridgeEnabled"):
874
+ tool_line.append(" (skybridge)", style="cyan")
875
+
859
876
  rich_print(tool_line)
860
877
 
861
878
  # Description lines - show 2-3 rows if needed
@@ -909,6 +926,11 @@ class InteractivePrompt:
909
926
  args_text = args_text[:77] + "..."
910
927
  rich_print(f" [dim magenta]args: {args_text}[/dim magenta]")
911
928
 
929
+ if meta.get("openai/skybridgeEnabled"):
930
+ template = meta.get("openai/skybridgeTemplate")
931
+ if template:
932
+ rich_print(f" [dim magenta]template:[/dim magenta] {template}")
933
+
912
934
  rich_print() # Space between tools
913
935
 
914
936
  except Exception as e:
@@ -962,22 +984,23 @@ class InteractivePrompt:
962
984
 
963
985
  # Get server count for display
964
986
  server_count = 0
965
- if hasattr(agent, "_aggregator") and hasattr(agent._aggregator, "server_names"):
966
- server_count = (
967
- len(agent._aggregator.server_names) if agent._aggregator.server_names else 0
968
- )
987
+ if isinstance(agent, McpAgentProtocol):
988
+ server_names = agent.aggregator.server_names
989
+ server_count = len(server_names) if server_names else 0
969
990
 
970
991
  # Use the display utility to show the system prompt
971
- if hasattr(agent, "display") and agent.display:
972
- agent.display.show_system_message(
992
+ agent_display = getattr(agent, "display", None)
993
+ if agent_display:
994
+ agent_display.show_system_message(
973
995
  system_prompt=system_prompt, agent_name=agent_name, server_count=server_count
974
996
  )
975
997
  else:
976
998
  # Fallback to basic display
977
999
  from fast_agent.ui.console_display import ConsoleDisplay
978
1000
 
1001
+ agent_context = getattr(agent, "context", None)
979
1002
  display = ConsoleDisplay(
980
- config=agent.context.config if hasattr(agent, "context") else None
1003
+ config=agent_context.config if hasattr(agent_context, "config") else None
981
1004
  )
982
1005
  display.show_system_message(
983
1006
  system_prompt=system_prompt, agent_name=agent_name, server_count=server_count
@@ -1005,11 +1028,11 @@ class InteractivePrompt:
1005
1028
  agent = prompt_provider._agent(agent_name)
1006
1029
 
1007
1030
  # Check if agent has message history
1008
- if not hasattr(agent, "_llm") or not agent._llm:
1031
+ if not agent.llm:
1009
1032
  rich_print("[yellow]No message history available[/yellow]")
1010
1033
  return
1011
1034
 
1012
- message_history = agent._llm.message_history
1035
+ message_history = agent.llm.message_history
1013
1036
  if not message_history:
1014
1037
  rich_print("[yellow]No messages in history[/yellow]")
1015
1038
  return