feat(desktop): buzz-agent MCP server configuration#1785
Draft
wpfleger96 wants to merge 13 commits into
Draft
Conversation
59c45c7 to
ac45018
Compare
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>
ac45018 to
bdfd142
Compare
…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
Collaborator
Author
Empty global MCP servers editorGlobal agent config MCP editor in its empty state — label, helper text, and Add server button. Populated MCP servers editorTwo local server rows fully expanded: filesystem (npx, args, NODE_ENV env var) and sqlite-db (uvx, args). Enabled switches visible. Inherited server rows with local overrideAgent-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). Validation: reserved env key errorInline subrow error for a reserved env key — Read-only WYSIWYG MCP servers panelAgent config profile panel for a buzz-agent showing the effective-merged "filesystem" server under the MCP Servers label with the full command line. |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.





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 withname,command,args,env,enabledMcpServerTransport— wire shape (stripsenabled; 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-layerglobal/definition/agentresolve with 15-server cap (slot 16 is reserved for the bundledbuzz-dev-mcp)replace_mcp_servers— absent-preserves/present-replaces update contract, matchesenv_varssemanticsEnv injection (
runtime.rs):set_buzz_agent_mcp_servers_env— setsBUZZ_ACP_MCP_SERVERSto serialized transport JSON forbuzz-agentchildren; explicitly removes the var for all other runtimesSpawn-hash (
spawn_hash.rs):buzz-agentonly; config drift triggers auto-restartDeploy payload (
agents_deploy.rs):build_deploy_payloadresolves effective MCP transport and forwards it asmcp_servers— same path as local spawnSnapshot exclusion (
agent_snapshot.rs):mcp_serversexcluded from export snapshots (credentials in env vars)Data model (
types.rs):GlobalAgentConfig.mcp_servers— inherits to everybuzz-agentinstanceAgentDefinition.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 layerIPC (
requests.rs,agents.rs,agent_models.rs,personas/mod.rs):CreateManagedAgentRequest,UpdateManagedAgentRequest,CreatePersonaRequest,UpdatePersonaRequestall carry optional MCP fields with validation at every save boundarybuzz-acp (
config.rs,lib.rs):ConfiguredMcpServerdeserializes fromBUZZ_ACP_MCP_SERVERSbuild_mcp_serversappends desktop-resolved servers after the built-inbuzz-dev-mcpRebase compatibility
mcp_servers: vec![]discovery fixtures afterdiscovery/tests.rshad reached its existing grandfathered ceiling. The file-size override ratchets from1029to1031; this test file should be split in follow-up work.team_snapshotconstruction sites introduced after the original branch base initializemcp_serversempty, mirroring the local-onlyenv_varsbehavior. 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_serversabsent-preserves/present-replaces contractSpawn-hash: MCP edit changes buzz-agent hash; Goose ignores it; definition layer contributes without becoming an agent override
Runtime:
set_buzz_agent_mcp_servers_envsets for buzz-agent, removes for other runtimesSnapshot:
mcp_serversabsent from export JSONbuzz-acp:
parse_configured_mcp_serversround-trip;build_mcp_serversappends configured servers after builtinBorrowed the disabled-mask test value via
std::slice::from_refto satisfy clippy before the value is moved into the merge assertion.