diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index 778013122..b4f8a9933 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -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); diff --git a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx index ef7b80354..e2983b3d7 100644 --- a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx +++ b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx @@ -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({}); + const [mcpServers, setMcpServers] = React.useState([]); 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} diff --git a/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx b/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx index 52f839431..fe792876b 100644 --- a/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx +++ b/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx @@ -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(agent.envVars); + const [mcpServers, setMcpServers] = React.useState( + 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} diff --git a/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx b/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx index 614c44434..2d75aff10 100644 --- a/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx +++ b/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx @@ -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; + /** 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 */} + + {/* Tier-1 buzz-agent model-tuning knobs — only shown for buzz-agent. */} {isBuzzAgentRuntime(modelTuningRuntimeId) ? ( ) { const effort = config.env_vars[BUZZ_AGENT_THINKING_EFFORT]; const merged = @@ -284,6 +292,16 @@ export function GlobalAgentConfigFields({ )} /> + + {/* MCP servers */} +
+ +
); } diff --git a/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx b/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx index 113e5e5a7..47a235208 100644 --- a/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx +++ b/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx @@ -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} /> + + {/* Tier-1 buzz-agent model-tuning knobs — only shown for buzz-agent. */} {isBuzzAgentRuntime(modelTuningRuntimeId) ? (