Skip to content
Draft
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 @@ -160,6 +160,11 @@ RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz
# Binary for an optional MCP server sidecar (e.g. buzz-dev-mcp for buzz-agent).
# BUZZ_ACP_MCP_COMMAND=

# Path to an optional version 1 JSON file defining additional stdio MCP servers.
# This file may contain credentials. Keep it out of Git and restrict it to
# its owner.
# BUZZ_ACP_MCP_CONFIG=/absolute/path/to/mcp-servers.json

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

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion crates/buzz-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ tokio-tungstenite = { workspace = true }
tokio-util = { workspace = true }
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
futures-util = { workspace = true }
aho-corasick = "1.1"

# HTTP (channel discovery REST API)
reqwest = { workspace = true }
Expand Down Expand Up @@ -61,6 +62,7 @@ tracing-subscriber = { workspace = true }
# Error handling
thiserror = { workspace = true }
anyhow = { workspace = true }
zeroize = { workspace = true }

# CLI
clap = { version = "4", features = ["derive", "env"] }
Expand All @@ -74,7 +76,7 @@ evalexpr = { workspace = true }
# 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]
nix = { version = "0.31", default-features = false, features = ["signal"] }
nix = { version = "0.31", default-features = false, features = ["signal", "user"] }

[dev-dependencies]
tokio = { workspace = true, features = ["test-util"] }
Expand Down
94 changes: 94 additions & 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_MCP_CONFIG` | no | `""` (empty) | Path to a version 1 JSON file defining additional stdio and Streamable HTTP MCP servers. |
| `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 All @@ -119,6 +120,99 @@ All configuration is via environment variables (or CLI flags — every env var h

**Legacy env vars:** `BUZZ_ACP_PRIVATE_KEY`, `BUZZ_ACP_API_TOKEN`, and `BUZZ_ACP_TURN_TIMEOUT` (replaced by `BUZZ_ACP_IDLE_TIMEOUT`) are still accepted as fallbacks.

### Multiple MCP servers

Use `--mcp-config <absolute-path>` or `BUZZ_ACP_MCP_CONFIG` to add named local
and remote MCP servers:

```json
{
"version": 1,
"servers": [
{
"name": "analytics",
"transport": "stdio",
"command": "/opt/mcp/analytics-server",
"args": ["--stdio"],
"env": {
"ANALYTICS_TOKEN": "replace-me"
}
},
{
"name": "hosted-context",
"transport": "http",
"url": "https://mcp.example.test/mcp",
"headers": [
{
"name": "Authorization",
"value": "Bearer replace-me"
}
]
}
]
}
```

This file is a resolved, device-local launch handoff. It is not a persona or
template authoring format. MCP declarations parsed by `buzz-persona` are not
loaded by `buzz-acp` automatically; a launcher must resolve and approve them
before writing this document.

The JSON is strict. The only top-level fields are `version` and `servers`.
Each server has a `name` and tagged `transport`. A `stdio` entry contains
`command`, `args`, and `env`; an `http` entry contains a URL and optional
headers. URLs and header values are final resolved values; Project or deployment
code must resolve any secret selectors before writing the protected handoff.
HTTP is allowed only on a literal loopback address and must be credential-free;
all other endpoints require HTTPS. Server names must
be unique, contain 1 to 128 ASCII bytes using only letters, digits, `_`, or `-`,
and cannot contain `__`.
Names are checked across both structured entries and the legacy server.

The config file is limited to 64 KiB. A harness can have at most 16 MCP
servers in total, including the server from `BUZZ_ACP_MCP_COMMAND`. An
unreadable file, malformed JSON, an unsupported version, an unknown field, or
an invalid server entry stops startup. Buzz does not silently drop a server.

`BUZZ_ACP_MCP_COMMAND` keeps its current behavior. It defines one privileged
Buzz companion and receives the relay URL and Buzz identity credentials.
For a structured server, Buzz puts only the values listed in its `env` object
into the ACP `env` list. Protected Buzz identity and authentication keys are
rejected. Buzz sends the list to the ACP adapter in `session/new`, and the
adapter controls the MCP processes. Treat the adapter as a credential broker
and use one you trust. Buzz sends arguments and environment values unchanged
to that adapter, but redacts both from its own wire logs and observer feed.

The adapter still inherits the harness environment so its shell tools can use
the `buzz` CLI. Some adapters may propagate inherited variables to MCP child
processes. Per-server `env` entries are explicit configuration, not a process
isolation boundary. Use a separate account, container, or credential-brokered
service when the MCP process must not inherit adapter credentials.

MCP transport capability and tool authorization are separate gates. Before an
unattended launch, the Project or deployment resolver must pre-authorize the
resolved servers and tools in the selected adapter's protected, agent-specific
configuration. Do not restore a global permission bypass. An adapter that
advertises HTTP support can still deny every tool call under a non-interactive
`dontAsk` policy; ACP currently exposes no portable initialize-time proof of
those adapter-local approvals, so the launcher must fail closed when it cannot
establish them.

Release acceptance for a mixed configuration must exercise the real adapter,
not only configuration parsing: create a session containing the legacy Buzz
stdio companion and at least one structured HTTP server, call one harmless tool
from each transport, then publish the result through the companion's constrained
`send` tool. Tool listing, model completion, or a fake-adapter payload capture
alone does not prove end-to-end composition or outbound publishing.

If the JSON contains secrets, keep it outside Git and restrict the file to its
owner. On Unix:

```bash
chmod 600 /absolute/path/to/mcp-servers.json
buzz-acp --mcp-config /absolute/path/to/mcp-servers.json
```

### Parallel Agents & Heartbeat

| Flag | Env Var | Default | Description |
Expand Down
Loading