Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Byte-compiled / optimized / DLL files
s# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
Expand Down Expand Up @@ -200,3 +200,4 @@ fastagent.jsonl
# JetBrains IDEs
.idea/
tests/e2e/smoke/base/weather_location.txt
tests/integration/roots/fastagent.jsonl
12 changes: 10 additions & 2 deletions .vscode/fastagent.config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@
"title": "Truncate Tools",
"type": "boolean",
"description": "Truncate display of long tool calls"
},
"enable_markup": {
"default": true,
"title": "Enable Markup",
"type": "boolean",
"description": "Enable markup in console output. Disable for outputs that may conflict with rich console formatting"
}
},
"title": "LoggerSettings",
Expand Down Expand Up @@ -348,7 +354,8 @@
"default": "stdio",
"enum": [
"stdio",
"sse"
"sse",
"http"
],
"title": "Transport",
"type": "string",
Expand Down Expand Up @@ -822,7 +829,8 @@
"http_timeout": 5.0,
"show_chat": true,
"show_tools": true,
"truncate_tools": true
"truncate_tools": true,
"enable_markup": true
},
"description": "Logger settings for the fast-agent application"
}
Expand Down
68 changes: 68 additions & 0 deletions src/mcp_agent/cli/commands/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Fast-Agent CLI Commands

This directory contains the command implementations for the fast-agent CLI.

## Go Command

The `go` command allows you to run an interactive agent directly from the command line without
creating a dedicated agent.py file.

### Usage

```bash
fast-agent go [OPTIONS]
```

### Options

- `--name TEXT`: Name for the agent (default: "FastAgent CLI")
- `--instruction`, `-i TEXT`: Instruction for the agent (default: "You are a helpful AI Agent.")
- `--config-path`, `-c TEXT`: Path to config file
- `--servers TEXT`: Comma-separated list of server names to enable from config
- `--url TEXT`: Comma-separated list of HTTP/SSE URLs to connect to directly
- `--auth TEXT`: Bearer token for authorization with URL-based servers
- `--model TEXT`: Override the default model (e.g., haiku, sonnet, gpt-4)
- `--message`, `-m TEXT`: Message to send to the agent (skips interactive mode)
- `--prompt-file`, `-p TEXT`: Path to a prompt file to use (either text or JSON)
- `--quiet`: Disable progress display and logging

### Examples

```bash
# Basic usage with interactive mode
fast-agent go --model=haiku

# Specifying servers from configuration
fast-agent go --servers=fetch,filesystem --model=haiku

# Directly connecting to HTTP/SSE servers via URLs
fast-agent go --url=http://localhost:8001/mcp,http://api.example.com/sse

# Connecting to an authenticated API endpoint
fast-agent go --url=https://api.example.com/mcp --auth=YOUR_API_TOKEN

# Non-interactive mode with a single message
fast-agent go --message="What is the weather today?" --model=haiku

# Using a prompt file
fast-agent go --prompt-file=my-prompt.txt --model=haiku
```

### URL Connection Details

The `--url` parameter allows you to connect directly to HTTP or SSE servers using URLs.

- URLs must have http or https scheme
- The transport type is determined by the URL path:
- URLs ending with `/sse` are treated as SSE transport
- URLs ending with `/mcp` or automatically appended with `/mcp` are treated as HTTP transport
- Server names are generated automatically based on the hostname, port, and path
- The URL-based servers are added to the agent's configuration and enabled

### Authentication

The `--auth` parameter provides authentication for URL-based servers:

- When provided, it creates an `Authorization: Bearer TOKEN` header for all URL-based servers
- This is commonly used with API endpoints that require authentication
- Example: `fast-agent go --url=https://api.example.com/mcp --auth=12345abcde`
123 changes: 94 additions & 29 deletions src/mcp_agent/cli/commands/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,80 @@

import asyncio
import sys
from typing import List, Optional
from typing import Dict, List, Optional

import typer

from mcp_agent.cli.commands.url_parser import generate_server_configs, parse_server_urls
from mcp_agent.core.fastagent import FastAgent

app = typer.Typer(
help="Run an interactive agent directly from the command line without creating an agent.py file"
)


async def _run_agent(
name: str = "FastAgent CLI",
instruction: str = "You are a helpful AI Agent.",
config_path: Optional[str] = None,
server_list: Optional[List[str]] = None,
model: Optional[str] = None,
message: Optional[str] = None,
prompt_file: Optional[str] = None
prompt_file: Optional[str] = None,
url_servers: Optional[Dict[str, Dict[str, str]]] = None,
) -> None:
"""Async implementation to run an interactive agent."""
from pathlib import Path

from mcp_agent.config import MCPServerSettings, MCPSettings
from mcp_agent.mcp.prompts.prompt_load import load_prompt_multipart

# Create the FastAgent instance with CLI arg parsing enabled
# It will automatically parse args like --model, --quiet, etc.
# Create the FastAgent instance
fast_kwargs = {
"name": name,
"config_path": config_path,
"ignore_unknown_args": True,
"parse_cli_args": False, # Don't parse CLI args, we're handling it ourselves
}

fast = FastAgent(**fast_kwargs)

# Add URL-based servers to the context configuration
if url_servers:
# Initialize the app to ensure context is ready
await fast.app.initialize()

# Initialize mcp settings if needed
if not hasattr(fast.app.context.config, "mcp"):
fast.app.context.config.mcp = MCPSettings()

# Initialize servers dictionary if needed
if (
not hasattr(fast.app.context.config.mcp, "servers")
or fast.app.context.config.mcp.servers is None
):
fast.app.context.config.mcp.servers = {}

# Add each URL server to the config
for server_name, server_config in url_servers.items():
server_settings = {"transport": server_config["transport"], "url": server_config["url"]}

# Add headers if present in the server config
if "headers" in server_config:
server_settings["headers"] = server_config["headers"]

fast.app.context.config.mcp.servers[server_name] = MCPServerSettings(**server_settings)

# Define the agent with specified parameters
agent_kwargs = {"instruction": instruction}
if server_list:
agent_kwargs["servers"] = server_list
if model:
agent_kwargs["model"] = model

# Handle prompt file and message options
if message or prompt_file:

@fast.agent(**agent_kwargs)
async def cli_agent():
async with fast.run() as agent:
Expand All @@ -55,7 +85,7 @@ async def cli_agent():
print(response)
elif prompt_file:
prompt = load_prompt_multipart(Path(prompt_file))
response = await agent.generate(prompt)
response = await agent.default.generate(prompt)
# Print the response text and exit
print(response.last_text())
else:
Expand All @@ -68,18 +98,37 @@ async def cli_agent():
# Run the agent
await cli_agent()


def run_async_agent(
name: str,
instruction: str,
config_path: Optional[str] = None,
name: str,
instruction: str,
config_path: Optional[str] = None,
servers: Optional[str] = None,
urls: Optional[str] = None,
auth: Optional[str] = None,
model: Optional[str] = None,
message: Optional[str] = None,
prompt_file: Optional[str] = None
prompt_file: Optional[str] = None,
):
"""Run the async agent function with proper loop handling."""
server_list = servers.split(',') if servers else None

server_list = servers.split(",") if servers else None

# Parse URLs and generate server configurations if provided
url_servers = None
if urls:
try:
parsed_urls = parse_server_urls(urls, auth)
url_servers = generate_server_configs(parsed_urls)
# If we have servers from URLs, add their names to the server_list
if url_servers and not server_list:
server_list = list(url_servers.keys())
elif url_servers and server_list:
# Merge both lists
server_list.extend(list(url_servers.keys()))
except ValueError as e:
print(f"Error parsing URLs: {e}")
return

# Check if we're already in an event loop
try:
loop = asyncio.get_event_loop()
Expand All @@ -92,24 +141,27 @@ def run_async_agent(
# No event loop exists, so we'll create one
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

try:
loop.run_until_complete(_run_agent(
name=name,
instruction=instruction,
config_path=config_path,
server_list=server_list,
model=model,
message=message,
prompt_file=prompt_file
))
loop.run_until_complete(
_run_agent(
name=name,
instruction=instruction,
config_path=config_path,
server_list=server_list,
model=model,
message=message,
prompt_file=prompt_file,
url_servers=url_servers,
)
)
finally:
try:
# Clean up the loop
tasks = asyncio.all_tasks(loop)
for task in tasks:
task.cancel()

# Run the event loop until all tasks are done
if sys.version_info >= (3, 7):
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
Expand All @@ -118,6 +170,7 @@ def run_async_agent(
except Exception:
pass


@app.callback(invoke_without_command=True)
def go(
ctx: typer.Context,
Expand All @@ -131,6 +184,12 @@ def go(
servers: Optional[str] = typer.Option(
None, "--servers", help="Comma-separated list of server names to enable from config"
),
urls: Optional[str] = typer.Option(
None, "--url", help="Comma-separated list of HTTP/SSE URLs to connect to"
),
auth: Optional[str] = typer.Option(
None, "--auth", help="Bearer token for authorization with URL-based servers"
),
model: Optional[str] = typer.Option(
None, "--model", help="Override the default model (e.g., haiku, sonnet, gpt-4)"
),
Expand All @@ -148,6 +207,8 @@ def go(
fast-agent go --model=haiku --instruction="You are a coding assistant" --servers=fetch,filesystem
fast-agent go --message="What is the weather today?" --model=haiku
fast-agent go --prompt-file=my-prompt.txt --model=haiku
fast-agent go --url=http://localhost:8001/mcp,http://api.example.com/sse
fast-agent go --url=https://api.example.com/mcp --auth=YOUR_API_TOKEN

This will start an interactive session with the agent, using the specified model
and instruction. It will use the default configuration from fastagent.config.yaml
Expand All @@ -157,15 +218,19 @@ def go(
--model Override the default model (e.g., --model=haiku)
--quiet Disable progress display and logging
--servers Comma-separated list of server names to enable from config
--url Comma-separated list of HTTP/SSE URLs to connect to
--auth Bearer token for authorization with URL-based servers
--message, -m Send a single message and exit
--prompt-file, -p Use a prompt file instead of interactive mode
"""
run_async_agent(
name=name,
instruction=instruction,
config_path=config_path,
name=name,
instruction=instruction,
config_path=config_path,
servers=servers,
urls=urls,
auth=auth,
model=model,
message=message,
prompt_file=prompt_file
)
prompt_file=prompt_file,
)
Loading
Loading