fast-agent-mcp 0.2.13__py3-none-any.whl → 0.2.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fast_agent_mcp-0.2.13.dist-info → fast_agent_mcp-0.2.14.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.2.13.dist-info → fast_agent_mcp-0.2.14.dist-info}/RECORD +33 -33
- mcp_agent/agents/agent.py +2 -2
- mcp_agent/agents/base_agent.py +3 -3
- mcp_agent/agents/workflow/chain_agent.py +2 -2
- mcp_agent/agents/workflow/evaluator_optimizer.py +3 -3
- mcp_agent/agents/workflow/orchestrator_agent.py +3 -3
- mcp_agent/agents/workflow/parallel_agent.py +2 -2
- mcp_agent/agents/workflow/router_agent.py +2 -2
- mcp_agent/cli/commands/check_config.py +450 -0
- mcp_agent/cli/commands/setup.py +1 -1
- mcp_agent/cli/main.py +8 -15
- mcp_agent/core/agent_types.py +8 -8
- mcp_agent/core/direct_decorators.py +10 -8
- mcp_agent/core/direct_factory.py +4 -1
- mcp_agent/core/validation.py +6 -4
- mcp_agent/event_progress.py +6 -6
- mcp_agent/llm/augmented_llm.py +10 -2
- mcp_agent/llm/augmented_llm_passthrough.py +5 -3
- mcp_agent/llm/augmented_llm_playback.py +2 -1
- mcp_agent/llm/model_factory.py +7 -27
- mcp_agent/llm/provider_key_manager.py +83 -0
- mcp_agent/llm/provider_types.py +16 -0
- mcp_agent/llm/providers/augmented_llm_anthropic.py +5 -26
- mcp_agent/llm/providers/augmented_llm_deepseek.py +5 -24
- mcp_agent/llm/providers/augmented_llm_generic.py +2 -16
- mcp_agent/llm/providers/augmented_llm_openai.py +4 -26
- mcp_agent/llm/providers/augmented_llm_openrouter.py +17 -45
- mcp_agent/mcp/interfaces.py +2 -1
- mcp_agent/mcp_server/agent_server.py +120 -38
- mcp_agent/cli/commands/config.py +0 -11
- mcp_agent/executor/temporal.py +0 -383
- mcp_agent/executor/workflow.py +0 -195
- {fast_agent_mcp-0.2.13.dist-info → fast_agent_mcp-0.2.14.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.13.dist-info → fast_agent_mcp-0.2.14.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.13.dist-info → fast_agent_mcp-0.2.14.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,450 @@
|
|
1
|
+
"""Command to check FastAgent configuration."""
|
2
|
+
|
3
|
+
import platform
|
4
|
+
import sys
|
5
|
+
from importlib.metadata import version
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Optional
|
8
|
+
|
9
|
+
import typer
|
10
|
+
import yaml
|
11
|
+
from rich.console import Console
|
12
|
+
from rich.panel import Panel
|
13
|
+
from rich.table import Table
|
14
|
+
|
15
|
+
from mcp_agent.llm.provider_key_manager import API_KEY_HINT_TEXT, ProviderKeyManager
|
16
|
+
from mcp_agent.llm.provider_types import Provider
|
17
|
+
|
18
|
+
app = typer.Typer(
|
19
|
+
help="Check and diagnose FastAgent configuration",
|
20
|
+
no_args_is_help=False, # Allow showing our custom help instead
|
21
|
+
)
|
22
|
+
console = Console()
|
23
|
+
|
24
|
+
|
25
|
+
def find_config_files(start_path: Path) -> dict[str, Optional[Path]]:
|
26
|
+
"""Find FastAgent configuration files in current directory or parents."""
|
27
|
+
results = {
|
28
|
+
"config": None,
|
29
|
+
"secrets": None,
|
30
|
+
}
|
31
|
+
|
32
|
+
current = start_path
|
33
|
+
while current != current.parent: # Stop at root directory
|
34
|
+
config_path = current / "fastagent.config.yaml"
|
35
|
+
secrets_path = current / "fastagent.secrets.yaml"
|
36
|
+
|
37
|
+
if config_path.exists() and results["config"] is None:
|
38
|
+
results["config"] = config_path
|
39
|
+
|
40
|
+
if secrets_path.exists() and results["secrets"] is None:
|
41
|
+
results["secrets"] = secrets_path
|
42
|
+
|
43
|
+
# Stop searching if we found both files
|
44
|
+
if results["config"] is not None and results["secrets"] is not None:
|
45
|
+
break
|
46
|
+
|
47
|
+
current = current.parent
|
48
|
+
|
49
|
+
return results
|
50
|
+
|
51
|
+
|
52
|
+
def get_system_info() -> dict:
|
53
|
+
"""Get system information including Python version, OS, etc."""
|
54
|
+
return {
|
55
|
+
"platform": platform.system(),
|
56
|
+
"platform_version": platform.version(),
|
57
|
+
"python_version": sys.version,
|
58
|
+
"python_path": sys.executable,
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
def get_secrets_summary(secrets_path: Optional[Path]) -> dict:
|
63
|
+
"""Extract information from the secrets file."""
|
64
|
+
result = {
|
65
|
+
"status": "not_found", # Default status: not found
|
66
|
+
"error": None,
|
67
|
+
"secrets": {},
|
68
|
+
}
|
69
|
+
|
70
|
+
if not secrets_path:
|
71
|
+
return result
|
72
|
+
|
73
|
+
if not secrets_path.exists():
|
74
|
+
result["status"] = "not_found"
|
75
|
+
return result
|
76
|
+
|
77
|
+
# File exists, attempt to parse
|
78
|
+
try:
|
79
|
+
with open(secrets_path, "r") as f:
|
80
|
+
secrets = yaml.safe_load(f)
|
81
|
+
|
82
|
+
# Mark as successfully parsed
|
83
|
+
result["status"] = "parsed"
|
84
|
+
result["secrets"] = secrets or {}
|
85
|
+
|
86
|
+
except Exception as e:
|
87
|
+
# File exists but has parse errors
|
88
|
+
result["status"] = "error"
|
89
|
+
result["error"] = str(e)
|
90
|
+
console.print(f"[yellow]Warning:[/yellow] Error parsing secrets file: {e}")
|
91
|
+
|
92
|
+
return result
|
93
|
+
|
94
|
+
|
95
|
+
def check_api_keys(secrets_summary: dict) -> dict:
|
96
|
+
"""Check if API keys are configured in secrets file or environment."""
|
97
|
+
import os
|
98
|
+
|
99
|
+
# Initialize results dict using Provider enum values
|
100
|
+
results = {
|
101
|
+
provider.value: {"env": None, "config": None}
|
102
|
+
for provider in Provider
|
103
|
+
if provider != Provider.FAST_AGENT
|
104
|
+
} # Include GENERIC but exclude FAST_AGENT
|
105
|
+
|
106
|
+
# Get secrets if available
|
107
|
+
secrets = secrets_summary.get("secrets", {})
|
108
|
+
secrets_status = secrets_summary.get("status", "not_found")
|
109
|
+
|
110
|
+
# Check both environment variables and config file for each provider
|
111
|
+
for provider_value in results:
|
112
|
+
# Check environment variables using ProviderKeyManager
|
113
|
+
env_key_name = ProviderKeyManager.get_env_key_name(provider_value)
|
114
|
+
env_key_value = os.environ.get(env_key_name)
|
115
|
+
if env_key_value:
|
116
|
+
# Store the last 5 characters if key is long enough
|
117
|
+
if len(env_key_value) > 5:
|
118
|
+
results[provider_value]["env"] = f"...{env_key_value[-5:]}"
|
119
|
+
else:
|
120
|
+
results[provider_value]["env"] = "...***"
|
121
|
+
|
122
|
+
# Check secrets file if it was parsed successfully
|
123
|
+
if secrets_status == "parsed":
|
124
|
+
config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets)
|
125
|
+
if config_key and config_key != API_KEY_HINT_TEXT:
|
126
|
+
# Store the last 5 characters if key is long enough
|
127
|
+
if len(config_key) > 5:
|
128
|
+
results[provider_value]["config"] = f"...{config_key[-5:]}"
|
129
|
+
else:
|
130
|
+
results[provider_value]["config"] = "...***"
|
131
|
+
|
132
|
+
return results
|
133
|
+
|
134
|
+
|
135
|
+
def get_fastagent_version() -> str:
|
136
|
+
"""Get the installed version of FastAgent."""
|
137
|
+
try:
|
138
|
+
return version("fast-agent-mcp")
|
139
|
+
except: # noqa: E722
|
140
|
+
return "unknown"
|
141
|
+
|
142
|
+
|
143
|
+
def get_config_summary(config_path: Optional[Path]) -> dict:
|
144
|
+
"""Extract key information from the configuration file."""
|
145
|
+
result = {
|
146
|
+
"status": "not_found", # Default status: not found
|
147
|
+
"error": None,
|
148
|
+
"default_model": "haiku (system default)",
|
149
|
+
"logger": {},
|
150
|
+
"mcp_servers": [],
|
151
|
+
}
|
152
|
+
|
153
|
+
if not config_path:
|
154
|
+
return result
|
155
|
+
|
156
|
+
if not config_path.exists():
|
157
|
+
result["status"] = "not_found"
|
158
|
+
return result
|
159
|
+
|
160
|
+
# File exists, attempt to parse
|
161
|
+
try:
|
162
|
+
with open(config_path, "r") as f:
|
163
|
+
config = yaml.safe_load(f)
|
164
|
+
|
165
|
+
# Mark as successfully parsed
|
166
|
+
result["status"] = "parsed"
|
167
|
+
|
168
|
+
if not config:
|
169
|
+
return result
|
170
|
+
|
171
|
+
# Get default model
|
172
|
+
if "default_model" in config:
|
173
|
+
result["default_model"] = config["default_model"]
|
174
|
+
|
175
|
+
# Get logger settings
|
176
|
+
if "logger" in config:
|
177
|
+
logger_config = config["logger"]
|
178
|
+
result["logger"] = {
|
179
|
+
"level": logger_config.get("level", "info (default)"),
|
180
|
+
"type": logger_config.get("type", "console (default)"),
|
181
|
+
"progress_display": str(logger_config.get("progress_display", True)),
|
182
|
+
"show_chat": str(logger_config.get("show_chat", True)),
|
183
|
+
"show_tools": str(logger_config.get("show_tools", True)),
|
184
|
+
}
|
185
|
+
|
186
|
+
# Get MCP server info
|
187
|
+
if "mcp" in config and "servers" in config["mcp"]:
|
188
|
+
for server_name, server_config in config["mcp"]["servers"].items():
|
189
|
+
server_info = {
|
190
|
+
"name": server_name,
|
191
|
+
"transport": "STDIO", # Default transport type
|
192
|
+
"command": "",
|
193
|
+
"url": "",
|
194
|
+
}
|
195
|
+
|
196
|
+
# Determine transport type
|
197
|
+
if "url" in server_config:
|
198
|
+
server_info["transport"] = "SSE"
|
199
|
+
server_info["url"] = server_config.get("url", "")
|
200
|
+
|
201
|
+
# Get command and args
|
202
|
+
command = server_config.get("command", "")
|
203
|
+
args = server_config.get("args", [])
|
204
|
+
|
205
|
+
if command:
|
206
|
+
if args:
|
207
|
+
args_str = " ".join([str(arg) for arg in args])
|
208
|
+
full_cmd = f"{command} {args_str}"
|
209
|
+
# Truncate if too long
|
210
|
+
if len(full_cmd) > 60:
|
211
|
+
full_cmd = full_cmd[:57] + "..."
|
212
|
+
server_info["command"] = full_cmd
|
213
|
+
else:
|
214
|
+
server_info["command"] = command
|
215
|
+
|
216
|
+
# Truncate URL if too long
|
217
|
+
if server_info["url"] and len(server_info["url"]) > 60:
|
218
|
+
server_info["url"] = server_info["url"][:57] + "..."
|
219
|
+
|
220
|
+
result["mcp_servers"].append(server_info)
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
# File exists but has parse errors
|
224
|
+
result["status"] = "error"
|
225
|
+
result["error"] = str(e)
|
226
|
+
console.print(f"[red]Error parsing configuration file:[/red] {e}")
|
227
|
+
|
228
|
+
return result
|
229
|
+
|
230
|
+
|
231
|
+
def show_check_summary() -> None:
|
232
|
+
"""Show a summary of checks with colorful styling."""
|
233
|
+
cwd = Path.cwd()
|
234
|
+
config_files = find_config_files(cwd)
|
235
|
+
system_info = get_system_info()
|
236
|
+
config_summary = get_config_summary(config_files["config"])
|
237
|
+
secrets_summary = get_secrets_summary(config_files["secrets"])
|
238
|
+
api_keys = check_api_keys(secrets_summary)
|
239
|
+
fastagent_version = get_fastagent_version()
|
240
|
+
|
241
|
+
# System info panel
|
242
|
+
system_table = Table(show_header=False, box=None)
|
243
|
+
system_table.add_column("Key", style="cyan")
|
244
|
+
system_table.add_column("Value")
|
245
|
+
|
246
|
+
system_table.add_row("FastAgent Version", fastagent_version)
|
247
|
+
system_table.add_row("Platform", system_info["platform"])
|
248
|
+
system_table.add_row("Python Version", ".".join(system_info["python_version"].split(".")[:3]))
|
249
|
+
system_table.add_row("Python Path", system_info["python_path"])
|
250
|
+
|
251
|
+
console.print(Panel(system_table, title="System Information", border_style="blue"))
|
252
|
+
|
253
|
+
# Configuration files panel
|
254
|
+
config_path = config_files["config"]
|
255
|
+
secrets_path = config_files["secrets"]
|
256
|
+
|
257
|
+
files_table = Table(show_header=False, box=None)
|
258
|
+
files_table.add_column("Setting", style="cyan")
|
259
|
+
files_table.add_column("Value")
|
260
|
+
|
261
|
+
# Show secrets file status
|
262
|
+
secrets_status = secrets_summary.get("status", "not_found")
|
263
|
+
if secrets_status == "not_found":
|
264
|
+
files_table.add_row("Secrets File", "[yellow]Not found[/yellow]")
|
265
|
+
elif secrets_status == "error":
|
266
|
+
files_table.add_row("Secrets File", f"[orange_red1]Errors[/orange_red1] ({secrets_path})")
|
267
|
+
files_table.add_row(
|
268
|
+
"Secrets Error",
|
269
|
+
f"[orange_red1]{secrets_summary.get('error', 'Unknown error')}[/orange_red1]",
|
270
|
+
)
|
271
|
+
else: # parsed successfully
|
272
|
+
files_table.add_row("Secrets File", f"[green]Found[/green] ({secrets_path})")
|
273
|
+
|
274
|
+
# Show config file status
|
275
|
+
config_status = config_summary.get("status", "not_found")
|
276
|
+
if config_status == "not_found":
|
277
|
+
files_table.add_row("Config File", "[red]Not found[/red]")
|
278
|
+
elif config_status == "error":
|
279
|
+
files_table.add_row("Config File", f"[orange_red1]Errors[/orange_red1] ({config_path})")
|
280
|
+
files_table.add_row(
|
281
|
+
"Config Error",
|
282
|
+
f"[orange_red1]{config_summary.get('error', 'Unknown error')}[/orange_red1]",
|
283
|
+
)
|
284
|
+
else: # parsed successfully
|
285
|
+
files_table.add_row("Config File", f"[green]Found[/green] ({config_path})")
|
286
|
+
files_table.add_row(
|
287
|
+
"Default Model", config_summary.get("default_model", "haiku (system default)")
|
288
|
+
)
|
289
|
+
|
290
|
+
# Add logger settings if available
|
291
|
+
logger = config_summary.get("logger", {})
|
292
|
+
if logger:
|
293
|
+
files_table.add_row("Logger Level", logger.get("level", "info (default)"))
|
294
|
+
files_table.add_row("Logger Type", logger.get("type", "console (default)"))
|
295
|
+
files_table.add_row("Progress Display", logger.get("progress_display", "True"))
|
296
|
+
files_table.add_row("Show Chat", logger.get("show_chat", "True"))
|
297
|
+
files_table.add_row("Show Tools", logger.get("show_tools", "True"))
|
298
|
+
|
299
|
+
console.print(Panel(files_table, title="Configuration Files", border_style="blue"))
|
300
|
+
|
301
|
+
# API keys panel
|
302
|
+
keys_table = Table(show_header=True, box=None)
|
303
|
+
keys_table.add_column("Provider", style="cyan")
|
304
|
+
keys_table.add_column("Env", justify="center")
|
305
|
+
keys_table.add_column("Config", justify="center")
|
306
|
+
keys_table.add_column("Active Key", style="green")
|
307
|
+
|
308
|
+
for provider, status in api_keys.items():
|
309
|
+
# Environment key indicator
|
310
|
+
if status["env"] and status["config"]:
|
311
|
+
# Both exist but config takes precedence (env is present but not active)
|
312
|
+
env_status = "[yellow]✓[/yellow]"
|
313
|
+
elif status["env"]:
|
314
|
+
# Only env exists and is active
|
315
|
+
env_status = "[bold green]✓[/bold green]"
|
316
|
+
else:
|
317
|
+
# No env key
|
318
|
+
env_status = "[dim]✗[/dim]"
|
319
|
+
|
320
|
+
# Config file key indicator
|
321
|
+
if status["config"]:
|
322
|
+
# Config exists and takes precedence (is active)
|
323
|
+
config_status = "[bold green]✓[/bold green]"
|
324
|
+
else:
|
325
|
+
# No config key
|
326
|
+
config_status = "[dim]✗[/dim]"
|
327
|
+
|
328
|
+
# Display active key
|
329
|
+
if status["config"]:
|
330
|
+
# Config key is active
|
331
|
+
active = f"[bold green]{status['config']}[/bold green]"
|
332
|
+
elif status["env"]:
|
333
|
+
# Env key is active
|
334
|
+
active = f"[bold green]{status['env']}[/bold green]"
|
335
|
+
elif provider == "generic":
|
336
|
+
# Generic provider uses "ollama" as a default when no key is set
|
337
|
+
active = "[green]ollama (default)[/green]"
|
338
|
+
else:
|
339
|
+
# No key available for other providers
|
340
|
+
active = "[dim]Not configured[/dim]"
|
341
|
+
|
342
|
+
keys_table.add_row(provider.capitalize(), env_status, config_status, active)
|
343
|
+
|
344
|
+
console.print(Panel(keys_table, title="API Keys", border_style="blue"))
|
345
|
+
|
346
|
+
# MCP Servers panel (shown after API Keys)
|
347
|
+
if config_summary.get("status") == "parsed":
|
348
|
+
mcp_servers = config_summary.get("mcp_servers", [])
|
349
|
+
if mcp_servers:
|
350
|
+
servers_table = Table(show_header=True, box=None)
|
351
|
+
servers_table.add_column("Name", style="cyan")
|
352
|
+
servers_table.add_column("Transport", style="magenta")
|
353
|
+
servers_table.add_column("Command/URL")
|
354
|
+
|
355
|
+
for server in mcp_servers:
|
356
|
+
name = server["name"]
|
357
|
+
transport = server["transport"]
|
358
|
+
|
359
|
+
# Show either command or URL based on transport type
|
360
|
+
if transport == "STDIO":
|
361
|
+
command_url = server["command"] or "[dim]Not configured[/dim]"
|
362
|
+
servers_table.add_row(name, transport, command_url)
|
363
|
+
else: # SSE
|
364
|
+
command_url = server["url"] or "[dim]Not configured[/dim]"
|
365
|
+
servers_table.add_row(name, transport, command_url)
|
366
|
+
|
367
|
+
console.print(Panel(servers_table, title="MCP Servers", border_style="blue"))
|
368
|
+
|
369
|
+
# Show help tips
|
370
|
+
if config_status == "not_found" or secrets_status == "not_found":
|
371
|
+
console.print("\n[bold]Setup Tips:[/bold]")
|
372
|
+
console.print("Run [cyan]fast-agent setup[/cyan] to create configuration files")
|
373
|
+
elif config_status == "error" or secrets_status == "error":
|
374
|
+
console.print("\n[bold]Config File Issues:[/bold]")
|
375
|
+
console.print("Fix the YAML syntax errors in your configuration files")
|
376
|
+
|
377
|
+
if all(
|
378
|
+
not api_keys[provider]["env"] and not api_keys[provider]["config"] for provider in api_keys
|
379
|
+
):
|
380
|
+
console.print(
|
381
|
+
"\n[yellow]No API keys configured. Set up API keys to use LLM services:[/yellow]"
|
382
|
+
)
|
383
|
+
console.print("1. Add keys to fastagent.secrets.yaml")
|
384
|
+
env_vars = ", ".join(
|
385
|
+
[
|
386
|
+
ProviderKeyManager.get_env_key_name(p.value)
|
387
|
+
for p in Provider
|
388
|
+
if p != Provider.FAST_AGENT
|
389
|
+
]
|
390
|
+
)
|
391
|
+
console.print(f"2. Or set environment variables ({env_vars})")
|
392
|
+
|
393
|
+
|
394
|
+
@app.command()
|
395
|
+
def show(
|
396
|
+
path: Optional[str] = typer.Argument(None, help="Path to configuration file to display"),
|
397
|
+
secrets: bool = typer.Option(
|
398
|
+
False, "--secrets", "-s", help="Show secrets file instead of config"
|
399
|
+
),
|
400
|
+
) -> None:
|
401
|
+
"""Display the configuration file content or search for it."""
|
402
|
+
file_type = "secrets" if secrets else "config"
|
403
|
+
|
404
|
+
if path:
|
405
|
+
config_path = Path(path).resolve()
|
406
|
+
if not config_path.exists():
|
407
|
+
console.print(
|
408
|
+
f"[red]Error:[/red] {file_type.capitalize()} file not found at {config_path}"
|
409
|
+
)
|
410
|
+
raise typer.Exit(1)
|
411
|
+
else:
|
412
|
+
config_files = find_config_files(Path.cwd())
|
413
|
+
config_path = config_files[file_type]
|
414
|
+
if not config_path:
|
415
|
+
console.print(
|
416
|
+
f"[yellow]No {file_type} file found in current directory or parents[/yellow]"
|
417
|
+
)
|
418
|
+
console.print("Run [cyan]fast-agent setup[/cyan] to create configuration files")
|
419
|
+
raise typer.Exit(1)
|
420
|
+
|
421
|
+
console.print(f"\n[bold]{file_type.capitalize()} file:[/bold] {config_path}\n")
|
422
|
+
|
423
|
+
try:
|
424
|
+
with open(config_path, "r") as f:
|
425
|
+
content = f.read()
|
426
|
+
|
427
|
+
# Try to parse as YAML to check validity
|
428
|
+
parsed = yaml.safe_load(content)
|
429
|
+
|
430
|
+
# Show parsing success status
|
431
|
+
console.print("[green]YAML syntax is valid[/green]")
|
432
|
+
if parsed is None:
|
433
|
+
console.print("[yellow]Warning: File is empty or contains only comments[/yellow]\n")
|
434
|
+
else:
|
435
|
+
console.print(
|
436
|
+
f"[green]Successfully parsed {len(parsed) if isinstance(parsed, dict) else 0} root keys[/green]\n"
|
437
|
+
)
|
438
|
+
|
439
|
+
# Print the content
|
440
|
+
console.print(content)
|
441
|
+
|
442
|
+
except Exception as e:
|
443
|
+
console.print(f"[red]Error parsing {file_type} file:[/red] {e}")
|
444
|
+
|
445
|
+
|
446
|
+
@app.callback(invoke_without_command=True)
|
447
|
+
def main(ctx: typer.Context) -> None:
|
448
|
+
"""Check and diagnose FastAgent configuration."""
|
449
|
+
if ctx.invoked_subcommand is None:
|
450
|
+
show_check_summary()
|
mcp_agent/cli/commands/setup.py
CHANGED
@@ -190,7 +190,7 @@ def init(
|
|
190
190
|
# Check for existing .gitignore
|
191
191
|
needs_gitignore = not find_gitignore(config_path)
|
192
192
|
|
193
|
-
console.print("\n[bold]fast-agent
|
193
|
+
console.print("\n[bold]fast-agent setup[/bold]\n")
|
194
194
|
console.print("This will create the following files:")
|
195
195
|
console.print(f" - {config_path}/fastagent.config.yaml")
|
196
196
|
console.print(f" - {config_path}/fastagent.secrets.yaml")
|
mcp_agent/cli/main.py
CHANGED
@@ -4,7 +4,7 @@ import typer
|
|
4
4
|
from rich.console import Console
|
5
5
|
from rich.table import Table
|
6
6
|
|
7
|
-
from mcp_agent.cli.commands import quickstart, setup
|
7
|
+
from mcp_agent.cli.commands import check_config, quickstart, setup
|
8
8
|
from mcp_agent.cli.terminal import Application
|
9
9
|
|
10
10
|
app = typer.Typer(
|
@@ -14,6 +14,7 @@ app = typer.Typer(
|
|
14
14
|
|
15
15
|
# Subcommands
|
16
16
|
app.add_typer(setup.app, name="setup", help="Set up a new agent project")
|
17
|
+
app.add_typer(check_config.app, name="check", help="Show or diagnose fast-agent configuration")
|
17
18
|
app.add_typer(quickstart.app, name="bootstrap", help="Create example applications")
|
18
19
|
app.add_typer(quickstart.app, name="quickstart", help="Create example applications")
|
19
20
|
|
@@ -31,30 +32,22 @@ def show_welcome() -> None:
|
|
31
32
|
except: # noqa: E722
|
32
33
|
app_version = "unknown"
|
33
34
|
|
34
|
-
console.print(f"\
|
35
|
-
console.print("Build effective agents using Model Context Protocol (MCP)")
|
35
|
+
console.print(f"\nfast-agent {app_version} [dim](fast-agent-mcp)[/dim] ")
|
36
36
|
|
37
37
|
# Create a table for commands
|
38
38
|
table = Table(title="\nAvailable Commands")
|
39
39
|
table.add_column("Command", style="green")
|
40
40
|
table.add_column("Description")
|
41
41
|
|
42
|
-
table.add_row("setup", "
|
42
|
+
table.add_row("setup", "Create a new agent and configuration files")
|
43
|
+
table.add_row("check", "Show or diagnose fast-agent configuration")
|
43
44
|
table.add_row("quickstart", "Create example applications (workflow, researcher, etc.)")
|
44
|
-
# table.add_row("config", "Manage agent configuration settings")
|
45
45
|
|
46
46
|
console.print(table)
|
47
47
|
|
48
|
-
console.print(
|
49
|
-
|
50
|
-
|
51
|
-
console.print("\n2. Create Building Effective Agents workflow examples:")
|
52
|
-
console.print(" fastagent quickstart workflow")
|
53
|
-
console.print("\n3. Explore other examples:")
|
54
|
-
console.print(" fastagent quickstart")
|
55
|
-
|
56
|
-
console.print("\nUse --help with any command for more information")
|
57
|
-
console.print("Example: fastagent quickstart --help")
|
48
|
+
console.print(
|
49
|
+
"\n[italic]get started with:[/italic] [cyan]fast-agent[/cyan] [green]setup[/green]"
|
50
|
+
)
|
58
51
|
|
59
52
|
|
60
53
|
@app.callback(invoke_without_command=True)
|
mcp_agent/core/agent_types.py
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
Type definitions for agents and agent configurations.
|
3
3
|
"""
|
4
4
|
|
5
|
-
import dataclasses
|
6
|
-
from dataclasses import dataclass
|
7
5
|
from enum import Enum
|
8
6
|
from typing import List
|
9
7
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
9
|
+
|
10
10
|
# Forward imports to avoid circular dependencies
|
11
11
|
from mcp_agent.core.request_params import RequestParams
|
12
12
|
|
@@ -22,22 +22,21 @@ class AgentType(Enum):
|
|
22
22
|
CHAIN = "chain"
|
23
23
|
|
24
24
|
|
25
|
-
|
26
|
-
class AgentConfig:
|
25
|
+
class AgentConfig(BaseModel):
|
27
26
|
"""Configuration for an Agent instance"""
|
28
27
|
|
29
28
|
name: str
|
30
29
|
instruction: str = "You are a helpful agent."
|
31
|
-
servers: List[str] =
|
30
|
+
servers: List[str] = Field(default_factory=list)
|
32
31
|
model: str | None = None
|
33
32
|
use_history: bool = True
|
34
33
|
default_request_params: RequestParams | None = None
|
35
34
|
human_input: bool = False
|
36
|
-
agent_type:
|
35
|
+
agent_type: AgentType = AgentType.BASIC
|
37
36
|
|
38
|
-
|
37
|
+
@model_validator(mode='after')
|
38
|
+
def ensure_default_request_params(self) -> 'AgentConfig':
|
39
39
|
"""Ensure default_request_params exists with proper history setting"""
|
40
|
-
|
41
40
|
if self.default_request_params is None:
|
42
41
|
self.default_request_params = RequestParams(
|
43
42
|
use_history=self.use_history, systemPrompt=self.instruction
|
@@ -45,3 +44,4 @@ class AgentConfig:
|
|
45
44
|
else:
|
46
45
|
# Override the request params history setting if explicitly configured
|
47
46
|
self.default_request_params.use_history = self.use_history
|
47
|
+
return self
|
@@ -206,15 +206,22 @@ def agent(
|
|
206
206
|
)
|
207
207
|
|
208
208
|
|
209
|
+
DEFAULT_INSTRUCTION_ORCHESTRATOR = """
|
210
|
+
You are an expert planner. Given an objective task and a list of Agents
|
211
|
+
(which are collections of capabilities), your job is to break down the objective
|
212
|
+
into a series of steps, which can be performed by these agents.
|
213
|
+
"""
|
214
|
+
|
215
|
+
|
209
216
|
def orchestrator(
|
210
217
|
self,
|
211
218
|
name: str,
|
212
219
|
*,
|
213
220
|
agents: List[str],
|
214
|
-
instruction:
|
221
|
+
instruction: str = DEFAULT_INSTRUCTION_ORCHESTRATOR,
|
215
222
|
model: Optional[str] = None,
|
216
|
-
use_history: bool = False,
|
217
223
|
request_params: RequestParams | None = None,
|
224
|
+
use_history: bool = False,
|
218
225
|
human_input: bool = False,
|
219
226
|
plan_type: Literal["full", "iterative"] = "full",
|
220
227
|
max_iterations: int = 30,
|
@@ -236,11 +243,6 @@ def orchestrator(
|
|
236
243
|
Returns:
|
237
244
|
A decorator that registers the orchestrator with proper type annotations
|
238
245
|
"""
|
239
|
-
default_instruction = """
|
240
|
-
You are an expert planner. Given an objective task and a list of Agents
|
241
|
-
(which are collections of capabilities), your job is to break down the objective
|
242
|
-
into a series of steps, which can be performed by these agents.
|
243
|
-
"""
|
244
246
|
|
245
247
|
# Create final request params with max_iterations
|
246
248
|
|
@@ -250,7 +252,7 @@ def orchestrator(
|
|
250
252
|
self,
|
251
253
|
AgentType.ORCHESTRATOR,
|
252
254
|
name=name,
|
253
|
-
instruction=instruction
|
255
|
+
instruction=instruction,
|
254
256
|
servers=[], # Orchestrators don't connect to servers directly
|
255
257
|
model=model,
|
256
258
|
use_history=use_history,
|
mcp_agent/core/direct_factory.py
CHANGED
@@ -133,11 +133,13 @@ async def create_agents_by_type(
|
|
133
133
|
},
|
134
134
|
)
|
135
135
|
|
136
|
+
# Compare type string from config with Enum value
|
136
137
|
if agent_data["type"] == agent_type.value:
|
137
138
|
# Get common configuration
|
138
139
|
config = agent_data["config"]
|
139
140
|
|
140
|
-
|
141
|
+
# Type-specific initialization based on the Enum type
|
142
|
+
# Note: Above we compared string values from config, here we compare Enum objects directly
|
141
143
|
if agent_type == AgentType.BASIC:
|
142
144
|
# Create a basic agent
|
143
145
|
agent = Agent(
|
@@ -338,6 +340,7 @@ async def create_agents_in_dependency_order(
|
|
338
340
|
# Create agent proxies for each group in dependency order
|
339
341
|
for group in dependencies:
|
340
342
|
# Create basic agents first
|
343
|
+
# Note: We compare string values from config with the Enum's string value
|
341
344
|
if AgentType.BASIC.value in [agents_dict[name]["type"] for name in group]:
|
342
345
|
basic_agents = await create_agents_by_type(
|
343
346
|
app_instance,
|
mcp_agent/core/validation.py
CHANGED
@@ -51,8 +51,9 @@ def validate_workflow_references(agents: Dict[str, Dict[str, Any]]) -> None:
|
|
51
51
|
available_components = set(agents.keys())
|
52
52
|
|
53
53
|
for name, agent_data in agents.items():
|
54
|
-
agent_type = agent_data["type"]
|
55
|
-
|
54
|
+
agent_type = agent_data["type"] # This is a string from config
|
55
|
+
|
56
|
+
# Note: Compare string values from config with the Enum's string value
|
56
57
|
if agent_type == AgentType.PARALLEL.value:
|
57
58
|
# Check fan_in exists
|
58
59
|
fan_in = agent_data["fan_in"]
|
@@ -224,8 +225,9 @@ def get_dependencies_groups(
|
|
224
225
|
|
225
226
|
# Build the dependency graph
|
226
227
|
for name, agent_data in agents_dict.items():
|
227
|
-
agent_type = agent_data["type"]
|
228
|
-
|
228
|
+
agent_type = agent_data["type"] # This is a string from config
|
229
|
+
|
230
|
+
# Note: Compare string values from config with the Enum's string value
|
229
231
|
if agent_type == AgentType.PARALLEL.value:
|
230
232
|
# Parallel agents depend on their fan-out and fan-in agents
|
231
233
|
dependencies[name].update(agent_data.get("parallel_agents", []))
|
mcp_agent/event_progress.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
"""Module for converting log events to progress events."""
|
2
2
|
|
3
|
-
from dataclasses import dataclass
|
4
3
|
from enum import Enum
|
5
4
|
from typing import Optional
|
6
5
|
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
7
8
|
from mcp_agent.logging.events import Event
|
8
9
|
|
9
10
|
|
@@ -24,8 +25,7 @@ class ProgressAction(str, Enum):
|
|
24
25
|
FATAL_ERROR = "Error"
|
25
26
|
|
26
27
|
|
27
|
-
|
28
|
-
class ProgressEvent:
|
28
|
+
class ProgressEvent(BaseModel):
|
29
29
|
"""Represents a progress event converted from a log event."""
|
30
30
|
|
31
31
|
action: ProgressAction
|
@@ -87,8 +87,8 @@ def convert_log_event(event: Event) -> Optional[ProgressEvent]:
|
|
87
87
|
target = event_data.get("target", "unknown")
|
88
88
|
|
89
89
|
return ProgressEvent(
|
90
|
-
ProgressAction(progress_action),
|
91
|
-
target,
|
92
|
-
details,
|
90
|
+
action=ProgressAction(progress_action),
|
91
|
+
target=target,
|
92
|
+
details=details,
|
93
93
|
agent_name=event_data.get("agent_name"),
|
94
94
|
)
|