From 35e0fb264ee046c916528c450c7ddb7dd3bbf3cf Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Wed, 8 Apr 2026 14:58:04 +0200 Subject: [PATCH 1/9] LLM-26016 [Codex] Show mcp initialization errors in chat --- src/CodexAcpClient.ts | 18 ++ src/CodexAcpServer.ts | 110 ++++++++++- src/CodexAppServerClient.ts | 36 +++- src/CodexEventHandler.ts | 35 ++++ .../CodexACPAgent/CodexAcpClient.test.ts | 177 ++++++++++++++++++ 5 files changed, 373 insertions(+), 3 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 47d76716..99233106 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -7,6 +7,7 @@ import open from "open"; import type {Disposable} from "vscode-jsonrpc"; import type { ClientInfo, + McpStartupCompleteEvent, ReasoningEffort, ServerNotification } from "./app-server"; @@ -21,6 +22,7 @@ import type { GetAccountResponse, ListMcpServerStatusParams, ListMcpServerStatusResponse, + McpServerStatusUpdatedNotification, Model, SkillsListParams, SkillsListResponse, @@ -275,6 +277,22 @@ export class CodexAcpClient { return startup.ready; } + async awaitMcpStartupResult(mcpStartupVersion: number): Promise { + return await this.codexClient.awaitMcpStartup(mcpStartupVersion); + } + + onMcpServerStatusUpdated(handler: (event: McpServerStatusUpdatedNotification) => void): void { + this.codexClient.onMcpServerStatusUpdated(handler); + } + + getMcpServerStatusVersion(): number { + return this.codexClient.getMcpServerStatusVersion(); + } + + getMcpServerStatusUpdates(afterVersion: number): Array { + return this.codexClient.getMcpServerStatusUpdates(afterVersion); + } + getMcpStartupCompleteVersion(): number { return this.codexClient.getMcpStartupCompleteVersion(); } diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 3b695fa1..0ca3120a 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -10,9 +10,18 @@ import {CodexApprovalHandler} from "./CodexApprovalHandler"; import {CodexAuthMethods, type CodexAuthRequest} from "./CodexAuthMethod"; import {CodexAcpClient, type SessionMetadata, type SessionMetadataWithThread} from "./CodexAcpClient"; import {ACPSessionConnection, type UpdateSessionEvent} from "./ACPSessionConnection"; -import type {Account, CollabAgentToolCallStatus, Model, Thread, ThreadItem, UserInput, ReasoningEffortOption} from "./app-server/v2"; +import type {McpStartupCompleteEvent, InputModality, ReasoningEffort} from "./app-server"; +import type { + Account, + CollabAgentToolCallStatus, + McpServerStatusUpdatedNotification, + Model, + Thread, + ThreadItem, + UserInput, + ReasoningEffortOption +} from "./app-server/v2"; import type {RateLimitsMap} from "./RateLimitsMap"; -import type {InputModality, ReasoningEffort} from "./app-server"; import {ModelId} from "./ModelId"; import {AgentMode} from "./AgentMode"; import type {TokenCount} from "./TokenCount"; @@ -43,6 +52,12 @@ export interface SessionState { sessionMcpServers?: Array; } +interface PendingMcpStartupSession { + requestedServers: Set; + terminalServers: Set; + publishedFailures: Set; +} + export class CodexAcpServer implements acp.Agent { private readonly codexAcpClient: CodexAcpClient; private readonly connection: acp.AgentSideConnection; @@ -51,6 +66,7 @@ export class CodexAcpServer implements acp.Agent { private readonly availableCommands: CodexCommands; private readonly sessions: Map; + private readonly pendingMcpStartupSessions: Map; constructor( connection: acp.AgentSideConnection, @@ -59,6 +75,7 @@ export class CodexAcpServer implements acp.Agent { getExitCode?: () => number | null, ) { this.sessions = new Map(); + this.pendingMcpStartupSessions = new Map(); this.connection = connection; this.codexAcpClient = codexAcpClient; this.defaultAuthRequest = defaultAuthRequest ?? null; @@ -68,6 +85,9 @@ export class CodexAcpServer implements acp.Agent { codexAcpClient, (operation) => this.runWithProcessCheck(operation) ); + this.codexAcpClient.onMcpServerStatusUpdated((event) => { + void this.handleMcpServerStatusUpdated(event); + }); } async initialize( @@ -128,6 +148,7 @@ export class CodexAcpServer implements acp.Agent { async getOrCreateSession(request: acp.NewSessionRequest | acp.ResumeSessionRequest): Promise<[SessionId, SessionModelState, SessionModeState]> { await this.checkAuthorization(); const mcpStartupVersion = this.codexAcpClient.getMcpStartupCompleteVersion(); + const mcpStatusVersion = this.codexAcpClient.getMcpServerStatusVersion(); let sessionMetadata: SessionMetadata; if ("sessionId" in request) { @@ -159,6 +180,17 @@ export class CodexAcpServer implements acp.Agent { } this.sessions.set(sessionId, sessionState); + const requestedMcpServers = request.mcpServers ?? []; + if (requestedMcpServers.length > 0) { + this.pendingMcpStartupSessions.set(sessionId, { + requestedServers: new Set(requestedMcpServers.map(server => server.name)), + terminalServers: new Set(), + publishedFailures: new Set(), + }); + await this.replayBufferedMcpStartupStatus(sessionId, mcpStatusVersion); + this.publishMcpStartupStatusAsync(sessionId, mcpStartupVersion); + } + this.publishAvailableCommandsAsync(sessionId); const sessionModelState: SessionModelState = this.createModelState(models, currentModelId); const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState(); @@ -621,6 +653,80 @@ export class CodexAcpServer implements acp.Agent { return await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartup(mcpStartupVersion)); } + private publishMcpStartupStatusAsync(sessionId: string, mcpStartupVersion: number): void { + void (async () => { + try { + const mcpStartup = await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartupResult(mcpStartupVersion)); + const sessionState = this.sessions.get(sessionId); + if (sessionState) { + sessionState.sessionMcpServers = mcpStartup.ready; + } + await this.publishMcpStartupStatus(sessionId, mcpStartup); + this.pendingMcpStartupSessions.delete(sessionId); + } catch (err) { + logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err); + } + })(); + } + + private async publishMcpStartupStatus(sessionId: string, mcpStartup: McpStartupCompleteEvent): Promise { + for (const update of CodexEventHandler.createMcpStartupUpdates(mcpStartup)) { + await this.connection.sessionUpdate({ + sessionId, + update, + }); + } + } + + private async handleMcpServerStatusUpdated(event: McpServerStatusUpdatedNotification): Promise { + for (const [sessionId, pending] of this.pendingMcpStartupSessions) { + if (!pending.requestedServers.has(event.name)) { + continue; + } + + if (event.status === "ready") { + pending.terminalServers.add(event.name); + const sessionState = this.sessions.get(sessionId); + if (sessionState && !(sessionState.sessionMcpServers ?? []).includes(event.name)) { + sessionState.sessionMcpServers = [...(sessionState.sessionMcpServers ?? []), event.name]; + } + } else if (event.status === "failed" || event.status === "cancelled") { + pending.terminalServers.add(event.name); + if (!pending.publishedFailures.has(event.name)) { + pending.publishedFailures.add(event.name); + const updates = CodexEventHandler.createMcpStartupUpdates({ + ready: [], + failed: event.status === "failed" ? [{ + server: event.name, + error: event.error ?? "Unknown MCP startup error", + }] : [], + cancelled: event.status === "cancelled" ? [event.name] : [], + }); + for (const update of updates) { + await this.connection.sessionUpdate({ + sessionId, + update, + }); + } + } + } + + if (pending.terminalServers.size >= pending.requestedServers.size) { + this.pendingMcpStartupSessions.delete(sessionId); + } + } + } + + private async replayBufferedMcpStartupStatus(sessionId: string, afterVersion: number): Promise { + const updates = this.codexAcpClient.getMcpServerStatusUpdates(afterVersion); + for (const update of updates) { + await this.handleMcpServerStatusUpdated(update); + if (!this.pendingMcpStartupSessions.has(sessionId)) { + return; + } + } + } + async prompt(params: acp.PromptRequest): Promise { logger.log("Prompt received", { sessionId: params.sessionId, diff --git a/src/CodexAppServerClient.ts b/src/CodexAppServerClient.ts index d9c9fda0..16c17ff2 100644 --- a/src/CodexAppServerClient.ts +++ b/src/CodexAppServerClient.ts @@ -11,6 +11,7 @@ import type { GetAccountParams, GetAccountResponse, LoginAccountParams, LoginAccountResponse, LogoutAccountResponse, ModelListParams, ModelListResponse, + McpServerStatusUpdatedNotification, ThreadStartParams, ThreadStartResponse, ThreadLoadedListParams, @@ -63,6 +64,12 @@ export class CodexAppServerClient { private mcpStartupCompleteVersion = 0; private lastMcpStartupComplete: McpStartupCompleteEvent | null = null; private readonly mcpStartupCompleteResolvers: Array> = []; + private mcpServerStatusVersion = 0; + private readonly mcpServerStatusUpdatedHandlers: Array<(event: McpServerStatusUpdatedNotification) => void> = []; + private readonly mcpServerStatusHistory: Array<{ + version: number; + event: McpServerStatusUpdatedNotification; + }> = []; constructor(connection: MessageConnection) { this.connection = connection; @@ -76,8 +83,10 @@ export class CodexAppServerClient { } return; } - const serverNotification = data as ServerNotification; + if (serverNotification.method === "mcpServer/startupStatus/updated") { + this.recordMcpServerStatusUpdated(serverNotification.params); + } this.notify(serverNotification); for (const callback of this.codexEventHandlers) { callback({ eventType: "notification", ...serverNotification }); @@ -166,6 +175,20 @@ export class CodexAppServerClient { ); } + onMcpServerStatusUpdated(handler: (event: McpServerStatusUpdatedNotification) => void): void { + this.mcpServerStatusUpdatedHandlers.push(handler); + } + + getMcpServerStatusVersion(): number { + return this.mcpServerStatusVersion; + } + + getMcpServerStatusUpdates(afterVersion: number): Array { + return this.mcpServerStatusHistory + .filter(entry => entry.version > afterVersion) + .map(entry => entry.event); + } + async accountRead(params: GetAccountParams): Promise { return await this.sendRequest({ method: "account/read", params: params }); } @@ -237,6 +260,17 @@ export class CodexAppServerClient { }); } + private recordMcpServerStatusUpdated(event: McpServerStatusUpdatedNotification): void { + this.mcpServerStatusVersion += 1; + this.mcpServerStatusHistory.push({ + version: this.mcpServerStatusVersion, + event, + }); + for (const handler of this.mcpServerStatusUpdatedHandlers) { + handler(event); + } + } + private async sendRequest(request: CodexRequest): Promise { for (const callback of this.codexEventHandlers) { callback({ eventType: "request", ...request}); diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index 00e27310..762df553 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -20,6 +20,7 @@ import type { ThreadTokenUsageUpdatedNotification, TurnPlanUpdatedNotification } from "./app-server/v2"; +import type { McpStartupCompleteEvent } from "./app-server"; import {toTokenCount} from "./TokenCount"; import { createCommandExecutionUpdate, @@ -258,6 +259,40 @@ export class CodexEventHandler { } } + static createMcpStartupUpdates(event: McpStartupCompleteEvent): UpdateSessionEvent[] { + const failedUpdates = event.failed.map((server: McpStartupCompleteEvent["failed"][number]) => this.createMcpStartupToolCallUpdate( + server.server, + `[codex-acp forwarded startup error] MCP server \`${server.server}\` failed to start: ${server.error}` + )); + const cancelledUpdates = event.cancelled.map((server: McpStartupCompleteEvent["cancelled"][number]) => this.createMcpStartupToolCallUpdate( + server, + `[codex-acp forwarded startup error] MCP server \`${server}\` startup was cancelled.` + )); + + return [...failedUpdates, ...cancelledUpdates]; + } + + private static createMcpStartupToolCallUpdate(serverName: string, message: string): UpdateSessionEvent { + return { + sessionUpdate: "tool_call", + toolCallId: this.getMcpStartupToolCallId(serverName), + kind: "other", + title: `mcp__${serverName}__startup`, + status: "failed", + content: [{ + type: "content", + content: { + type: "text", + text: message, + }, + }], + }; + } + + private static getMcpStartupToolCallId(serverName: string): string { + return `mcp_startup.${encodeURIComponent(serverName)}`; + } + private completeCommandExecutionEvent(item: ThreadItem & { "type": "commandExecution" }): UpdateSessionEvent { return { sessionUpdate: "tool_call_update", diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index 94883384..388cc1f1 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -277,6 +277,183 @@ describe('ACP server test', { timeout: 40_000 }, () => { expect(mcpServers).toEqual(["alpha", "beta"]); }); + it('forwards failed MCP startup as failed tool call updates after new session', async () => { + const mockFixture = createCodexMockTestFixture(); + const codexAcpAgent = mockFixture.getCodexAcpAgent(); + const codexAppServerClient = mockFixture.getCodexAppServerClient(); + + vi.spyOn(codexAcpAgent, "checkAuthorization").mockResolvedValue(undefined); + vi.spyOn(codexAppServerClient, "threadStart").mockResolvedValue({ + thread: { id: "thread-id" } as any, + model: "gpt-5", + reasoningEffort: "medium", + } as any); + vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({ + data: [{ + id: "gpt-5", + name: "GPT-5", + inputModalities: ["text"], + supportedReasoningEfforts: [], + }], + hasMore: false, + } as any); + vi.spyOn(codexAppServerClient, "accountRead").mockResolvedValue({ + requiresOpenaiAuth: false, + account: null, + } as any); + vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({ data: [] }); + const mcpServer = { + name: "broken-mcp", + command: "npx", + args: ["broken"], + env: [], + } as unknown as acp.McpServerStdio; + + const session = await codexAcpAgent.newSession({ + cwd: "/workspace", + mcpServers: [mcpServer] + }); + + mockFixture.sendServerNotification({ + method: "codex/event/mcp_startup_complete", + params: { + msg: { + type: "mcp_startup_complete", + ready: [], + failed: [{ + server: "broken-mcp", + error: "boom", + }], + cancelled: [], + } + } + }); + + await vi.waitFor(() => { + const dump = mockFixture.getAcpConnectionDump([]); + expect(dump).toContain('"sessionId": "thread-id"'); + expect(dump).toContain('"sessionUpdate": "tool_call"'); + expect(dump).toContain('"toolCallId": "mcp_startup.broken-mcp"'); + expect(dump).toContain('MCP server `broken-mcp` failed to start: boom'); + }); + + expect(session.sessionId).toBe("thread-id"); + }); + + it('forwards MCP startup failure from startupStatus notification after new session', async () => { + const mockFixture = createCodexMockTestFixture(); + const codexAcpAgent = mockFixture.getCodexAcpAgent(); + const codexAppServerClient = mockFixture.getCodexAppServerClient(); + + vi.spyOn(codexAcpAgent, "checkAuthorization").mockResolvedValue(undefined); + vi.spyOn(codexAppServerClient, "threadStart").mockResolvedValue({ + thread: { id: "thread-id" } as any, + model: "gpt-5", + reasoningEffort: "medium", + } as any); + vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({ + data: [{ + id: "gpt-5", + name: "GPT-5", + inputModalities: ["text"], + supportedReasoningEfforts: [], + }], + hasMore: false, + } as any); + vi.spyOn(codexAppServerClient, "accountRead").mockResolvedValue({ + requiresOpenaiAuth: false, + account: null, + } as any); + vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({ data: [] }); + const mcpServer = { + name: "broken-mcp", + command: "npx", + args: ["broken"], + env: [], + } as unknown as acp.McpServerStdio; + + await codexAcpAgent.newSession({ + cwd: "/workspace", + mcpServers: [mcpServer] + }); + + mockFixture.sendServerNotification({ + method: "mcpServer/startupStatus/updated", + params: { + name: "broken-mcp", + status: "failed", + error: "boom", + } + }); + + await vi.waitFor(() => { + const dump = mockFixture.getAcpConnectionDump([]); + expect(dump).toContain('"sessionId": "thread-id"'); + expect(dump).toContain('"sessionUpdate": "tool_call"'); + expect(dump).toContain('"toolCallId": "mcp_startup.broken-mcp"'); + expect(dump).toContain('MCP server `broken-mcp` failed to start: boom'); + }); + }); + + it('replays MCP startup failure that arrives before session state is registered', async () => { + const mockFixture = createCodexMockTestFixture(); + const codexAcpAgent = mockFixture.getCodexAcpAgent(); + const codexAcpClient = mockFixture.getCodexAcpClient(); + const codexAppServerClient = mockFixture.getCodexAppServerClient(); + + vi.spyOn(codexAcpAgent, "checkAuthorization").mockResolvedValue(undefined); + vi.spyOn(codexAppServerClient, "threadStart").mockImplementation(async () => { + mockFixture.sendServerNotification({ + method: "mcpServer/startupStatus/updated", + params: { + name: "broken-mcp", + status: "failed", + error: "boom", + } + }); + + return { + thread: { id: "thread-id" } as any, + model: "gpt-5", + reasoningEffort: "medium", + } as any; + }); + vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({ + data: [{ + id: "gpt-5", + name: "GPT-5", + inputModalities: ["text"], + supportedReasoningEfforts: [], + }], + hasMore: false, + } as any); + vi.spyOn(codexAppServerClient, "accountRead").mockResolvedValue({ + requiresOpenaiAuth: false, + account: null, + } as any); + vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({ data: [] }); + + const mcpServer = { + name: "broken-mcp", + command: "npx", + args: ["broken"], + env: [], + } as unknown as acp.McpServerStdio; + + await codexAcpAgent.newSession({ + cwd: "/workspace", + mcpServers: [mcpServer] + }); + + await vi.waitFor(() => { + const dump = mockFixture.getAcpConnectionDump([]); + expect(dump).toContain('"toolCallId": "mcp_startup.broken-mcp"'); + expect(dump).toContain('MCP server `broken-mcp` failed to start: boom'); + }); + + expect(codexAcpClient.getMcpServerStatusUpdates(0)).toHaveLength(1); + }); + it('prefetches session additional skill roots before turn start', async () => { const mockFixture = createCodexMockTestFixture(); const codexAcpAgent = mockFixture.getCodexAcpAgent(); From eab1aed8213602fd748320c7ad2c83273a92b405 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Wed, 8 Apr 2026 15:54:09 +0200 Subject: [PATCH 2/9] LLM-26758 fix Failed to initialize ACP process, add script for building binary --- scripts/build-macos-local.sh | 92 ++++++++++++++++++++++++++++++++++++ src/index.ts | 19 +++++++- 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100755 scripts/build-macos-local.sh diff --git a/scripts/build-macos-local.sh b/scripts/build-macos-local.sh new file mode 100755 index 00000000..336d837c --- /dev/null +++ b/scripts/build-macos-local.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERSION_TAG="$VERSION_TAG" +DIST_DIR="dist/bin" +IDEA_CODEX_BIN_DIR="${IDEA_CODEX_BIN_DIR:-}" +PACKAGE_JSON="package.json" +PACKAGE_JSON_BAK="$(mktemp)" + +cp "$PACKAGE_JSON" "$PACKAGE_JSON_BAK" + +cleanup() { + cp "$PACKAGE_JSON_BAK" "$PACKAGE_JSON" + rm -f "$PACKAGE_JSON_BAK" +} +trap cleanup EXIT + +mkdir -p "$DIST_DIR" + +# Keep CI-compatible artifact names in dist/bin. +rm -f \ + "$DIST_DIR/codex-acp-x64-darwin" \ + "$DIST_DIR/codex-acp-arm64-darwin" \ + "$DIST_DIR/codex-acp-x64-darwin.zip" \ + "$DIST_DIR/codex-acp-arm64-darwin.zip" + +echo "Temporarily setting package version to: ${VERSION_TAG}" +perl -i -pe 's/"version":\s*"[^"]+"/"version": "'"${VERSION_TAG}"'"/' "$PACKAGE_JSON" + +echo "Building macOS binaries..." +bun build src/index.ts --minify --sourcemap --compile --target=bun-darwin-x64-baseline --outfile dist/bin/codex-acp-x64-darwin +bun build src/index.ts --minify --sourcemap --compile --target=bun-darwin-arm64 --outfile dist/bin/codex-acp-arm64-darwin + +echo "Packaging artifacts in GitHub Actions format..." + +( + cd "$DIST_DIR" + zip -q codex-acp-x64-darwin.zip codex-acp-x64-darwin + zip -q codex-acp-arm64-darwin.zip codex-acp-arm64-darwin +) + +X64_VERSION="$("$DIST_DIR/codex-acp-x64-darwin" --version | tail -n 1)" +ARM64_VERSION="$("$DIST_DIR/codex-acp-arm64-darwin" --version | tail -n 1)" + +if [[ "$X64_VERSION" != *" ${VERSION_TAG}" ]]; then + echo "Version check failed for x64: $X64_VERSION" + exit 1 +fi + +if [[ "$ARM64_VERSION" != *" ${VERSION_TAG}" ]]; then + echo "Version check failed for arm64: $ARM64_VERSION" + exit 1 +fi + +echo "Done. Artifacts:" +ls -lh \ + "$DIST_DIR/codex-acp-x64-darwin" \ + "$DIST_DIR/codex-acp-arm64-darwin" \ + "$DIST_DIR/codex-acp-x64-darwin.zip" \ + "$DIST_DIR/codex-acp-arm64-darwin.zip" + +if [[ -n "$IDEA_CODEX_BIN_DIR" ]]; then + echo "Copying local macOS binaries to IntelliJ dev Codex bin dir..." + COPIED_ARTIFACTS=() + + for artifact in \ + "codex-acp-x64-darwin" \ + "codex-acp-arm64-darwin" \ + "codex-acp-x64-darwin.zip" \ + "codex-acp-arm64-darwin.zip"; do + source_path="$DIST_DIR/$artifact" + target_path="$IDEA_CODEX_BIN_DIR/$artifact" + + if [[ -e "$target_path" ]]; then + cp -f "$source_path" "$target_path" + COPIED_ARTIFACTS+=("$target_path") + else + echo "Skipping missing target: $target_path" + fi + done + + echo "Copied artifacts:" + if [[ ${#COPIED_ARTIFACTS[@]} -gt 0 ]]; then + ls -lh "${COPIED_ARTIFACTS[@]}" + else + echo "No existing target artifacts were updated." + fi +else + echo "IDEA_CODEX_BIN_DIR is not set; skipping IntelliJ artifact copy." +fi + +echo "Embedded version: ${VERSION_TAG}" diff --git a/src/index.ts b/src/index.ts index d925c155..6c7ab482 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,8 +30,7 @@ if (process.argv[2] === "login") { } function startAcpServer() { - const defaultCodexPath = createRequire(import.meta.url).resolve("@openai/codex/bin/codex.js"); - const codexPath = process.env["CODEX_PATH"] ?? defaultCodexPath; + const codexPath = resolveCodexPath(); const configString = process.env["CODEX_CONFIG"]; const authRequestString = process.env["DEFAULT_AUTH_REQUEST"]; const modelProvider = process.env["MODEL_PROVIDER"]; @@ -71,3 +70,19 @@ function startAcpServer() { new acp.AgentSideConnection(createAgent, acpJsonStream); } + +function resolveCodexPath(): string { + const configuredCodexPath = process.env["CODEX_PATH"]; + if (configuredCodexPath) { + return configuredCodexPath; + } + + try { + return createRequire(import.meta.url).resolve("@openai/codex/bin/codex.js"); + } catch (error) { + logger.log("Falling back to codex from PATH because @openai/codex/bin/codex.js could not be resolved", { + error: error instanceof Error ? error.message : String(error), + }); + return "codex"; + } +} From 510bd5795aed0ae3bde92435f504265a46f91ce8 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Wed, 8 Apr 2026 19:17:56 +0200 Subject: [PATCH 3/9] LLM-26222 [Codex] Show input params, result of running mcp tools --- scripts/build-macos-local.sh | 55 ++++---- src/CodexAcpServer.ts | 3 + src/CodexEventHandler.ts | 51 ++++++- src/CodexToolCallMapper.ts | 36 ++++- .../command-action-events.test.ts | 126 ++++++++++++++++++ .../data/load-session-history.json | 7 +- .../data/mcp-tool-completed-with-logs.json | 71 ++++++++++ .../data/mcp-tool-in-progress.json | 9 +- .../data/mcp-tool-repeated-progress.json | 82 ++++++++++++ src/__tests__/acp-test-utils.ts | 1 + 10 files changed, 407 insertions(+), 34 deletions(-) create mode 100644 src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json create mode 100644 src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json diff --git a/scripts/build-macos-local.sh b/scripts/build-macos-local.sh index 336d837c..9e6bee93 100755 --- a/scripts/build-macos-local.sh +++ b/scripts/build-macos-local.sh @@ -1,12 +1,17 @@ #!/usr/bin/env bash set -euo pipefail -VERSION_TAG="$VERSION_TAG" +VERSION_TAG="$1" DIST_DIR="dist/bin" -IDEA_CODEX_BIN_DIR="${IDEA_CODEX_BIN_DIR:-}" +IDEA_CODEX_BIN_DIR="$2" PACKAGE_JSON="package.json" PACKAGE_JSON_BAK="$(mktemp)" +if [[ -z "${VERSION_TAG}" || -z "${IDEA_CODEX_BIN_DIR}" ]]; then + echo "Usage: $0 " + exit 1 +fi + cp "$PACKAGE_JSON" "$PACKAGE_JSON_BAK" cleanup() { @@ -59,34 +64,30 @@ ls -lh \ "$DIST_DIR/codex-acp-x64-darwin.zip" \ "$DIST_DIR/codex-acp-arm64-darwin.zip" -if [[ -n "$IDEA_CODEX_BIN_DIR" ]]; then - echo "Copying local macOS binaries to IntelliJ dev Codex bin dir..." - COPIED_ARTIFACTS=() - - for artifact in \ - "codex-acp-x64-darwin" \ - "codex-acp-arm64-darwin" \ - "codex-acp-x64-darwin.zip" \ - "codex-acp-arm64-darwin.zip"; do - source_path="$DIST_DIR/$artifact" - target_path="$IDEA_CODEX_BIN_DIR/$artifact" - - if [[ -e "$target_path" ]]; then - cp -f "$source_path" "$target_path" - COPIED_ARTIFACTS+=("$target_path") - else - echo "Skipping missing target: $target_path" - fi - done - - echo "Copied artifacts:" - if [[ ${#COPIED_ARTIFACTS[@]} -gt 0 ]]; then - ls -lh "${COPIED_ARTIFACTS[@]}" +echo "Copying local macOS binaries to IntelliJ dev Codex bin dir..." +COPIED_ARTIFACTS=() + +for artifact in \ + "codex-acp-x64-darwin" \ + "codex-acp-arm64-darwin" \ + "codex-acp-x64-darwin.zip" \ + "codex-acp-arm64-darwin.zip"; do + source_path="$DIST_DIR/$artifact" + target_path="$IDEA_CODEX_BIN_DIR/$artifact" + + if [[ -e "$target_path" ]]; then + cp -f "$source_path" "$target_path" + COPIED_ARTIFACTS+=("$target_path") else - echo "No existing target artifacts were updated." + echo "Skipping missing target: $target_path" fi +done + +echo "Copied artifacts:" +if [[ ${#COPIED_ARTIFACTS[@]} -gt 0 ]]; then + ls -lh "${COPIED_ARTIFACTS[@]}" else - echo "IDEA_CODEX_BIN_DIR is not set; skipping IntelliJ artifact copy." + echo "No existing target artifacts were updated." fi echo "Embedded version: ${VERSION_TAG}" diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 0ca3120a..6f0e6c87 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -50,6 +50,7 @@ export interface SessionState { account: Account | null; cwd: string; sessionMcpServers?: Array; + mcpToolLogs: Map>; } interface PendingMcpStartupSession { @@ -177,6 +178,7 @@ export class CodexAcpServer implements acp.Agent { account: accountResponse.account, cwd: request.cwd, sessionMcpServers: sessionMcpServers, + mcpToolLogs: new Map(), } this.sessions.set(sessionId, sessionState); @@ -388,6 +390,7 @@ export class CodexAcpServer implements acp.Agent { account: accountResponse.account, cwd: request.cwd, sessionMcpServers: sessionMcpServers, + mcpToolLogs: new Map(), }; this.sessions.set(sessionId, sessionState); diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index 762df553..c911cadc 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -26,6 +26,8 @@ import { createCommandExecutionUpdate, createDynamicToolCallUpdate, createFileChangeUpdate, + createMcpRawInput, + createMcpRawOutput, createFuzzyFileSearchComplete, createFuzzyFileSearchStartOrUpdate, createMcpToolCallUpdate, @@ -102,12 +104,13 @@ export class CodexEventHandler { case "turn/diff/updated": case "item/commandExecution/terminalInteraction": case "item/fileChange/outputDelta": - case "item/mcpToolCall/progress": case "serverRequest/resolved": case "account/updated": case "fs/changed": case "mcpServer/startupStatus/updated": return null; + case "item/mcpToolCall/progress": + return this.createMcpToolProgressEvent(notification.params); case "account/rateLimits/updated": this.handleRateLimitsUpdated(notification.params); return null; @@ -211,13 +214,24 @@ export class CodexEventHandler { private async completeItemEvent(event: ItemCompletedNotification): Promise { switch (event.item.type) { - case "mcpToolCall": case "fileChange": case "dynamicToolCall": return { sessionUpdate: "tool_call_update", toolCallId: event.item.id, - status: event.item.status === "completed" ? "completed" : "failed" + status: event.item.status === "completed" ? "completed" : "failed", + } + case "mcpToolCall": + return { + sessionUpdate: "tool_call_update", + toolCallId: event.item.id, + status: event.item.status === "completed" ? "completed" : "failed", + rawInput: createMcpRawInput(event.item.server, event.item.tool, event.item.arguments), + rawOutput: createMcpRawOutput( + this.consumeMcpToolLogs(event.item.id), + event.item.result, + event.item.error, + ), } case "commandExecution": return this.completeCommandExecutionEvent(event.item); @@ -259,6 +273,37 @@ export class CodexEventHandler { } } + private createMcpToolProgressEvent(event: { itemId: string, message: string }): UpdateSessionEvent { + const logDelta = this.appendMcpToolLog(event.itemId, event.message); + return { + sessionUpdate: "tool_call_update", + toolCallId: event.itemId, + _meta: { + mcp_output_delta: { + data: logDelta, + } + } + }; + } + + private appendMcpToolLog(toolCallId: string, message: string): string { + const cleaned = message.trim(); + if (cleaned.length === 0) { + return ""; + } + + const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; + logs.push(cleaned); + this.sessionState.mcpToolLogs.set(toolCallId, logs); + return cleaned; + } + + private consumeMcpToolLogs(toolCallId: string): Array { + const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; + this.sessionState.mcpToolLogs.delete(toolCallId); + return logs; + } + static createMcpStartupUpdates(event: McpStartupCompleteEvent): UpdateSessionEvent[] { const failedUpdates = event.failed.map((server: McpStartupCompleteEvent["failed"][number]) => this.createMcpStartupToolCallUpdate( server.server, diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index 4f4cb26e..9e2021bf 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -13,6 +13,8 @@ import type { CommandExecutionStatus, DynamicToolCallStatus, FileUpdateChange, + McpToolCallError, + McpToolCallResult, McpToolCallStatus, PatchApplyStatus, ThreadItem, @@ -84,7 +86,12 @@ export async function createCommandExecutionUpdate( export async function createMcpToolCallUpdate( item: ThreadItem & { type: "mcpToolCall" } ): Promise { - return createExecuteToolCallUpdate(item, `mcp.${item.server}.${item.tool}`); + return createExecuteToolCallUpdate( + item, + `mcp.${item.server}.${item.tool}`, + createMcpRawInput(item.server, item.tool, item.arguments), + createMcpRawOutput([], item.result, item.error), + ); } export async function createDynamicToolCallUpdate( @@ -96,7 +103,8 @@ export async function createDynamicToolCallUpdate( export async function createExecuteToolCallUpdate( item: ThreadItem & ({ type: "mcpToolCall" } | { type: "dynamicToolCall" }), title: string, - rawInput?: { arguments: JsonValue } + rawInput?: Record, + rawOutput?: Record, ): Promise { return { sessionUpdate: "tool_call", @@ -105,6 +113,30 @@ export async function createExecuteToolCallUpdate( title: title, status: toAcpStatus(item.status), rawInput: rawInput, + rawOutput: rawOutput, + }; +} + +export function createMcpRawInput(server: string, tool: string, argumentsValue: JsonValue): Record { + return { + server, + tool, + arguments: argumentsValue, + }; +} + +export function createMcpRawOutput( + _logs: Array, + result: McpToolCallResult | null, + error: McpToolCallError | null, +): Record | undefined { + if (result === null && error === null) { + return undefined; + } + + return { + result, + error, }; } diff --git a/src/__tests__/CodexACPAgent/command-action-events.test.ts b/src/__tests__/CodexACPAgent/command-action-events.test.ts index c697d980..ebb3d8c7 100644 --- a/src/__tests__/CodexACPAgent/command-action-events.test.ts +++ b/src/__tests__/CodexACPAgent/command-action-events.test.ts @@ -260,6 +260,132 @@ describe('CodexEventHandler - command action events', () => { ); }); + it('should include mcp progress and final logs', async () => { + const notifications: ServerNotification[] = [ + { + method: 'item/started', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "ijproxy", + tool: "read_file", + status: "inProgress", + arguments: { file_path: ".ai/local.md", mode: "slice", start_line: 1, max_lines: 200 }, + result: null, + error: null, + durationMs: null, + }, + }, + }, + { + method: 'item/mcpToolCall/progress', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + itemId: 'call-id', + message: "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened", + }, + }, + { + method: 'item/completed', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "ijproxy", + tool: "read_file", + status: "failed", + arguments: { file_path: ".ai/local.md", mode: "slice", start_line: 1, max_lines: 200 }, + result: null, + error: { + message: "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened", + }, + durationMs: 15, + }, + }, + }, + ]; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + 'data/mcp-tool-completed-with-logs.json' + ); + }); + + it('should preserve repeated mcp progress messages in final output', async () => { + const repeatedMessage = 'Polling for status'; + const notifications: ServerNotification[] = [ + { + method: 'item/started', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "server-name", + tool: "tool-name", + status: "inProgress", + arguments: { argument: "example" }, + result: null, + error: null, + durationMs: null, + }, + }, + }, + { + method: 'item/mcpToolCall/progress', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + itemId: 'call-id', + message: repeatedMessage, + }, + }, + { + method: 'item/mcpToolCall/progress', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + itemId: 'call-id', + message: repeatedMessage, + }, + }, + { + method: 'item/completed', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "server-name", + tool: "tool-name", + status: "failed", + arguments: { argument: "example" }, + result: null, + error: { + message: repeatedMessage, + }, + durationMs: 15, + }, + }, + }, + ]; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + 'data/mcp-tool-repeated-progress.json' + ); + }); + it('should handle dynamic tools', async () => { const dynamicToolNotification: ServerNotification = { method: 'item/started', diff --git a/src/__tests__/CodexACPAgent/data/load-session-history.json b/src/__tests__/CodexACPAgent/data/load-session-history.json index ba06a341..097e7f02 100644 --- a/src/__tests__/CodexACPAgent/data/load-session-history.json +++ b/src/__tests__/CodexACPAgent/data/load-session-history.json @@ -158,7 +158,12 @@ "toolCallId": "item-mcp-1", "kind": "execute", "title": "mcp.github.search", - "status": "completed" + "status": "completed", + "rawInput": { + "server": "github", + "tool": "search", + "arguments": {} + } } } ] diff --git a/src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json b/src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json new file mode 100644 index 00000000..f2530266 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json @@ -0,0 +1,71 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "_meta": { + "mcp_output_delta": { + "data": "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "status": "failed", + "rawInput": { + "server": "ijproxy", + "tool": "read_file", + "arguments": { + "file_path": ".ai/local.md", + "mode": "slice", + "start_line": 1, + "max_lines": 200 + } + }, + "rawOutput": { + "result": null, + "error": { + "message": "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "call-id", + "kind": "execute", + "title": "mcp.ijproxy.read_file", + "status": "in_progress", + "rawInput": { + "server": "ijproxy", + "tool": "read_file", + "arguments": { + "file_path": ".ai/local.md", + "mode": "slice", + "start_line": 1, + "max_lines": 200 + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json b/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json index ea95bdcb..d6a5122c 100644 --- a/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json +++ b/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json @@ -8,7 +8,14 @@ "toolCallId": "call-id", "kind": "execute", "title": "mcp.server-name.tool-name", - "status": "in_progress" + "status": "in_progress", + "rawInput": { + "server": "server-name", + "tool": "tool-name", + "arguments": { + "argument": "example" + } + } } } ] diff --git a/src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json b/src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json new file mode 100644 index 00000000..d60c6e28 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json @@ -0,0 +1,82 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "_meta": { + "mcp_output_delta": { + "data": "Polling for status" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "_meta": { + "mcp_output_delta": { + "data": "Polling for status" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "status": "failed", + "rawInput": { + "server": "server-name", + "tool": "tool-name", + "arguments": { + "argument": "example" + } + }, + "rawOutput": { + "result": null, + "error": { + "message": "Polling for status" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "call-id", + "kind": "execute", + "title": "mcp.server-name.tool-name", + "status": "in_progress", + "rawInput": { + "server": "server-name", + "tool": "tool-name", + "arguments": { + "argument": "example" + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/acp-test-utils.ts b/src/__tests__/acp-test-utils.ts index a25138b0..861c2ffc 100644 --- a/src/__tests__/acp-test-utils.ts +++ b/src/__tests__/acp-test-utils.ts @@ -274,6 +274,7 @@ export function createTestSessionState(overrides?: Partial): Sessi rateLimits: null, account: null, cwd: "/test/cwd", + mcpToolLogs: new Map(), sessionId: "session-id", currentModelId: "model-id[effort]", supportedReasoningEfforts: [], From b598da71a67e027a5218fd9bfb2c4c62b491e2e0 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Wed, 8 Apr 2026 23:43:11 +0200 Subject: [PATCH 4/9] fix possible mcp race --- src/CodexAcpServer.ts | 92 +++++--------- .../CodexACPAgent/CodexAcpClient.test.ts | 114 ------------------ .../CodexACPAgent/load-session.test.ts | 101 ++++++++++++++++ 3 files changed, 129 insertions(+), 178 deletions(-) diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 6f0e6c87..8cf35617 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -14,7 +14,6 @@ import type {McpStartupCompleteEvent, InputModality, ReasoningEffort} from "./ap import type { Account, CollabAgentToolCallStatus, - McpServerStatusUpdatedNotification, Model, Thread, ThreadItem, @@ -55,8 +54,6 @@ export interface SessionState { interface PendingMcpStartupSession { requestedServers: Set; - terminalServers: Set; - publishedFailures: Set; } export class CodexAcpServer implements acp.Agent { @@ -86,9 +83,6 @@ export class CodexAcpServer implements acp.Agent { codexAcpClient, (operation) => this.runWithProcessCheck(operation) ); - this.codexAcpClient.onMcpServerStatusUpdated((event) => { - void this.handleMcpServerStatusUpdated(event); - }); } async initialize( @@ -149,7 +143,6 @@ export class CodexAcpServer implements acp.Agent { async getOrCreateSession(request: acp.NewSessionRequest | acp.ResumeSessionRequest): Promise<[SessionId, SessionModelState, SessionModeState]> { await this.checkAuthorization(); const mcpStartupVersion = this.codexAcpClient.getMcpStartupCompleteVersion(); - const mcpStatusVersion = this.codexAcpClient.getMcpServerStatusVersion(); let sessionMetadata: SessionMetadata; if ("sessionId" in request) { @@ -186,10 +179,7 @@ export class CodexAcpServer implements acp.Agent { if (requestedMcpServers.length > 0) { this.pendingMcpStartupSessions.set(sessionId, { requestedServers: new Set(requestedMcpServers.map(server => server.name)), - terminalServers: new Set(), - publishedFailures: new Set(), }); - await this.replayBufferedMcpStartupStatus(sessionId, mcpStatusVersion); this.publishMcpStartupStatusAsync(sessionId, mcpStartupVersion); } @@ -394,6 +384,14 @@ export class CodexAcpServer implements acp.Agent { }; this.sessions.set(sessionId, sessionState); + const requestedMcpServers = request.mcpServers ?? []; + if (requestedMcpServers.length > 0) { + this.pendingMcpStartupSessions.set(sessionId, { + requestedServers: new Set(requestedMcpServers.map(server => server.name)), + }); + this.publishMcpStartupStatusAsync(sessionId, mcpStartupVersion); + } + await this.availableCommands.publish(sessionId); const sessionModelState: SessionModelState = this.createModelState(models, currentModelId); const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState(); @@ -661,10 +659,13 @@ export class CodexAcpServer implements acp.Agent { try { const mcpStartup = await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartupResult(mcpStartupVersion)); const sessionState = this.sessions.get(sessionId); - if (sessionState) { - sessionState.sessionMcpServers = mcpStartup.ready; + const pendingStartup = this.pendingMcpStartupSessions.get(sessionId); + if (sessionState && pendingStartup) { + sessionState.sessionMcpServers = mcpStartup.ready.filter(serverName => + pendingStartup.requestedServers.has(serverName) + ); } - await this.publishMcpStartupStatus(sessionId, mcpStartup); + await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup?.requestedServers); this.pendingMcpStartupSessions.delete(sessionId); } catch (err) { logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err); @@ -672,8 +673,20 @@ export class CodexAcpServer implements acp.Agent { })(); } - private async publishMcpStartupStatus(sessionId: string, mcpStartup: McpStartupCompleteEvent): Promise { - for (const update of CodexEventHandler.createMcpStartupUpdates(mcpStartup)) { + private async publishMcpStartupStatus( + sessionId: string, + mcpStartup: McpStartupCompleteEvent, + requestedServers?: Set + ): Promise { + const filteredStartup = requestedServers + ? { + ready: mcpStartup.ready.filter(server => requestedServers.has(server)), + failed: mcpStartup.failed.filter(server => requestedServers.has(server.server)), + cancelled: mcpStartup.cancelled.filter(server => requestedServers.has(server)), + } + : mcpStartup; + + for (const update of CodexEventHandler.createMcpStartupUpdates(filteredStartup)) { await this.connection.sessionUpdate({ sessionId, update, @@ -681,55 +694,6 @@ export class CodexAcpServer implements acp.Agent { } } - private async handleMcpServerStatusUpdated(event: McpServerStatusUpdatedNotification): Promise { - for (const [sessionId, pending] of this.pendingMcpStartupSessions) { - if (!pending.requestedServers.has(event.name)) { - continue; - } - - if (event.status === "ready") { - pending.terminalServers.add(event.name); - const sessionState = this.sessions.get(sessionId); - if (sessionState && !(sessionState.sessionMcpServers ?? []).includes(event.name)) { - sessionState.sessionMcpServers = [...(sessionState.sessionMcpServers ?? []), event.name]; - } - } else if (event.status === "failed" || event.status === "cancelled") { - pending.terminalServers.add(event.name); - if (!pending.publishedFailures.has(event.name)) { - pending.publishedFailures.add(event.name); - const updates = CodexEventHandler.createMcpStartupUpdates({ - ready: [], - failed: event.status === "failed" ? [{ - server: event.name, - error: event.error ?? "Unknown MCP startup error", - }] : [], - cancelled: event.status === "cancelled" ? [event.name] : [], - }); - for (const update of updates) { - await this.connection.sessionUpdate({ - sessionId, - update, - }); - } - } - } - - if (pending.terminalServers.size >= pending.requestedServers.size) { - this.pendingMcpStartupSessions.delete(sessionId); - } - } - } - - private async replayBufferedMcpStartupStatus(sessionId: string, afterVersion: number): Promise { - const updates = this.codexAcpClient.getMcpServerStatusUpdates(afterVersion); - for (const update of updates) { - await this.handleMcpServerStatusUpdated(update); - if (!this.pendingMcpStartupSessions.has(sessionId)) { - return; - } - } - } - async prompt(params: acp.PromptRequest): Promise { logger.log("Prompt received", { sessionId: params.sessionId, diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index 388cc1f1..b8572be4 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -340,120 +340,6 @@ describe('ACP server test', { timeout: 40_000 }, () => { expect(session.sessionId).toBe("thread-id"); }); - it('forwards MCP startup failure from startupStatus notification after new session', async () => { - const mockFixture = createCodexMockTestFixture(); - const codexAcpAgent = mockFixture.getCodexAcpAgent(); - const codexAppServerClient = mockFixture.getCodexAppServerClient(); - - vi.spyOn(codexAcpAgent, "checkAuthorization").mockResolvedValue(undefined); - vi.spyOn(codexAppServerClient, "threadStart").mockResolvedValue({ - thread: { id: "thread-id" } as any, - model: "gpt-5", - reasoningEffort: "medium", - } as any); - vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({ - data: [{ - id: "gpt-5", - name: "GPT-5", - inputModalities: ["text"], - supportedReasoningEfforts: [], - }], - hasMore: false, - } as any); - vi.spyOn(codexAppServerClient, "accountRead").mockResolvedValue({ - requiresOpenaiAuth: false, - account: null, - } as any); - vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({ data: [] }); - const mcpServer = { - name: "broken-mcp", - command: "npx", - args: ["broken"], - env: [], - } as unknown as acp.McpServerStdio; - - await codexAcpAgent.newSession({ - cwd: "/workspace", - mcpServers: [mcpServer] - }); - - mockFixture.sendServerNotification({ - method: "mcpServer/startupStatus/updated", - params: { - name: "broken-mcp", - status: "failed", - error: "boom", - } - }); - - await vi.waitFor(() => { - const dump = mockFixture.getAcpConnectionDump([]); - expect(dump).toContain('"sessionId": "thread-id"'); - expect(dump).toContain('"sessionUpdate": "tool_call"'); - expect(dump).toContain('"toolCallId": "mcp_startup.broken-mcp"'); - expect(dump).toContain('MCP server `broken-mcp` failed to start: boom'); - }); - }); - - it('replays MCP startup failure that arrives before session state is registered', async () => { - const mockFixture = createCodexMockTestFixture(); - const codexAcpAgent = mockFixture.getCodexAcpAgent(); - const codexAcpClient = mockFixture.getCodexAcpClient(); - const codexAppServerClient = mockFixture.getCodexAppServerClient(); - - vi.spyOn(codexAcpAgent, "checkAuthorization").mockResolvedValue(undefined); - vi.spyOn(codexAppServerClient, "threadStart").mockImplementation(async () => { - mockFixture.sendServerNotification({ - method: "mcpServer/startupStatus/updated", - params: { - name: "broken-mcp", - status: "failed", - error: "boom", - } - }); - - return { - thread: { id: "thread-id" } as any, - model: "gpt-5", - reasoningEffort: "medium", - } as any; - }); - vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({ - data: [{ - id: "gpt-5", - name: "GPT-5", - inputModalities: ["text"], - supportedReasoningEfforts: [], - }], - hasMore: false, - } as any); - vi.spyOn(codexAppServerClient, "accountRead").mockResolvedValue({ - requiresOpenaiAuth: false, - account: null, - } as any); - vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({ data: [] }); - - const mcpServer = { - name: "broken-mcp", - command: "npx", - args: ["broken"], - env: [], - } as unknown as acp.McpServerStdio; - - await codexAcpAgent.newSession({ - cwd: "/workspace", - mcpServers: [mcpServer] - }); - - await vi.waitFor(() => { - const dump = mockFixture.getAcpConnectionDump([]); - expect(dump).toContain('"toolCallId": "mcp_startup.broken-mcp"'); - expect(dump).toContain('MCP server `broken-mcp` failed to start: boom'); - }); - - expect(codexAcpClient.getMcpServerStatusUpdates(0)).toHaveLength(1); - }); - it('prefetches session additional skill roots before turn start', async () => { const mockFixture = createCodexMockTestFixture(); const codexAcpAgent = mockFixture.getCodexAcpAgent(); diff --git a/src/__tests__/CodexACPAgent/load-session.test.ts b/src/__tests__/CodexACPAgent/load-session.test.ts index 951cb86e..e4464c7a 100644 --- a/src/__tests__/CodexACPAgent/load-session.test.ts +++ b/src/__tests__/CodexACPAgent/load-session.test.ts @@ -236,4 +236,105 @@ describe("CodexACPAgent - loadSession", () => { expect(awaitMcpStartupSpy).toHaveBeenCalledWith(0); expect(codexAcpAgent.getSessionState("session-1").sessionMcpServers).toEqual(["persisted-mcp"]); }); + + it("publishes MCP startup failure for explicitly requested servers during loadSession", async () => { + const fixture = createCodexMockTestFixture(); + const codexAcpAgent = fixture.getCodexAcpAgent(); + const codexAcpClient = fixture.getCodexAcpClient(); + const codexAppServerClient = fixture.getCodexAppServerClient(); + + codexAcpClient.authRequired = vi.fn().mockResolvedValue(false); + codexAcpClient.getAccount = vi.fn().mockResolvedValue({ + account: null, + requiresOpenaiAuth: false, + }); + codexAcpClient.listSkills = vi.fn().mockResolvedValue({ data: [] }); + + const model: Model = { + id: "gpt-5.2", + model: "gpt-5.2", + upgrade: null, + upgradeInfo: null, + availabilityNux: null, + displayName: "GPT-5.2", + description: "Test model", + hidden: false, + supportedReasoningEfforts: [{ reasoningEffort: "medium", description: "Medium" }], + defaultReasoningEffort: "medium", + inputModalities: ["text"], + supportsPersonality: false, + isDefault: true, + }; + + codexAppServerClient.listModels = vi.fn().mockResolvedValue({ + data: [model], + nextCursor: null, + }); + codexAppServerClient.threadResume = vi.fn().mockResolvedValue({ + thread: { + id: "session-1", + preview: "", + ephemeral: false, + modelProvider: "openai", + createdAt: 0, + updatedAt: 0, + status: { type: "idle" }, + path: null, + cwd: "/test/project", + cliVersion: "0.0.0", + source: "cli", + agentNickname: null, + agentRole: null, + gitInfo: null, + name: null, + turns: [], + }, + model: model.id, + modelProvider: "openai", + cwd: "/test/project", + approvalPolicy: "never", + sandbox: { type: "dangerFullAccess" }, + reasoningEffort: model.defaultReasoningEffort, + }); + + await codexAcpAgent.initialize({ protocolVersion: 1 }); + + const loadPromise = codexAcpAgent.loadSession({ + sessionId: "session-1", + cwd: "/test/project", + mcpServers: [{ + name: "broken-mcp", + command: "npx", + args: ["broken"], + env: [], + }], + }); + + await vi.waitFor(() => { + expect(codexAcpAgent.getSessionState("session-1").sessionMcpServers).toEqual(["broken-mcp"]); + }); + + fixture.sendServerNotification({ + method: "codex/event/mcp_startup_complete", + params: { + msg: { + type: "mcp_startup_complete", + ready: [], + failed: [{ + server: "broken-mcp", + error: "boom", + }], + cancelled: [], + } + } + }); + + await loadPromise; + + await vi.waitFor(() => { + const dump = fixture.getAcpConnectionDump([]); + expect(dump).toContain('"toolCallId": "mcp_startup.broken-mcp"'); + expect(dump).toContain('MCP server `broken-mcp` failed to start: boom'); + }); + }); }); From ce488d15f6c881852cdd118d83299d37bd378569 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Thu, 9 Apr 2026 16:02:37 +0200 Subject: [PATCH 5/9] review: remove log stored on acp side --- src/CodexAcpServer.ts | 3 --- src/CodexEventHandler.ts | 26 ++------------------------ src/CodexToolCallMapper.ts | 3 +-- src/__tests__/acp-test-utils.ts | 1 - 4 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 8cf35617..a66340ea 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -49,7 +49,6 @@ export interface SessionState { account: Account | null; cwd: string; sessionMcpServers?: Array; - mcpToolLogs: Map>; } interface PendingMcpStartupSession { @@ -171,7 +170,6 @@ export class CodexAcpServer implements acp.Agent { account: accountResponse.account, cwd: request.cwd, sessionMcpServers: sessionMcpServers, - mcpToolLogs: new Map(), } this.sessions.set(sessionId, sessionState); @@ -380,7 +378,6 @@ export class CodexAcpServer implements acp.Agent { account: accountResponse.account, cwd: request.cwd, sessionMcpServers: sessionMcpServers, - mcpToolLogs: new Map(), }; this.sessions.set(sessionId, sessionState); diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index c911cadc..81cb8f09 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -227,11 +227,7 @@ export class CodexEventHandler { toolCallId: event.item.id, status: event.item.status === "completed" ? "completed" : "failed", rawInput: createMcpRawInput(event.item.server, event.item.tool, event.item.arguments), - rawOutput: createMcpRawOutput( - this.consumeMcpToolLogs(event.item.id), - event.item.result, - event.item.error, - ), + rawOutput: createMcpRawOutput(event.item.result, event.item.error), } case "commandExecution": return this.completeCommandExecutionEvent(event.item); @@ -274,7 +270,7 @@ export class CodexEventHandler { } private createMcpToolProgressEvent(event: { itemId: string, message: string }): UpdateSessionEvent { - const logDelta = this.appendMcpToolLog(event.itemId, event.message); + const logDelta = event.message.trim(); return { sessionUpdate: "tool_call_update", toolCallId: event.itemId, @@ -286,24 +282,6 @@ export class CodexEventHandler { }; } - private appendMcpToolLog(toolCallId: string, message: string): string { - const cleaned = message.trim(); - if (cleaned.length === 0) { - return ""; - } - - const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; - logs.push(cleaned); - this.sessionState.mcpToolLogs.set(toolCallId, logs); - return cleaned; - } - - private consumeMcpToolLogs(toolCallId: string): Array { - const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; - this.sessionState.mcpToolLogs.delete(toolCallId); - return logs; - } - static createMcpStartupUpdates(event: McpStartupCompleteEvent): UpdateSessionEvent[] { const failedUpdates = event.failed.map((server: McpStartupCompleteEvent["failed"][number]) => this.createMcpStartupToolCallUpdate( server.server, diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index 9e2021bf..ab818662 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -90,7 +90,7 @@ export async function createMcpToolCallUpdate( item, `mcp.${item.server}.${item.tool}`, createMcpRawInput(item.server, item.tool, item.arguments), - createMcpRawOutput([], item.result, item.error), + createMcpRawOutput(item.result, item.error), ); } @@ -126,7 +126,6 @@ export function createMcpRawInput(server: string, tool: string, argumentsValue: } export function createMcpRawOutput( - _logs: Array, result: McpToolCallResult | null, error: McpToolCallError | null, ): Record | undefined { diff --git a/src/__tests__/acp-test-utils.ts b/src/__tests__/acp-test-utils.ts index 861c2ffc..a25138b0 100644 --- a/src/__tests__/acp-test-utils.ts +++ b/src/__tests__/acp-test-utils.ts @@ -274,7 +274,6 @@ export function createTestSessionState(overrides?: Partial): Sessi rateLimits: null, account: null, cwd: "/test/cwd", - mcpToolLogs: new Map(), sessionId: "session-id", currentModelId: "model-id[effort]", supportedReasoningEfforts: [], From cfa10751dfee37a2de9108bb01a896f10322d960 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Thu, 9 Apr 2026 16:25:30 +0200 Subject: [PATCH 6/9] review: revert LLM-26758 changes --- scripts/build-macos-local.sh | 93 ------------------------------------ src/index.ts | 19 +------- 2 files changed, 2 insertions(+), 110 deletions(-) delete mode 100755 scripts/build-macos-local.sh diff --git a/scripts/build-macos-local.sh b/scripts/build-macos-local.sh deleted file mode 100755 index 9e6bee93..00000000 --- a/scripts/build-macos-local.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -VERSION_TAG="$1" -DIST_DIR="dist/bin" -IDEA_CODEX_BIN_DIR="$2" -PACKAGE_JSON="package.json" -PACKAGE_JSON_BAK="$(mktemp)" - -if [[ -z "${VERSION_TAG}" || -z "${IDEA_CODEX_BIN_DIR}" ]]; then - echo "Usage: $0 " - exit 1 -fi - -cp "$PACKAGE_JSON" "$PACKAGE_JSON_BAK" - -cleanup() { - cp "$PACKAGE_JSON_BAK" "$PACKAGE_JSON" - rm -f "$PACKAGE_JSON_BAK" -} -trap cleanup EXIT - -mkdir -p "$DIST_DIR" - -# Keep CI-compatible artifact names in dist/bin. -rm -f \ - "$DIST_DIR/codex-acp-x64-darwin" \ - "$DIST_DIR/codex-acp-arm64-darwin" \ - "$DIST_DIR/codex-acp-x64-darwin.zip" \ - "$DIST_DIR/codex-acp-arm64-darwin.zip" - -echo "Temporarily setting package version to: ${VERSION_TAG}" -perl -i -pe 's/"version":\s*"[^"]+"/"version": "'"${VERSION_TAG}"'"/' "$PACKAGE_JSON" - -echo "Building macOS binaries..." -bun build src/index.ts --minify --sourcemap --compile --target=bun-darwin-x64-baseline --outfile dist/bin/codex-acp-x64-darwin -bun build src/index.ts --minify --sourcemap --compile --target=bun-darwin-arm64 --outfile dist/bin/codex-acp-arm64-darwin - -echo "Packaging artifacts in GitHub Actions format..." - -( - cd "$DIST_DIR" - zip -q codex-acp-x64-darwin.zip codex-acp-x64-darwin - zip -q codex-acp-arm64-darwin.zip codex-acp-arm64-darwin -) - -X64_VERSION="$("$DIST_DIR/codex-acp-x64-darwin" --version | tail -n 1)" -ARM64_VERSION="$("$DIST_DIR/codex-acp-arm64-darwin" --version | tail -n 1)" - -if [[ "$X64_VERSION" != *" ${VERSION_TAG}" ]]; then - echo "Version check failed for x64: $X64_VERSION" - exit 1 -fi - -if [[ "$ARM64_VERSION" != *" ${VERSION_TAG}" ]]; then - echo "Version check failed for arm64: $ARM64_VERSION" - exit 1 -fi - -echo "Done. Artifacts:" -ls -lh \ - "$DIST_DIR/codex-acp-x64-darwin" \ - "$DIST_DIR/codex-acp-arm64-darwin" \ - "$DIST_DIR/codex-acp-x64-darwin.zip" \ - "$DIST_DIR/codex-acp-arm64-darwin.zip" - -echo "Copying local macOS binaries to IntelliJ dev Codex bin dir..." -COPIED_ARTIFACTS=() - -for artifact in \ - "codex-acp-x64-darwin" \ - "codex-acp-arm64-darwin" \ - "codex-acp-x64-darwin.zip" \ - "codex-acp-arm64-darwin.zip"; do - source_path="$DIST_DIR/$artifact" - target_path="$IDEA_CODEX_BIN_DIR/$artifact" - - if [[ -e "$target_path" ]]; then - cp -f "$source_path" "$target_path" - COPIED_ARTIFACTS+=("$target_path") - else - echo "Skipping missing target: $target_path" - fi -done - -echo "Copied artifacts:" -if [[ ${#COPIED_ARTIFACTS[@]} -gt 0 ]]; then - ls -lh "${COPIED_ARTIFACTS[@]}" -else - echo "No existing target artifacts were updated." -fi - -echo "Embedded version: ${VERSION_TAG}" diff --git a/src/index.ts b/src/index.ts index 6c7ab482..d925c155 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,7 +30,8 @@ if (process.argv[2] === "login") { } function startAcpServer() { - const codexPath = resolveCodexPath(); + const defaultCodexPath = createRequire(import.meta.url).resolve("@openai/codex/bin/codex.js"); + const codexPath = process.env["CODEX_PATH"] ?? defaultCodexPath; const configString = process.env["CODEX_CONFIG"]; const authRequestString = process.env["DEFAULT_AUTH_REQUEST"]; const modelProvider = process.env["MODEL_PROVIDER"]; @@ -70,19 +71,3 @@ function startAcpServer() { new acp.AgentSideConnection(createAgent, acpJsonStream); } - -function resolveCodexPath(): string { - const configuredCodexPath = process.env["CODEX_PATH"]; - if (configuredCodexPath) { - return configuredCodexPath; - } - - try { - return createRequire(import.meta.url).resolve("@openai/codex/bin/codex.js"); - } catch (error) { - logger.log("Falling back to codex from PATH because @openai/codex/bin/codex.js could not be resolved", { - error: error instanceof Error ? error.message : String(error), - }); - return "codex"; - } -} From 50c8901d6e2e1a8bf3fcae4c26ed48869ae24a9f Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Thu, 9 Apr 2026 18:32:04 +0200 Subject: [PATCH 7/9] review: remove unused code --- src/CodexAcpClient.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 99233106..1ce7df73 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -281,18 +281,6 @@ export class CodexAcpClient { return await this.codexClient.awaitMcpStartup(mcpStartupVersion); } - onMcpServerStatusUpdated(handler: (event: McpServerStatusUpdatedNotification) => void): void { - this.codexClient.onMcpServerStatusUpdated(handler); - } - - getMcpServerStatusVersion(): number { - return this.codexClient.getMcpServerStatusVersion(); - } - - getMcpServerStatusUpdates(afterVersion: number): Array { - return this.codexClient.getMcpServerStatusUpdates(afterVersion); - } - getMcpStartupCompleteVersion(): number { return this.codexClient.getMcpStartupCompleteVersion(); } From 8f2b8cc90bf68eb822b56a620f69eeaa16c13d2f Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Thu, 9 Apr 2026 18:55:01 +0200 Subject: [PATCH 8/9] review: refactor publishMcpStartupStatusAsync --- src/CodexAcpServer.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index a66340ea..4e3832e8 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -652,22 +652,25 @@ export class CodexAcpServer implements acp.Agent { } private publishMcpStartupStatusAsync(sessionId: string, mcpStartupVersion: number): void { - void (async () => { - try { - const mcpStartup = await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartupResult(mcpStartupVersion)); - const sessionState = this.sessions.get(sessionId); - const pendingStartup = this.pendingMcpStartupSessions.get(sessionId); - if (sessionState && pendingStartup) { - sessionState.sessionMcpServers = mcpStartup.ready.filter(serverName => - pendingStartup.requestedServers.has(serverName) - ); - } - await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup?.requestedServers); - this.pendingMcpStartupSessions.delete(sessionId); - } catch (err) { - logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err); + void this.doPublishMcpStartupStatus(sessionId, mcpStartupVersion); + } + + private async doPublishMcpStartupStatus(sessionId: string, mcpStartupVersion: number): Promise { + try { + const mcpStartup = await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartupResult(mcpStartupVersion)); + const sessionState = this.sessions.get(sessionId); + const pendingStartup = this.pendingMcpStartupSessions.get(sessionId); + if (sessionState && pendingStartup) { + sessionState.sessionMcpServers = mcpStartup.ready.filter(serverName => + pendingStartup.requestedServers.has(serverName) + ); } - })(); + await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup?.requestedServers); + } catch (err) { + logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err); + } finally { + this.pendingMcpStartupSessions.delete(sessionId); + } } private async publishMcpStartupStatus( From 1d7180275a6163f6d2c904d3223d3a857b49155b Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Thu, 9 Apr 2026 19:41:46 +0200 Subject: [PATCH 9/9] review: cleanup --- src/CodexAcpClient.ts | 1 - src/CodexAppServerClient.ts | 35 ----------------------------------- 2 files changed, 36 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 1ce7df73..39ed8998 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -22,7 +22,6 @@ import type { GetAccountResponse, ListMcpServerStatusParams, ListMcpServerStatusResponse, - McpServerStatusUpdatedNotification, Model, SkillsListParams, SkillsListResponse, diff --git a/src/CodexAppServerClient.ts b/src/CodexAppServerClient.ts index 16c17ff2..8fb6ce71 100644 --- a/src/CodexAppServerClient.ts +++ b/src/CodexAppServerClient.ts @@ -11,7 +11,6 @@ import type { GetAccountParams, GetAccountResponse, LoginAccountParams, LoginAccountResponse, LogoutAccountResponse, ModelListParams, ModelListResponse, - McpServerStatusUpdatedNotification, ThreadStartParams, ThreadStartResponse, ThreadLoadedListParams, @@ -64,12 +63,6 @@ export class CodexAppServerClient { private mcpStartupCompleteVersion = 0; private lastMcpStartupComplete: McpStartupCompleteEvent | null = null; private readonly mcpStartupCompleteResolvers: Array> = []; - private mcpServerStatusVersion = 0; - private readonly mcpServerStatusUpdatedHandlers: Array<(event: McpServerStatusUpdatedNotification) => void> = []; - private readonly mcpServerStatusHistory: Array<{ - version: number; - event: McpServerStatusUpdatedNotification; - }> = []; constructor(connection: MessageConnection) { this.connection = connection; @@ -84,9 +77,6 @@ export class CodexAppServerClient { return; } const serverNotification = data as ServerNotification; - if (serverNotification.method === "mcpServer/startupStatus/updated") { - this.recordMcpServerStatusUpdated(serverNotification.params); - } this.notify(serverNotification); for (const callback of this.codexEventHandlers) { callback({ eventType: "notification", ...serverNotification }); @@ -175,20 +165,6 @@ export class CodexAppServerClient { ); } - onMcpServerStatusUpdated(handler: (event: McpServerStatusUpdatedNotification) => void): void { - this.mcpServerStatusUpdatedHandlers.push(handler); - } - - getMcpServerStatusVersion(): number { - return this.mcpServerStatusVersion; - } - - getMcpServerStatusUpdates(afterVersion: number): Array { - return this.mcpServerStatusHistory - .filter(entry => entry.version > afterVersion) - .map(entry => entry.event); - } - async accountRead(params: GetAccountParams): Promise { return await this.sendRequest({ method: "account/read", params: params }); } @@ -260,17 +236,6 @@ export class CodexAppServerClient { }); } - private recordMcpServerStatusUpdated(event: McpServerStatusUpdatedNotification): void { - this.mcpServerStatusVersion += 1; - this.mcpServerStatusHistory.push({ - version: this.mcpServerStatusVersion, - event, - }); - for (const handler of this.mcpServerStatusUpdatedHandlers) { - handler(event); - } - } - private async sendRequest(request: CodexRequest): Promise { for (const callback of this.codexEventHandlers) { callback({ eventType: "request", ...request});