feat(desktop): mount McpServersEditor in all three agent dialogs (P3)

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)
This commit is contained in:
Will Pfleger
2026-07-14 17:34:04 -04:00
parent 7db5831597
commit 17893e4c7f
6 changed files with 96 additions and 1 deletions
+3 -1
View File
@@ -486,7 +486,9 @@ const overrides = new Map([
// +2 provider-aware effort: model/provider props threaded to BuzzAgentModelTuningFields.
// +15 provider/model dropdown fixes: useBakedBuildEnvKeysQuery + hideProviderIds
// for Databricks v1 gate; prospectiveRuntimeId default fallback for builtins.
["src/features/agents/ui/AgentInstanceEditDialog.tsx", 1180],
// +18 PR3 MCP servers UI: mcpServers state, inheritedMcpServers memo,
// submit wire, and EditAgentAdvancedFields prop threading.
["src/features/agents/ui/AgentInstanceEditDialog.tsx", 1198],
// AgentDefinitionDialog grew past 1000 with the following load-bearing fixes:
// isRuntimeAutoSeededRef tracking for edit-mode seeding (Fizz shows models);
// runtimeSupportsLlmProviderSelection guard on discovery provider (codex fix);
@@ -16,6 +16,7 @@ import { Textarea } from "@/shared/ui/textarea";
import { AgentCreationPreview } from "./AgentCreationPreview";
import { PersonaDropdownField } from "./PersonaDropdownField";
import type { EnvVarsValue } from "./EnvVarsEditor";
import type { McpServersValue } from "./McpServersEditor";
import { PersonaAdvancedFields } from "./PersonaAdvancedFields";
import { PersonaModelField } from "./PersonaModelField";
import {
@@ -128,6 +129,7 @@ export function AgentDefinitionDialog({
React.useState(false);
const [namePoolText, setNamePoolText] = React.useState("");
const [envVars, setEnvVars] = React.useState<EnvVarsValue>({});
const [mcpServers, setMcpServers] = React.useState<McpServersValue>([]);
const [behaviorDraft, setBehaviorDraft] = React.useState(
emptyPersonaBehaviorDraft,
);
@@ -179,14 +181,18 @@ export function AgentDefinitionDialog({
: "";
const nextEnvVars =
"envVars" in initialValues ? (initialValues.envVars ?? {}) : {};
const nextMcpServers =
"mcpServers" in initialValues ? (initialValues.mcpServers ?? []) : [];
const nextBehaviorDraft = draftFromBehavior(initialValues.behavior);
behaviorSeedRef.current = draftFromBehavior(initialValues.behavior);
setBehaviorDraft(nextBehaviorDraft);
setNamePoolText(nextNamePoolText);
setEnvVars(nextEnvVars);
setMcpServers(nextMcpServers);
setShowAdvancedFields(
nextNamePoolText.trim().length > 0 ||
Object.keys(nextEnvVars).length > 0 ||
nextMcpServers.length > 0 ||
nextBehaviorDraft.respondTo !== null ||
nextBehaviorDraft.parallelism.trim().length > 0,
);
@@ -282,6 +288,7 @@ export function AgentDefinitionDialog({
provider: providerForSubmit,
namePool: namePoolInput,
envVars,
mcpServers,
behavior: behaviorForSubmit(
behaviorDraft,
behaviorSeedRef.current,
@@ -867,11 +874,14 @@ export function AgentDefinitionDialog({
envVars={envVars}
fileSatisfiedEnvKeys={localModeGate.fileSatisfiedEnvKeys}
inheritedEnvVars={globalConfig.env_vars}
inheritedMcpServers={globalConfig.mcp_servers}
mcpServers={mcpServers}
model={model}
modelTuningRuntimeId={runtime}
namePoolText={namePoolText}
onBehaviorDraftChange={setBehaviorDraft}
onEnvVarsChange={setEnvVars}
onMcpServersChange={setMcpServers}
onNamePoolTextChange={setNamePoolText}
provider={provider}
requiredEnvKeys={requiredEnvKeys}
@@ -63,6 +63,11 @@ import {
} from "./runtimeModelProviderSelection";
import { AgentCreationPreview } from "./AgentCreationPreview";
import type { EnvVarsValue } from "./EnvVarsEditor";
import {
mergeMcpServersByName,
serversEqual,
type McpServersValue,
} from "./McpServersEditor";
import { useRequiredCredentialState } from "./useRequiredCredentialState";
import { CreateAgentRespondToField } from "./RespondToField";
import { PersonaDropdownField } from "./PersonaDropdownField";
@@ -125,6 +130,9 @@ export function AgentInstanceEditDialog({
const [isCustomProviderEditing, setIsCustomProviderEditing] =
React.useState(false);
const [envVars, setEnvVars] = React.useState<EnvVarsValue>(agent.envVars);
const [mcpServers, setMcpServers] = React.useState<McpServersValue>(
agent.mcpServers,
);
const [autoRestartOnConfigChange, setAutoRestartOnConfigChange] =
React.useState(agent.autoRestartOnConfigChange);
const personasQuery = usePersonasQuery();
@@ -175,6 +183,7 @@ export function AgentInstanceEditDialog({
setProvider(agent.provider ?? "");
setIsCustomProviderEditing(false);
setEnvVars(agent.envVars);
setMcpServers(agent.mcpServers);
setAutoRestartOnConfigChange(agent.autoRestartOnConfigChange);
setRespondTo(agent.respondTo);
setRespondToAllowlist(agent.respondToAllowlist);
@@ -424,6 +433,17 @@ export function AgentInstanceEditDialog({
return { ...globalConfig.env_vars, ...inheritedEnvVars };
}, [globalConfig.env_vars, inheritedEnvVars]);
// Merge global + persona MCP servers for the inherited display hint in
// McpServersEditor. Persona-layer servers override same-named global ones.
const inheritedMcpServers = React.useMemo(
() =>
mergeMcpServersByName(
globalConfig.mcp_servers,
linkedPersona?.mcpServers ?? [],
),
[globalConfig.mcp_servers, linkedPersona?.mcpServers],
);
// Auto-expand Advanced whenever the prospective runtime is buzz-agent so the
// model-tuning knobs are reachable even when no required key is missing.
// Fires once per dialog-open cycle; does not re-open if the user manually
@@ -684,6 +704,9 @@ export function AgentInstanceEditDialog({
envVars: envVarsChanged(submitEnvVars, agent.envVars)
? submitEnvVars
: undefined,
mcpServers: serversEqual(mcpServers, agent.mcpServers)
? undefined
: mcpServers,
respondTo: respondTo !== agent.respondTo ? respondTo : undefined,
// The allowlist is preserved across mode toggles in local UI state
// (so a user can flip away from allowlist and back without losing
@@ -1081,8 +1104,10 @@ export function AgentInstanceEditDialog({
: undefined
}
inheritedEnvVars={inheritedWithGlobal}
inheritedMcpServers={inheritedMcpServers}
inheritHarness={inheritHarness}
linkedPersona={linkedPersona}
mcpServers={mcpServers}
model={model}
modelTuningRuntimeId={prospectiveRuntimeId}
parallelism={parallelism}
@@ -1097,6 +1122,7 @@ export function AgentInstanceEditDialog({
onAutoRestartChange={setAutoRestartOnConfigChange}
onEnvVarsChange={setEnvVars}
onInheritHarnessChange={setInheritHarness}
onMcpServersChange={setMcpServers}
onParallelismChange={setParallelism}
onRelayUrlChange={setRelayUrl}
onSystemPromptChange={setSystemPrompt}
@@ -2,6 +2,7 @@ import { cn } from "@/shared/lib/cn";
import { Input } from "@/shared/ui/input";
import { Textarea } from "@/shared/ui/textarea";
import { EnvVarsEditor, type EnvVarsValue } from "./EnvVarsEditor";
import { McpServersEditor, type McpServersValue } from "./McpServersEditor";
import {
PERSONA_FIELD_CONTROL_CLASS,
PERSONA_FIELD_SHELL_CLASS,
@@ -21,8 +22,10 @@ export function EditAgentAdvancedFields({
fileSatisfiedEnvKeys,
focusKey,
inheritedEnvVars,
inheritedMcpServers = [],
inheritHarness,
linkedPersona,
mcpServers,
model,
modelTuningRuntimeId,
parallelism,
@@ -36,6 +39,7 @@ export function EditAgentAdvancedFields({
onAgentCommandChange,
onEnvVarsChange,
onInheritHarnessChange,
onMcpServersChange,
onParallelismChange,
onRelayUrlChange,
onAutoRestartChange,
@@ -51,7 +55,11 @@ export function EditAgentAdvancedFields({
/** When set, EnvVarsEditor scrolls and focuses this key's input on mount. */
focusKey?: string;
inheritedEnvVars: Record<string, string>;
/** Read-only MCP servers inherited from the global + persona layers, merged. */
inheritedMcpServers?: McpServersValue;
inheritHarness: boolean;
/** This agent instance's own MCP server layer. */
mcpServers: McpServersValue;
linkedPersona: AgentPersona | null;
/** Active LLM model — forwarded to BuzzAgentModelTuningFields for effort filtering. */
model?: string;
@@ -73,6 +81,7 @@ export function EditAgentAdvancedFields({
onAgentCommandChange: (value: string) => void;
onEnvVarsChange: (value: EnvVarsValue) => void;
onInheritHarnessChange: (value: boolean) => void;
onMcpServersChange: (value: McpServersValue) => void;
onParallelismChange: (value: string) => void;
onRelayUrlChange: (value: string) => void;
onAutoRestartChange: (value: boolean) => void;
@@ -312,6 +321,17 @@ export function EditAgentAdvancedFields({
value={envVars}
/>
{/* MCP servers */}
<McpServersEditor
disabled={disabled}
helperText="Per-instance MCP servers. Override same-named servers from the template and global layers."
inheritedLabel="template / global defaults"
inheritedServers={inheritedMcpServers}
label="MCP servers"
onChange={onMcpServersChange}
value={mcpServers}
/>
{/* Tier-1 buzz-agent model-tuning knobs — only shown for buzz-agent. */}
{isBuzzAgentRuntime(modelTuningRuntimeId) ? (
<BuzzAgentModelTuningFields
@@ -14,6 +14,10 @@ import type {
} from "@/shared/api/types";
import { EnvVarsEditor } from "@/features/agents/ui/EnvVarsEditor";
import type { InheritedEnvRow } from "@/features/agents/ui/EnvVarsEditor";
import {
McpServersEditor,
type McpServersValue,
} from "@/features/agents/ui/McpServersEditor";
import { getBakedProviderInheritLabel } from "@/features/agents/ui/bakedEnvHelpers";
import {
AUTO_PROVIDER_DROPDOWN_VALUE,
@@ -142,6 +146,10 @@ export function GlobalAgentConfigFields({
onConfigChange({ ...config, model: value || null });
}
function handleMcpServersChange(next: McpServersValue) {
onConfigChange({ ...config, mcp_servers: next });
}
function handleEnvVarsChange(next: Record<string, string>) {
const effort = config.env_vars[BUZZ_AGENT_THINKING_EFFORT];
const merged =
@@ -284,6 +292,16 @@ export function GlobalAgentConfigFields({
)}
/>
</div>
{/* MCP servers */}
<div className="p-3">
<McpServersEditor
helperText="Global base layer — applied to all buzz-agent instances. Per-definition and per-instance servers layer on top."
label="Global MCP servers"
onChange={handleMcpServersChange}
value={config.mcp_servers}
/>
</div>
</SettingsOptionGroup>
);
}
@@ -1,6 +1,7 @@
import { Input } from "@/shared/ui/input";
import { cn } from "@/shared/lib/cn";
import { EnvVarsEditor, type EnvVarsValue } from "./EnvVarsEditor";
import { McpServersEditor, type McpServersValue } from "./McpServersEditor";
import { CreateAgentRespondToField } from "./RespondToField";
import type { PersonaBehaviorDraft } from "./personaBehaviorDraft";
import { isBuzzAgentRuntime } from "./buzzAgentConfig";
@@ -16,11 +17,14 @@ export function PersonaAdvancedFields({
disabled,
envVars,
inheritedEnvVars = {},
mcpServers,
inheritedMcpServers = [],
model,
modelTuningRuntimeId = "",
namePoolText,
onBehaviorDraftChange,
onEnvVarsChange,
onMcpServersChange,
onNamePoolTextChange,
provider,
requiredEnvKeys = [],
@@ -32,6 +36,10 @@ export function PersonaAdvancedFields({
/** Env vars to display as inherited defaults in tuning-field placeholders.
* For templates, pass `globalConfig.env_vars` (the fallback layer). */
inheritedEnvVars?: EnvVarsValue;
/** This definition's local-only MCP server layer. */
mcpServers: McpServersValue;
/** Read-only servers inherited from the global layer, already merged. */
inheritedMcpServers?: McpServersValue;
/** Active LLM model — forwarded to BuzzAgentModelTuningFields for effort filtering. */
model?: string;
/** Runtime id for the buzz-agent tuning knobs visibility gate. */
@@ -39,6 +47,7 @@ export function PersonaAdvancedFields({
namePoolText: string;
onBehaviorDraftChange: (value: PersonaBehaviorDraft) => void;
onEnvVarsChange: (value: EnvVarsValue) => void;
onMcpServersChange: (value: McpServersValue) => void;
onNamePoolTextChange: (value: string) => void;
/** Active LLM provider id — forwarded to BuzzAgentModelTuningFields for effort filtering. */
provider?: string;
@@ -144,6 +153,16 @@ export function PersonaAdvancedFields({
value={envVars}
/>
<McpServersEditor
disabled={disabled}
helperText="Local-only MCP servers spawned alongside buzz-agent instances created from this definition."
inheritedLabel="global"
inheritedServers={inheritedMcpServers}
label="MCP servers"
onChange={onMcpServersChange}
value={mcpServers}
/>
{/* Tier-1 buzz-agent model-tuning knobs — only shown for buzz-agent. */}
{isBuzzAgentRuntime(modelTuningRuntimeId) ? (
<BuzzAgentModelTuningFields