fast-agent-mcp 0.2.40__py3-none-any.whl → 0.2.42__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 (45) hide show
  1. {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/METADATA +2 -1
  2. {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/RECORD +45 -40
  3. {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/entry_points.txt +2 -2
  4. mcp_agent/agents/base_agent.py +111 -1
  5. mcp_agent/cli/__main__.py +29 -3
  6. mcp_agent/cli/commands/check_config.py +140 -81
  7. mcp_agent/cli/commands/go.py +151 -38
  8. mcp_agent/cli/commands/quickstart.py +6 -2
  9. mcp_agent/cli/commands/server_helpers.py +106 -0
  10. mcp_agent/cli/constants.py +25 -0
  11. mcp_agent/cli/main.py +1 -1
  12. mcp_agent/config.py +111 -44
  13. mcp_agent/core/agent_app.py +104 -15
  14. mcp_agent/core/agent_types.py +5 -1
  15. mcp_agent/core/direct_decorators.py +38 -0
  16. mcp_agent/core/direct_factory.py +18 -4
  17. mcp_agent/core/enhanced_prompt.py +173 -13
  18. mcp_agent/core/fastagent.py +4 -0
  19. mcp_agent/core/interactive_prompt.py +37 -37
  20. mcp_agent/core/usage_display.py +11 -1
  21. mcp_agent/core/validation.py +21 -2
  22. mcp_agent/human_input/elicitation_form.py +53 -21
  23. mcp_agent/llm/augmented_llm.py +28 -9
  24. mcp_agent/llm/augmented_llm_silent.py +48 -0
  25. mcp_agent/llm/model_database.py +20 -0
  26. mcp_agent/llm/model_factory.py +21 -0
  27. mcp_agent/llm/provider_key_manager.py +22 -8
  28. mcp_agent/llm/provider_types.py +20 -12
  29. mcp_agent/llm/providers/augmented_llm_anthropic.py +7 -2
  30. mcp_agent/llm/providers/augmented_llm_azure.py +7 -1
  31. mcp_agent/llm/providers/augmented_llm_bedrock.py +1787 -0
  32. mcp_agent/llm/providers/augmented_llm_google_native.py +4 -1
  33. mcp_agent/llm/providers/augmented_llm_openai.py +12 -3
  34. mcp_agent/llm/providers/augmented_llm_xai.py +38 -0
  35. mcp_agent/llm/usage_tracking.py +28 -3
  36. mcp_agent/logging/logger.py +7 -0
  37. mcp_agent/mcp/hf_auth.py +32 -4
  38. mcp_agent/mcp/mcp_agent_client_session.py +2 -0
  39. mcp_agent/mcp/mcp_aggregator.py +38 -44
  40. mcp_agent/mcp/sampling.py +15 -11
  41. mcp_agent/resources/examples/mcp/elicitations/forms_demo.py +0 -6
  42. mcp_agent/resources/examples/workflows/router.py +9 -0
  43. mcp_agent/ui/console_display.py +125 -13
  44. {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/WHEEL +0 -0
  45. {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/licenses/LICENSE +0 -0
@@ -9,6 +9,7 @@ from functools import wraps
9
9
  from typing import (
10
10
  Awaitable,
11
11
  Callable,
12
+ Dict,
12
13
  List,
13
14
  Literal,
14
15
  Optional,
@@ -93,6 +94,9 @@ def _decorator_impl(
93
94
  request_params: RequestParams | None = None,
94
95
  human_input: bool = False,
95
96
  default: bool = False,
97
+ tools: Optional[Dict[str, List[str]]] = None,
98
+ resources: Optional[Dict[str, List[str]]] = None,
99
+ prompts: Optional[Dict[str, List[str]]] = None,
96
100
  **extra_kwargs,
97
101
  ) -> Callable[[AgentCallable[P, R]], DecoratedAgentProtocol[P, R]]:
98
102
  """
@@ -133,11 +137,15 @@ def _decorator_impl(
133
137
  name=name,
134
138
  instruction=instruction,
135
139
  servers=servers,
140
+ tools=tools,
141
+ resources=resources,
142
+ prompts=prompts,
136
143
  model=model,
137
144
  use_history=use_history,
138
145
  human_input=human_input,
139
146
  default=default,
140
147
  elicitation_handler=extra_kwargs.get("elicitation_handler"),
148
+ api_key=extra_kwargs.get("api_key"),
141
149
  )
142
150
 
143
151
  # Update request params if provided
@@ -176,12 +184,16 @@ def agent(
176
184
  *,
177
185
  instruction: str = "You are a helpful agent.",
178
186
  servers: List[str] = [],
187
+ tools: Optional[Dict[str, List[str]]] = None,
188
+ resources: Optional[Dict[str, List[str]]] = None,
189
+ prompts: Optional[Dict[str, List[str]]] = None,
179
190
  model: Optional[str] = None,
180
191
  use_history: bool = True,
181
192
  request_params: RequestParams | None = None,
182
193
  human_input: bool = False,
183
194
  default: bool = False,
184
195
  elicitation_handler: Optional[ElicitationFnT] = None,
196
+ api_key: str | None = None,
185
197
  ) -> Callable[[AgentCallable[P, R]], DecoratedAgentProtocol[P, R]]:
186
198
  """
187
199
  Decorator to create and register a standard agent with type-safe signature.
@@ -191,12 +203,16 @@ def agent(
191
203
  instruction_or_kwarg: Optional positional parameter for instruction
192
204
  instruction: Base instruction for the agent (keyword arg)
193
205
  servers: List of server names the agent should connect to
206
+ tools: Optional list of tool names or patterns to include
207
+ resources: Optional list of resource names or patterns to include
208
+ prompts: Optional list of prompt names or patterns to include
194
209
  model: Model specification string
195
210
  use_history: Whether to maintain conversation history
196
211
  request_params: Additional request parameters for the LLM
197
212
  human_input: Whether to enable human input capabilities
198
213
  default: Whether to mark this as the default agent
199
214
  elicitation_handler: Custom elicitation handler function (ElicitationFnT)
215
+ api_key: Optional API key for the LLM provider
200
216
 
201
217
  Returns:
202
218
  A decorator that registers the agent with proper type annotations
@@ -215,6 +231,10 @@ def agent(
215
231
  human_input=human_input,
216
232
  default=default,
217
233
  elicitation_handler=elicitation_handler,
234
+ tools=tools,
235
+ resources=resources,
236
+ prompts=prompts,
237
+ api_key=api_key,
218
238
  )
219
239
 
220
240
 
@@ -226,12 +246,16 @@ def custom(
226
246
  *,
227
247
  instruction: str = "You are a helpful agent.",
228
248
  servers: List[str] = [],
249
+ tools: Optional[Dict[str, List[str]]] = None,
250
+ resources: Optional[Dict[str, List[str]]] = None,
251
+ prompts: Optional[Dict[str, List[str]]] = None,
229
252
  model: Optional[str] = None,
230
253
  use_history: bool = True,
231
254
  request_params: RequestParams | None = None,
232
255
  human_input: bool = False,
233
256
  default: bool = False,
234
257
  elicitation_handler: Optional[ElicitationFnT] = None,
258
+ api_key: str | None = None,
235
259
  ) -> Callable[[AgentCallable[P, R]], DecoratedAgentProtocol[P, R]]:
236
260
  """
237
261
  Decorator to create and register a standard agent with type-safe signature.
@@ -265,6 +289,10 @@ def custom(
265
289
  agent_class=cls,
266
290
  default=default,
267
291
  elicitation_handler=elicitation_handler,
292
+ api_key=api_key,
293
+ tools=tools,
294
+ resources=resources,
295
+ prompts=prompts,
268
296
  )
269
297
 
270
298
 
@@ -288,6 +316,7 @@ def orchestrator(
288
316
  plan_type: Literal["full", "iterative"] = "full",
289
317
  plan_iterations: int = 5,
290
318
  default: bool = False,
319
+ api_key: str | None = None,
291
320
  ) -> Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]:
292
321
  """
293
322
  Decorator to create and register an orchestrator agent with type-safe signature.
@@ -326,6 +355,7 @@ def orchestrator(
326
355
  plan_type=plan_type,
327
356
  plan_iterations=plan_iterations,
328
357
  default=default,
358
+ api_key=api_key,
329
359
  ),
330
360
  )
331
361
 
@@ -337,6 +367,9 @@ def router(
337
367
  agents: List[str],
338
368
  instruction: Optional[str] = None,
339
369
  servers: List[str] = [],
370
+ tools: Optional[Dict[str, List[str]]] = None,
371
+ resources: Optional[Dict[str, List[str]]] = None,
372
+ prompts: Optional[Dict[str, List[str]]] = None,
340
373
  model: Optional[str] = None,
341
374
  use_history: bool = False,
342
375
  request_params: RequestParams | None = None,
@@ -345,6 +378,7 @@ def router(
345
378
  elicitation_handler: Optional[
346
379
  ElicitationFnT
347
380
  ] = None, ## exclude from docs, decide whether allowable
381
+ api_key: str | None = None,
348
382
  ) -> Callable[[AgentCallable[P, R]], DecoratedRouterProtocol[P, R]]:
349
383
  """
350
384
  Decorator to create and register a router agent with type-safe signature.
@@ -383,6 +417,10 @@ def router(
383
417
  default=default,
384
418
  router_agents=agents,
385
419
  elicitation_handler=elicitation_handler,
420
+ api_key=api_key,
421
+ tools=tools,
422
+ prompts=prompts,
423
+ resources=resources,
386
424
  ),
387
425
  )
388
426
 
@@ -150,7 +150,11 @@ async def create_agents_by_type(
150
150
 
151
151
  # Attach LLM to the agent
152
152
  llm_factory = model_factory_func(model=config.model)
153
- await agent.attach_llm(llm_factory, request_params=config.default_request_params)
153
+ await agent.attach_llm(
154
+ llm_factory,
155
+ request_params=config.default_request_params,
156
+ api_key=config.api_key
157
+ )
154
158
  result_agents[name] = agent
155
159
 
156
160
  elif agent_type == AgentType.CUSTOM:
@@ -165,7 +169,11 @@ async def create_agents_by_type(
165
169
 
166
170
  # Attach LLM to the agent
167
171
  llm_factory = model_factory_func(model=config.model)
168
- await agent.attach_llm(llm_factory, request_params=config.default_request_params)
172
+ await agent.attach_llm(
173
+ llm_factory,
174
+ request_params=config.default_request_params,
175
+ api_key=config.api_key
176
+ )
169
177
  result_agents[name] = agent
170
178
 
171
179
  elif agent_type == AgentType.ORCHESTRATOR:
@@ -200,7 +208,9 @@ async def create_agents_by_type(
200
208
  # Attach LLM to the orchestrator
201
209
  llm_factory = model_factory_func(model=config.model)
202
210
  await orchestrator.attach_llm(
203
- llm_factory, request_params=config.default_request_params
211
+ llm_factory,
212
+ request_params=config.default_request_params,
213
+ api_key=config.api_key
204
214
  )
205
215
 
206
216
  result_agents[name] = orchestrator
@@ -261,7 +271,11 @@ async def create_agents_by_type(
261
271
 
262
272
  # Attach LLM to the router
263
273
  llm_factory = model_factory_func(model=config.model)
264
- await router.attach_llm(llm_factory, request_params=config.default_request_params)
274
+ await router.attach_llm(
275
+ llm_factory,
276
+ request_params=config.default_request_params,
277
+ api_key=config.api_key
278
+ )
265
279
  result_agents[name] = router
266
280
 
267
281
  elif agent_type == AgentType.CHAIN:
@@ -68,30 +68,189 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: object) ->
68
68
  len(tools_result.tools) if tools_result and hasattr(tools_result, "tools") else 0
69
69
  )
70
70
 
71
+ resources_dict = await agent.list_resources()
72
+ resource_count = sum(len(resources) for resources in resources_dict.values()) if resources_dict else 0
73
+
71
74
  prompts_dict = await agent.list_prompts()
72
75
  prompt_count = sum(len(prompts) for prompts in prompts_dict.values()) if prompts_dict else 0
73
76
 
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
- )
77
+ # Handle different agent types
78
+ if agent.agent_type == AgentType.PARALLEL:
79
+ # Count child agents for parallel agents
80
+ child_count = 0
81
+ if hasattr(agent, "fan_out_agents") and agent.fan_out_agents:
82
+ child_count += len(agent.fan_out_agents)
83
+ if hasattr(agent, "fan_in_agent") and agent.fan_in_agent:
84
+ child_count += 1
85
+
86
+ if child_count > 0:
87
+ child_word = "child agent" if child_count == 1 else "child agents"
88
+ rich_print(
89
+ f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {child_count:,}[dim] {child_word}[/dim]"
90
+ )
91
+ elif agent.agent_type == AgentType.ROUTER:
92
+ # Count child agents for router agents
93
+ child_count = 0
94
+ if hasattr(agent, "routing_agents") and agent.routing_agents:
95
+ child_count = len(agent.routing_agents)
96
+ elif hasattr(agent, "agents") and agent.agents:
97
+ child_count = len(agent.agents)
98
+
99
+ if child_count > 0:
100
+ child_word = "child agent" if child_count == 1 else "child agents"
101
+ rich_print(
102
+ f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {child_count:,}[dim] {child_word}[/dim]"
103
+ )
79
104
  else:
105
+ # For regular agents, only display if they have MCP servers attached
106
+ if server_count > 0:
107
+ # Pluralization helpers
108
+ server_word = "Server" if server_count == 1 else "Servers"
109
+ tool_word = "tool" if tool_count == 1 else "tools"
110
+ resource_word = "resource" if resource_count == 1 else "resources"
111
+ prompt_word = "prompt" if prompt_count == 1 else "prompts"
112
+
113
+ rich_print(
114
+ f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {server_count:,}[dim] MCP {server_word}, [/dim]{tool_count:,}[dim] {tool_word}, [/dim]{resource_count:,}[dim] {resource_word}, [/dim]{prompt_count:,}[dim] {prompt_word} available[/dim]"
115
+ )
116
+
117
+ # Mark as shown
118
+ _agent_info_shown.add(agent_name)
119
+
120
+ except Exception:
121
+ # Silently ignore errors to not disrupt the user experience
122
+ pass
123
+
124
+
125
+ async def _display_all_agents_with_hierarchy(available_agents: List[str], agent_provider) -> None:
126
+ """Display all agents with tree structure for workflow agents."""
127
+ # Track which agents are children to avoid displaying them twice
128
+ child_agents = set()
129
+
130
+ # First pass: identify all child agents
131
+ for agent_name in available_agents:
132
+ try:
133
+ if hasattr(agent_provider, "_agent"):
134
+ agent = agent_provider._agent(agent_name)
135
+ else:
136
+ agent = agent_provider
137
+
138
+ if agent.agent_type == AgentType.PARALLEL:
139
+ if hasattr(agent, "fan_out_agents") and agent.fan_out_agents:
140
+ for child_agent in agent.fan_out_agents:
141
+ child_agents.add(child_agent.name)
142
+ if hasattr(agent, "fan_in_agent") and agent.fan_in_agent:
143
+ child_agents.add(agent.fan_in_agent.name)
144
+ elif agent.agent_type == AgentType.ROUTER:
145
+ if hasattr(agent, "routing_agents") and agent.routing_agents:
146
+ for child_agent in agent.routing_agents:
147
+ child_agents.add(child_agent.name)
148
+ elif hasattr(agent, "agents") and agent.agents:
149
+ for child_agent in agent.agents:
150
+ child_agents.add(child_agent.name)
151
+ except Exception:
152
+ continue
153
+
154
+ # Second pass: display agents (parents with children, standalone agents without children)
155
+ for agent_name in sorted(available_agents):
156
+ # Skip if this agent is a child of another agent
157
+ if agent_name in child_agents:
158
+ continue
159
+
160
+ try:
161
+ if hasattr(agent_provider, "_agent"):
162
+ agent = agent_provider._agent(agent_name)
163
+ else:
164
+ agent = agent_provider
165
+
166
+ # Display parent agent
167
+ await _display_agent_info_helper(agent_name, agent_provider)
168
+
169
+ # If it's a workflow agent, display its children
170
+ if agent.agent_type == AgentType.PARALLEL:
171
+ await _display_parallel_children(agent, agent_provider)
172
+ elif agent.agent_type == AgentType.ROUTER:
173
+ await _display_router_children(agent, agent_provider)
174
+
175
+ except Exception:
176
+ continue
177
+
178
+
179
+ async def _display_parallel_children(parallel_agent, agent_provider) -> None:
180
+ """Display child agents of a parallel agent in tree format."""
181
+ children = []
182
+
183
+ # Collect fan-out agents
184
+ if hasattr(parallel_agent, "fan_out_agents") and parallel_agent.fan_out_agents:
185
+ for child_agent in parallel_agent.fan_out_agents:
186
+ children.append(child_agent)
187
+
188
+ # Collect fan-in agent
189
+ if hasattr(parallel_agent, "fan_in_agent") and parallel_agent.fan_in_agent:
190
+ children.append(parallel_agent.fan_in_agent)
191
+
192
+ # Display children with tree formatting
193
+ for i, child_agent in enumerate(children):
194
+ is_last = i == len(children) - 1
195
+ prefix = "└─" if is_last else "├─"
196
+ await _display_child_agent_info(child_agent, prefix, agent_provider)
197
+
198
+
199
+ async def _display_router_children(router_agent, agent_provider) -> None:
200
+ """Display child agents of a router agent in tree format."""
201
+ children = []
202
+
203
+ # Collect routing agents
204
+ if hasattr(router_agent, "routing_agents") and router_agent.routing_agents:
205
+ children = router_agent.routing_agents
206
+ elif hasattr(router_agent, "agents") and router_agent.agents:
207
+ children = router_agent.agents
208
+
209
+ # Display children with tree formatting
210
+ for i, child_agent in enumerate(children):
211
+ is_last = i == len(children) - 1
212
+ prefix = "└─" if is_last else "├─"
213
+ await _display_child_agent_info(child_agent, prefix, agent_provider)
214
+
215
+
216
+ async def _display_child_agent_info(child_agent, prefix: str, agent_provider) -> None:
217
+ """Display info for a child agent with tree prefix."""
218
+ try:
219
+ # Get counts for child agent
220
+ servers = await child_agent.list_servers()
221
+ server_count = len(servers) if servers else 0
222
+
223
+ tools_result = await child_agent.list_tools()
224
+ tool_count = (
225
+ len(tools_result.tools) if tools_result and hasattr(tools_result, "tools") else 0
226
+ )
227
+
228
+ resources_dict = await child_agent.list_resources()
229
+ resource_count = sum(len(resources) for resources in resources_dict.values()) if resources_dict else 0
230
+
231
+ prompts_dict = await child_agent.list_prompts()
232
+ prompt_count = sum(len(prompts) for prompts in prompts_dict.values()) if prompts_dict else 0
233
+
234
+ # Only display if child has MCP servers
235
+ if server_count > 0:
80
236
  # Pluralization helpers
81
237
  server_word = "Server" if server_count == 1 else "Servers"
82
238
  tool_word = "tool" if tool_count == 1 else "tools"
239
+ resource_word = "resource" if resource_count == 1 else "resources"
83
240
  prompt_word = "prompt" if prompt_count == 1 else "prompts"
84
241
 
85
242
  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]"
243
+ f"[dim] {prefix} [/dim][blue]{child_agent.name}[/blue][dim]:[/dim] {server_count:,}[dim] MCP {server_word}, [/dim]{tool_count:,}[dim] {tool_word}, [/dim]{resource_count:,}[dim] {resource_word}, [/dim]{prompt_count:,}[dim] {prompt_word} available[/dim]"
244
+ )
245
+ else:
246
+ # Show child even without MCP servers for context
247
+ rich_print(
248
+ f"[dim] {prefix} [/dim][blue]{child_agent.name}[/blue][dim]: No MCP Servers[/dim]"
87
249
  )
88
-
89
- # Mark as shown
90
- _agent_info_shown.add(agent_name)
91
250
 
92
251
  except Exception:
93
- # Silently ignore errors to not disrupt the user experience
94
- pass
252
+ # Fallback: just show the name
253
+ rich_print(f"[dim] {prefix} [/dim][blue]{child_agent.name}[/blue]")
95
254
 
96
255
 
97
256
  class AgentCompleter(Completer):
@@ -429,12 +588,13 @@ async def get_enhanced_input(
429
588
  rich_print("[dim]Type /help for commands. Ctrl+T toggles multiline mode.[/dim]")
430
589
  else:
431
590
  rich_print(
432
- "[dim]Type /help for commands, @agent to switch agent. Ctrl+T toggles multiline mode.[/dim]\n"
591
+ "[dim]Type '/' for commands, '@' to switch agent. Ctrl+T multiline, CTRL+E external editor.[/dim]\n"
433
592
  )
434
593
 
435
594
  # Display agent info right after help text if agent_provider is available
436
595
  if agent_provider and not is_human_input:
437
- await _display_agent_info_helper(agent_name, agent_provider)
596
+ # Display info for all available agents with tree structure for workflows
597
+ await _display_all_agents_with_hierarchy(available_agents, agent_provider)
438
598
 
439
599
  rich_print()
440
600
  help_message_shown = True
@@ -56,6 +56,7 @@ from mcp_agent.core.exceptions import (
56
56
  )
57
57
  from mcp_agent.core.usage_display import display_usage_report
58
58
  from mcp_agent.core.validation import (
59
+ validate_provider_keys_post_creation,
59
60
  validate_server_references,
60
61
  validate_workflow_references,
61
62
  )
@@ -313,6 +314,9 @@ class FastAgent:
313
314
  self.agents,
314
315
  model_factory_func,
315
316
  )
317
+
318
+ # Validate API keys after agent creation
319
+ validate_provider_keys_post_creation(active_agents)
316
320
 
317
321
  # Create a wrapper with all agents for simplified access
318
322
  wrapper = AgentApp(active_agents)
@@ -53,6 +53,7 @@ class PromptProvider(Protocol):
53
53
  async def apply_prompt(
54
54
  self,
55
55
  prompt_name: str,
56
+ prompt_title: Optional[str] = None,
56
57
  arguments: Optional[Dict[str, str]] = None,
57
58
  agent_name: Optional[str] = None,
58
59
  **kwargs,
@@ -243,9 +244,10 @@ class InteractivePrompt:
243
244
  "server": server_name,
244
245
  "name": prompt.name,
245
246
  "namespaced_name": f"{server_name}{SEP}{prompt.name}",
246
- "description": getattr(prompt, "description", "No description"),
247
- "arg_count": len(getattr(prompt, "arguments", [])),
248
- "arguments": getattr(prompt, "arguments", []),
247
+ "title": prompt.title or None,
248
+ "description": prompt.description or "No description",
249
+ "arg_count": len(prompt.arguments or []),
250
+ "arguments": prompt.arguments or [],
249
251
  }
250
252
  )
251
253
  elif isinstance(prompts_info, list) and prompts_info:
@@ -256,6 +258,7 @@ class InteractivePrompt:
256
258
  "server": server_name,
257
259
  "name": prompt["name"],
258
260
  "namespaced_name": f"{server_name}{SEP}{prompt['name']}",
261
+ "title": prompt.get("title", None),
259
262
  "description": prompt.get("description", "No description"),
260
263
  "arg_count": len(prompt.get("arguments", [])),
261
264
  "arguments": prompt.get("arguments", []),
@@ -263,17 +266,15 @@ class InteractivePrompt:
263
266
  )
264
267
  else:
265
268
  # Handle Prompt objects from mcp.types
266
- prompt_name = getattr(prompt, "name", str(prompt))
267
- description = getattr(prompt, "description", "No description")
268
- arguments = getattr(prompt, "arguments", [])
269
269
  all_prompts.append(
270
270
  {
271
271
  "server": server_name,
272
- "name": prompt_name,
273
- "namespaced_name": f"{server_name}{SEP}{prompt_name}",
274
- "description": description,
275
- "arg_count": len(arguments),
276
- "arguments": arguments,
272
+ "name": prompt.name,
273
+ "namespaced_name": f"{server_name}{SEP}{prompt.name}",
274
+ "title": prompt.title or None,
275
+ "description": prompt.description or "No description",
276
+ "arg_count": len(prompt.arguments or []),
277
+ "arguments": prompt.arguments or [],
277
278
  }
278
279
  )
279
280
 
@@ -314,6 +315,7 @@ class InteractivePrompt:
314
315
  table.add_column("#", justify="right", style="cyan")
315
316
  table.add_column("Server", style="green")
316
317
  table.add_column("Prompt Name", style="bright_blue")
318
+ table.add_column("Title")
317
319
  table.add_column("Description")
318
320
  table.add_column("Args", justify="center")
319
321
 
@@ -323,6 +325,7 @@ class InteractivePrompt:
323
325
  str(i + 1),
324
326
  prompt["server"],
325
327
  prompt["name"],
328
+ prompt["title"],
326
329
  prompt["description"],
327
330
  str(prompt["arg_count"]),
328
331
  )
@@ -378,7 +381,7 @@ class InteractivePrompt:
378
381
  continue
379
382
 
380
383
  # Extract prompts
381
- prompts = []
384
+ prompts: List[Prompt] = []
382
385
  if hasattr(prompts_info, "prompts"):
383
386
  prompts = prompts_info.prompts
384
387
  elif isinstance(prompts_info, list):
@@ -387,8 +390,9 @@ class InteractivePrompt:
387
390
  # Process each prompt
388
391
  for prompt in prompts:
389
392
  # Get basic prompt info
390
- prompt_name = getattr(prompt, "name", "Unknown")
391
- prompt_description = getattr(prompt, "description", "No description")
393
+ prompt_name = prompt.name
394
+ prompt_title = prompt.title or None
395
+ prompt_description = prompt.description or "No description"
392
396
 
393
397
  # Extract argument information
394
398
  arg_names = []
@@ -397,23 +401,19 @@ class InteractivePrompt:
397
401
  arg_descriptions = {}
398
402
 
399
403
  # Get arguments list
400
- arguments = getattr(prompt, "arguments", None)
401
- if arguments:
402
- for arg in arguments:
403
- name = getattr(arg, "name", None)
404
- if name:
405
- arg_names.append(name)
406
-
407
- # Store description if available
408
- description = getattr(arg, "description", None)
409
- if description:
410
- arg_descriptions[name] = description
411
-
412
- # Check if required
413
- if getattr(arg, "required", False):
414
- required_args.append(name)
415
- else:
416
- optional_args.append(name)
404
+ if prompt.arguments:
405
+ for arg in prompt.arguments:
406
+ arg_names.append(arg.name)
407
+
408
+ # Store description if available
409
+ if arg.description:
410
+ arg_descriptions[arg.name] = arg.description
411
+
412
+ # Check if required
413
+ if arg.required:
414
+ required_args.append(arg.name)
415
+ else:
416
+ optional_args.append(arg.name)
417
417
 
418
418
  # Create namespaced version using the consistent separator
419
419
  namespaced_name = f"{server_name}{SEP}{prompt_name}"
@@ -424,6 +424,7 @@ class InteractivePrompt:
424
424
  "server": server_name,
425
425
  "name": prompt_name,
426
426
  "namespaced_name": namespaced_name,
427
+ "title": prompt_title,
427
428
  "description": prompt_description,
428
429
  "arg_count": len(arg_names),
429
430
  "arg_names": arg_names,
@@ -486,6 +487,7 @@ class InteractivePrompt:
486
487
  table.add_column("#", justify="right", style="cyan")
487
488
  table.add_column("Server", style="green")
488
489
  table.add_column("Prompt Name", style="bright_blue")
490
+ table.add_column("Title")
489
491
  table.add_column("Description")
490
492
  table.add_column("Args", justify="center")
491
493
 
@@ -508,6 +510,7 @@ class InteractivePrompt:
508
510
  str(i + 1),
509
511
  prompt["server"],
510
512
  prompt["name"],
513
+ prompt["title"] or "No title",
511
514
  prompt["description"] or "No description",
512
515
  args_display,
513
516
  )
@@ -669,6 +672,7 @@ class InteractivePrompt:
669
672
  table = Table(title="Available MCP Tools")
670
673
  table.add_column("#", justify="right", style="cyan")
671
674
  table.add_column("Tool Name", style="bright_blue")
675
+ table.add_column("Title")
672
676
  table.add_column("Description")
673
677
 
674
678
  # Add tools to table
@@ -676,16 +680,12 @@ class InteractivePrompt:
676
680
  table.add_row(
677
681
  str(i + 1),
678
682
  tool.name,
679
- getattr(tool, "description", "No description") or "No description",
683
+ tool.title or "No title",
684
+ tool.description or "No description",
680
685
  )
681
686
 
682
687
  console.print(table)
683
688
 
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
689
  except Exception as e:
690
690
  import traceback
691
691