diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 54a4c2e2bf28e..1734f9e0af88b 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -1530,6 +1530,14 @@ describe("CodexAdapterV2 post-settle continuation", () => { url: "http://127.0.0.1:43123/mcp", tool_timeout_sec: 3_900, http_headers: { Authorization: "Bearer secret-codex-token" }, + disabled_tools: ["t3_thread_wait"], + }, + "mcp_servers.t3code": { + url: "http://127.0.0.1:43123/mcp", + tool_timeout_sec: 3_900, + http_headers: { Authorization: "Bearer secret-codex-token" }, + enabled_tools: ["t3_thread_wait"], + omit_tools_from: ["code_mode", "deferred"], }, }, }, diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index fa6bb58a9dcbe..bda631ec99c66 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -1334,6 +1334,9 @@ export const CODEX_THREAD_CONFIG = { // T3 waits can block for an hour; keep Codex's MCP timeout above that. const CODEX_T3_MCP_TOOL_TIMEOUT_SEC = 65 * 60; +// Calls arrive as `t3code.`; t3McpToolPresentation.ts must keep resolving that form. +const CODEX_T3_WAIT_MCP_SERVER = "t3code"; +const CODEX_DIRECT_ONLY_T3_TOOL = "t3_thread_wait"; function codexThreadRuntimeParams(input: { readonly threadId: ThreadId | null; @@ -1346,6 +1349,14 @@ function codexThreadRuntimeParams(input: { readonly config: Readonly>; } { const { mcpSession } = input; + const t3Server = + mcpSession === undefined + ? undefined + : { + url: mcpSession.endpoint, + tool_timeout_sec: CODEX_T3_MCP_TOOL_TIMEOUT_SEC, + http_headers: { Authorization: mcpSession.authorizationHeader }, + }; return { ...(input.runtimePolicy?.cwd == null ? {} : { cwd: input.runtimePolicy.cwd }), ...(input.modelSelection === undefined ? {} : { model: input.modelSelection.model }), @@ -1355,16 +1366,17 @@ function codexThreadRuntimeParams(input: { ...(input.threadId === null ? {} : { [`shell_environment_policy.set.${PortScanner.AGENT_THREAD_ENV}`]: input.threadId }), - ...(mcpSession === undefined + ...(t3Server === undefined ? {} : { - // Override only T3's server so launch-argument MCP servers survive. - "mcp_servers.t3-code": { - url: mcpSession.endpoint, - tool_timeout_sec: CODEX_T3_MCP_TOOL_TIMEOUT_SEC, - http_headers: { - Authorization: mcpSession.authorizationHeader, - }, + // Override only T3's servers so launch-argument MCP servers survive. + "mcp_servers.t3-code": { ...t3Server, disabled_tools: [CODEX_DIRECT_ONLY_T3_TOOL] }, + // Code Mode runs a blocking wait as a nested call, and the model keeps working + // around it. A second, direct-only registration makes it a plain tool call. + [`mcp_servers.${CODEX_T3_WAIT_MCP_SERVER}`]: { + ...t3Server, + enabled_tools: [CODEX_DIRECT_ONLY_T3_TOOL], + omit_tools_from: ["code_mode", "deferred"], }, }), }, diff --git a/docs/orchestration-v2/orchestrator-mcp-server.md b/docs/orchestration-v2/orchestrator-mcp-server.md index f46803f9d67b6..77f95873f06a6 100644 --- a/docs/orchestration-v2/orchestrator-mcp-server.md +++ b/docs/orchestration-v2/orchestrator-mcp-server.md @@ -58,17 +58,18 @@ check the `orchestration` capability before reading or mutating state. ### Codex V2 -Codex app-server receives the remote MCP server through command-line config -overrides: - -```text --c mcp_servers.t3-code.url=http://127.0.0.1:/mcp --c mcp_servers.t3-code.bearer_token_env_var="T3_MCP_BEARER_TOKEN" -``` - -The provider-session token is placed in `T3_MCP_BEARER_TOKEN`. Both the -production Codex launcher and the injectable test launcher use the same -projection helper. +Codex gets T3's MCP server in each thread's `thread/start`, `thread/resume`, +and `thread/fork` config, with the provider-session token in an +`Authorization` header. The same server is registered twice: + +- `t3-code`: every tool except `t3_thread_wait`. +- `t3code`: only `t3_thread_wait`, with `omit_tools_from = ["code_mode", "deferred"]`. + +Code Mode runs a nested call as part of a script, so the model keeps working +while a blocking wait runs. The second entry makes the wait a direct tool call, +like Codex's own wait tools. Codex reports calls as `.`, so +`packages/shared/src/t3McpToolPresentation.ts` must resolve both server names. +Tool display and pending-work detection depend on it. ### Claude Agent SDK V2 diff --git a/packages/shared/src/t3McpToolPresentation.test.ts b/packages/shared/src/t3McpToolPresentation.test.ts index 1c2683753656c..cd27e5cc452bd 100644 --- a/packages/shared/src/t3McpToolPresentation.test.ts +++ b/packages/shared/src/t3McpToolPresentation.test.ts @@ -16,6 +16,7 @@ describe("resolveT3McpToolPresentation", () => { "mcp__t3_code__", "mcp__t3code__", "T3-code.", + "t3code.", "t3_code/", "t3code:", "mcp_t3-code_",