Skip to content

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

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

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

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 13, 2026

Copy link
Copy Markdown
Member

Adds local-only MCP server layering for the bundled buzz-agent. Goose, Codex, Claude, and other runtimes retain their existing MCP configuration behavior.

Behavior

  • Resolves MCP servers as global < definition < agent; a higher layer replaces a server by name, while a disabled entry masks an inherited server.
  • Validates names, commands, argument and environment payloads, duplicate entries, byte limits, and the reserved BUZZ_ACP_MCP_SERVERS key at every save boundary.
  • Injects the resolved transport JSON into BUZZ_ACP_MCP_SERVERS only for buzz-agent, explicitly removing it for other runtimes.
  • Includes the effective MCP transport in the buzz-agent spawn hash so configuration drift triggers restart, without affecting other runtimes.
  • Passes desktop-resolved servers to buzz-acp, which appends them after the bundled buzz-dev-mcp server.

Desktop configuration

  • Adds MCP server types and IPC fields for global configuration, agent definitions, and agent overrides.
  • Adds the editor to global, persona, and agent dialogs, plus the effective merged read-only configuration surface.
  • Covers layering, validation, update semantics, runtime injection, spawn hashing, config-surface behavior, ACP parsing/fallback, and desktop E2E configuration flows.

Local-only data handling

  • Keeps MCP configuration out of persona, agent, and team snapshot exports because server environments can hold credentials.
  • Leaves newly minted and snapshot-imported agent overrides empty so definition-level MCP configuration remains live rather than being copied into an instance.
  • Team snapshot imports initialize MCP fields empty and assert that no local MCP configuration crosses the shared snapshot boundary.

@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 3 times, most recently from ac45018 to bdfd142 Compare July 13, 2026 21:07
wpfleger96 added a commit that referenced this pull request Jul 14, 2026
@wpfleger96

Copy link
Copy Markdown
Member 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

@wpfleger96 wpfleger96 force-pushed the duncan/buzz-agent-mcp-servers branch from c403f21 to f580a2c Compare July 14, 2026 17:27
@wpfleger96 wpfleger96 marked this pull request as ready for review July 14, 2026 17:30
@wpfleger96 wpfleger96 force-pushed the duncan/buzz-agent-mcp-servers branch from 5fe4767 to 9a4ff3d Compare July 14, 2026 18:50
@wpfleger96 wpfleger96 marked this pull request as draft July 14, 2026 21:32
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 17 commits July 14, 2026 17:34
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>
…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).
An inherited-layer mutation (global config or persona definition) could
silently push an existing buzz-agent over the 15-server effective cap,
breaking it at next spawn. Add pre-save cross-checks at both save
paths so the save is rejected atomically with the offending agent named.
D2: The MCP editor's command field is passed verbatim to
Command::new(), so "uv run /path/to/jambot" tries to find a single
binary with that full string as its name. Shell-split in
configured_mcp_server(): first whitespace token becomes the executable,
remaining tokens prepend before user-supplied args.

D1: When a user-configured MCP server fails to spawn, the ACP agent
returns a JSON-RPC error that previously killed the entire turn. Catch
MCP-related AgentError in create_session_and_apply_model and retry
session creation with empty mcp_servers so the agent can still respond.
Surface the original error via observer as a warning.
F1: Replace `split_whitespace()` with `shlex::split()` in
`configured_mcp_server` so quoted paths with internal spaces (e.g.
`uv run "/My Bot/jambot"`) survive the split. On unmatched quotes,
fall back to verbatim command — D1 surfaces the spawn failure as a
warning instead of a dead agent. Added `shlex` 1.x as a direct dep
(already in Cargo.lock as a transitive dep).

F2: Add focused regression tests for `create_session_and_apply_model`
MCP fallback path — scripted AcpClient returns an MCP-flavored error
on first session/new, success on retry with empty mcpServers. Also
covers non-MCP AgentError propagation (no retry triggered).
Current main added `PromptContext.team_instructions`, so the MCP fallback test fixture must explicitly initialize it.

The MCP snapshot exclusion coverage and upstream shared type growth also require narrowly documented desktop file-size exceptions.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The team snapshot refactor introduced constructors after the MCP layer, and the team argument was added to spawn hashes.

Keep local MCP configuration absent from imported shared snapshots while preserving regression coverage.

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 9a4ff3d to a2ccaa6 Compare July 14, 2026 21:42
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