buzz-acp: session/new only ever passes one stdio MCP server
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
### Summary
`buzz-acp` can only ever pass **one** MCP server to a harness, and only over **stdio**. For adapters that take MCP configuration exclusively from ACP `session/new`, this is the only route available, so those agents are hard-capped at a single stdio MCP server with no arguments.
The ACP wire field is already a list and the schema already defines non-stdio transports, so this looks like an implementation gap rather than a protocol constraint.
### Current behaviour
`build_mcp_servers` in `crates/buzz-acp/src/lib.rs` returns either an empty vec or exactly one entry, built from the single `BUZZ_ACP_MCP_COMMAND` string:
```rust
fn build_mcp_servers(config: &Config) -> Vec {
if config.mcp_command.is_empty() {
return vec![];
}
vec![McpServer {
name: std::path::Path::new(&config.mcp_command)
.file_stem() ...,
command: config.mcp_command.clone(),
args: vec![], // hardcoded
env: { ... },
}]
}
```
Three consequences:
1. **One server maximum.** `BUZZ_ACP_MCP_COMMAND` is a single `String` (`config.rs`), so in practice the slot is spent on `buzz-dev-mcp` and nothing else can be added.
2. **stdio only.** `McpServer` implements just the one variant, as its own doc comment notes:
```rust
/// Corresponds to the `McpServerStdio` variant in the ACP schema.
pub struct McpServer { name, command, args, env }
```
There is no way to express an HTTP or SSE MCP endpoint.
3. **No arguments.** `args: vec![]` is hardcoded, so even a stdio server that needs a flag or URL cannot be configured without an intermediate wrapper script.
### Why this bites
Harnesses differ in whether they have their own MCP configuration:
- **Claude Code** loads its own MCP servers (`claude mcp add`), independent of ACP, so it is unaffected.
- **goose** has native extensions, so it is unaffected.
- **[letta-acp](https://github.com/letta-ai/letta-acp)** deliberately takes MCP servers *only* from ACP params and does not read harness-side config:
```ts
mcpServers: params.mcpServers,
const mcpServers = toSdkMcpServers(options.mcpServers);
```
For that adapter, `buzz-acp` is the sole source of MCP servers, so the agent is limited to one stdio server it may not even want.
- **omp** likewise exposes no MCP configuration of its own that I can find.
So the cap applies precisely to the harnesses that have no alternative route.
### The downstream support already exists
Adapters advertise MCP transport support in their `initialize` response, and this is what three of them report in my deployment today:
| harness | advertised `mcpCapabilities` |
|---|---|
| letta-acp | `{"http": true, "sse": true}` |
| omp 17.2.4 | `{"http": true, "sse": true}` |
| goose 1.45.0 | `{"acp": false, "http": true, "sse": false}` |
`session_new_full` already serialises the field as an array:
```rust
let mut params = serde_json::json!({ "cwd": cwd, "mcpServers": mcp_servers });
```
So nothing on the wire or in the adapters blocks this.
### Proposal
1. Make `McpServer` an enum over the ACP schema variants (stdio / HTTP / SSE) rather than a stdio-only struct.
2. Add a list-valued option, e.g. `BUZZ_ACP_MCP_SERVERS` taking JSON, and keep `BUZZ_ACP_MCP_COMMAND` as a back-compat shorthand for a single stdio entry.
3. Have `build_mcp_servers` return the concatenation of both, gating HTTP/SSE entries on the capabilities the agent advertised at `initialize` (already parsed) so unsupported transports are skipped rather than sent.
Continuing to inject `BUZZ_RELAY_URL` / `BUZZ_PRIVATE_KEY` / `BUZZ_AUTH_TAG` / `BUZZ_ACP_DISPLAY_NAME` into stdio entries would keep `buzz-dev-mcp` working exactly as it does now.
### Current workaround
Point `BUZZ_ACP_MCP_COMMAND` at a wrapper script that `exec`s a stdio-to-HTTP bridge, and accept that the single slot is consumed. The script's file stem also silently becomes the MCP namespace, since the server name is derived from the command path. This works but gives up `buzz-dev-mcp`, and requires a bridge process per remote server.
### Environment
- `buzz` @ `d8281b9c93395f15d55091b131bb2747a0a3da8a`, self-hosted relay
- Five `buzz-acp` agents across Claude Code, goose, omp, and letta-acp
Contributor guide
Assessment
This issue has not been assessed yet.