Skip to content

feat(desktop): buzz-agent MCP server configuration#1785

Draft
wpfleger96 wants to merge 12 commits into
mainfrom
duncan/buzz-agent-mcp-servers
Draft

feat(desktop): buzz-agent MCP server configuration#1785
wpfleger96 wants to merge 12 commits into
mainfrom
duncan/buzz-agent-mcp-servers

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Adds local-only MCP server layering for buzz-agent. Other runtimes (Goose, Codex, Claude) manage their own MCP config and are unchanged.

What ships

Three-layer merge: global < definition < agent. Higher layers replace whole entries by name; a disabled override masks an inherited server (no leak-through).

Desktop types (mcp_servers.rs):

  • McpServerConfig — persisted stdio server with name, command, args, env, enabled
  • McpServerTransport — wire shape (strips enabled; never crosses the process/provider boundary)
  • validate_mcp_servers — empty name, duplicate names, enabled with empty command, NUL bytes, per-field/total-payload byte caps, reserved-key guard (BUZZ_ACP_MCP_SERVERS)
  • merge_mcp_servers — three-layer global/definition/agent resolve with 15-server cap (slot 16 is reserved for the bundled buzz-dev-mcp)
  • replace_mcp_servers — absent-preserves/present-replaces update contract, matches env_vars semantics

Env injection (runtime.rs):

  • set_buzz_agent_mcp_servers_env — sets BUZZ_ACP_MCP_SERVERS to serialized transport JSON for buzz-agent children; explicitly removes the var for all other runtimes

Spawn-hash (spawn_hash.rs):

  • MCP layer included in hash for buzz-agent only; config drift triggers auto-restart

Deploy payload (agents_deploy.rs):

  • build_deploy_payload resolves effective MCP transport and forwards it as mcp_servers — same path as local spawn

Snapshot exclusion (agent_snapshot.rs):

  • mcp_servers excluded from export snapshots (credentials in env vars)

Data model (types.rs):

  • GlobalAgentConfig.mcp_servers — inherits to every buzz-agent instance
  • AgentDefinition.mcp_servers — definition layer (NOT copied into the record on persona→agent mint, so later persona edits aren't shadowed)
  • ManagedAgentRecord.mcp_servers — per-agent override layer

IPC (requests.rs, agents.rs, agent_models.rs, personas/mod.rs):

  • CreateManagedAgentRequest, UpdateManagedAgentRequest, CreatePersonaRequest, UpdatePersonaRequest all carry optional MCP fields with validation at every save boundary

buzz-acp (config.rs, lib.rs):

  • ConfiguredMcpServer deserializes from BUZZ_ACP_MCP_SERVERS
  • build_mcp_servers appends desktop-resolved servers after the built-in buzz-dev-mcp

Rebase compatibility

  • The rebase added two compile-required mcp_servers: vec![] discovery fixtures after discovery/tests.rs had reached its existing grandfathered ceiling. The file-size override ratchets from 1029 to 1031; this test file should be split in follow-up work.
  • team_snapshot construction sites introduced after the original branch base initialize mcp_servers empty, mirroring the local-only env_vars behavior. Snapshot imports therefore do not transfer MCP server definitions or their credential-bearing environment values.

Tests added

  • Merge semantics: higher layer replaces by name, disabled mask blocks inherited server, mask needs only a name

  • Validation: empty name, duplicates, enabled-without-command, reserved env key, per-field/total-bytes caps

  • replace_mcp_servers absent-preserves/present-replaces contract

  • Spawn-hash: MCP edit changes buzz-agent hash; Goose ignores it; definition layer contributes without becoming an agent override

  • Runtime: set_buzz_agent_mcp_servers_env sets for buzz-agent, removes for other runtimes

  • Snapshot: mcp_servers absent from export JSON

  • buzz-acp: parse_configured_mcp_servers round-trip; build_mcp_servers appends configured servers after builtin

  • Borrowed the disabled-mask test value via std::slice::from_ref to satisfy clippy before the value is moved into the merge assertion.

@wpfleger96 wpfleger96 requested a review from a team as a code owner July 13, 2026 01:40
@wpfleger96 wpfleger96 marked this pull request as draft July 13, 2026 01:51
@wpfleger96 wpfleger96 force-pushed the duncan/buzz-agent-mcp-servers branch 2 times, most recently from 59c45c7 to ac45018 Compare July 13, 2026 16:36
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 4 commits July 13, 2026 17:06
Add local-only MCP server layering (global < definition < agent) for
buzz-agent. Other runtimes manage their own MCP config and are unchanged.

- McpServerConfig/McpServerTransport types with validate/merge/replace helpers
- BUZZ_ACP_MCP_SERVERS reserved env key; desktop injects transport JSON only
  for buzz-agent children, explicitly removes it for all others
- spawn_config_hash includes effective MCP layer so config drift triggers restart
- build_deploy_payload forwards resolved Mcp transport to providers
- agent_snapshot excludes mcp_servers (credentials in env vars)
- GlobalAgentConfig.mcp_servers: inherits to every buzz-agent instance
- AgentDefinition.mcp_servers: definition layer (not copied into record on mint)
- ManagedAgentRecord.mcp_servers: per-agent override layer
- buzz-acp: ConfiguredMcpServer deserialized from BUZZ_ACP_MCP_SERVERS; appended
  after built-in buzz-dev-mcp in build_mcp_servers
- Max 15 user-defined servers (slot 16 is reserved for buzz-dev-mcp)
- IPC validation at every save boundary: empty name, duplicate names, enabled
  with empty command, NUL bytes, per-field length cap, total payload cap,
  reserved env key guard

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
F1: Extend env_secrets_from_request in backend.rs to harvest
mcp_servers[*].env[*].value alongside env_vars, so provider errors
that echo MCP env values are redacted from desktop-visible last_error.
Add two regression tests: one verifying harvest coverage, one verifying
end-to-end redaction.

F2: Extend validate_mcp_servers in types/mcp_servers.rs to mirror the
buzz-agent name grammar (1-128 bytes, ASCII alnum/_/-, no __) and
reserve the built-in buzz-dev-mcp name. Both checks fire at every IPC
save boundary, so names that would fail at spawn are rejected at edit
time. Add tests covering invalid chars, double-underscore, reserved
name, valid grammar.

F3: Restructure build_mcp_servers in buzz-acp/src/lib.rs so the
built-in server push is conditional on mcp_command being non-empty, but
configured_mcp_servers are always appended. Previously the early-return
on empty mcp_command silently discarded user-configured servers even
though they don't depend on the built-in slot. Update the test that
pinned the old drop behavior; add a complementary empty-with-no-servers
test.

F4: Add an enabled-server count check to validate_mcp_servers: a layer
with more than MAX_USER_MCP_SERVERS enabled servers is rejected at save
time rather than only at merge/spawn. Add boundary tests (15 passes,
16 fails).

MINOR: Add mcp_servers to the MUST NEVER list in agent_events.rs so
future edits don't miss the local-only invariant.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
New snapshot construction sites added after the original MCP change require empty local-only MCP configuration. Raise the discovery fixture-file exception by the two required fields.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Borrow the disabled mask for validation before moving it into the merge assertion, avoiding an unnecessary clone.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 force-pushed the duncan/buzz-agent-mcp-servers branch from ac45018 to bdfd142 Compare July 13, 2026 21:07
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 8 commits July 13, 2026 17:53
…face

Add buzz_agent_mcp_servers to RuntimeConfigSurface, populated by
resolve_config_surface via the existing effective_buzz_agent_mcp_servers
merge (global < definition < agent, enabled-only) for the buzz-agent
runtime only. Every other runtime keeps the field empty and continues
to surface its servers via extensions.

This is the WYSIWYG read path the PR3 UI's read-only buzzAgentSlot will
consume — the effective list is now reachable from the config-bridge
surface the frontend already reads, matching what actually runs at
spawn (spawn_hash.rs, runtime.rs) rather than just the agent's own
record layer.

Also bumps two pre-existing file-size overrides (agent_config.rs,
readiness.rs) to their gate's own split-count, matching this diff and
a prior unaccounted +1 line already on the branch.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Add McpServerConfig/McpServerEnvVar FE types (camelCase mirror of the
Rust McpServerConfig) and mcpServers fields on ManagedAgent, AgentPersona,
GlobalAgentConfig, and the create/update managed-agent and persona IPC
inputs. Threads mcpServers through fromRawManagedAgent/fromRawPersona and
the invoke builders in tauri.ts/tauriPersonas.ts with the same
absent-vs-present contract as envVars (create sends mcpServers ?? [],
update sends it only when provided). Adds the buzzAgentMcpServers surface
field (populated on the backend by 859b8bb) to RuntimeConfigSurface.

Wires the e2eBridge.ts mock to match: mcp_servers on the local
RawManagedAgent/RawPersona types, a cloneMcpServer helper mirroring the
existing clone* helpers, mcp_servers handling in the create/update
persona and managed-agent mock handlers, and buzzAgentMcpServers on every
buildMockConfigSurface fixture (a sample entry on the buzz-agent fixture
so the read-only display has something to render by default).

Bumps two file-size overrides (tauri.ts, types.ts) to the gate's own
split-count for this diff.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…d-only panel

Wires the buzzAgentSlot prop that McpServersSection has exposed since
#1780 with the RuntimeConfigSurface.buzzAgentMcpServers field added in
P0. AgentConfigPanel now renders the effective merged (global <
definition < agent) enabled-server list via a new BuzzAgentMcpServerRow,
giving Will's WYSIWYG requirement (Q2) its read-only half. Also fixes
McpServersSection's fallback text, which previously rendered unreachable
for buzz-agent (the extensions-based branch never executes there since
buzz-agent always reports empty extensions).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
New row-based editor for the local buzz-agent MCP server layer, modeled
on EnvVarsEditor: per-server Name/Command/repeatable-Args/repeatable-Env
rows plus an Enabled toggle, client-side validation mirroring the Rust
validate_mcp_servers grammar (name charset, length, __, reserved name,
uniqueness, command-required-when-enabled), and read-only inherited-layer
rows a same-named local row overrides or masks. Command and Args are
separate fields (Q1) — no combined command string, no shell-quote
dependency. Not yet mounted in any dialog (P3).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Wires the McpServersEditor into the global config, definition, and
instance-edit dialogs — completing the UI layer for the editable MCP
servers feature.

- GlobalAgentConfigFields: global base-layer editor (no inherited rows)
- PersonaAdvancedFields: definition-layer editor with global as inherited
- AgentDefinitionDialog: state, init reset, advanced-expand gate, submit
- EditAgentAdvancedFields: per-instance editor with merged global+persona
  inherited rows; new mcpServers/inheritedMcpServers/onMcpServersChange props
- AgentInstanceEditDialog: mcpServers state, inheritedMcpServers memo
  (mergeMcpServersByName(global, persona)), absent-preserves submit wire,
  and EditAgentAdvancedFields prop threading
- check-file-sizes.mjs: bump AgentInstanceEditDialog override 1180→1198
  (+18 lines from load-bearing P3 prop threading)
Adds invoke-layer round-trip tests + UI interaction tests for the
editable MCP servers feature (P3), covering all three dialog layers.

Round-trip tests (invokeTauri, behavior not impl):
- persona create+update mcp_servers with add/change/drop
- update_persona absent-preserves contract (omit → keeps, []→ clears)
- agent create+update mcp_servers round-trip + absent-preserves

UI tests with screenshots:
- McpServersEditor in PersonaDialog (buzz-agent auto-expands Advanced)
- McpServersEditor in global config card with pre-seeded servers
- McpServersEditor in agent-instance edit dialog with pre-seeded servers

Bridge changes:
- MockManagedAgentSeed: add agentCommand (default "goose" preserved) and
  mcpServers seeding, wired into buildSeededManagedAgent
- bridge.ts helper: add agentCommand/mcpServers to mock seed type;
  add mcp_servers to globalAgentConfig mock shape
D1 CRITICAL: default each field in get_global_agent_config bridge handler
so partial seeds don't crash McpServersEditor; add defense-in-depth
coercion at the McpServersEditor value boundary.

D2 IMPORTANT: split buzzAgentSurface into empty/populated fixtures so
test 06 ("empty MCP servers") doesn't collide with the filesystem row;
add test 06b covering populated WYSIWYG read-only display.

D3 IMPORTANT: count effective enabled servers (local + inherited not
overridden) for the cap, gating both Add button and Enabled switch;
extract effectiveEnabledCount helper with 6 unit tests.

D4 IMPORTANT: add validateMcpServerEnvEntry mirroring Rust's
validate_user_env_keys boundary (POSIX key format, reserved keys,
NUL-free values, per-value byte cap); render inline subrow errors;
12 unit tests covering all rejection paths.

D5 MINOR: replace localeCompare with ASCII byte-order comparator in
mergeMcpServersByName to match Rust BTreeMap ordering; add mixed-case
/symbol test.
…idation mirror

F1: Effective-merge MCP cap check at agent create/update save time.
validate_effective_mcp_cap calls effective_buzz_agent_mcp_servers
(the same resolver used at spawn) and rejects when the three-layer
merge (global < definition < agent) exceeds MAX_USER_MCP_SERVERS.
Prevents a per-layer-valid record from silently breaking at spawn or
emptying the WYSIWYG surface. Editor now shows a destructive error
whenever effective count exceeds the cap (fires on rename too).
4 Rust unit tests: 15-global+1-local → Err, at-cap → Ok,
rename-unmask → Err, non-buzz-agent skip.

F2: Complete the client-side Rust mirror — validateMcpServerRow now
checks command NUL + ≤32KB; new validateMcpServerArg checks arg
NUL + ≤32KB with inline subrow errors; new validateMcpServerListPayload
checks aggregate total payload ≤256KB (name+command+args+env bytes
across all servers) with editor-level error. Adds MAX_ENV_TOTAL_BYTES
constant. 9 unit tests: command NUL/oversize/at-limit, arg NUL/oversize
/valid, payload under/over/at-limit.

check-file-sizes.mjs: agent_models.rs override bumped 1079→1082
(+3 lines for the effective-cap block).
wpfleger96 added a commit that referenced this pull request Jul 14, 2026
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Empty global MCP servers editor

Global agent config MCP editor in its empty state — label, helper text, and Add server button.

pr1785-editor-global-empty

Populated MCP servers editor

Two local server rows fully expanded: filesystem (npx, args, NODE_ENV env var) and sqlite-db (uvx, args). Enabled switches visible.

pr1785-editor-populated

Inherited server rows with local override

Agent-level editor showing an inherited global row (sqlite-db, "Inherited from global" badge with Mask button) and a local override of filesystem (bunx, "Overrides global server" hint).

pr1785-editor-inherited

Validation: reserved env key error

Inline subrow error for a reserved env key — BUZZ_PRIVATE_KEY is flagged as reserved by Buzz in destructive red text.

pr1785-editor-validation

Read-only WYSIWYG MCP servers panel

Agent config profile panel for a buzz-agent showing the effective-merged "filesystem" server under the MCP Servers label with the full command line.

pr1785-wysiwyg-populated

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