Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
7ed0d27
feat(desktop): buzz-agent MCP server configuration
Jul 13, 2026
b1ba455
fix(desktop): address pass-1 review findings on MCP server configuration
Jul 13, 2026
f75a9a9
fix(desktop): initialize MCP defaults after rebase
Jul 13, 2026
8123015
fix(desktop): satisfy MCP test clippy lint
Jul 13, 2026
c3386a9
feat(desktop): surface effective buzz-agent MCP servers on config sur…
Jul 13, 2026
c7bfd0d
feat(desktop): thread mcpServers through FE types and invoke plumbing
Jul 13, 2026
95afd59
feat(desktop): surface effective-merged buzz-agent MCP servers in rea…
Jul 14, 2026
7db5831
feat(desktop): add McpServersEditor component (P2)
Jul 14, 2026
17893e4
feat(desktop): mount McpServersEditor in all three agent dialogs (P3)
wpfleger96 Jul 14, 2026
26a4637
test(desktop): add MCP servers E2E and bridge seeding support (P4)
wpfleger96 Jul 14, 2026
4a60c5e
fix(desktop): address review findings D1–D5 for MCP servers UI
wpfleger96 Jul 14, 2026
e27200b
fix(desktop): add effective-cap save-backstop and complete client val…
wpfleger96 Jul 14, 2026
203a18d
fix(desktop): validate effective MCP cap at global and persona save
wpfleger96 Jul 14, 2026
4d3544d
fix(desktop): shell-split MCP command field and retry on spawn failure
wpfleger96 Jul 14, 2026
1b58093
fix(buzz-acp): use shlex for MCP command split and add fallback tests
wpfleger96 Jul 14, 2026
b2bc6ea
fix: restore post-rebase MCP compatibility checks
Jul 14, 2026
a2ccaa6
fix(desktop): repair MCP team rebase integration
Jul 14, 2026
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 @@ -71,6 +71,9 @@ toml = "1.0"
# Filter expressions
evalexpr = { workspace = true }

# Shell-word splitting (MCP server command field)
shlex = "1"

# Process-group kill (safe wrapper around killpg) — Unix-only; kill_process_group
# has a #[cfg(not(unix))] fallback in acp.rs.
[target.'cfg(unix)'.dependencies]
Expand Down
62 changes: 62 additions & 0 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//! Config file (TOML) for complex subscription rules.

use std::collections::{HashMap, HashSet};

use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use clap::Parser;
Expand Down Expand Up @@ -37,6 +39,9 @@ pub(crate) const MAX_TURN_DURATION_CEILING_SECS: u64 = 604_800;

#[derive(Debug, Error)]
pub enum ConfigError {
#[error("invalid BUZZ_ACP_MCP_SERVERS: {0}")]
McpServers(String),

#[error("failed to parse nostr keys: {0}")]
KeyParse(#[from] nostr::key::Error),

Expand Down Expand Up @@ -193,6 +198,34 @@ pub struct ModelsArgs {
pub json: bool,
}

/// User-configured stdio MCP server received from desktop via
/// `BUZZ_ACP_MCP_SERVERS`. The desktop resolves `enabled` as local layering
/// metadata before serializing this transport shape.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ConfiguredMcpServer {
pub name: String,
pub command: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub env: Vec<ConfiguredMcpEnvVar>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ConfiguredMcpEnvVar {
pub name: String,
pub value: String,
}

pub fn parse_configured_mcp_servers(
raw: Option<&str>,
) -> Result<Vec<ConfiguredMcpServer>, ConfigError> {
let Some(raw) = raw.filter(|value| !value.trim().is_empty()) else {
return Ok(Vec::new());
};
serde_json::from_str(raw).map_err(|error| ConfigError::McpServers(error.to_string()))
}

#[derive(Debug, Parser)]
#[command(
name = "buzz-acp",
Expand Down Expand Up @@ -447,6 +480,8 @@ pub struct Config {
pub agent_command: String,
pub agent_args: Vec<String>,
pub mcp_command: String,
/// Desktop-resolved MCP servers, appended after the built-in Buzz server.
pub configured_mcp_servers: Vec<ConfiguredMcpServer>,
pub idle_timeout_secs: u64,
pub max_turn_duration_secs: u64,
pub agents: u32,
Expand Down Expand Up @@ -917,12 +952,16 @@ impl Config {

validate_multiple_event_handling(args.multiple_event_handling, args.dedup)?;

let configured_mcp_servers =
parse_configured_mcp_servers(std::env::var("BUZZ_ACP_MCP_SERVERS").ok().as_deref())?;

let config = Config {
keys,
relay_url: args.relay_url,
agent_command,
agent_args,
mcp_command: args.mcp_command,
configured_mcp_servers,
idle_timeout_secs,
max_turn_duration_secs,
agents: args.agents,
Expand Down Expand Up @@ -1300,6 +1339,7 @@ mod tests {
agent_command: "goose".into(),
agent_args: vec!["acp".into()],
mcp_command: "".into(),
configured_mcp_servers: vec![],
idle_timeout_secs: DEFAULT_IDLE_TIMEOUT_SECS,
max_turn_duration_secs: DEFAULT_MAX_TURN_DURATION_SECS,
agents: 1,
Expand Down Expand Up @@ -1706,6 +1746,28 @@ mod tests {
assert!(f.kinds.is_none(), "wildcard should propagate");
}

#[test]
fn parse_configured_mcp_servers_accepts_json_transport() {
let servers = parse_configured_mcp_servers(Some(
r#"[{"name":"github","command":"npx","args":["github-mcp"],"env":[{"name":"TOKEN","value":"secret"}]}]"#,
))
.unwrap();

assert_eq!(servers.len(), 1);
assert_eq!(servers[0].name, "github");
assert_eq!(servers[0].args, vec!["github-mcp"]);
assert_eq!(servers[0].env[0].name, "TOKEN");
assert_eq!(
serde_json::to_value(&servers).unwrap()[0]["enabled"],
serde_json::Value::Null
);
}

#[test]
fn parse_configured_mcp_servers_rejects_invalid_json() {
assert!(parse_configured_mcp_servers(Some("not-json")).is_err());
}

#[test]
fn test_config_mode_no_matching_rules_empty_result() {
let config = test_config(SubscribeMode::Config);
Expand Down
Loading
Loading