fast-agent-mcp 0.2.39__py3-none-any.whl → 0.2.41__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- {fast_agent_mcp-0.2.39.dist-info → fast_agent_mcp-0.2.41.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.2.39.dist-info → fast_agent_mcp-0.2.41.dist-info}/RECORD +41 -37
- {fast_agent_mcp-0.2.39.dist-info → fast_agent_mcp-0.2.41.dist-info}/entry_points.txt +2 -2
- mcp_agent/cli/__main__.py +29 -3
- mcp_agent/cli/commands/check_config.py +140 -81
- mcp_agent/cli/commands/go.py +151 -38
- mcp_agent/cli/commands/quickstart.py +8 -4
- mcp_agent/cli/commands/server_helpers.py +106 -0
- mcp_agent/cli/constants.py +25 -0
- mcp_agent/cli/main.py +1 -1
- mcp_agent/config.py +94 -44
- mcp_agent/core/agent_app.py +104 -15
- mcp_agent/core/agent_types.py +1 -0
- mcp_agent/core/direct_decorators.py +9 -0
- mcp_agent/core/direct_factory.py +18 -4
- mcp_agent/core/enhanced_prompt.py +165 -13
- mcp_agent/core/fastagent.py +4 -0
- mcp_agent/core/interactive_prompt.py +37 -37
- mcp_agent/core/usage_display.py +11 -1
- mcp_agent/core/validation.py +21 -2
- mcp_agent/human_input/elicitation_form.py +55 -22
- mcp_agent/llm/augmented_llm.py +28 -9
- mcp_agent/llm/augmented_llm_silent.py +48 -0
- mcp_agent/llm/model_database.py +20 -0
- mcp_agent/llm/model_factory.py +12 -0
- mcp_agent/llm/provider_key_manager.py +22 -8
- mcp_agent/llm/provider_types.py +19 -12
- mcp_agent/llm/providers/augmented_llm_anthropic.py +7 -2
- mcp_agent/llm/providers/augmented_llm_azure.py +7 -1
- mcp_agent/llm/providers/augmented_llm_google_native.py +4 -1
- mcp_agent/llm/providers/augmented_llm_openai.py +9 -2
- mcp_agent/llm/providers/augmented_llm_xai.py +38 -0
- mcp_agent/llm/usage_tracking.py +28 -3
- mcp_agent/mcp/mcp_agent_client_session.py +2 -0
- mcp_agent/mcp/mcp_aggregator.py +38 -44
- mcp_agent/mcp/sampling.py +15 -11
- mcp_agent/resources/examples/mcp/elicitations/forms_demo.py +0 -6
- mcp_agent/resources/examples/workflows/router.py +9 -0
- mcp_agent/ui/console_display.py +125 -13
- {fast_agent_mcp-0.2.39.dist-info → fast_agent_mcp-0.2.41.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.39.dist-info → fast_agent_mcp-0.2.41.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/agent_app.py
CHANGED
|
@@ -9,6 +9,7 @@ from mcp.types import PromptMessage
|
|
|
9
9
|
from rich import print as rich_print
|
|
10
10
|
|
|
11
11
|
from mcp_agent.agents.agent import Agent
|
|
12
|
+
from mcp_agent.core.agent_types import AgentType
|
|
12
13
|
from mcp_agent.core.interactive_prompt import InteractivePrompt
|
|
13
14
|
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
|
14
15
|
from mcp_agent.progress_display import progress_display
|
|
@@ -234,13 +235,14 @@ class AgentApp:
|
|
|
234
235
|
"""
|
|
235
236
|
return await self.interactive(agent_name=agent_name, default_prompt=default_prompt)
|
|
236
237
|
|
|
237
|
-
async def interactive(self, agent_name: str | None = None, default_prompt: str = "") -> str:
|
|
238
|
+
async def interactive(self, agent_name: str | None = None, default_prompt: str = "", pretty_print_parallel: bool = False) -> str:
|
|
238
239
|
"""
|
|
239
240
|
Interactive prompt for sending messages with advanced features.
|
|
240
241
|
|
|
241
242
|
Args:
|
|
242
243
|
agent_name: Optional target agent name (uses default if not specified)
|
|
243
244
|
default: Default message to use when user presses enter
|
|
245
|
+
pretty_print_parallel: Enable clean parallel results display for parallel agents
|
|
244
246
|
|
|
245
247
|
Returns:
|
|
246
248
|
The result of the interactive session
|
|
@@ -275,10 +277,18 @@ class AgentApp:
|
|
|
275
277
|
# Define the wrapper for send function
|
|
276
278
|
async def send_wrapper(message, agent_name):
|
|
277
279
|
result = await self.send(message, agent_name)
|
|
278
|
-
|
|
280
|
+
|
|
281
|
+
# Show parallel results if enabled and this is a parallel agent
|
|
282
|
+
if pretty_print_parallel:
|
|
283
|
+
agent = self._agents.get(agent_name)
|
|
284
|
+
if agent and agent.agent_type == AgentType.PARALLEL:
|
|
285
|
+
from mcp_agent.ui.console_display import ConsoleDisplay
|
|
286
|
+
display = ConsoleDisplay(config=None)
|
|
287
|
+
display.show_parallel_results(agent)
|
|
288
|
+
|
|
279
289
|
# Show usage info after each turn if progress display is enabled
|
|
280
290
|
self._show_turn_usage(agent_name)
|
|
281
|
-
|
|
291
|
+
|
|
282
292
|
return result
|
|
283
293
|
|
|
284
294
|
# Start the prompt loop with the agent name (not the agent object)
|
|
@@ -293,32 +303,111 @@ class AgentApp:
|
|
|
293
303
|
def _show_turn_usage(self, agent_name: str) -> None:
|
|
294
304
|
"""Show subtle usage information after each turn."""
|
|
295
305
|
agent = self._agents.get(agent_name)
|
|
296
|
-
if not agent
|
|
306
|
+
if not agent:
|
|
297
307
|
return
|
|
298
|
-
|
|
308
|
+
|
|
309
|
+
# Check if this is a parallel agent
|
|
310
|
+
if agent.agent_type == AgentType.PARALLEL:
|
|
311
|
+
self._show_parallel_agent_usage(agent)
|
|
312
|
+
else:
|
|
313
|
+
self._show_regular_agent_usage(agent)
|
|
314
|
+
|
|
315
|
+
def _show_regular_agent_usage(self, agent) -> None:
|
|
316
|
+
"""Show usage for a regular (non-parallel) agent."""
|
|
317
|
+
usage_info = self._format_agent_usage(agent)
|
|
318
|
+
if usage_info:
|
|
319
|
+
with progress_display.paused():
|
|
320
|
+
rich_print(
|
|
321
|
+
f"[dim]Last turn: {usage_info['display_text']}[/dim]{usage_info['cache_suffix']}"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def _show_parallel_agent_usage(self, parallel_agent) -> None:
|
|
325
|
+
"""Show usage for a parallel agent and its children."""
|
|
326
|
+
# Collect usage from all child agents
|
|
327
|
+
child_usage_data = []
|
|
328
|
+
total_input = 0
|
|
329
|
+
total_output = 0
|
|
330
|
+
total_tool_calls = 0
|
|
331
|
+
|
|
332
|
+
# Get usage from fan-out agents
|
|
333
|
+
if hasattr(parallel_agent, "fan_out_agents") and parallel_agent.fan_out_agents:
|
|
334
|
+
for child_agent in parallel_agent.fan_out_agents:
|
|
335
|
+
usage_info = self._format_agent_usage(child_agent)
|
|
336
|
+
if usage_info:
|
|
337
|
+
child_usage_data.append({**usage_info, "name": child_agent.name})
|
|
338
|
+
total_input += usage_info["input_tokens"]
|
|
339
|
+
total_output += usage_info["output_tokens"]
|
|
340
|
+
total_tool_calls += usage_info["tool_calls"]
|
|
341
|
+
|
|
342
|
+
# Get usage from fan-in agent
|
|
343
|
+
if hasattr(parallel_agent, "fan_in_agent") and parallel_agent.fan_in_agent:
|
|
344
|
+
usage_info = self._format_agent_usage(parallel_agent.fan_in_agent)
|
|
345
|
+
if usage_info:
|
|
346
|
+
child_usage_data.append({**usage_info, "name": parallel_agent.fan_in_agent.name})
|
|
347
|
+
total_input += usage_info["input_tokens"]
|
|
348
|
+
total_output += usage_info["output_tokens"]
|
|
349
|
+
total_tool_calls += usage_info["tool_calls"]
|
|
350
|
+
|
|
351
|
+
if not child_usage_data:
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
# Show aggregated usage for parallel agent (no context percentage)
|
|
355
|
+
with progress_display.paused():
|
|
356
|
+
tool_info = f", {total_tool_calls} tool calls" if total_tool_calls > 0 else ""
|
|
357
|
+
rich_print(
|
|
358
|
+
f"[dim]Last turn (parallel): {total_input:,} Input, {total_output:,} Output{tool_info}[/dim]"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Show individual child agent usage
|
|
362
|
+
for i, usage_data in enumerate(child_usage_data):
|
|
363
|
+
is_last = i == len(child_usage_data) - 1
|
|
364
|
+
prefix = "└─" if is_last else "├─"
|
|
365
|
+
rich_print(
|
|
366
|
+
f"[dim] {prefix} {usage_data['name']}: {usage_data['display_text']}[/dim]{usage_data['cache_suffix']}"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
def _format_agent_usage(self, agent) -> Optional[Dict]:
|
|
370
|
+
"""Format usage information for a single agent."""
|
|
371
|
+
if not agent or not agent.usage_accumulator:
|
|
372
|
+
return None
|
|
373
|
+
|
|
299
374
|
# Get the last turn's usage (if any)
|
|
300
375
|
turns = agent.usage_accumulator.turns
|
|
301
376
|
if not turns:
|
|
302
|
-
return
|
|
303
|
-
|
|
377
|
+
return None
|
|
378
|
+
|
|
304
379
|
last_turn = turns[-1]
|
|
305
380
|
input_tokens = last_turn.display_input_tokens
|
|
306
381
|
output_tokens = last_turn.output_tokens
|
|
307
|
-
|
|
382
|
+
|
|
308
383
|
# Build cache indicators with bright colors
|
|
309
384
|
cache_indicators = ""
|
|
310
385
|
if last_turn.cache_usage.cache_write_tokens > 0:
|
|
311
386
|
cache_indicators += "[bright_yellow]^[/bright_yellow]"
|
|
312
|
-
if
|
|
387
|
+
if (
|
|
388
|
+
last_turn.cache_usage.cache_read_tokens > 0
|
|
389
|
+
or last_turn.cache_usage.cache_hit_tokens > 0
|
|
390
|
+
):
|
|
313
391
|
cache_indicators += "[bright_green]*[/bright_green]"
|
|
314
|
-
|
|
392
|
+
|
|
315
393
|
# Build context percentage - get from accumulator, not individual turn
|
|
316
394
|
context_info = ""
|
|
317
395
|
context_percentage = agent.usage_accumulator.context_usage_percentage
|
|
318
396
|
if context_percentage is not None:
|
|
319
397
|
context_info = f" ({context_percentage:.1f}%)"
|
|
320
|
-
|
|
321
|
-
#
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
398
|
+
|
|
399
|
+
# Build tool call info
|
|
400
|
+
tool_info = f", {last_turn.tool_calls} tool calls" if last_turn.tool_calls > 0 else ""
|
|
401
|
+
|
|
402
|
+
# Build display text
|
|
403
|
+
display_text = f"{input_tokens:,} Input, {output_tokens:,} Output{tool_info}{context_info}"
|
|
404
|
+
cache_suffix = f" {cache_indicators}" if cache_indicators else ""
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
"input_tokens": input_tokens,
|
|
408
|
+
"output_tokens": output_tokens,
|
|
409
|
+
"tool_calls": last_turn.tool_calls,
|
|
410
|
+
"context_percentage": context_percentage,
|
|
411
|
+
"display_text": display_text,
|
|
412
|
+
"cache_suffix": cache_suffix,
|
|
413
|
+
}
|
mcp_agent/core/agent_types.py
CHANGED
|
@@ -38,6 +38,7 @@ class AgentConfig:
|
|
|
38
38
|
agent_type: AgentType = AgentType.BASIC
|
|
39
39
|
default: bool = False
|
|
40
40
|
elicitation_handler: ElicitationFnT | None = None
|
|
41
|
+
api_key: str | None = None
|
|
41
42
|
|
|
42
43
|
def __post_init__(self):
|
|
43
44
|
"""Ensure default_request_params exists with proper history setting"""
|
|
@@ -138,6 +138,7 @@ def _decorator_impl(
|
|
|
138
138
|
human_input=human_input,
|
|
139
139
|
default=default,
|
|
140
140
|
elicitation_handler=extra_kwargs.get("elicitation_handler"),
|
|
141
|
+
api_key=extra_kwargs.get("api_key"),
|
|
141
142
|
)
|
|
142
143
|
|
|
143
144
|
# Update request params if provided
|
|
@@ -182,6 +183,7 @@ def agent(
|
|
|
182
183
|
human_input: bool = False,
|
|
183
184
|
default: bool = False,
|
|
184
185
|
elicitation_handler: Optional[ElicitationFnT] = None,
|
|
186
|
+
api_key: str | None = None,
|
|
185
187
|
) -> Callable[[AgentCallable[P, R]], DecoratedAgentProtocol[P, R]]:
|
|
186
188
|
"""
|
|
187
189
|
Decorator to create and register a standard agent with type-safe signature.
|
|
@@ -215,6 +217,7 @@ def agent(
|
|
|
215
217
|
human_input=human_input,
|
|
216
218
|
default=default,
|
|
217
219
|
elicitation_handler=elicitation_handler,
|
|
220
|
+
api_key=api_key,
|
|
218
221
|
)
|
|
219
222
|
|
|
220
223
|
|
|
@@ -232,6 +235,7 @@ def custom(
|
|
|
232
235
|
human_input: bool = False,
|
|
233
236
|
default: bool = False,
|
|
234
237
|
elicitation_handler: Optional[ElicitationFnT] = None,
|
|
238
|
+
api_key: str | None = None,
|
|
235
239
|
) -> Callable[[AgentCallable[P, R]], DecoratedAgentProtocol[P, R]]:
|
|
236
240
|
"""
|
|
237
241
|
Decorator to create and register a standard agent with type-safe signature.
|
|
@@ -265,6 +269,7 @@ def custom(
|
|
|
265
269
|
agent_class=cls,
|
|
266
270
|
default=default,
|
|
267
271
|
elicitation_handler=elicitation_handler,
|
|
272
|
+
api_key=api_key,
|
|
268
273
|
)
|
|
269
274
|
|
|
270
275
|
|
|
@@ -288,6 +293,7 @@ def orchestrator(
|
|
|
288
293
|
plan_type: Literal["full", "iterative"] = "full",
|
|
289
294
|
plan_iterations: int = 5,
|
|
290
295
|
default: bool = False,
|
|
296
|
+
api_key: str | None = None,
|
|
291
297
|
) -> Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]:
|
|
292
298
|
"""
|
|
293
299
|
Decorator to create and register an orchestrator agent with type-safe signature.
|
|
@@ -326,6 +332,7 @@ def orchestrator(
|
|
|
326
332
|
plan_type=plan_type,
|
|
327
333
|
plan_iterations=plan_iterations,
|
|
328
334
|
default=default,
|
|
335
|
+
api_key=api_key,
|
|
329
336
|
),
|
|
330
337
|
)
|
|
331
338
|
|
|
@@ -345,6 +352,7 @@ def router(
|
|
|
345
352
|
elicitation_handler: Optional[
|
|
346
353
|
ElicitationFnT
|
|
347
354
|
] = None, ## exclude from docs, decide whether allowable
|
|
355
|
+
api_key: str | None = None,
|
|
348
356
|
) -> Callable[[AgentCallable[P, R]], DecoratedRouterProtocol[P, R]]:
|
|
349
357
|
"""
|
|
350
358
|
Decorator to create and register a router agent with type-safe signature.
|
|
@@ -383,6 +391,7 @@ def router(
|
|
|
383
391
|
default=default,
|
|
384
392
|
router_agents=agents,
|
|
385
393
|
elicitation_handler=elicitation_handler,
|
|
394
|
+
api_key=api_key,
|
|
386
395
|
),
|
|
387
396
|
)
|
|
388
397
|
|
mcp_agent/core/direct_factory.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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,
|
|
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(
|
|
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:
|
|
@@ -71,27 +71,178 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: object) ->
|
|
|
71
71
|
prompts_dict = await agent.list_prompts()
|
|
72
72
|
prompt_count = sum(len(prompts) for prompts in prompts_dict.values()) if prompts_dict else 0
|
|
73
73
|
|
|
74
|
-
#
|
|
75
|
-
if
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
74
|
+
# Handle different agent types
|
|
75
|
+
if agent.agent_type == AgentType.PARALLEL:
|
|
76
|
+
# Count child agents for parallel agents
|
|
77
|
+
child_count = 0
|
|
78
|
+
if hasattr(agent, "fan_out_agents") and agent.fan_out_agents:
|
|
79
|
+
child_count += len(agent.fan_out_agents)
|
|
80
|
+
if hasattr(agent, "fan_in_agent") and agent.fan_in_agent:
|
|
81
|
+
child_count += 1
|
|
82
|
+
|
|
83
|
+
if child_count > 0:
|
|
84
|
+
child_word = "child agent" if child_count == 1 else "child agents"
|
|
85
|
+
rich_print(
|
|
86
|
+
f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {child_count:,}[dim] {child_word}[/dim]"
|
|
87
|
+
)
|
|
88
|
+
elif agent.agent_type == AgentType.ROUTER:
|
|
89
|
+
# Count child agents for router agents
|
|
90
|
+
child_count = 0
|
|
91
|
+
if hasattr(agent, "routing_agents") and agent.routing_agents:
|
|
92
|
+
child_count = len(agent.routing_agents)
|
|
93
|
+
elif hasattr(agent, "agents") and agent.agents:
|
|
94
|
+
child_count = len(agent.agents)
|
|
95
|
+
|
|
96
|
+
if child_count > 0:
|
|
97
|
+
child_word = "child agent" if child_count == 1 else "child agents"
|
|
98
|
+
rich_print(
|
|
99
|
+
f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {child_count:,}[dim] {child_word}[/dim]"
|
|
100
|
+
)
|
|
79
101
|
else:
|
|
102
|
+
# For regular agents, only display if they have MCP servers attached
|
|
103
|
+
if server_count > 0:
|
|
104
|
+
# Pluralization helpers
|
|
105
|
+
server_word = "Server" if server_count == 1 else "Servers"
|
|
106
|
+
tool_word = "tool" if tool_count == 1 else "tools"
|
|
107
|
+
prompt_word = "prompt" if prompt_count == 1 else "prompts"
|
|
108
|
+
|
|
109
|
+
rich_print(
|
|
110
|
+
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]"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Mark as shown
|
|
114
|
+
_agent_info_shown.add(agent_name)
|
|
115
|
+
|
|
116
|
+
except Exception:
|
|
117
|
+
# Silently ignore errors to not disrupt the user experience
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def _display_all_agents_with_hierarchy(available_agents: List[str], agent_provider) -> None:
|
|
122
|
+
"""Display all agents with tree structure for workflow agents."""
|
|
123
|
+
# Track which agents are children to avoid displaying them twice
|
|
124
|
+
child_agents = set()
|
|
125
|
+
|
|
126
|
+
# First pass: identify all child agents
|
|
127
|
+
for agent_name in available_agents:
|
|
128
|
+
try:
|
|
129
|
+
if hasattr(agent_provider, "_agent"):
|
|
130
|
+
agent = agent_provider._agent(agent_name)
|
|
131
|
+
else:
|
|
132
|
+
agent = agent_provider
|
|
133
|
+
|
|
134
|
+
if agent.agent_type == AgentType.PARALLEL:
|
|
135
|
+
if hasattr(agent, "fan_out_agents") and agent.fan_out_agents:
|
|
136
|
+
for child_agent in agent.fan_out_agents:
|
|
137
|
+
child_agents.add(child_agent.name)
|
|
138
|
+
if hasattr(agent, "fan_in_agent") and agent.fan_in_agent:
|
|
139
|
+
child_agents.add(agent.fan_in_agent.name)
|
|
140
|
+
elif agent.agent_type == AgentType.ROUTER:
|
|
141
|
+
if hasattr(agent, "routing_agents") and agent.routing_agents:
|
|
142
|
+
for child_agent in agent.routing_agents:
|
|
143
|
+
child_agents.add(child_agent.name)
|
|
144
|
+
elif hasattr(agent, "agents") and agent.agents:
|
|
145
|
+
for child_agent in agent.agents:
|
|
146
|
+
child_agents.add(child_agent.name)
|
|
147
|
+
except Exception:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Second pass: display agents (parents with children, standalone agents without children)
|
|
151
|
+
for agent_name in sorted(available_agents):
|
|
152
|
+
# Skip if this agent is a child of another agent
|
|
153
|
+
if agent_name in child_agents:
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
if hasattr(agent_provider, "_agent"):
|
|
158
|
+
agent = agent_provider._agent(agent_name)
|
|
159
|
+
else:
|
|
160
|
+
agent = agent_provider
|
|
161
|
+
|
|
162
|
+
# Display parent agent
|
|
163
|
+
await _display_agent_info_helper(agent_name, agent_provider)
|
|
164
|
+
|
|
165
|
+
# If it's a workflow agent, display its children
|
|
166
|
+
if agent.agent_type == AgentType.PARALLEL:
|
|
167
|
+
await _display_parallel_children(agent, agent_provider)
|
|
168
|
+
elif agent.agent_type == AgentType.ROUTER:
|
|
169
|
+
await _display_router_children(agent, agent_provider)
|
|
170
|
+
|
|
171
|
+
except Exception:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def _display_parallel_children(parallel_agent, agent_provider) -> None:
|
|
176
|
+
"""Display child agents of a parallel agent in tree format."""
|
|
177
|
+
children = []
|
|
178
|
+
|
|
179
|
+
# Collect fan-out agents
|
|
180
|
+
if hasattr(parallel_agent, "fan_out_agents") and parallel_agent.fan_out_agents:
|
|
181
|
+
for child_agent in parallel_agent.fan_out_agents:
|
|
182
|
+
children.append(child_agent)
|
|
183
|
+
|
|
184
|
+
# Collect fan-in agent
|
|
185
|
+
if hasattr(parallel_agent, "fan_in_agent") and parallel_agent.fan_in_agent:
|
|
186
|
+
children.append(parallel_agent.fan_in_agent)
|
|
187
|
+
|
|
188
|
+
# Display children with tree formatting
|
|
189
|
+
for i, child_agent in enumerate(children):
|
|
190
|
+
is_last = i == len(children) - 1
|
|
191
|
+
prefix = "└─" if is_last else "├─"
|
|
192
|
+
await _display_child_agent_info(child_agent, prefix, agent_provider)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def _display_router_children(router_agent, agent_provider) -> None:
|
|
196
|
+
"""Display child agents of a router agent in tree format."""
|
|
197
|
+
children = []
|
|
198
|
+
|
|
199
|
+
# Collect routing agents
|
|
200
|
+
if hasattr(router_agent, "routing_agents") and router_agent.routing_agents:
|
|
201
|
+
children = router_agent.routing_agents
|
|
202
|
+
elif hasattr(router_agent, "agents") and router_agent.agents:
|
|
203
|
+
children = router_agent.agents
|
|
204
|
+
|
|
205
|
+
# Display children with tree formatting
|
|
206
|
+
for i, child_agent in enumerate(children):
|
|
207
|
+
is_last = i == len(children) - 1
|
|
208
|
+
prefix = "└─" if is_last else "├─"
|
|
209
|
+
await _display_child_agent_info(child_agent, prefix, agent_provider)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
async def _display_child_agent_info(child_agent, prefix: str, agent_provider) -> None:
|
|
213
|
+
"""Display info for a child agent with tree prefix."""
|
|
214
|
+
try:
|
|
215
|
+
# Get counts for child agent
|
|
216
|
+
servers = await child_agent.list_servers()
|
|
217
|
+
server_count = len(servers) if servers else 0
|
|
218
|
+
|
|
219
|
+
tools_result = await child_agent.list_tools()
|
|
220
|
+
tool_count = (
|
|
221
|
+
len(tools_result.tools) if tools_result and hasattr(tools_result, "tools") else 0
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
prompts_dict = await child_agent.list_prompts()
|
|
225
|
+
prompt_count = sum(len(prompts) for prompts in prompts_dict.values()) if prompts_dict else 0
|
|
226
|
+
|
|
227
|
+
# Only display if child has MCP servers
|
|
228
|
+
if server_count > 0:
|
|
80
229
|
# Pluralization helpers
|
|
81
230
|
server_word = "Server" if server_count == 1 else "Servers"
|
|
82
231
|
tool_word = "tool" if tool_count == 1 else "tools"
|
|
83
232
|
prompt_word = "prompt" if prompt_count == 1 else "prompts"
|
|
84
233
|
|
|
85
234
|
rich_print(
|
|
86
|
-
f"[dim]
|
|
235
|
+
f"[dim] {prefix} [/dim][blue]{child_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]"
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
# Show child even without MCP servers for context
|
|
239
|
+
rich_print(
|
|
240
|
+
f"[dim] {prefix} [/dim][blue]{child_agent.name}[/blue][dim]: No MCP Servers[/dim]"
|
|
87
241
|
)
|
|
88
|
-
|
|
89
|
-
# Mark as shown
|
|
90
|
-
_agent_info_shown.add(agent_name)
|
|
91
242
|
|
|
92
243
|
except Exception:
|
|
93
|
-
#
|
|
94
|
-
|
|
244
|
+
# Fallback: just show the name
|
|
245
|
+
rich_print(f"[dim] {prefix} [/dim][blue]{child_agent.name}[/blue]")
|
|
95
246
|
|
|
96
247
|
|
|
97
248
|
class AgentCompleter(Completer):
|
|
@@ -429,12 +580,13 @@ async def get_enhanced_input(
|
|
|
429
580
|
rich_print("[dim]Type /help for commands. Ctrl+T toggles multiline mode.[/dim]")
|
|
430
581
|
else:
|
|
431
582
|
rich_print(
|
|
432
|
-
"[dim]Type /
|
|
583
|
+
"[dim]Type '/' for commands, '@' to switch agent. Ctrl+T multiline, CTRL+E external editor.[/dim]\n"
|
|
433
584
|
)
|
|
434
585
|
|
|
435
586
|
# Display agent info right after help text if agent_provider is available
|
|
436
587
|
if agent_provider and not is_human_input:
|
|
437
|
-
|
|
588
|
+
# Display info for all available agents with tree structure for workflows
|
|
589
|
+
await _display_all_agents_with_hierarchy(available_agents, agent_provider)
|
|
438
590
|
|
|
439
591
|
rich_print()
|
|
440
592
|
help_message_shown = True
|
mcp_agent/core/fastagent.py
CHANGED
|
@@ -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)
|