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
|
@@ -23,30 +23,14 @@ console = Console()
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def find_config_files(start_path: Path) -> dict[str, Optional[Path]]:
|
|
26
|
-
"""Find FastAgent configuration files
|
|
27
|
-
|
|
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
|
|
26
|
+
"""Find FastAgent configuration files, preferring secrets file next to config file."""
|
|
27
|
+
from mcp_agent.config import find_fastagent_config_files
|
|
42
28
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return results
|
|
29
|
+
config_path, secrets_path = find_fastagent_config_files(start_path)
|
|
30
|
+
return {
|
|
31
|
+
"config": config_path,
|
|
32
|
+
"secrets": secrets_path,
|
|
33
|
+
}
|
|
50
34
|
|
|
51
35
|
|
|
52
36
|
def get_system_info() -> dict:
|
|
@@ -109,13 +93,23 @@ def check_api_keys(secrets_summary: dict, config_summary: dict) -> dict:
|
|
|
109
93
|
secrets_status = secrets_summary.get("status", "not_found")
|
|
110
94
|
# Get config if available
|
|
111
95
|
config = config_summary if config_summary.get("status") == "parsed" else {}
|
|
96
|
+
|
|
112
97
|
config_azure = {}
|
|
113
98
|
if config and "azure" in config.get("config", {}):
|
|
114
99
|
config_azure = config["config"]["azure"]
|
|
115
100
|
|
|
116
|
-
for
|
|
101
|
+
for provider in results:
|
|
102
|
+
# Always check environment variables first
|
|
103
|
+
env_key_name = ProviderKeyManager.get_env_key_name(provider)
|
|
104
|
+
env_key_value = os.environ.get(env_key_name)
|
|
105
|
+
if env_key_value:
|
|
106
|
+
if len(env_key_value) > 5:
|
|
107
|
+
results[provider]["env"] = f"...{env_key_value[-5:]}"
|
|
108
|
+
else:
|
|
109
|
+
results[provider]["env"] = "...***"
|
|
110
|
+
|
|
117
111
|
# Special handling for Azure: support api_key and DefaultAzureCredential
|
|
118
|
-
if
|
|
112
|
+
if provider == "azure":
|
|
119
113
|
# Prefer secrets if present, else fallback to config
|
|
120
114
|
azure_cfg = {}
|
|
121
115
|
if secrets_status == "parsed" and "azure" in secrets:
|
|
@@ -125,40 +119,18 @@ def check_api_keys(secrets_summary: dict, config_summary: dict) -> dict:
|
|
|
125
119
|
|
|
126
120
|
use_default_cred = azure_cfg.get("use_default_azure_credential", False)
|
|
127
121
|
base_url = azure_cfg.get("base_url")
|
|
128
|
-
api_key = azure_cfg.get("api_key")
|
|
129
|
-
# DefaultAzureCredential mode
|
|
130
122
|
if use_default_cred and base_url:
|
|
131
|
-
results[
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
123
|
+
results[provider]["config"] = "DefaultAzureCredential"
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# Check secrets file if it was parsed successfully
|
|
127
|
+
if secrets_status == "parsed":
|
|
128
|
+
config_key = ProviderKeyManager.get_config_file_key(provider, secrets)
|
|
129
|
+
if config_key and config_key != API_KEY_HINT_TEXT:
|
|
130
|
+
if len(config_key) > 5:
|
|
131
|
+
results[provider]["config"] = f"...{config_key[-5:]}"
|
|
139
132
|
else:
|
|
140
|
-
|
|
141
|
-
results[provider_value]["config"] += " + api_key"
|
|
142
|
-
else:
|
|
143
|
-
results[provider_value]["config"] = "...***"
|
|
144
|
-
else:
|
|
145
|
-
# Check environment variables using ProviderKeyManager
|
|
146
|
-
env_key_name = ProviderKeyManager.get_env_key_name(provider_value)
|
|
147
|
-
env_key_value = os.environ.get(env_key_name)
|
|
148
|
-
if env_key_value:
|
|
149
|
-
if len(env_key_value) > 5:
|
|
150
|
-
results[provider_value]["env"] = f"...{env_key_value[-5:]}"
|
|
151
|
-
else:
|
|
152
|
-
results[provider_value]["env"] = "...***"
|
|
153
|
-
|
|
154
|
-
# Check secrets file if it was parsed successfully
|
|
155
|
-
if secrets_status == "parsed":
|
|
156
|
-
config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets)
|
|
157
|
-
if config_key and config_key != API_KEY_HINT_TEXT:
|
|
158
|
-
if len(config_key) > 5:
|
|
159
|
-
results[provider_value]["config"] = f"...{config_key[-5:]}"
|
|
160
|
-
else:
|
|
161
|
-
results[provider_value]["config"] = "...***"
|
|
133
|
+
results[provider]["config"] = "...***"
|
|
162
134
|
|
|
163
135
|
return results
|
|
164
136
|
|
|
@@ -173,11 +145,24 @@ def get_fastagent_version() -> str:
|
|
|
173
145
|
|
|
174
146
|
def get_config_summary(config_path: Optional[Path]) -> dict:
|
|
175
147
|
"""Extract key information from the configuration file."""
|
|
148
|
+
from mcp_agent.config import Settings
|
|
149
|
+
|
|
150
|
+
# Get actual defaults from Settings class
|
|
151
|
+
default_settings = Settings()
|
|
152
|
+
|
|
176
153
|
result = {
|
|
177
154
|
"status": "not_found", # Default status: not found
|
|
178
155
|
"error": None,
|
|
179
|
-
"default_model":
|
|
180
|
-
"logger": {
|
|
156
|
+
"default_model": default_settings.default_model,
|
|
157
|
+
"logger": {
|
|
158
|
+
"level": default_settings.logger.level,
|
|
159
|
+
"type": default_settings.logger.type,
|
|
160
|
+
"progress_display": default_settings.logger.progress_display,
|
|
161
|
+
"show_chat": default_settings.logger.show_chat,
|
|
162
|
+
"show_tools": default_settings.logger.show_tools,
|
|
163
|
+
"truncate_tools": default_settings.logger.truncate_tools,
|
|
164
|
+
"enable_markup": default_settings.logger.enable_markup,
|
|
165
|
+
},
|
|
181
166
|
"mcp_servers": [],
|
|
182
167
|
}
|
|
183
168
|
|
|
@@ -207,11 +192,19 @@ def get_config_summary(config_path: Optional[Path]) -> dict:
|
|
|
207
192
|
if "logger" in config:
|
|
208
193
|
logger_config = config["logger"]
|
|
209
194
|
result["logger"] = {
|
|
210
|
-
"level": logger_config.get("level",
|
|
211
|
-
"type": logger_config.get("type",
|
|
212
|
-
"progress_display":
|
|
213
|
-
|
|
214
|
-
|
|
195
|
+
"level": logger_config.get("level", default_settings.logger.level),
|
|
196
|
+
"type": logger_config.get("type", default_settings.logger.type),
|
|
197
|
+
"progress_display": logger_config.get(
|
|
198
|
+
"progress_display", default_settings.logger.progress_display
|
|
199
|
+
),
|
|
200
|
+
"show_chat": logger_config.get("show_chat", default_settings.logger.show_chat),
|
|
201
|
+
"show_tools": logger_config.get("show_tools", default_settings.logger.show_tools),
|
|
202
|
+
"truncate_tools": logger_config.get(
|
|
203
|
+
"truncate_tools", default_settings.logger.truncate_tools
|
|
204
|
+
),
|
|
205
|
+
"enable_markup": logger_config.get(
|
|
206
|
+
"enable_markup", default_settings.logger.enable_markup
|
|
207
|
+
),
|
|
215
208
|
}
|
|
216
209
|
|
|
217
210
|
# Get MCP server info
|
|
@@ -228,10 +221,11 @@ def get_config_summary(config_path: Optional[Path]) -> dict:
|
|
|
228
221
|
if "url" in server_config:
|
|
229
222
|
url = server_config.get("url", "")
|
|
230
223
|
server_info["url"] = url
|
|
231
|
-
|
|
224
|
+
|
|
232
225
|
# Use URL path to determine transport type
|
|
233
226
|
try:
|
|
234
227
|
from .url_parser import parse_server_url
|
|
228
|
+
|
|
235
229
|
_, transport_type, _ = parse_server_url(url)
|
|
236
230
|
server_info["transport"] = transport_type.upper()
|
|
237
231
|
except Exception:
|
|
@@ -288,7 +282,9 @@ def show_check_summary() -> None:
|
|
|
288
282
|
system_table.add_row("Python Version", ".".join(system_info["python_version"].split(".")[:3]))
|
|
289
283
|
system_table.add_row("Python Path", system_info["python_path"])
|
|
290
284
|
|
|
291
|
-
console.print(
|
|
285
|
+
console.print(
|
|
286
|
+
Panel(system_table, title="System Information", title_align="left", border_style="blue")
|
|
287
|
+
)
|
|
292
288
|
|
|
293
289
|
# Configuration files panel
|
|
294
290
|
config_path = config_files["config"]
|
|
@@ -327,25 +323,59 @@ def show_check_summary() -> None:
|
|
|
327
323
|
"Default Model", config_summary.get("default_model", "haiku (system default)")
|
|
328
324
|
)
|
|
329
325
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
326
|
+
console.print(
|
|
327
|
+
Panel(files_table, title="Configuration Files", title_align="left", border_style="blue")
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Logger Settings panel with two-column layout
|
|
331
|
+
logger = config_summary.get("logger", {})
|
|
332
|
+
logger_table = Table(show_header=True, box=None)
|
|
333
|
+
logger_table.add_column("Setting", style="cyan")
|
|
334
|
+
logger_table.add_column("Value")
|
|
335
|
+
logger_table.add_column("Setting", style="cyan")
|
|
336
|
+
logger_table.add_column("Value")
|
|
337
|
+
|
|
338
|
+
def bool_to_symbol(value):
|
|
339
|
+
return "[bold green]✓[/bold green]" if value else "[bold red]✗[/bold red]"
|
|
340
|
+
|
|
341
|
+
# Prepare all settings as pairs
|
|
342
|
+
settings_data = [
|
|
343
|
+
("Logger Level", logger.get("level", "warning (default)")),
|
|
344
|
+
("Logger Type", logger.get("type", "file (default)")),
|
|
345
|
+
("Progress Display", bool_to_symbol(logger.get("progress_display", True))),
|
|
346
|
+
("Show Chat", bool_to_symbol(logger.get("show_chat", True))),
|
|
347
|
+
("Show Tools", bool_to_symbol(logger.get("show_tools", True))),
|
|
348
|
+
("Truncate Tools", bool_to_symbol(logger.get("truncate_tools", True))),
|
|
349
|
+
("Enable Markup", bool_to_symbol(logger.get("enable_markup", True))),
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
# Add rows in two-column layout
|
|
353
|
+
for i in range(0, len(settings_data), 2):
|
|
354
|
+
left_setting, left_value = settings_data[i]
|
|
355
|
+
if i + 1 < len(settings_data):
|
|
356
|
+
right_setting, right_value = settings_data[i + 1]
|
|
357
|
+
logger_table.add_row(left_setting, left_value, right_setting, right_value)
|
|
358
|
+
else:
|
|
359
|
+
# Odd number of settings - fill right column with empty strings
|
|
360
|
+
logger_table.add_row(left_setting, left_value, "", "")
|
|
338
361
|
|
|
339
|
-
console.print(
|
|
362
|
+
console.print(
|
|
363
|
+
Panel(logger_table, title="Logger Settings", title_align="left", border_style="blue")
|
|
364
|
+
)
|
|
340
365
|
|
|
341
|
-
# API keys panel
|
|
366
|
+
# API keys panel with two-column layout
|
|
342
367
|
keys_table = Table(show_header=True, box=None)
|
|
343
368
|
keys_table.add_column("Provider", style="cyan")
|
|
344
369
|
keys_table.add_column("Env", justify="center")
|
|
345
370
|
keys_table.add_column("Config", justify="center")
|
|
346
371
|
keys_table.add_column("Active Key", style="green")
|
|
372
|
+
keys_table.add_column("Provider", style="cyan")
|
|
373
|
+
keys_table.add_column("Env", justify="center")
|
|
374
|
+
keys_table.add_column("Config", justify="center")
|
|
375
|
+
keys_table.add_column("Active Key", style="green")
|
|
347
376
|
|
|
348
|
-
|
|
377
|
+
def format_provider_row(provider, status):
|
|
378
|
+
"""Format a single provider's status for display."""
|
|
349
379
|
# Environment key indicator
|
|
350
380
|
if status["env"] and status["config"]:
|
|
351
381
|
# Both exist but config takes precedence (env is present but not active)
|
|
@@ -379,10 +409,35 @@ def show_check_summary() -> None:
|
|
|
379
409
|
# No key available for other providers
|
|
380
410
|
active = "[dim]Not configured[/dim]"
|
|
381
411
|
|
|
382
|
-
|
|
412
|
+
# Get the proper display name for the provider
|
|
413
|
+
from mcp_agent.llm.provider_types import Provider
|
|
414
|
+
|
|
415
|
+
provider_enum = Provider(provider)
|
|
416
|
+
display_name = provider_enum.display_name
|
|
417
|
+
|
|
418
|
+
return display_name, env_status, config_status, active
|
|
419
|
+
|
|
420
|
+
# Split providers into two columns
|
|
421
|
+
providers_list = list(api_keys.items())
|
|
422
|
+
mid_point = (len(providers_list) + 1) // 2 # Round up for odd numbers
|
|
423
|
+
|
|
424
|
+
for i in range(mid_point):
|
|
425
|
+
# Left column
|
|
426
|
+
left_provider, left_status = providers_list[i]
|
|
427
|
+
left_data = format_provider_row(left_provider, left_status)
|
|
428
|
+
|
|
429
|
+
# Right column (if exists)
|
|
430
|
+
if i + mid_point < len(providers_list):
|
|
431
|
+
right_provider, right_status = providers_list[i + mid_point]
|
|
432
|
+
right_data = format_provider_row(right_provider, right_status)
|
|
433
|
+
# Add row with both columns
|
|
434
|
+
keys_table.add_row(*left_data, *right_data)
|
|
435
|
+
else:
|
|
436
|
+
# Add row with only left column (right column empty)
|
|
437
|
+
keys_table.add_row(*left_data, "", "", "", "")
|
|
383
438
|
|
|
384
439
|
# Print the API Keys panel (fix: this was missing)
|
|
385
|
-
keys_panel = Panel(keys_table, title="API Keys",
|
|
440
|
+
keys_panel = Panel(keys_table, title="API Keys", title_align="left", border_style="blue")
|
|
386
441
|
console.print(keys_panel)
|
|
387
442
|
|
|
388
443
|
# MCP Servers panel (shown after API Keys)
|
|
@@ -406,12 +461,16 @@ def show_check_summary() -> None:
|
|
|
406
461
|
command_url = server["url"] or "[dim]Not configured[/dim]"
|
|
407
462
|
servers_table.add_row(name, transport, command_url)
|
|
408
463
|
|
|
409
|
-
console.print(
|
|
464
|
+
console.print(
|
|
465
|
+
Panel(servers_table, title="MCP Servers", title_align="left", border_style="blue")
|
|
466
|
+
)
|
|
410
467
|
|
|
411
468
|
# Show help tips
|
|
412
469
|
if config_status == "not_found" or secrets_status == "not_found":
|
|
413
470
|
console.print("\n[bold]Setup Tips:[/bold]")
|
|
414
|
-
console.print(
|
|
471
|
+
console.print(
|
|
472
|
+
"Run [cyan]fast-agent setup[/cyan] to create configuration files. Visit [cyan][link=https://fast-agent.ai]fast-agent.ai[/link][/cyan] for configuration guides. "
|
|
473
|
+
)
|
|
415
474
|
elif config_status == "error" or secrets_status == "error":
|
|
416
475
|
console.print("\n[bold]Config File Issues:[/bold]")
|
|
417
476
|
console.print("Fix the YAML syntax errors in your configuration files")
|
mcp_agent/cli/commands/go.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
"""Run an interactive agent directly from the command line."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import shlex
|
|
4
5
|
import sys
|
|
5
6
|
from typing import Dict, List, Optional
|
|
6
7
|
|
|
7
8
|
import typer
|
|
8
9
|
|
|
10
|
+
from mcp_agent.cli.commands.server_helpers import add_servers_to_config, generate_server_name
|
|
9
11
|
from mcp_agent.cli.commands.url_parser import generate_server_configs, parse_server_urls
|
|
10
12
|
from mcp_agent.core.fastagent import FastAgent
|
|
13
|
+
from mcp_agent.ui.console_display import ConsoleDisplay
|
|
11
14
|
|
|
12
15
|
app = typer.Typer(
|
|
13
|
-
help="Run an interactive agent directly from the command line without creating an agent.py file"
|
|
16
|
+
help="Run an interactive agent directly from the command line without creating an agent.py file",
|
|
17
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
14
18
|
)
|
|
15
19
|
|
|
16
20
|
|
|
@@ -23,11 +27,11 @@ async def _run_agent(
|
|
|
23
27
|
message: Optional[str] = None,
|
|
24
28
|
prompt_file: Optional[str] = None,
|
|
25
29
|
url_servers: Optional[Dict[str, Dict[str, str]]] = None,
|
|
30
|
+
stdio_servers: Optional[Dict[str, Dict[str, str]]] = None,
|
|
26
31
|
) -> None:
|
|
27
32
|
"""Async implementation to run an interactive agent."""
|
|
28
33
|
from pathlib import Path
|
|
29
34
|
|
|
30
|
-
from mcp_agent.config import MCPServerSettings, MCPSettings
|
|
31
35
|
from mcp_agent.mcp.prompts.prompt_load import load_prompt_multipart
|
|
32
36
|
|
|
33
37
|
# Create the FastAgent instance
|
|
@@ -40,41 +44,69 @@ async def _run_agent(
|
|
|
40
44
|
|
|
41
45
|
fast = FastAgent(**fast_kwargs)
|
|
42
46
|
|
|
43
|
-
# Add
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await fast.app.initialize()
|
|
47
|
+
# Add all dynamic servers to the configuration
|
|
48
|
+
await add_servers_to_config(fast, url_servers)
|
|
49
|
+
await add_servers_to_config(fast, stdio_servers)
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
# Check if we have multiple models (comma-delimited)
|
|
52
|
+
if model and "," in model:
|
|
53
|
+
# Parse multiple models
|
|
54
|
+
models = [m.strip() for m in model.split(",") if m.strip()]
|
|
51
55
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
):
|
|
57
|
-
fast.app.context.config.mcp.servers = {}
|
|
56
|
+
# Create an agent for each model
|
|
57
|
+
fan_out_agents = []
|
|
58
|
+
for i, model_name in enumerate(models):
|
|
59
|
+
agent_name = f"{model_name}"
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
# Define the agent with specified parameters
|
|
62
|
+
agent_kwargs = {"instruction": instruction, "name": agent_name}
|
|
63
|
+
if server_list:
|
|
64
|
+
agent_kwargs["servers"] = server_list
|
|
65
|
+
agent_kwargs["model"] = model_name
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
@fast.agent(**agent_kwargs)
|
|
68
|
+
async def model_agent():
|
|
69
|
+
pass
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
fan_out_agents.append(agent_name)
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
# Create a silent fan-in agent for cleaner output
|
|
74
|
+
@fast.agent(
|
|
75
|
+
name="aggregate",
|
|
76
|
+
model="silent",
|
|
77
|
+
instruction="You are a silent agent that combines outputs from parallel agents.",
|
|
78
|
+
)
|
|
79
|
+
async def fan_in_agent():
|
|
80
|
+
pass
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
# Create a parallel agent with silent fan_in
|
|
83
|
+
@fast.parallel(
|
|
84
|
+
name="parallel",
|
|
85
|
+
fan_out=fan_out_agents,
|
|
86
|
+
fan_in="aggregate",
|
|
87
|
+
include_request=True,
|
|
88
|
+
)
|
|
89
|
+
async def cli_agent():
|
|
90
|
+
async with fast.run() as agent:
|
|
91
|
+
if message:
|
|
92
|
+
await agent.parallel.send(message)
|
|
93
|
+
display = ConsoleDisplay(config=None)
|
|
94
|
+
display.show_parallel_results(agent.parallel)
|
|
95
|
+
elif prompt_file:
|
|
96
|
+
prompt = load_prompt_multipart(Path(prompt_file))
|
|
97
|
+
await agent.parallel.generate(prompt)
|
|
98
|
+
display = ConsoleDisplay(config=None)
|
|
99
|
+
display.show_parallel_results(agent.parallel)
|
|
100
|
+
else:
|
|
101
|
+
await agent.interactive(agent_name="parallel", pretty_print_parallel=True)
|
|
102
|
+
else:
|
|
103
|
+
# Single model - use original behavior
|
|
104
|
+
# Define the agent with specified parameters
|
|
105
|
+
agent_kwargs = {"instruction": instruction}
|
|
106
|
+
if server_list:
|
|
107
|
+
agent_kwargs["servers"] = server_list
|
|
108
|
+
if model:
|
|
109
|
+
agent_kwargs["model"] = model
|
|
78
110
|
|
|
79
111
|
@fast.agent(**agent_kwargs)
|
|
80
112
|
async def cli_agent():
|
|
@@ -88,12 +120,8 @@ async def _run_agent(
|
|
|
88
120
|
response = await agent.default.generate(prompt)
|
|
89
121
|
# Print the response text and exit
|
|
90
122
|
print(response.last_text())
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@fast.agent(**agent_kwargs)
|
|
94
|
-
async def cli_agent():
|
|
95
|
-
async with fast.run() as agent:
|
|
96
|
-
await agent.interactive()
|
|
123
|
+
else:
|
|
124
|
+
await agent.interactive()
|
|
97
125
|
|
|
98
126
|
# Run the agent
|
|
99
127
|
await cli_agent()
|
|
@@ -109,6 +137,7 @@ def run_async_agent(
|
|
|
109
137
|
model: Optional[str] = None,
|
|
110
138
|
message: Optional[str] = None,
|
|
111
139
|
prompt_file: Optional[str] = None,
|
|
140
|
+
stdio_commands: Optional[List[str]] = None,
|
|
112
141
|
):
|
|
113
142
|
"""Run the async agent function with proper loop handling."""
|
|
114
143
|
server_list = servers.split(",") if servers else None
|
|
@@ -129,6 +158,60 @@ def run_async_agent(
|
|
|
129
158
|
print(f"Error parsing URLs: {e}")
|
|
130
159
|
return
|
|
131
160
|
|
|
161
|
+
# Generate STDIO server configurations if provided
|
|
162
|
+
stdio_servers = None
|
|
163
|
+
|
|
164
|
+
if stdio_commands:
|
|
165
|
+
stdio_servers = {}
|
|
166
|
+
for i, stdio_cmd in enumerate(stdio_commands):
|
|
167
|
+
# Parse the stdio command string
|
|
168
|
+
try:
|
|
169
|
+
parsed_command = shlex.split(stdio_cmd)
|
|
170
|
+
if not parsed_command:
|
|
171
|
+
print(f"Error: Empty stdio command: {stdio_cmd}")
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
command = parsed_command[0]
|
|
175
|
+
initial_args = parsed_command[1:] if len(parsed_command) > 1 else []
|
|
176
|
+
|
|
177
|
+
# Generate a server name from the command
|
|
178
|
+
if initial_args:
|
|
179
|
+
# Try to extract a meaningful name from the args
|
|
180
|
+
for arg in initial_args:
|
|
181
|
+
if arg.endswith(".py") or arg.endswith(".js") or arg.endswith(".ts"):
|
|
182
|
+
base_name = generate_server_name(arg)
|
|
183
|
+
break
|
|
184
|
+
else:
|
|
185
|
+
# Fallback to command name
|
|
186
|
+
base_name = generate_server_name(command)
|
|
187
|
+
else:
|
|
188
|
+
base_name = generate_server_name(command)
|
|
189
|
+
|
|
190
|
+
# Ensure unique server names when multiple servers
|
|
191
|
+
server_name = base_name
|
|
192
|
+
if len(stdio_commands) > 1:
|
|
193
|
+
server_name = f"{base_name}_{i + 1}"
|
|
194
|
+
|
|
195
|
+
# Build the complete args list
|
|
196
|
+
stdio_command_args = initial_args.copy()
|
|
197
|
+
|
|
198
|
+
# Add this server to the configuration
|
|
199
|
+
stdio_servers[server_name] = {
|
|
200
|
+
"transport": "stdio",
|
|
201
|
+
"command": command,
|
|
202
|
+
"args": stdio_command_args,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Add STDIO server to the server list
|
|
206
|
+
if not server_list:
|
|
207
|
+
server_list = [server_name]
|
|
208
|
+
else:
|
|
209
|
+
server_list.append(server_name)
|
|
210
|
+
|
|
211
|
+
except ValueError as e:
|
|
212
|
+
print(f"Error parsing stdio command '{stdio_cmd}': {e}")
|
|
213
|
+
continue
|
|
214
|
+
|
|
132
215
|
# Check if we're already in an event loop
|
|
133
216
|
try:
|
|
134
217
|
loop = asyncio.get_event_loop()
|
|
@@ -153,6 +236,7 @@ def run_async_agent(
|
|
|
153
236
|
message=message,
|
|
154
237
|
prompt_file=prompt_file,
|
|
155
238
|
url_servers=url_servers,
|
|
239
|
+
stdio_servers=stdio_servers,
|
|
156
240
|
)
|
|
157
241
|
)
|
|
158
242
|
finally:
|
|
@@ -171,7 +255,7 @@ def run_async_agent(
|
|
|
171
255
|
pass
|
|
172
256
|
|
|
173
257
|
|
|
174
|
-
@app.callback(invoke_without_command=True)
|
|
258
|
+
@app.callback(invoke_without_command=True, no_args_is_help=False)
|
|
175
259
|
def go(
|
|
176
260
|
ctx: typer.Context,
|
|
177
261
|
name: str = typer.Option("FastAgent CLI", "--name", help="Name for the agent"),
|
|
@@ -191,7 +275,7 @@ def go(
|
|
|
191
275
|
None, "--auth", help="Bearer token for authorization with URL-based servers"
|
|
192
276
|
),
|
|
193
277
|
model: Optional[str] = typer.Option(
|
|
194
|
-
None, "--model", help="Override the default model (e.g., haiku, sonnet, gpt-4)"
|
|
278
|
+
None, "--model", "--models", help="Override the default model (e.g., haiku, sonnet, gpt-4)"
|
|
195
279
|
),
|
|
196
280
|
message: Optional[str] = typer.Option(
|
|
197
281
|
None, "--message", "-m", help="Message to send to the agent (skips interactive mode)"
|
|
@@ -199,6 +283,15 @@ def go(
|
|
|
199
283
|
prompt_file: Optional[str] = typer.Option(
|
|
200
284
|
None, "--prompt-file", "-p", help="Path to a prompt file to use (either text or JSON)"
|
|
201
285
|
),
|
|
286
|
+
npx: Optional[str] = typer.Option(
|
|
287
|
+
None, "--npx", help="NPX package and args to run as MCP server (quoted)"
|
|
288
|
+
),
|
|
289
|
+
uvx: Optional[str] = typer.Option(
|
|
290
|
+
None, "--uvx", help="UVX package and args to run as MCP server (quoted)"
|
|
291
|
+
),
|
|
292
|
+
stdio: Optional[str] = typer.Option(
|
|
293
|
+
None, "--stdio", help="Command to run as STDIO MCP server (quoted)"
|
|
294
|
+
),
|
|
202
295
|
) -> None:
|
|
203
296
|
"""
|
|
204
297
|
Run an interactive agent directly from the command line.
|
|
@@ -209,6 +302,10 @@ def go(
|
|
|
209
302
|
fast-agent go --prompt-file=my-prompt.txt --model=haiku
|
|
210
303
|
fast-agent go --url=http://localhost:8001/mcp,http://api.example.com/sse
|
|
211
304
|
fast-agent go --url=https://api.example.com/mcp --auth=YOUR_API_TOKEN
|
|
305
|
+
fast-agent go --npx "@modelcontextprotocol/server-filesystem /path/to/data"
|
|
306
|
+
fast-agent go --uvx "mcp-server-fetch --verbose"
|
|
307
|
+
fast-agent go --stdio "python my_server.py --debug"
|
|
308
|
+
fast-agent go --stdio "uv run server.py --config=settings.json"
|
|
212
309
|
|
|
213
310
|
This will start an interactive session with the agent, using the specified model
|
|
214
311
|
and instruction. It will use the default configuration from fastagent.config.yaml
|
|
@@ -222,7 +319,22 @@ def go(
|
|
|
222
319
|
--auth Bearer token for authorization with URL-based servers
|
|
223
320
|
--message, -m Send a single message and exit
|
|
224
321
|
--prompt-file, -p Use a prompt file instead of interactive mode
|
|
322
|
+
--npx NPX package and args to run as MCP server (quoted)
|
|
323
|
+
--uvx UVX package and args to run as MCP server (quoted)
|
|
324
|
+
--stdio Command to run as STDIO MCP server (quoted)
|
|
225
325
|
"""
|
|
326
|
+
# Collect all stdio commands from convenience options
|
|
327
|
+
stdio_commands = []
|
|
328
|
+
|
|
329
|
+
if npx:
|
|
330
|
+
stdio_commands.append(f"npx {npx}")
|
|
331
|
+
|
|
332
|
+
if uvx:
|
|
333
|
+
stdio_commands.append(f"uvx {uvx}")
|
|
334
|
+
|
|
335
|
+
if stdio:
|
|
336
|
+
stdio_commands.append(stdio)
|
|
337
|
+
|
|
226
338
|
run_async_agent(
|
|
227
339
|
name=name,
|
|
228
340
|
instruction=instruction,
|
|
@@ -233,4 +345,5 @@ def go(
|
|
|
233
345
|
model=model,
|
|
234
346
|
message=message,
|
|
235
347
|
prompt_file=prompt_file,
|
|
348
|
+
stdio_commands=stdio_commands,
|
|
236
349
|
)
|
|
@@ -383,12 +383,16 @@ def _show_completion_message(example_type: str, created: list[str]) -> None:
|
|
|
383
383
|
"On Windows platforms, please edit the fastagent.config.yaml and adjust the volume mount point."
|
|
384
384
|
)
|
|
385
385
|
elif example_type == "state-transfer":
|
|
386
|
-
console.print(
|
|
386
|
+
console.print(
|
|
387
|
+
"Check [cyan][link=https://fast-agent.ai]fast-agent.ai[/link][/cyan] for quick start walkthroughs"
|
|
388
|
+
)
|
|
387
389
|
elif example_type == "elicitations":
|
|
388
390
|
console.print("1. Go to the `elicitations` subdirectory (cd elicitations)")
|
|
389
391
|
console.print("2. Try the forms demo: uv run forms_demo.py")
|
|
390
392
|
console.print("3. Run the game character creator: uv run game_character.py")
|
|
391
|
-
console.print(
|
|
393
|
+
console.print(
|
|
394
|
+
"Check [cyan][link=https://fast-agent.ai/mcp/elicitations/]https://fast-agent.ai/mcp/elicitations/[/link][/cyan] for more details"
|
|
395
|
+
)
|
|
392
396
|
else:
|
|
393
397
|
console.print("\n[yellow]No files were created.[/yellow]")
|
|
394
398
|
|