Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/buzz-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ clap = { version = "4", features = ["derive", "env"] }
# Config file
toml = "1.0"

# Shell-style argv splitting for BUZZ_ACP_AGENT_ARGS and extra MCP commands
shlex = "1.3"

# Filter expressions
evalexpr = { workspace = true }

Expand Down
1 change: 1 addition & 0 deletions crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ All configuration is via environment variables (or CLI flags — every env var h
| `BUZZ_ACP_AGENT_COMMAND` | no | `goose` | Agent binary to spawn. |
| `BUZZ_ACP_AGENT_ARGS` | no | `acp` | Agent arguments (comma-separated). |
| `BUZZ_ACP_MCP_COMMAND` | no | `""` (empty) | Path to an optional MCP server binary to provide to the agent subprocess. |
| `BUZZ_ACP_EXTRA_MCP_COMMANDS` | no | — | Comma-separated additional MCP server commands. Each entry is shell-split with POSIX quoting (e.g. `npx -y my-mcp-server`). Extra servers do **not** receive `BUZZ_PRIVATE_KEY`, `BUZZ_RELAY_URL`, or `BUZZ_AUTH_TAG` — they are third-party tools, not Buzz-native MCP. Names are derived from the executable stem, sanitized to ASCII alphanumeric/hyphen, and disambiguated with numeric suffixes. Malformed quoting fails startup with the entry index (the raw command is not logged). |
| `BUZZ_ACP_IDLE_TIMEOUT` | no | `620` | Idle timeout: max seconds of silence before cancelling a turn. Resets on any agent stdout activity. |
| `BUZZ_ACP_MAX_TURN_DURATION` | no | `7200` | Absolute wall-clock cap per turn (safety valve). |
| `BUZZ_API_TOKEN` | no | — | API token (required if relay enforces token auth). |
Expand Down
12 changes: 12 additions & 0 deletions crates/buzz-acp/src/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,23 @@ const MAX_LINE_SIZE: usize = 10_000_000; // 10 MB
///
/// Corresponds to the `McpServerStdio` variant in the ACP schema.
/// All four fields are **required** by the schema (`args` and `env` may be empty arrays).
/// `trusted` controls whether the agent runtime passes Buzz identity credentials
/// (`BUZZ_PRIVATE_KEY`, `BUZZ_RELAY_URL`, `BUZZ_AUTH_TAG`) into the child process.
/// Only the built-in `buzz-dev-mcp` server is trusted; extra MCP servers
/// configured via `BUZZ_ACP_EXTRA_MCP_COMMANDS` are untrusted and receive no
/// Buzz credentials.
#[derive(Debug, Clone, serde::Serialize)]
pub struct McpServer {
pub name: String,
pub command: String,
pub args: Vec<String>,
pub env: Vec<EnvVar>,
#[serde(default, skip_serializing_if = "is_false")]
pub trusted: bool,
}

fn is_false(b: &bool) -> bool {
!b
}

/// A single environment variable for an MCP server.
Expand Down Expand Up @@ -2538,6 +2549,7 @@ mod tests {
value: "nsec1abc".into(),
},
],
trusted: true,
};
let serialized = serde_json::to_value(&server).unwrap();
assert_eq!(serialized["name"].as_str(), Some("test-mcp"));
Expand Down
14 changes: 14 additions & 0 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,17 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_ACP_MCP_COMMAND", default_value = "")]
pub mcp_command: String,

/// Additional MCP server commands to pass to the agent session alongside
/// the primary MCP server. Entries are comma-separated; each entry is
/// shell-split (shlex) into a command and its args, so quoted paths and
/// arguments with spaces are preserved. Server names are derived from the
/// executable stem and disambiguated with a numeric suffix if duplicates
/// occur (e.g. two `npx` wrappers become `npx` and `npx-2`). Entries with
/// malformed quoting are skipped with a warning. Example:
/// `npx -y mcp-remote https://mcp.tavily.com/mcp/?tavilyApiKey=...,other-server`
#[arg(long, env = "BUZZ_ACP_EXTRA_MCP_COMMANDS", value_delimiter = ',')]
pub extra_mcp_commands: Vec<String>,

/// Idle timeout: max seconds of silence before killing a turn.
/// Resets on any agent stdout activity.
#[arg(long, env = "BUZZ_ACP_IDLE_TIMEOUT")]
Expand Down Expand Up @@ -521,6 +532,7 @@ pub struct Config {
pub agent_command: String,
pub agent_args: Vec<String>,
pub mcp_command: String,
pub extra_mcp_commands: Vec<String>,
pub idle_timeout_secs: u64,
pub max_turn_duration_secs: u64,
pub agents: u32,
Expand Down Expand Up @@ -1097,6 +1109,7 @@ impl Config {
agent_command,
agent_args,
mcp_command: args.mcp_command,
extra_mcp_commands: args.extra_mcp_commands,
idle_timeout_secs,
max_turn_duration_secs,
agents: args.agents,
Expand Down Expand Up @@ -1478,6 +1491,7 @@ mod tests {
agent_command: "goose".into(),
agent_args: vec!["acp".into()],
mcp_command: "".into(),
extra_mcp_commands: vec![],
idle_timeout_secs: DEFAULT_IDLE_TIMEOUT_SECS,
max_turn_duration_secs: DEFAULT_MAX_TURN_DURATION_SECS,
agents: 1,
Expand Down
Loading