fast-agent-mcp 0.2.35__py3-none-any.whl → 0.2.37__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 (73) hide show
  1. {fast_agent_mcp-0.2.35.dist-info → fast_agent_mcp-0.2.37.dist-info}/METADATA +15 -12
  2. {fast_agent_mcp-0.2.35.dist-info → fast_agent_mcp-0.2.37.dist-info}/RECORD +55 -56
  3. {fast_agent_mcp-0.2.35.dist-info → fast_agent_mcp-0.2.37.dist-info}/licenses/LICENSE +1 -1
  4. mcp_agent/agents/base_agent.py +2 -2
  5. mcp_agent/agents/workflow/router_agent.py +1 -1
  6. mcp_agent/cli/commands/quickstart.py +59 -5
  7. mcp_agent/config.py +10 -0
  8. mcp_agent/context.py +1 -4
  9. mcp_agent/core/agent_types.py +7 -6
  10. mcp_agent/core/direct_decorators.py +14 -0
  11. mcp_agent/core/direct_factory.py +1 -0
  12. mcp_agent/core/enhanced_prompt.py +73 -13
  13. mcp_agent/core/fastagent.py +23 -2
  14. mcp_agent/core/interactive_prompt.py +118 -8
  15. mcp_agent/human_input/elicitation_form.py +723 -0
  16. mcp_agent/human_input/elicitation_forms.py +59 -0
  17. mcp_agent/human_input/elicitation_handler.py +88 -0
  18. mcp_agent/human_input/elicitation_state.py +34 -0
  19. mcp_agent/llm/augmented_llm.py +31 -0
  20. mcp_agent/llm/providers/augmented_llm_anthropic.py +11 -23
  21. mcp_agent/llm/providers/augmented_llm_azure.py +4 -4
  22. mcp_agent/llm/providers/augmented_llm_google_native.py +4 -2
  23. mcp_agent/llm/providers/augmented_llm_openai.py +195 -12
  24. mcp_agent/llm/providers/multipart_converter_openai.py +4 -3
  25. mcp_agent/mcp/elicitation_factory.py +84 -0
  26. mcp_agent/mcp/elicitation_handlers.py +155 -0
  27. mcp_agent/mcp/helpers/content_helpers.py +27 -0
  28. mcp_agent/mcp/helpers/server_config_helpers.py +10 -8
  29. mcp_agent/mcp/interfaces.py +1 -1
  30. mcp_agent/mcp/mcp_agent_client_session.py +44 -1
  31. mcp_agent/mcp/mcp_aggregator.py +56 -11
  32. mcp_agent/mcp/mcp_connection_manager.py +30 -18
  33. mcp_agent/mcp_server/agent_server.py +2 -0
  34. mcp_agent/mcp_server_registry.py +16 -8
  35. mcp_agent/resources/examples/data-analysis/analysis.py +1 -2
  36. mcp_agent/resources/examples/mcp/elicitations/README.md +157 -0
  37. mcp_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  38. mcp_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +232 -0
  39. mcp_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  40. mcp_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  41. mcp_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  42. mcp_agent/resources/examples/mcp/elicitations/forms_demo.py +111 -0
  43. mcp_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  44. mcp_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  45. mcp_agent/resources/examples/{prompting/agent.py → mcp/elicitations/tool_call.py} +4 -5
  46. mcp_agent/resources/examples/mcp/state-transfer/agent_two.py +1 -1
  47. mcp_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +1 -1
  48. mcp_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +1 -0
  49. mcp_agent/resources/examples/workflows/evaluator.py +1 -1
  50. mcp_agent/resources/examples/workflows/graded_report.md +89 -0
  51. mcp_agent/resources/examples/workflows/orchestrator.py +7 -9
  52. mcp_agent/resources/examples/workflows/parallel.py +0 -2
  53. mcp_agent/resources/examples/workflows/short_story.md +13 -0
  54. mcp_agent/resources/examples/in_dev/agent_build.py +0 -84
  55. mcp_agent/resources/examples/in_dev/css-LICENSE.txt +0 -21
  56. mcp_agent/resources/examples/in_dev/slides.py +0 -110
  57. mcp_agent/resources/examples/internal/agent.py +0 -20
  58. mcp_agent/resources/examples/internal/fastagent.config.yaml +0 -66
  59. mcp_agent/resources/examples/internal/history_transfer.py +0 -35
  60. mcp_agent/resources/examples/internal/job.py +0 -84
  61. mcp_agent/resources/examples/internal/prompt_category.py +0 -21
  62. mcp_agent/resources/examples/internal/prompt_sizing.py +0 -51
  63. mcp_agent/resources/examples/internal/simple.txt +0 -2
  64. mcp_agent/resources/examples/internal/sizer.py +0 -20
  65. mcp_agent/resources/examples/internal/social.py +0 -67
  66. mcp_agent/resources/examples/prompting/__init__.py +0 -3
  67. mcp_agent/resources/examples/prompting/delimited_prompt.txt +0 -14
  68. mcp_agent/resources/examples/prompting/fastagent.config.yaml +0 -43
  69. mcp_agent/resources/examples/prompting/image_server.py +0 -52
  70. mcp_agent/resources/examples/prompting/prompt1.txt +0 -6
  71. mcp_agent/resources/examples/prompting/work_with_image.py +0 -19
  72. {fast_agent_mcp-0.2.35.dist-info → fast_agent_mcp-0.2.37.dist-info}/WHEEL +0 -0
  73. {fast_agent_mcp-0.2.35.dist-info → fast_agent_mcp-0.2.37.dist-info}/entry_points.txt +0 -0
@@ -40,6 +40,59 @@ in_multiline_mode = False
40
40
  # Track whether help text has been shown globally
41
41
  help_message_shown = False
42
42
 
43
+ # Track which agents have shown their info
44
+ _agent_info_shown = set()
45
+
46
+
47
+ async def _display_agent_info_helper(agent_name: str, agent_provider: object) -> None:
48
+ """Helper function to display agent information."""
49
+ # Only show once per agent
50
+ if agent_name in _agent_info_shown:
51
+ return
52
+
53
+ try:
54
+ # Get agent info
55
+ if hasattr(agent_provider, "_agent"):
56
+ # This is an AgentApp - get the specific agent
57
+ agent = agent_provider._agent(agent_name)
58
+ else:
59
+ # This is a single agent
60
+ agent = agent_provider
61
+
62
+ # Get counts
63
+ servers = await agent.list_servers()
64
+ server_count = len(servers) if servers else 0
65
+
66
+ tools_result = await agent.list_tools()
67
+ tool_count = (
68
+ len(tools_result.tools) if tools_result and hasattr(tools_result, "tools") else 0
69
+ )
70
+
71
+ prompts_dict = await agent.list_prompts()
72
+ prompt_count = sum(len(prompts) for prompts in prompts_dict.values()) if prompts_dict else 0
73
+
74
+ # Display with proper pluralization and subdued formatting
75
+ if server_count == 0:
76
+ rich_print(
77
+ f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]: No MCP Servers attached[/dim]"
78
+ )
79
+ else:
80
+ # Pluralization helpers
81
+ server_word = "Server" if server_count == 1 else "Servers"
82
+ tool_word = "tool" if tool_count == 1 else "tools"
83
+ prompt_word = "prompt" if prompt_count == 1 else "prompts"
84
+
85
+ rich_print(
86
+ f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {server_count:,}[dim] MCP {server_word}, [/dim]{tool_count:,}[dim] {tool_word}, [/dim]{prompt_count:,}[dim] {prompt_word} available[/dim]"
87
+ )
88
+
89
+ # Mark as shown
90
+ _agent_info_shown.add(agent_name)
91
+
92
+ except Exception:
93
+ # Silently ignore errors to not disrupt the user experience
94
+ pass
95
+
43
96
 
44
97
  class AgentCompleter(Completer):
45
98
  """Provide completion for agent names and common commands."""
@@ -54,11 +107,11 @@ class AgentCompleter(Completer):
54
107
  self.agents = agents
55
108
  # Map commands to their descriptions for better completion hints
56
109
  self.commands = {
57
- "help": "Show available commands",
58
- "prompts": "List and select MCP prompts", # Changed description
59
- "prompt": "Apply a specific prompt by name (/prompt <name>)", # New command
110
+ "tools": "List and call MCP tools",
111
+ "prompt": "List and select MCP prompts, or apply specific prompt (/prompt <name>)",
60
112
  "agents": "List available agents",
61
113
  "usage": "Show current usage statistics",
114
+ "help": "Show available commands",
62
115
  "clear": "Clear the screen",
63
116
  "STOP": "Stop this prompting session and move to next workflow step",
64
117
  "EXIT": "Exit fast-agent, terminating any running workflows",
@@ -66,8 +119,8 @@ class AgentCompleter(Completer):
66
119
  }
67
120
  if is_human_input:
68
121
  self.commands.pop("agents")
69
- self.commands.pop("prompts") # Remove prompts command in human input mode
70
122
  self.commands.pop("prompt", None) # Remove prompt command in human input mode
123
+ self.commands.pop("tools", None) # Remove tools command in human input mode
71
124
  self.commands.pop("usage", None) # Remove usage command in human input mode
72
125
  self.agent_types = agent_types or {}
73
126
 
@@ -260,6 +313,7 @@ async def get_enhanced_input(
260
313
  agent_types: dict[str, AgentType] = None,
261
314
  is_human_input: bool = False,
262
315
  toolbar_color: str = "ansiblue",
316
+ agent_provider: object = None,
263
317
  ) -> str:
264
318
  """
265
319
  Enhanced input with advanced prompt_toolkit features.
@@ -274,6 +328,7 @@ async def get_enhanced_input(
274
328
  agent_types: Dictionary mapping agent names to their types for display
275
329
  is_human_input: Whether this is a human input request (disables agent selection features)
276
330
  toolbar_color: Color to use for the agent name in the toolbar (default: "ansiblue")
331
+ agent_provider: Optional agent provider for displaying agent info
277
332
 
278
333
  Returns:
279
334
  User input string
@@ -300,14 +355,15 @@ async def get_enhanced_input(
300
355
  if in_multiline_mode:
301
356
  mode_style = "ansired" # More noticeable for multiline mode
302
357
  mode_text = "MULTILINE"
303
- toggle_text = "Normal Editing"
358
+ toggle_text = "Normal"
304
359
  else:
305
360
  mode_style = "ansigreen"
306
361
  mode_text = "NORMAL"
307
- toggle_text = "Multiline Editing"
362
+ toggle_text = "Multiline"
308
363
 
309
364
  shortcuts = [
310
365
  ("Ctrl+T", toggle_text),
366
+ ("Ctrl+E", "External"),
311
367
  ("Ctrl+L", "Clear"),
312
368
  ("↑/↓", "History"),
313
369
  ]
@@ -373,8 +429,13 @@ async def get_enhanced_input(
373
429
  rich_print("[dim]Type /help for commands. Ctrl+T toggles multiline mode.[/dim]")
374
430
  else:
375
431
  rich_print(
376
- "[dim]Type /help for commands, @agent to switch agent. Ctrl+T toggles multiline mode.[/dim]"
432
+ "[dim]Type /help for commands, @agent to switch agent. Ctrl+T toggles multiline mode.[/dim]\n"
377
433
  )
434
+
435
+ # Display agent info right after help text if agent_provider is available
436
+ if agent_provider and not is_human_input:
437
+ await _display_agent_info_helper(agent_name, agent_provider)
438
+
378
439
  rich_print()
379
440
  help_message_shown = True
380
441
 
@@ -394,12 +455,8 @@ async def get_enhanced_input(
394
455
  return "LIST_AGENTS"
395
456
  elif cmd == "usage":
396
457
  return "SHOW_USAGE"
397
- elif cmd == "prompts":
398
- # Return a dictionary with select_prompt action instead of a string
399
- # This way it will match what the command handler expects
400
- return {"select_prompt": True, "prompt_name": None}
401
458
  elif cmd == "prompt":
402
- # Handle /prompt with no arguments the same way as /prompts
459
+ # Handle /prompt with no arguments as interactive mode
403
460
  if len(cmd_parts) > 1:
404
461
  # Direct prompt selection with name or number
405
462
  prompt_arg = cmd_parts[1].strip()
@@ -409,8 +466,11 @@ async def get_enhanced_input(
409
466
  else:
410
467
  return f"SELECT_PROMPT:{prompt_arg}"
411
468
  else:
412
- # If /prompt is used without arguments, treat it the same as /prompts
469
+ # If /prompt is used without arguments, show interactive selection
413
470
  return {"select_prompt": True, "prompt_name": None}
471
+ elif cmd == "tools":
472
+ # Return a dictionary with list_tools action
473
+ return {"list_tools": True}
414
474
  elif cmd == "exit":
415
475
  return "EXIT"
416
476
  elif cmd.lower() == "stop":
@@ -81,7 +81,8 @@ class FastAgent:
81
81
  name: str,
82
82
  config_path: str | None = None,
83
83
  ignore_unknown_args: bool = False,
84
- parse_cli_args: bool = True, # Add new parameter with default True
84
+ parse_cli_args: bool = True,
85
+ quiet: bool = False, # Add quiet parameter
85
86
  ) -> None:
86
87
  """
87
88
  Initialize the fast-agent application.
@@ -94,8 +95,10 @@ class FastAgent:
94
95
  parse_cli_args: If True, parse command line arguments using argparse.
95
96
  Set to False when embedding FastAgent in another framework
96
97
  (like FastAPI/Uvicorn) that handles its own arguments.
98
+ quiet: If True, disable progress display, tool and message logging for cleaner output
97
99
  """
98
100
  self.args = argparse.Namespace() # Initialize args always
101
+ self._programmatic_quiet = quiet # Store the programmatic quiet setting
99
102
 
100
103
  # --- Wrap argument parsing logic ---
101
104
  if parse_cli_args:
@@ -173,6 +176,10 @@ class FastAgent:
173
176
  sys.exit(0)
174
177
  # --- End of wrapped logic ---
175
178
 
179
+ # Apply programmatic quiet setting (overrides CLI if both are set)
180
+ if self._programmatic_quiet:
181
+ self.args.quiet = True
182
+
176
183
  self.name = name
177
184
  self.config_path = config_path
178
185
 
@@ -180,12 +187,26 @@ class FastAgent:
180
187
  # Load configuration directly for this instance
181
188
  self._load_config()
182
189
 
190
+ # Apply programmatic quiet mode to config before creating app
191
+ if self._programmatic_quiet and hasattr(self, "config"):
192
+ if "logger" not in self.config:
193
+ self.config["logger"] = {}
194
+ self.config["logger"]["progress_display"] = False
195
+ self.config["logger"]["show_chat"] = False
196
+ self.config["logger"]["show_tools"] = False
197
+
183
198
  # Create the app with our local settings
184
199
  self.app = MCPApp(
185
200
  name=name,
186
201
  settings=config.Settings(**self.config) if hasattr(self, "config") else None,
187
202
  )
188
203
 
204
+ # Stop progress display immediately if quiet mode is requested
205
+ if self._programmatic_quiet:
206
+ from mcp_agent.progress_display import progress_display
207
+
208
+ progress_display.stop()
209
+
189
210
  except yaml.parser.ParserError as e:
190
211
  handle_error(
191
212
  e,
@@ -486,7 +507,7 @@ class FastAgent:
486
507
 
487
508
  async def start_server(
488
509
  self,
489
- transport: str = "sse",
510
+ transport: str = "http",
490
511
  host: str = "0.0.0.0",
491
512
  port: int = 8000,
492
513
  server_name: Optional[str] = None,
@@ -23,6 +23,7 @@ from rich.table import Table
23
23
 
24
24
  from mcp_agent.core.agent_types import AgentType
25
25
  from mcp_agent.core.enhanced_prompt import (
26
+ _display_agent_info_helper,
26
27
  get_argument_input,
27
28
  get_enhanced_input,
28
29
  get_selection_input,
@@ -121,6 +122,7 @@ class InteractivePrompt:
121
122
  multiline=False, # Default to single-line mode
122
123
  available_agent_names=available_agents,
123
124
  agent_types=self.agent_types, # Pass agent types for display
125
+ agent_provider=prompt_provider, # Pass agent provider for info display
124
126
  )
125
127
 
126
128
  # Handle special commands - pass "True" to enable agent switching
@@ -132,6 +134,9 @@ class InteractivePrompt:
132
134
  new_agent = command_result["switch_agent"]
133
135
  if new_agent in available_agents_set:
134
136
  agent = new_agent
137
+ # Display new agent info immediately when switching
138
+ rich_print() # Add spacing
139
+ await _display_agent_info_helper(agent, prompt_provider)
135
140
  continue
136
141
  else:
137
142
  rich_print(f"[red]Agent '{new_agent}' not found[/red]")
@@ -174,6 +179,10 @@ class InteractivePrompt:
174
179
  # Use the name-based selection
175
180
  await self._select_prompt(prompt_provider, agent, prompt_name)
176
181
  continue
182
+ elif "list_tools" in command_result and prompt_provider:
183
+ # Handle tools list display
184
+ await self._list_tools(prompt_provider, agent)
185
+ continue
177
186
  elif "show_usage" in command_result:
178
187
  # Handle usage display
179
188
  await self._show_usage(prompt_provider, agent)
@@ -333,13 +342,17 @@ class InteractivePrompt:
333
342
  rich_print(f"[dim]{traceback.format_exc()}[/dim]")
334
343
 
335
344
  async def _select_prompt(
336
- self, prompt_provider: PromptProvider, agent_name: str, requested_name: Optional[str] = None
345
+ self,
346
+ prompt_provider: PromptProvider,
347
+ agent_name: str,
348
+ requested_name: Optional[str] = None,
349
+ send_func: Optional[SendFunc] = None,
337
350
  ) -> None:
338
351
  """
339
352
  Select and apply a prompt.
340
353
 
341
354
  Args:
342
- prompt_provider: Provider that implements list_prompts and apply_prompt
355
+ prompt_provider: Provider that implements list_prompts and get_prompt
343
356
  agent_name: Name of the agent
344
357
  requested_name: Optional name of the prompt to apply
345
358
  """
@@ -569,12 +582,54 @@ class InteractivePrompt:
569
582
  if arg_value:
570
583
  arg_values[arg_name] = arg_value
571
584
 
572
- # Apply the prompt
585
+ # Apply the prompt using generate() for proper progress display
573
586
  namespaced_name = selected_prompt["namespaced_name"]
574
587
  rich_print(f"\n[bold]Applying prompt [cyan]{namespaced_name}[/cyan]...[/bold]")
575
588
 
576
- # Call apply_prompt on the provider with the prompt name and arguments
577
- await prompt_provider.apply_prompt(namespaced_name, arg_values, agent_name)
589
+ # Get the agent directly for generate() call
590
+ if hasattr(prompt_provider, "_agent"):
591
+ # This is an AgentApp - get the specific agent
592
+ agent = prompt_provider._agent(agent_name)
593
+ else:
594
+ # This is a single agent
595
+ agent = prompt_provider
596
+
597
+ try:
598
+ # Use agent.apply_prompt() which handles everything properly:
599
+ # - get_prompt() to fetch template
600
+ # - convert to multipart
601
+ # - call generate() for progress display
602
+ # - return response text
603
+ # Response display is handled by the agent's show_ methods, don't print it here
604
+
605
+ # Fetch the prompt first (without progress display)
606
+ prompt_result = await agent.get_prompt(namespaced_name, arg_values)
607
+
608
+ if not prompt_result or not prompt_result.messages:
609
+ rich_print(
610
+ f"[red]Prompt '{namespaced_name}' could not be found or contains no messages[/red]"
611
+ )
612
+ return
613
+
614
+ # Convert to multipart format
615
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
616
+
617
+ multipart_messages = PromptMessageMultipart.from_get_prompt_result(prompt_result)
618
+
619
+ # Now start progress display for the actual generation
620
+ progress_display.resume()
621
+ try:
622
+ await agent.generate(multipart_messages, None)
623
+ finally:
624
+ # Pause again for the next UI interaction
625
+ progress_display.pause()
626
+
627
+ # Show usage info after the turn (same as send_wrapper does)
628
+ if hasattr(prompt_provider, "_show_turn_usage"):
629
+ prompt_provider._show_turn_usage(agent_name)
630
+
631
+ except Exception as e:
632
+ rich_print(f"[red]Error applying prompt: {e}[/red]")
578
633
 
579
634
  except Exception as e:
580
635
  import traceback
@@ -582,6 +637,61 @@ class InteractivePrompt:
582
637
  rich_print(f"[red]Error selecting or applying prompt: {e}[/red]")
583
638
  rich_print(f"[dim]{traceback.format_exc()}[/dim]")
584
639
 
640
+ async def _list_tools(self, prompt_provider: PromptProvider, agent_name: str) -> None:
641
+ """
642
+ List available tools for an agent.
643
+
644
+ Args:
645
+ prompt_provider: Provider that implements list_tools
646
+ agent_name: Name of the agent
647
+ """
648
+ console = Console()
649
+
650
+ try:
651
+ # Get agent to list tools from
652
+ if hasattr(prompt_provider, "_agent"):
653
+ # This is an AgentApp - get the specific agent
654
+ agent = prompt_provider._agent(agent_name)
655
+ else:
656
+ # This is a single agent
657
+ agent = prompt_provider
658
+
659
+ rich_print(f"\n[bold]Fetching tools for agent [cyan]{agent_name}[/cyan]...[/bold]")
660
+
661
+ # Get tools using list_tools
662
+ tools_result = await agent.list_tools()
663
+
664
+ if not tools_result or not hasattr(tools_result, "tools") or not tools_result.tools:
665
+ rich_print("[yellow]No tools available for this agent[/yellow]")
666
+ return
667
+
668
+ # Create a table for better display
669
+ table = Table(title="Available MCP Tools")
670
+ table.add_column("#", justify="right", style="cyan")
671
+ table.add_column("Tool Name", style="bright_blue")
672
+ table.add_column("Description")
673
+
674
+ # Add tools to table
675
+ for i, tool in enumerate(tools_result.tools):
676
+ table.add_row(
677
+ str(i + 1),
678
+ tool.name,
679
+ getattr(tool, "description", "No description") or "No description",
680
+ )
681
+
682
+ console.print(table)
683
+
684
+ # Add usage instructions
685
+ rich_print("\n[bold]Usage:[/bold]")
686
+ rich_print(" • Tools are automatically available in your conversation")
687
+ rich_print(" • Just ask the agent to use a tool by name or description")
688
+
689
+ except Exception as e:
690
+ import traceback
691
+
692
+ rich_print(f"[red]Error listing tools: {e}[/red]")
693
+ rich_print(f"[dim]{traceback.format_exc()}[/dim]")
694
+
585
695
  async def _show_usage(self, prompt_provider: PromptProvider, agent_name: str) -> None:
586
696
  """
587
697
  Show usage statistics for the current agent(s) in a colorful table format.
@@ -593,13 +703,13 @@ class InteractivePrompt:
593
703
  try:
594
704
  # Collect all agents from the prompt provider
595
705
  agents_to_show = collect_agents_from_provider(prompt_provider, agent_name)
596
-
706
+
597
707
  if not agents_to_show:
598
708
  rich_print("[yellow]No usage data available[/yellow]")
599
709
  return
600
-
710
+
601
711
  # Use the shared display utility
602
712
  display_usage_report(agents_to_show, show_if_progress_disabled=True)
603
-
713
+
604
714
  except Exception as e:
605
715
  rich_print(f"[red]Error showing usage: {e}[/red]")