diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs
index f8746b1d8b..2d59948983 100644
--- a/desktop/scripts/check-file-sizes.mjs
+++ b/desktop/scripts/check-file-sizes.mjs
@@ -198,7 +198,8 @@ const overrides = new Map([
   // loginHint fields on AcpRuntimeCatalogEntry (+14 lines). Load-bearing new feature.
   // agent-lifecycle-fixes: GlobalAgentConfigSaveResult type grows with
   // failed_restart_count (+2 lines). Queued to split with the rest of this list.
-  ["src/shared/api/types.ts", 1030],
+  // mcp-readonly-view rebase: PR2 MCP config surface FE-type fields force +1 over the grandfathered ceiling.
+  ["src/shared/api/types.ts", 1031],
   // readiness-gate: PersonaDialog.tsx threads computeLocalModeGate +
   // requiredCredentialEnvKeys + RequiredFieldLabel so the "New agent" dialog
   // shows required markers and credential amber rows (parity with
diff --git a/desktop/src-tauri/src/managed_agents/config_bridge/codex.rs b/desktop/src-tauri/src/managed_agents/config_bridge/codex.rs
index 5ef8677c64..c7c7135ccb 100644
--- a/desktop/src-tauri/src/managed_agents/config_bridge/codex.rs
+++ b/desktop/src-tauri/src/managed_agents/config_bridge/codex.rs
@@ -122,7 +122,7 @@ fn toml_string(table: &toml::Table, key: &str) -> Option<String> {
         .map(str::to_string)
 }
 
-fn codex_config_path() -> Option<std::path::PathBuf> {
+pub(crate) fn codex_config_path() -> Option<std::path::PathBuf> {
     if let Ok(home) = std::env::var("CODEX_HOME") {
         return Some(std::path::PathBuf::from(home).join("config.toml"));
     }
diff --git a/desktop/src-tauri/src/managed_agents/config_bridge/goose.rs b/desktop/src-tauri/src/managed_agents/config_bridge/goose.rs
index 914acf3f59..fce54edc40 100644
--- a/desktop/src-tauri/src/managed_agents/config_bridge/goose.rs
+++ b/desktop/src-tauri/src/managed_agents/config_bridge/goose.rs
@@ -154,7 +154,7 @@ fn mapping_string(map: &serde_yaml::Mapping, key: &str) -> Option<String> {
         .map(str::to_string)
 }
 
-fn goose_config_path() -> Option<PathBuf> {
+pub(crate) fn goose_config_path() -> Option<PathBuf> {
     if let Ok(root) = std::env::var("GOOSE_PATH_ROOT") {
         return Some(PathBuf::from(root).join("config").join("config.yaml"));
     }
diff --git a/desktop/src-tauri/src/managed_agents/config_bridge/reader.rs b/desktop/src-tauri/src/managed_agents/config_bridge/reader.rs
index 3564b19423..372d2cfde1 100644
--- a/desktop/src-tauri/src/managed_agents/config_bridge/reader.rs
+++ b/desktop/src-tauri/src/managed_agents/config_bridge/reader.rs
@@ -167,6 +167,8 @@ pub(crate) fn read_config_surface(
     let config_file_path = runtime_meta
         .and_then(|m| m.config_file_path)
         .map(resolve_tilde);
+    let mcp_config_file_path = runtime_meta.and_then(mcp_config_file_path_for_runtime);
+    let extensions = file_config.extensions.clone();
 
     let sources = ConfigSourceReport {
         acp_native: if supports_acp_native {
@@ -197,6 +199,7 @@ pub(crate) fn read_config_surface(
             ConfigTierStatus::NotApplicable
         },
         config_file_path,
+        mcp_config_file_path,
     };
 
     RuntimeConfigSurface {
@@ -205,10 +208,24 @@ pub(crate) fn read_config_surface(
         is_pre_spawn,
         normalized,
         advanced,
+        extensions,
         sources,
     }
 }
 
+fn mcp_config_file_path_for_runtime(runtime: &KnownAcpRuntime) -> Option<String> {
+    match runtime.id {
+        "goose" => {
+            super::goose::goose_config_path().map(|path| path.to_string_lossy().into_owned())
+        }
+        "claude" => Some(resolve_tilde("~/.claude.json")),
+        "codex" => {
+            super::codex::codex_config_path().map(|path| path.to_string_lossy().into_owned())
+        }
+        _ => None,
+    }
+}
+
 #[allow(clippy::too_many_arguments)]
 fn build_model_field(
     record_model: &Option<String>,
diff --git a/desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs b/desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs
index 1444bc71d6..b7829ff111 100644
--- a/desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs
+++ b/desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs
@@ -2,12 +2,31 @@
 //! `reader.rs` stays under the 1000-line budget; `#[path]`-included from
 //! there).
 
-use std::collections::BTreeMap;
+use std::{collections::BTreeMap, path::Path, sync::Mutex};
 
 use super::*;
 use crate::managed_agents::discovery::KnownAcpRuntime;
 use crate::managed_agents::types::ManagedAgentRecord;
 
+static GOOSE_PATH_ROOT_LOCK: Mutex<()> = Mutex::new(());
+
+fn with_goose_path_root<T>(value: Option<&str>, body: impl FnOnce() -> T) -> T {
+    let _guard = GOOSE_PATH_ROOT_LOCK
+        .lock()
+        .unwrap_or_else(|err| err.into_inner());
+    let prior = std::env::var_os("GOOSE_PATH_ROOT");
+    match value {
+        Some(value) => std::env::set_var("GOOSE_PATH_ROOT", value),
+        None => std::env::remove_var("GOOSE_PATH_ROOT"),
+    }
+    let output = body();
+    match prior {
+        Some(value) => std::env::set_var("GOOSE_PATH_ROOT", value),
+        None => std::env::remove_var("GOOSE_PATH_ROOT"),
+    }
+    output
+}
+
 fn test_runtime() -> &'static KnownAcpRuntime {
     &KnownAcpRuntime {
         id: "goose",
@@ -111,6 +130,68 @@ fn pre_spawn_surface_reports_pending_acp_tiers() {
     assert_eq!(surface.sources.env_vars, ConfigTierStatus::Available);
 }
 
+#[test]
+fn surface_reports_mcp_specific_config_path() {
+    let record = test_record();
+    let runtime = test_runtime();
+    let surface = with_goose_path_root(None, || {
+        read_config_surface(&record, Some(runtime), None, None)
+    });
+
+    let path = surface
+        .sources
+        .mcp_config_file_path
+        .expect("mcp config path");
+    let expected_suffix = Path::new(".config").join("goose").join("config.yaml");
+    assert!(
+        Path::new(&path).ends_with(&expected_suffix),
+        "unexpected MCP config path: {path}"
+    );
+}
+
+#[test]
+fn goose_mcp_config_path_follows_path_root_override() {
+    let record = test_record();
+    let runtime = test_runtime();
+    let surface = with_goose_path_root(Some("/tmp/buzz-goose-root"), || {
+        read_config_surface(&record, Some(runtime), None, None)
+    });
+
+    let expected_path = Path::new("/tmp/buzz-goose-root")
+        .join("config")
+        .join("config.yaml");
+    assert_eq!(
+        surface
+            .sources
+            .mcp_config_file_path
+            .as_deref()
+            .map(Path::new),
+        Some(expected_path.as_path())
+    );
+}
+
+#[test]
+fn claude_surface_uses_mcp_config_path_not_settings_path() {
+    let record = test_record();
+    let runtime = &KnownAcpRuntime {
+        id: "claude",
+        config_file_path: Some("~/.claude/settings.json"),
+        ..*test_runtime()
+    };
+    let surface = read_config_surface(&record, Some(runtime), None, None);
+
+    assert!(surface
+        .sources
+        .config_file_path
+        .as_deref()
+        .is_some_and(|path| path.ends_with(".claude/settings.json")));
+    assert!(surface
+        .sources
+        .mcp_config_file_path
+        .as_deref()
+        .is_some_and(|path| path.ends_with(".claude.json")));
+}
+
 #[test]
 fn record_model_overrides_file_model() {
     let mut record = test_record();
diff --git a/desktop/src-tauri/src/managed_agents/config_bridge/types.rs b/desktop/src-tauri/src/managed_agents/config_bridge/types.rs
index 17589bad53..15ccb718e7 100644
--- a/desktop/src-tauri/src/managed_agents/config_bridge/types.rs
+++ b/desktop/src-tauri/src/managed_agents/config_bridge/types.rs
@@ -123,6 +123,7 @@ pub struct ConfigSourceReport {
     pub env_vars: ConfigTierStatus,
     pub config_file: ConfigTierStatus,
     pub config_file_path: Option<String>,
+    pub mcp_config_file_path: Option<String>,
 }
 
 /// Full config surface returned to the frontend.
@@ -134,6 +135,7 @@ pub struct RuntimeConfigSurface {
     pub is_pre_spawn: bool,
     pub normalized: NormalizedConfig,
     pub advanced: Vec<ConfigField>,
+    pub extensions: Vec<ExtensionEntry>,
     pub sources: ConfigSourceReport,
 }
 
diff --git a/desktop/src/features/agents/ui/AgentConfigPanel.tsx b/desktop/src/features/agents/ui/AgentConfigPanel.tsx
index df314a6bf8..26ecab3c04 100644
--- a/desktop/src/features/agents/ui/AgentConfigPanel.tsx
+++ b/desktop/src/features/agents/ui/AgentConfigPanel.tsx
@@ -17,6 +17,7 @@ import { useAgentConfigSurface } from "../hooks";
 import { cn } from "@/shared/lib/cn";
 import { copyTextToClipboard } from "@/shared/lib/clipboard";
 import { Spinner } from "@/shared/ui/spinner";
+import { McpServersSection } from "./McpServersSection";
 import type {
   ConfigField,
   ConfigOrigin,
@@ -365,7 +366,8 @@ export function AgentConfigPanel({
     );
   }
 
-  const { normalized, advanced, sources, isPreSpawn } = data;
+  const { normalized, advanced, extensions, runtimeId, sources, isPreSpawn } =
+    data;
   const configFilePath = sources.configFilePath;
 
   const normalizedEntries = (
@@ -416,8 +418,17 @@ export function AgentConfigPanel({
         )}
       </div>
 
+      <McpServersSection
+        extensions={extensions}
+        runtimeId={runtimeId}
+        variant={advancedMode === "flat" ? "profile" : "compact"}
+      />
+
       {advanced.length > 0 && advancedMode === "flat" ? (
         <div className="divide-y divide-border/50 border-t border-border/50">
+          <p className="px-4 py-3 text-xs font-medium text-foreground">
+            Advanced
+          </p>
           {advanced.map((field) => (
             <AdvancedRow
               key={field.key}
diff --git a/desktop/src/features/agents/ui/McpServersSection.tsx b/desktop/src/features/agents/ui/McpServersSection.tsx
new file mode 100644
index 0000000000..3e6c93ca5d
--- /dev/null
+++ b/desktop/src/features/agents/ui/McpServersSection.tsx
@@ -0,0 +1,102 @@
+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 = {
+  extensions: ExtensionEntry[];
+  runtimeId: string | null;
+  variant?: "compact" | "profile";
+  buzzAgentSlot?: React.ReactNode;
+};
+
+export function McpServersSection({
+  buzzAgentSlot,
+  extensions,
+  runtimeId,
+  variant = "compact",
+}: McpServersSectionProps) {
+  const isBuzzAgent = runtimeId === "buzz-agent";
+
+  if (!isBuzzAgent && extensions.length === 0) {
+    return null;
+  }
+
+  return (
+    <div
+      className={cn(
+        "border-t border-border/50",
+        variant === "compact" ? "mt-3 pt-2" : "divide-y divide-border/50",
+      )}
+    >
+      <p
+        className={cn(
+          "text-xs font-medium text-foreground",
+          variant === "compact" ? "py-2" : "px-4 py-3",
+        )}
+      >
+        MCP Servers
+      </p>
+
+      {isBuzzAgent && buzzAgentSlot ? buzzAgentSlot : null}
+
+      {extensions.length > 0 ? (
+        <div className="divide-y divide-border/50">
+          {extensions.map((extension) => (
+            <McpServerRow
+              extension={extension}
+              key={`${extension.kind}:${extension.name}`}
+              variant={variant}
+            />
+          ))}
+        </div>
+      ) : (
+        <p
+          className={cn(
+            "text-sm text-muted-foreground",
+            variant === "compact" ? "py-2" : "px-4 py-3",
+          )}
+        >
+          No custom servers configured
+        </p>
+      )}
+    </div>
+  );
+}
+
+function McpServerRow({
+  extension,
+  variant,
+}: {
+  extension: ExtensionEntry;
+  variant: "compact" | "profile";
+}) {
+  const StatusIcon = extension.enabled ? CheckCircle2 : CircleSlash;
+
+  return (
+    <div
+      className={cn(
+        "flex min-w-0 items-center gap-3",
+        variant === "compact" ? "py-2" : "px-4 py-3",
+      )}
+    >
+      <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted/50">
+        <StatusIcon
+          className={cn(
+            "h-4 w-4",
+            extension.enabled ? "text-emerald-600" : "text-muted-foreground",
+          )}
+        />
+      </span>
+      <span className="min-w-0 flex-1">
+        <span className="block truncate text-sm font-medium text-foreground">
+          {extension.name}
+        </span>
+        <span className="mt-0.5 block truncate text-2xs text-muted-foreground/70">
+          {extension.kind}
+          {extension.enabled ? " enabled" : " disabled"}
+        </span>
+      </span>
+    </div>
+  );
+}
diff --git a/desktop/src/shared/api/types.ts b/desktop/src/shared/api/types.ts
index 2156325634..5066b2a370 100644
--- a/desktop/src/shared/api/types.ts
+++ b/desktop/src/shared/api/types.ts
@@ -679,8 +679,11 @@ export type ConfigSourceReport = {
   envVars: ConfigTierStatus;
   configFile: ConfigTierStatus;
   configFilePath: string | null;
+  mcpConfigFilePath: string | null;
 };
 
+export type ExtensionEntry = { name: string; kind: string; enabled: boolean };
+
 export type NormalizedConfig = {
   model: NormalizedField | null;
   provider: NormalizedField | null;
@@ -697,6 +700,7 @@ export type RuntimeConfigSurface = {
   isPreSpawn: boolean;
   normalized: NormalizedConfig;
   advanced: ConfigField[];
+  extensions: ExtensionEntry[];
   sources: ConfigSourceReport;
 };
 
diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts
index 9b96c36d35..6cc0cace1c 100644
--- a/desktop/src/testing/e2eBridge.ts
+++ b/desktop/src/testing/e2eBridge.ts
@@ -1265,6 +1265,7 @@ function buildMockConfigSurface(pubkey: string): {
   isPreSpawn: boolean;
   normalized: Record<string, unknown>;
   advanced: unknown[];
+  extensions: unknown[];
   sources: Record<string, unknown>;
 } {
   // Goose running — mixed origins, override on model
@@ -1317,45 +1318,26 @@ function buildMockConfigSurface(pubkey: string): {
     },
     advanced: [
       {
-        key: "extensions.developer",
-        label: "Extension: developer",
-        value: "enabled",
-        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",
+        key: "active_provider",
+        label: "active_provider",
+        value: "openai",
         origin: "configFile",
-        schemaType: { type: "enum", options: ["enabled", "disabled"] },
-        writeVia: {
-          type: "gooseNativeConfigWrite",
-          configKey: "goose.extensions.memory",
-        },
+        schemaType: { type: "string" },
+        writeVia: { type: "readOnly" },
       },
     ],
+    extensions: [
+      { name: "developer", kind: "stdio", enabled: true },
+      { name: "web_search", kind: "stdio", enabled: true },
+      { name: "memory", kind: "stdio", enabled: false },
+    ],
     sources: {
       acpNative: "available",
       acpConfigOptions: "available",
       envVars: "available",
       configFile: "available",
       configFilePath: "~/.config/goose/config.yaml",
+      mcpConfigFilePath: "~/.config/goose/config.yaml",
     },
   };
 
@@ -1409,12 +1391,17 @@ function buildMockConfigSurface(pubkey: string): {
       systemPrompt: null,
     },
     advanced: [],
+    extensions: [
+      { name: "filesystem", kind: "mcp", enabled: true },
+      { name: "github", kind: "mcp", enabled: true },
+    ],
     sources: {
       acpNative: "available",
       acpConfigOptions: "available",
       envVars: "notApplicable",
       configFile: "available",
       configFilePath: "~/.claude/settings.json",
+      mcpConfigFilePath: "~/.claude.json",
     },
   };
 
@@ -1464,12 +1451,14 @@ function buildMockConfigSurface(pubkey: string): {
       systemPrompt: null,
     },
     advanced: [],
+    extensions: [{ name: "developer", kind: "stdio", enabled: true }],
     sources: {
       acpNative: "pending",
       acpConfigOptions: "pending",
       envVars: "available",
       configFile: "available",
       configFilePath: "~/.config/goose/config.yaml",
+      mcpConfigFilePath: "~/.config/goose/config.yaml",
     },
   };
 
@@ -1538,12 +1527,17 @@ function buildMockConfigSurface(pubkey: string): {
         writeVia: { type: "respawnWithEnvVar", envKey: "GOOSE_SANDBOX_MODE" },
       },
     ],
+    extensions: [
+      { name: "filesystem", kind: "mcp", enabled: true },
+      { name: "github", kind: "mcp", enabled: true },
+    ],
     sources: {
       acpNative: "notApplicable",
       acpConfigOptions: "notApplicable",
       envVars: "available",
       configFile: "available",
       configFilePath: "~/.codex/config.toml",
+      mcpConfigFilePath: "~/.codex/config.toml",
     },
   };
 
@@ -1595,12 +1589,14 @@ function buildMockConfigSurface(pubkey: string): {
       systemPrompt: null,
     },
     advanced: [],
+    extensions: [{ name: "web_search", kind: "stdio", enabled: true }],
     sources: {
       acpNative: "available",
       acpConfigOptions: "available",
       envVars: "available",
       configFile: "available",
       configFilePath: "~/.config/goose/config.yaml",
+      mcpConfigFilePath: "~/.config/goose/config.yaml",
     },
   };
 
@@ -1653,19 +1649,36 @@ function buildMockConfigSurface(pubkey: string): {
       systemPrompt: null,
     },
     advanced: [],
+    extensions: [],
     sources: {
       acpNative: "available",
       acpConfigOptions: "available",
       envVars: "available",
       configFile: "available",
       configFilePath: "~/.config/goose/config.yaml",
+      mcpConfigFilePath: "~/.config/goose/config.yaml",
+    },
+  };
+
+  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 agent for the multi-origin provenance showcase (not a TEST_IDENTITY).
+  // 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:
@@ -1678,6 +1691,8 @@ function buildMockConfigSurface(pubkey: string): {
       return runtimeOverrideSurface;
     case PUBKEY_MULTI_ORIGIN:
       return multiOriginSurface;
+    case PUBKEY_BUZZ_AGENT:
+      return buzzAgentSurface;
     default:
       return gooseSurface;
   }
diff --git a/desktop/tests/e2e/config-bridge-screenshots.spec.ts b/desktop/tests/e2e/config-bridge-screenshots.spec.ts
index 415d55a7fb..11d4a73fa3 100644
--- a/desktop/tests/e2e/config-bridge-screenshots.spec.ts
+++ b/desktop/tests/e2e/config-bridge-screenshots.spec.ts
@@ -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 = [
   {
@@ -243,14 +245,43 @@ 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();
+    // Advanced runtime fields render directly in the profile panel's flat list,
+    // grouped under their own "Advanced" header.
+    await expect(panel.getByText("active_provider")).toBeVisible();
+    await expect(panel.getByText("Advanced", { exact: true })).toHaveCount(1);
+    // MCP servers render once each under their group label.
+    await expect(panel.getByText("developer", { exact: true })).toHaveCount(1);
+    await expect(panel.getByText("MCP Servers", { exact: true })).toHaveCount(
+      1,
+    );
     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(
+      1,
+    );
+  });
+
+  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.
