diff --git a/src/features/chat/chat-app-server-controller.ts b/src/features/chat/chat-app-server-controller.ts index 52a99b65..567bb6af 100644 --- a/src/features/chat/chat-app-server-controller.ts +++ b/src/features/chat/chat-app-server-controller.ts @@ -13,6 +13,7 @@ import type { SkillMetadata } from "../../generated/app-server/v2/SkillMetadata" import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state"; import { requestedOrConfiguredServiceTier, type RuntimeSnapshot } from "../../runtime/state"; import type { ChatAction, ChatState, ChatStateStore } from "./chat-state"; +import { mcpStatusLines as buildMcpStatusLines } from "./mcp-status"; import { resumedThreadAction } from "./thread-resume"; export interface ChatAppServerControllerHost { @@ -264,6 +265,19 @@ export class ChatAppServerController { this.publishAppServerMetadataSnapshot(); } + async mcpStatusLines(): Promise { + const client = this.host.currentClient(); + if (!client) return ["MCP servers", "Codex app-server is not connected."]; + + try { + const response = await client.listMcpServerStatus(); + return buildMcpStatusLines(response.data, this.state.appServerDiagnostics.mcpServers); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return ["MCP servers", `Could not load MCP servers: ${message}`]; + } + } + recordMcpStartupStatus(name: string, startupStatus: "starting" | "ready" | "failed" | "cancelled", message: string | null): void { this.dispatch({ type: "thread/list-applied", diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index 0cfc76f0..ca29f3fb 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -16,7 +16,6 @@ import { ChatController } from "./chat-controller"; import { currentModel, type RuntimeSnapshot } from "../../runtime/state"; import { executeSlashCommand as runSlashCommand, type SlashCommandExecutionResult } from "./slash-commands"; import type { ThreadReferenceInput } from "./slash-commands"; -import { mcpStatusLines } from "./mcp-status"; import { ChatAppServerController } from "./chat-app-server-controller"; import { ThreadHistoryLoader } from "./thread-history"; import { ThreadRenameController } from "./thread-rename"; @@ -1301,15 +1300,7 @@ export class CodexChatView extends ItemView { } private async mcpStatusLines(): Promise { - if (!this.client) return ["MCP servers", "Codex app-server is not connected."]; - - try { - const response = await this.client.listMcpServerStatus(); - return mcpStatusLines(response.data, this.state.appServerDiagnostics.mcpServers); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return ["MCP servers", `Could not load MCP servers: ${message}`]; - } + return this.appServer.mcpStatusLines(); } private collaborationModeLabel(): string { diff --git a/tests/features/chat/chat-app-server-controller.test.ts b/tests/features/chat/chat-app-server-controller.test.ts index adf072ae..efab44d8 100644 --- a/tests/features/chat/chat-app-server-controller.test.ts +++ b/tests/features/chat/chat-app-server-controller.test.ts @@ -4,6 +4,7 @@ import type { AppServerClient } from "../../../src/app-server/client"; import { ChatAppServerController } from "../../../src/features/chat/chat-app-server-controller"; import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state"; import type { Model } from "../../../src/generated/app-server/v2/Model"; +import type { McpServerStatus } from "../../../src/generated/app-server/v2/McpServerStatus"; import type { RateLimitSnapshot } from "../../../src/generated/app-server/v2/RateLimitSnapshot"; import type { SkillMetadata } from "../../../src/generated/app-server/v2/SkillMetadata"; @@ -60,4 +61,35 @@ describe("ChatAppServerController", () => { summary: "available", }); }); + + it("loads MCP status lines with cached startup diagnostics", async () => { + const stateStore = createChatStateStore(createChatState()); + const client = { + listMcpServerStatus: vi.fn().mockResolvedValue({ data: [mcpServerStatus()] }), + } as unknown as AppServerClient; + const controller = new ChatAppServerController({ + stateStore, + vaultPath: "/vault", + currentClient: () => client, + runtimeSnapshot: () => ({}) as never, + forceMessagesToBottom: () => undefined, + publishAppServerMetadata: () => undefined, + }); + + controller.recordMcpStartupStatus("github", "ready", null); + + await expect(controller.mcpStatusLines()).resolves.toEqual(["MCP servers", "github: ready, auth oAuth, 1 tool, 0 resources"]); + }); }); + +function mcpServerStatus(): McpServerStatus { + return { + name: "github", + tools: { + search_issues: { name: "search_issues", inputSchema: {} }, + }, + resources: [], + resourceTemplates: [], + authStatus: "oAuth", + } as McpServerStatus; +}