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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ RUST_LOG=buzz_relay=debug,buzz_db=debug,buzz_auth=debug,buzz_pubsub=debug,tower_
# Binary for an optional MCP server sidecar (e.g. buzz-dev-mcp for buzz-agent).
# BUZZ_ACP_MCP_COMMAND=

# Comma-separated arguments passed to the MCP server binary. Needed by servers
# that are launched through a runner rather than as a bare binary, for example
# "npx" with args "-y,some-mcp-server".
# BUZZ_ACP_MCP_ARGS=

# Number of parallel agent subprocesses (1–32).
# BUZZ_ACP_AGENTS=1

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 @@ -102,6 +102,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_MCP_ARGS` | no | `""` (empty) | MCP server arguments (comma-separated). Lets a server be launched through a runner, e.g. command `npx` with args `-y,some-mcp-server`. |
| `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
15 changes: 14 additions & 1 deletion crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,15 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_ACP_MCP_COMMAND", default_value = "")]
pub mcp_command: String,

/// Arguments passed to the MCP server binary.
#[arg(
long,
env = "BUZZ_ACP_MCP_ARGS",
default_value = "",
value_delimiter = ','
)]
pub mcp_args: 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 @@ -489,6 +498,7 @@ pub struct Config {
pub agent_command: String,
pub agent_args: Vec<String>,
pub mcp_command: String,
pub mcp_args: Vec<String>,
pub idle_timeout_secs: u64,
pub max_turn_duration_secs: u64,
pub agents: u32,
Expand Down Expand Up @@ -964,6 +974,7 @@ impl Config {
agent_command,
agent_args,
mcp_command: args.mcp_command,
mcp_args: args.mcp_args,
idle_timeout_secs,
max_turn_duration_secs,
agents: args.agents,
Expand Down Expand Up @@ -1024,12 +1035,13 @@ impl Config {
format!(" allowed_respond_to=[{}]", modes.join(","))
};
format!(
"relay={} pubkey={} agent_cmd={} {} mcp_cmd={} idle_timeout={}s max_turn={}s agents={} heartbeat={}s subscribe={:?} dedup={:?} meh={:?} ignore_self={} context_limit={} max_turns_per_session={} presence={} typing={} memory={} model={} permission_mode={} {}{}",
"relay={} pubkey={} agent_cmd={} {} mcp_cmd={} {} idle_timeout={}s max_turn={}s agents={} heartbeat={}s subscribe={:?} dedup={:?} meh={:?} ignore_self={} context_limit={} max_turns_per_session={} presence={} typing={} memory={} model={} permission_mode={} {}{}",
self.relay_url,
self.keys.public_key().to_hex(),
self.agent_command,
self.agent_args.join(" "),
self.mcp_command,
self.mcp_args.join(" "),
self.idle_timeout_secs,
self.max_turn_duration_secs,
self.agents,
Expand Down Expand Up @@ -1338,6 +1350,7 @@ mod tests {
agent_command: "goose".into(),
agent_args: vec!["acp".into()],
mcp_command: "".into(),
mcp_args: vec![],
idle_timeout_secs: DEFAULT_IDLE_TIMEOUT_SECS,
max_turn_duration_secs: DEFAULT_MAX_TURN_DURATION_SECS,
agents: 1,
Expand Down
38 changes: 37 additions & 1 deletion crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4105,7 +4105,7 @@ fn build_mcp_servers(config: &Config) -> Vec<McpServer> {
.unwrap_or("mcp")
.to_string(),
command: config.mcp_command.clone(),
args: vec![],
args: config.mcp_args.clone(),
env: {
let mut env = vec![
EnvVar {
Expand Down Expand Up @@ -4906,6 +4906,7 @@ mod build_mcp_servers_tests {
agent_command: "goose".into(),
agent_args: vec!["acp".into()],
mcp_command: "test-mcp-server".into(),
mcp_args: vec![],
idle_timeout_secs: config::DEFAULT_IDLE_TIMEOUT_SECS,
max_turn_duration_secs: config::DEFAULT_MAX_TURN_DURATION_SECS,
agents: 1,
Expand Down Expand Up @@ -5035,6 +5036,40 @@ mod build_mcp_servers_tests {
"Path::new(\".\").file_stem() is None — should fall back to \"mcp\""
);
}

#[test]
fn mcp_args_default_to_empty() {
let mut config = test_config();
config.mcp_command = "/opt/bin/my-mcp-server".into();
config.mcp_args = vec![];
let servers = build_mcp_servers(&config);
assert_eq!(servers.len(), 1);
assert!(
servers[0].args.is_empty(),
"an MCP command with no args should spawn with no args"
);
}

#[test]
fn mcp_args_are_passed_to_the_server() {
let mut config = test_config();
config.mcp_command = "npx".into();
config.mcp_args = vec!["-y".into(), "some-mcp-server".into()];
let servers = build_mcp_servers(&config);
assert_eq!(servers.len(), 1);
assert_eq!(servers[0].command, "npx");
assert_eq!(servers[0].args, vec!["-y", "some-mcp-server"]);
}

#[test]
fn mcp_args_do_not_affect_the_derived_server_name() {
// The name still comes from the command's file stem, not the args.
let mut config = test_config();
config.mcp_command = "/opt/bin/my-mcp-server".into();
config.mcp_args = vec!["--port".into(), "8080".into()];
let servers = build_mcp_servers(&config);
assert_eq!(servers[0].name, "my-mcp-server");
}
}

#[cfg(test)]
Expand Down Expand Up @@ -5072,6 +5107,7 @@ mod error_outcome_emission_tests {
agent_command: "true".into(),
agent_args: vec![],
mcp_command: "test-mcp-server".into(),
mcp_args: vec![],
idle_timeout_secs: config::DEFAULT_IDLE_TIMEOUT_SECS,
max_turn_duration_secs: config::DEFAULT_MAX_TURN_DURATION_SECS,
agents: 1,
Expand Down