fix(desktop): trim duplicate MCP server rows

Render only server rows and retain the buzz-agent empty-state message.
Keep the screenshot fixture aligned with parser output so it cannot recreate duplicate rows.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
2026-07-13 13:44:51 -04:00
parent 42959fa8d1
commit 0ea6188a6d
4 changed files with 69 additions and 101 deletions

View File

@@ -369,7 +369,6 @@ export function AgentConfigPanel({
const { normalized, advanced, extensions, runtimeId, sources, isPreSpawn } =
data;
const configFilePath = sources.configFilePath;
const mcpConfigFilePath = sources.mcpConfigFilePath;
const normalizedEntries = (
Object.entries(normalized) as [
@@ -420,7 +419,6 @@ export function AgentConfigPanel({
</div>
<McpServersSection
configFilePath={mcpConfigFilePath}
extensions={extensions}
runtimeId={runtimeId}
variant={advancedMode === "flat" ? "profile" : "compact"}

View File

@@ -1,11 +1,9 @@
import * as React from "react";
import { CheckCircle2, CircleSlash, FolderOpen, Plug } from "lucide-react";
import { revealItemInDir } from "@tauri-apps/plugin-opener";
import type * as React from "react";
import { CheckCircle2, CircleSlash } from "lucide-react";
import type { ExtensionEntry } from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
type McpServersSectionProps = {
configFilePath: string | null;
extensions: ExtensionEntry[];
runtimeId: string | null;
variant?: "compact" | "profile";
@@ -14,23 +12,13 @@ type McpServersSectionProps = {
export function McpServersSection({
buzzAgentSlot,
configFilePath,
extensions,
runtimeId,
variant = "compact",
}: McpServersSectionProps) {
const isBuzzAgent = runtimeId === "buzz-agent";
const shouldRender =
isBuzzAgent || extensions.length > 0 || configFilePath !== null;
const revealConfigFile = React.useCallback(() => {
if (!configFilePath) {
return;
}
void revealItemInDir(configFilePath);
}, [configFilePath]);
if (!shouldRender) {
if (!isBuzzAgent && extensions.length === 0) {
return null;
}
@@ -41,58 +29,10 @@ export function McpServersSection({
variant === "compact" ? "mt-3 pt-2" : "divide-y divide-border/50",
)}
>
<div
className={cn(
"flex min-w-0 items-start gap-3",
variant === "compact" ? "py-2" : "px-4 py-3",
)}
>
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted/60">
<Plug className="h-4 w-4 text-muted-foreground" />
</span>
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center justify-between gap-3">
<div className="min-w-0">
<div className="truncate text-xs font-medium text-foreground">
MCP Servers
</div>
<div className="mt-0.5 truncate text-sm text-muted-foreground">
{extensions.length > 0
? `${extensions.length} configured`
: isBuzzAgent
? "No custom servers configured"
: "No servers found"}
</div>
</div>
{configFilePath ? (
<button
aria-label="Reveal MCP config file"
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
onClick={revealConfigFile}
title="Reveal MCP config file"
type="button"
>
<FolderOpen className="h-4 w-4" />
</button>
) : null}
</div>
{configFilePath ? (
<div className="mt-1 truncate text-2xs text-muted-foreground/70">
Edit in {configFilePath}
</div>
) : null}
</div>
</div>
{isBuzzAgent && buzzAgentSlot ? buzzAgentSlot : null}
{extensions.length > 0 ? (
<div
className={cn(
"divide-y divide-border/50",
variant === "compact" ? "mt-1" : "",
)}
>
<div className="divide-y divide-border/50">
{extensions.map((extension) => (
<McpServerRow
extension={extension}
@@ -101,7 +41,16 @@ export function McpServersSection({
/>
))}
</div>
) : null}
) : (
<p
className={cn(
"text-sm text-muted-foreground",
variant === "compact" ? "py-2" : "px-4 py-3",
)}
>
No custom servers configured
</p>
)}
</div>
);
}

View File

@@ -1318,37 +1318,12 @@ function buildMockConfigSurface(pubkey: string): {
},
advanced: [
{
key: "extensions.developer",
label: "Extension: developer",
value: "enabled",
key: "active_provider",
label: "active_provider",
value: "openai",
origin: "configFile",
schemaType: { type: "enum", options: ["enabled", "disabled"] },
writeVia: {
type: "gooseNativeConfigWrite",
configKey: "goose.extensions.developer",
},
},
{
key: "extensions.web_search",
label: "Extension: web_search",
value: "enabled",
origin: "configFile",
schemaType: { type: "enum", options: ["enabled", "disabled"] },
writeVia: {
type: "gooseNativeConfigWrite",
configKey: "goose.extensions.web_search",
},
},
{
key: "extensions.memory",
label: "Extension: memory",
value: "disabled",
origin: "configFile",
schemaType: { type: "enum", options: ["enabled", "disabled"] },
writeVia: {
type: "gooseNativeConfigWrite",
configKey: "goose.extensions.memory",
},
schemaType: { type: "string" },
writeVia: { type: "readOnly" },
},
],
extensions: [
@@ -1685,10 +1660,25 @@ function buildMockConfigSurface(pubkey: string): {
},
};
// Map well-known test pubkeys to specific fixtures
// Synthetic agent for the multi-origin provenance showcase (not a TEST_IDENTITY).
const buzzAgentSurface = {
...gooseSurface,
runtimeId: "buzz-agent",
runtimeLabel: "Buzz Agent",
advanced: [],
extensions: [],
sources: {
...gooseSurface.sources,
configFilePath: null,
mcpConfigFilePath: null,
},
};
// Map well-known test pubkeys to specific fixtures.
// Synthetic agents are intentionally not TEST_IDENTITIES.
const PUBKEY_MULTI_ORIGIN =
"abc1230000000000000000000000000000000000000000000000000000000def";
const PUBKEY_BUZZ_AGENT =
"b0220000000000000000000000000000000000000000000000000000000000a9";
switch (pubkey) {
case ALICE_PUBKEY:
@@ -1701,6 +1691,8 @@ function buildMockConfigSurface(pubkey: string): {
return runtimeOverrideSurface;
case PUBKEY_MULTI_ORIGIN:
return multiOriginSurface;
case PUBKEY_BUZZ_AGENT:
return buzzAgentSurface;
default:
return gooseSurface;
}

View File

@@ -12,6 +12,8 @@ const RUNTIME_OVERRIDE_PUBKEY = TEST_IDENTITIES.outsider.pubkey;
// (matches PUBKEY_MULTI_ORIGIN in e2eBridge buildMockConfigSurface).
const MULTI_ORIGIN_PUBKEY =
"abc1230000000000000000000000000000000000000000000000000000000def";
const BUZZ_AGENT_PUBKEY =
"b0220000000000000000000000000000000000000000000000000000000000a9";
const MANAGED_AGENTS = [
{
@@ -244,13 +246,40 @@ test.describe("config bridge screenshots", () => {
const panel = await openAgentProfileFromChannel(page, "Goose Agent");
// Advanced runtime fields render directly in the profile panel's flat list.
await expect(panel.getByText("Extension: developer")).toBeVisible();
await expect(panel.getByText("active_provider")).toBeVisible();
// MCP servers render once each, without the removed summary row.
await expect(panel.getByText("developer", { exact: true })).toHaveCount(1);
await expect(panel.getByText("MCP Servers", { exact: true })).toHaveCount(
0,
);
await settleAnimations(panel);
await panel.screenshot({ path: `${SHOTS}/05-advanced-expanded.png` });
});
test("06 — profile side panel — Configuration section", async ({ page }) => {
test("06 — buzz-agent empty MCP servers", async ({ page }) => {
await installMockBridge(page, {
managedAgents: [
{
pubkey: BUZZ_AGENT_PUBKEY,
name: "Buzz Agent",
status: "running" as const,
channelNames: ["agents"],
},
],
});
const panel = await openAgentProfileFromChannel(page, "Buzz Agent");
await expect(
panel.getByText("No custom servers configured", { exact: true }),
).toBeVisible();
await expect(panel.getByText("MCP Servers", { exact: true })).toHaveCount(
0,
);
});
test("07 — profile side panel — Configuration section", async ({ page }) => {
// charlie (554cef…) is the well-known test pubkey that the mock bridge
// seeds as a bot owned by the test viewer, so isBot + isOwner + managedAgent
// are all true — the Configuration section renders in the profile panel.