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.
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/METADATA +2 -1
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/RECORD +45 -40
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/entry_points.txt +2 -2
- mcp_agent/agents/base_agent.py +111 -1
- 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 +6 -2
- 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 +111 -44
- mcp_agent/core/agent_app.py +104 -15
- mcp_agent/core/agent_types.py +5 -1
- mcp_agent/core/direct_decorators.py +38 -0
- mcp_agent/core/direct_factory.py +18 -4
- mcp_agent/core/enhanced_prompt.py +173 -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 +53 -21
- 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 +21 -0
- mcp_agent/llm/provider_key_manager.py +22 -8
- mcp_agent/llm/provider_types.py +20 -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_bedrock.py +1787 -0
- mcp_agent/llm/providers/augmented_llm_google_native.py +4 -1
- mcp_agent/llm/providers/augmented_llm_openai.py +12 -3
- mcp_agent/llm/providers/augmented_llm_xai.py +38 -0
- mcp_agent/llm/usage_tracking.py +28 -3
- mcp_agent/logging/logger.py +7 -0
- mcp_agent/mcp/hf_auth.py +32 -4
- 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.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Helper functions for server configuration and naming."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_server_name(identifier: str) -> str:
|
|
7
|
+
"""Generate a clean server name from various identifiers.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
identifier: Package name, file path, or other identifier
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Clean server name with only alphanumeric and underscore characters
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
>>> generate_server_name("@modelcontextprotocol/server-filesystem")
|
|
17
|
+
'server_filesystem'
|
|
18
|
+
>>> generate_server_name("./src/my-server.py")
|
|
19
|
+
'src_my_server'
|
|
20
|
+
>>> generate_server_name("my-mcp-server")
|
|
21
|
+
'my_mcp_server'
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Remove leading ./ if present
|
|
25
|
+
if identifier.startswith("./"):
|
|
26
|
+
identifier = identifier[2:]
|
|
27
|
+
|
|
28
|
+
# Handle npm package names with org prefix (only if no file extension)
|
|
29
|
+
has_file_ext = any(identifier.endswith(ext) for ext in [".py", ".js", ".ts"])
|
|
30
|
+
if "/" in identifier and not has_file_ext:
|
|
31
|
+
# This is likely an npm package, take the part after the last slash
|
|
32
|
+
identifier = identifier.split("/")[-1]
|
|
33
|
+
|
|
34
|
+
# Remove file extension for common script files
|
|
35
|
+
for ext in [".py", ".js", ".ts"]:
|
|
36
|
+
if identifier.endswith(ext):
|
|
37
|
+
identifier = identifier[: -len(ext)]
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
# Replace special characters with underscores
|
|
41
|
+
# Remove @ prefix if present
|
|
42
|
+
identifier = identifier.lstrip("@")
|
|
43
|
+
|
|
44
|
+
# Replace non-alphanumeric characters with underscores
|
|
45
|
+
server_name = ""
|
|
46
|
+
for char in identifier:
|
|
47
|
+
if char.isalnum():
|
|
48
|
+
server_name += char
|
|
49
|
+
else:
|
|
50
|
+
server_name += "_"
|
|
51
|
+
|
|
52
|
+
# Clean up multiple underscores
|
|
53
|
+
while "__" in server_name:
|
|
54
|
+
server_name = server_name.replace("__", "_")
|
|
55
|
+
|
|
56
|
+
# Remove leading/trailing underscores
|
|
57
|
+
server_name = server_name.strip("_")
|
|
58
|
+
|
|
59
|
+
return server_name
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def add_servers_to_config(fast_app: Any, servers: Dict[str, Dict[str, Any]]) -> None:
|
|
63
|
+
"""Add server configurations to the FastAgent app config.
|
|
64
|
+
|
|
65
|
+
This function handles the repetitive initialization and configuration
|
|
66
|
+
of MCP servers, ensuring the app is initialized and the config
|
|
67
|
+
structure exists before adding servers.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
fast_app: The FastAgent instance
|
|
71
|
+
servers: Dictionary of server configurations
|
|
72
|
+
"""
|
|
73
|
+
if not servers:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
from mcp_agent.config import MCPServerSettings, MCPSettings
|
|
77
|
+
|
|
78
|
+
# Initialize the app to ensure context is ready
|
|
79
|
+
await fast_app.app.initialize()
|
|
80
|
+
|
|
81
|
+
# Initialize mcp settings if needed
|
|
82
|
+
if not hasattr(fast_app.app.context.config, "mcp"):
|
|
83
|
+
fast_app.app.context.config.mcp = MCPSettings()
|
|
84
|
+
|
|
85
|
+
# Initialize servers dictionary if needed
|
|
86
|
+
if (
|
|
87
|
+
not hasattr(fast_app.app.context.config.mcp, "servers")
|
|
88
|
+
or fast_app.app.context.config.mcp.servers is None
|
|
89
|
+
):
|
|
90
|
+
fast_app.app.context.config.mcp.servers = {}
|
|
91
|
+
|
|
92
|
+
# Add each server to the config
|
|
93
|
+
for server_name, server_config in servers.items():
|
|
94
|
+
# Build server settings based on transport type
|
|
95
|
+
server_settings = {"transport": server_config["transport"]}
|
|
96
|
+
|
|
97
|
+
# Add transport-specific settings
|
|
98
|
+
if server_config["transport"] == "stdio":
|
|
99
|
+
server_settings["command"] = server_config["command"]
|
|
100
|
+
server_settings["args"] = server_config["args"]
|
|
101
|
+
elif server_config["transport"] in ["http", "sse"]:
|
|
102
|
+
server_settings["url"] = server_config["url"]
|
|
103
|
+
if "headers" in server_config:
|
|
104
|
+
server_settings["headers"] = server_config["headers"]
|
|
105
|
+
|
|
106
|
+
fast_app.app.context.config.mcp.servers[server_name] = MCPServerSettings(**server_settings)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Shared constants for CLI routing and commands."""
|
|
2
|
+
|
|
3
|
+
# Options that should automatically route to the 'go' command
|
|
4
|
+
GO_SPECIFIC_OPTIONS = {
|
|
5
|
+
"--npx",
|
|
6
|
+
"--uvx",
|
|
7
|
+
"--stdio",
|
|
8
|
+
"--url",
|
|
9
|
+
"--model",
|
|
10
|
+
"--models",
|
|
11
|
+
"--instruction",
|
|
12
|
+
"-i",
|
|
13
|
+
"--message",
|
|
14
|
+
"-m",
|
|
15
|
+
"--prompt-file",
|
|
16
|
+
"-p",
|
|
17
|
+
"--servers",
|
|
18
|
+
"--auth",
|
|
19
|
+
"--name",
|
|
20
|
+
"--config-path",
|
|
21
|
+
"-c",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Known subcommands that should not trigger auto-routing
|
|
25
|
+
KNOWN_SUBCOMMANDS = {"go", "setup", "check", "bootstrap", "quickstart", "--help", "-h", "--version"}
|
mcp_agent/cli/main.py
CHANGED
|
@@ -48,7 +48,7 @@ def show_welcome() -> None:
|
|
|
48
48
|
console.print(table)
|
|
49
49
|
|
|
50
50
|
console.print(
|
|
51
|
-
"\n[italic]get started with:[/italic] [bold][cyan]fast-agent[/cyan][/bold] [green]setup[/green]"
|
|
51
|
+
"\n[italic]get started with:[/italic] [bold][cyan]fast-agent[/cyan][/bold] [green]setup[/green]. visit [cyan][link=https://fast-agent.ai]fast-agent.ai[/link][/cyan] for more information."
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
|
mcp_agent/config.py
CHANGED
|
@@ -6,7 +6,7 @@ for the application configuration.
|
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any, Dict, List, Literal, Optional
|
|
9
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, ConfigDict, field_validator
|
|
12
12
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
@@ -175,6 +175,17 @@ class GoogleSettings(BaseModel):
|
|
|
175
175
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
class XAISettings(BaseModel):
|
|
179
|
+
"""
|
|
180
|
+
Settings for using xAI Grok models in the fast-agent application.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
api_key: str | None = None
|
|
184
|
+
base_url: str | None = "https://api.x.ai/v1"
|
|
185
|
+
|
|
186
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
187
|
+
|
|
188
|
+
|
|
178
189
|
class GenericSettings(BaseModel):
|
|
179
190
|
"""
|
|
180
191
|
Settings for using OpenAI models in the fast-agent application.
|
|
@@ -242,6 +253,20 @@ class TensorZeroSettings(BaseModel):
|
|
|
242
253
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
243
254
|
|
|
244
255
|
|
|
256
|
+
class BedrockSettings(BaseModel):
|
|
257
|
+
"""
|
|
258
|
+
Settings for using AWS Bedrock models in the fast-agent application.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
region: str | None = None
|
|
262
|
+
"""AWS region for Bedrock service"""
|
|
263
|
+
|
|
264
|
+
profile: str | None = None
|
|
265
|
+
"""AWS profile to use for authentication"""
|
|
266
|
+
|
|
267
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
268
|
+
|
|
269
|
+
|
|
245
270
|
class HuggingFaceSettings(BaseModel):
|
|
246
271
|
"""
|
|
247
272
|
Settings for HuggingFace authentication (used for MCP connections).
|
|
@@ -296,6 +321,57 @@ class LoggerSettings(BaseModel):
|
|
|
296
321
|
"""Enable markup in console output. Disable for outputs that may conflict with rich console formatting"""
|
|
297
322
|
|
|
298
323
|
|
|
324
|
+
def find_fastagent_config_files(start_path: Path) -> Tuple[Optional[Path], Optional[Path]]:
|
|
325
|
+
"""
|
|
326
|
+
Find FastAgent configuration files with standardized behavior.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Tuple of (config_path, secrets_path) where either can be None if not found.
|
|
330
|
+
|
|
331
|
+
Strategy:
|
|
332
|
+
1. Find config file recursively from start_path upward
|
|
333
|
+
2. Prefer secrets file in same directory as config file
|
|
334
|
+
3. If no secrets file next to config, search recursively from start_path
|
|
335
|
+
"""
|
|
336
|
+
config_path = None
|
|
337
|
+
secrets_path = None
|
|
338
|
+
|
|
339
|
+
# First, find the config file with recursive search
|
|
340
|
+
current = start_path.resolve()
|
|
341
|
+
while current != current.parent:
|
|
342
|
+
potential_config = current / "fastagent.config.yaml"
|
|
343
|
+
if potential_config.exists():
|
|
344
|
+
config_path = potential_config
|
|
345
|
+
break
|
|
346
|
+
current = current.parent
|
|
347
|
+
|
|
348
|
+
# If config file found, prefer secrets file in the same directory
|
|
349
|
+
if config_path:
|
|
350
|
+
potential_secrets = config_path.parent / "fastagent.secrets.yaml"
|
|
351
|
+
if potential_secrets.exists():
|
|
352
|
+
secrets_path = potential_secrets
|
|
353
|
+
else:
|
|
354
|
+
# If no secrets file next to config, do recursive search from start
|
|
355
|
+
current = start_path.resolve()
|
|
356
|
+
while current != current.parent:
|
|
357
|
+
potential_secrets = current / "fastagent.secrets.yaml"
|
|
358
|
+
if potential_secrets.exists():
|
|
359
|
+
secrets_path = potential_secrets
|
|
360
|
+
break
|
|
361
|
+
current = current.parent
|
|
362
|
+
else:
|
|
363
|
+
# No config file found, just search for secrets file
|
|
364
|
+
current = start_path.resolve()
|
|
365
|
+
while current != current.parent:
|
|
366
|
+
potential_secrets = current / "fastagent.secrets.yaml"
|
|
367
|
+
if potential_secrets.exists():
|
|
368
|
+
secrets_path = potential_secrets
|
|
369
|
+
break
|
|
370
|
+
current = current.parent
|
|
371
|
+
|
|
372
|
+
return config_path, secrets_path
|
|
373
|
+
|
|
374
|
+
|
|
299
375
|
class Settings(BaseSettings):
|
|
300
376
|
"""
|
|
301
377
|
Settings class for the fast-agent application.
|
|
@@ -339,6 +415,9 @@ class Settings(BaseSettings):
|
|
|
339
415
|
google: GoogleSettings | None = None
|
|
340
416
|
"""Settings for using DeepSeek models in the fast-agent application"""
|
|
341
417
|
|
|
418
|
+
xai: XAISettings | None = None
|
|
419
|
+
"""Settings for using xAI Grok models in the fast-agent application"""
|
|
420
|
+
|
|
342
421
|
openrouter: OpenRouterSettings | None = None
|
|
343
422
|
"""Settings for using OpenRouter models in the fast-agent application"""
|
|
344
423
|
|
|
@@ -354,6 +433,9 @@ class Settings(BaseSettings):
|
|
|
354
433
|
aliyun: OpenAISettings | None = None
|
|
355
434
|
"""Settings for using Aliyun OpenAI Service in the fast-agent application"""
|
|
356
435
|
|
|
436
|
+
bedrock: BedrockSettings | None = None
|
|
437
|
+
"""Settings for using AWS Bedrock models in the fast-agent application"""
|
|
438
|
+
|
|
357
439
|
huggingface: HuggingFaceSettings | None = None
|
|
358
440
|
"""Settings for HuggingFace authentication (used for MCP connections)"""
|
|
359
441
|
|
|
@@ -445,51 +527,36 @@ def get_settings(config_path: str | None = None) -> Settings:
|
|
|
445
527
|
resolved_path = Path.cwd() / config_file.name
|
|
446
528
|
if resolved_path.exists():
|
|
447
529
|
config_file = resolved_path
|
|
530
|
+
|
|
531
|
+
# When config path is explicitly provided, find secrets using standardized logic
|
|
532
|
+
secrets_file = None
|
|
533
|
+
if config_file.exists():
|
|
534
|
+
_, secrets_file = find_fastagent_config_files(config_file.parent)
|
|
448
535
|
else:
|
|
449
|
-
|
|
536
|
+
# Use standardized discovery for both config and secrets
|
|
537
|
+
config_file, secrets_file = find_fastagent_config_files(Path.cwd())
|
|
450
538
|
|
|
451
539
|
merged_settings = {}
|
|
452
540
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
#
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
"fastagent.secrets.yaml",
|
|
475
|
-
]:
|
|
476
|
-
secrets_file = current_dir / secrets_filename
|
|
477
|
-
if secrets_file.exists():
|
|
478
|
-
with open(secrets_file, "r", encoding="utf-8") as f:
|
|
479
|
-
yaml_secrets = yaml.safe_load(f) or {}
|
|
480
|
-
# Resolve environment variables in the loaded secrets YAML
|
|
481
|
-
resolved_secrets_yaml = resolve_env_vars(yaml_secrets)
|
|
482
|
-
merged_settings = deep_merge(merged_settings, resolved_secrets_yaml)
|
|
483
|
-
found_secrets = True
|
|
484
|
-
break
|
|
485
|
-
if not found_secrets:
|
|
486
|
-
# Get the absolute path of the parent directory
|
|
487
|
-
current_dir = current_dir.parent.resolve()
|
|
488
|
-
|
|
489
|
-
_settings = Settings(**merged_settings)
|
|
490
|
-
return _settings
|
|
491
|
-
else:
|
|
492
|
-
pass
|
|
493
|
-
|
|
494
|
-
_settings = Settings()
|
|
541
|
+
import yaml # pylint: disable=C0415
|
|
542
|
+
|
|
543
|
+
# Load main config if it exists
|
|
544
|
+
if config_file and config_file.exists():
|
|
545
|
+
with open(config_file, "r", encoding="utf-8") as f:
|
|
546
|
+
yaml_settings = yaml.safe_load(f) or {}
|
|
547
|
+
# Resolve environment variables in the loaded YAML settings
|
|
548
|
+
resolved_yaml_settings = resolve_env_vars(yaml_settings)
|
|
549
|
+
merged_settings = resolved_yaml_settings
|
|
550
|
+
elif config_file and not config_file.exists():
|
|
551
|
+
print(f"Warning: Specified config file does not exist: {config_file}")
|
|
552
|
+
|
|
553
|
+
# Load secrets file if found (regardless of whether config file exists)
|
|
554
|
+
if secrets_file and secrets_file.exists():
|
|
555
|
+
with open(secrets_file, "r", encoding="utf-8") as f:
|
|
556
|
+
yaml_secrets = yaml.safe_load(f) or {}
|
|
557
|
+
# Resolve environment variables in the loaded secrets YAML
|
|
558
|
+
resolved_secrets_yaml = resolve_env_vars(yaml_secrets)
|
|
559
|
+
merged_settings = deep_merge(merged_settings, resolved_secrets_yaml)
|
|
560
|
+
|
|
561
|
+
_settings = Settings(**merged_settings)
|
|
495
562
|
return _settings
|
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
|
@@ -4,7 +4,7 @@ Type definitions for agents and agent configurations.
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import List
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
8
|
|
|
9
9
|
from mcp.client.session import ElicitationFnT
|
|
10
10
|
|
|
@@ -31,6 +31,9 @@ class AgentConfig:
|
|
|
31
31
|
name: str
|
|
32
32
|
instruction: str = "You are a helpful agent."
|
|
33
33
|
servers: List[str] = field(default_factory=list)
|
|
34
|
+
tools: Optional[Dict[str, List[str]]] = None
|
|
35
|
+
resources: Optional[Dict[str, List[str]]] = None
|
|
36
|
+
prompts: Optional[Dict[str, List[str]]] = None
|
|
34
37
|
model: str | None = None
|
|
35
38
|
use_history: bool = True
|
|
36
39
|
default_request_params: RequestParams | None = None
|
|
@@ -38,6 +41,7 @@ class AgentConfig:
|
|
|
38
41
|
agent_type: AgentType = AgentType.BASIC
|
|
39
42
|
default: bool = False
|
|
40
43
|
elicitation_handler: ElicitationFnT | None = None
|
|
44
|
+
api_key: str | None = None
|
|
41
45
|
|
|
42
46
|
def __post_init__(self):
|
|
43
47
|
"""Ensure default_request_params exists with proper history setting"""
|