Skip to content

feat(acp): allow BUZZ_ACP_MCP_COMMAND to run several MCP servers - #3196

Open
noah-shipley-centric wants to merge 1 commit into
block:mainfrom
noah-shipley-centric:feat/acp-multiple-mcp-servers
Open

feat(acp): allow BUZZ_ACP_MCP_COMMAND to run several MCP servers#3196
noah-shipley-centric wants to merge 1 commit into
block:mainfrom
noah-shipley-centric:feat/acp-multiple-mcp-servers

Conversation

@noah-shipley-centric

Copy link
Copy Markdown

Problem

build_mcp_servers in crates/buzz-acp/src/lib.rs builds at most one McpServer, so an agent driven by buzz-acp can hold exactly one MCP server.

buzz-agent was never the constraint — McpRegistry::spawn_all takes a slice and already enforces MAX_MCP_SERVERS = 16. The ceiling of one exists only in this function.

That single slot forces a trade-off between reading and acting. Point an agent at a data MCP and it loses the tooling it replies with. I hit this with an agent wired to a remote data MCP: it made its tool calls correctly, then reasoned itself into a dead end mid-turn —

"I don't have shell access from the script sandbox ... there's no buzz tool exposed here directly ... I'll rely on just producing the final text reply."

— and produced no visible output at all, because the slot that had been carrying its dev tools now carried the data bridge. Nothing errored; the turn just quietly went nowhere.

Change

--mcp-command / BUZZ_ACP_MCP_COMMAND now accepts a comma-separated list:

BUZZ_ACP_MCP_COMMAND=/usr/local/bin/buzz-dev-mcp,/usr/local/bin/data-bridge

This mirrors --agent-args / BUZZ_ACP_AGENT_ARGS, which already uses value_delimiter = ',' in the same struct — so the parsing convention isn't new to the crate.

Backward compatible. The flag and env var keep their names, and a single value produces exactly the server it produced before. No existing configuration changes behaviour.

Two deliberate details:

  • Blank entries are skipped. The "" default still yields no servers, and a stray trailing comma doesn't create a bogus one.
  • Colliding file stems are disambiguated rather than fatal. Names come from the command's file stem and spawn_all rejects duplicates, so /opt/a/bridge + /opt/b/bridge would have aborted startup. They now become bridge and bridge-2 — the operator's intent is unambiguous, and failing to boot would be unhelpful.

Tests

Existing build_mcp_servers tests are migrated to the list form and extended:

  • several commands produce one server each, in order
  • every server receives the identity env (BUZZ_RELAY_URL, BUZZ_PRIVATE_KEY)
  • colliding stems are disambiguated and names stay unique
  • blank entries are skipped without dropping valid ones
  • an empty list produces no servers
cargo test -p buzz-acp --lib build_mcp_servers   # 11 passed
cargo test -p buzz-acp -p buzz-agent --lib       # 603 + 292 passed

cargo fmt and cargo clippy -p buzz-acp --all-targets -- -D warnings are clean.

Notes

Independent of #3192 — different crate, no shared commits; either can merge first.

If you'd rather express this as a repeatable --mcp-server name=command flag (explicit names, no stem-collision handling needed), I'm happy to rework it — the delimited list just seemed the smaller change and consistent with --agent-args.

@noah-shipley-centric
noah-shipley-centric requested a review from a team as a code owner July 27, 2026 20:04
`build_mcp_servers` collapsed the whole MCP surface into a single server,
so an agent could hold exactly one. `buzz-agent` never had that limit —
`McpRegistry::spawn_all` takes a slice and enforces MAX_MCP_SERVERS (16)
— the ceiling of one existed only here.

One slot forces a real trade-off. An agent given a data MCP loses the
tools it acts with: it can read, then has no way to reply. Observed with
an agent wired to a remote data MCP, which reasoned itself into a corner
mid-turn — "I don't have shell access ... there's no buzz tool exposed
here directly" — and silently produced no output, because the slot that
previously carried its dev tools now carried the data bridge.

`--mcp-command` / `BUZZ_ACP_MCP_COMMAND` now accepts a comma-separated
list, matching how `--agent-args` / `BUZZ_ACP_AGENT_ARGS` already parses
in the same struct. A single value behaves exactly as before, so existing
configurations are unaffected.

Two details worth noting:

- Blank entries are skipped, so the `""` default (and a stray trailing
  comma) still yields no servers rather than a bogus one.
- Names are derived from the command's file stem, and `spawn_all` rejects
  duplicates — so colliding stems (`/opt/a/bridge`, `/opt/b/bridge`) are
  suffixed `bridge`, `bridge-2` instead of aborting startup. The
  operator's intent there is unambiguous; failing would be unhelpful.

Existing `build_mcp_servers` tests are migrated to the list form and
extended: several commands produce one server each in order, every server
receives the identity env, stems are disambiguated, blank entries are
skipped without dropping valid ones, and an empty list produces nothing.

Signed-off-by: Noah Shipley <nshipley@centricsoftware.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant