Codex Desktop sometimes does not inject configured project MCP tools into a thread

Open 💬 2 comments Opened May 23, 2026 by jarvanstack
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

Codex Desktop / CLI: 0.133.0-alpha.1

What subscription do you have?

pro

What platform is your computer?

23.6.0 arm64 arm

What issue are you seeing?

What happened?

Codex Desktop sometimes starts a thread without exposing configured MCP tools to the model, even though the same MCP server is correctly detected by the Codex CLI.

In this state, the model cannot call the MCP tools because they are absent from session_meta.dynamic_tools. This looks like a Desktop tool injection / MCP manager state issue rather than an MCP server outage.

Environment

  • Codex Desktop / CLI: 0.133.0-alpha.1
  • OS: macOS arm64
  • MCP transport: streamable_http
  • Workspace has project-level .codex/config.toml

Project config:

[mcp_servers.cysj-ai-support]
url = "https://example.com/mcp/project/mcp"
bearer_token_env_var = "AI_SUPPORT_TOKEN"

### What steps can reproduce the bug?

1. Configure a project-level streamable HTTP MCP server in `.codex/config.toml`.
2. Open the workspace in Codex Desktop.
3. Create a new thread.
4. Compare `codex mcp list` / `codex doctor` with the thread's available `dynamic_tools`.
5. Sometimes the CLI reports the MCP server as enabled, but Desktop does not inject any `mcp__...` tools into the thread.

### What is the expected behavior?

_No response_

### Additional information

_No response_

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #23738

Powered by Codex Action

jshaofa-ui · 3 months ago

openai/codex #24168: Desktop MCP Tools Not Injected Into Thread

🔍 Root Cause Analysis

Severity: P1 — MCP tools intermittently absent from thread, model cannot call configured tools

Codex Desktop sometimes starts a thread without exposing configured MCP tools to the model, even though the same MCP server is correctly detected by the Codex CLI. The tools are absent from session_meta.dynamic_tools.

Root Cause Chain

  1. Desktop vs CLI tool injection divergence: The Desktop app's tool injection pipeline has a race condition or state synchronization gap that the CLI does not have
  2. MCP manager state not propagated: The MCP server connection is established (CLI sees it), but the tool definitions are not injected into the thread's dynamic_tools
  3. Thread creation timing: The thread may be created before the MCP manager finishes loading/validating the server
  4. streamable_http transport specific: The issue occurs with streamable_http MCP servers — possibly a connection timing issue with HTTP-based MCP

🛠️ Proposed Fix

Fix 1: MCP Tool Injection Verification Gate (Critical)

// In ThreadManager — verify MCP tools are injected before thread starts
class ThreadManager {
  async createThread(config: ThreadConfig): Promise<Thread> {
    // Wait for MCP manager to be ready
    await this.mcpManager.waitForReady();

    // Get MCP tool definitions
    const mcpTools = await this.mcpManager.getAllToolDefinitions();

    // NEW: Verify tools are injected into session_meta
    const thread = await this.doCreateThread(config);
    const injectedTools = thread.session_meta.dynamic_tools || [];

    const expectedToolNames = mcpTools.map(t => `mcp__${t.name}`);
    const missingTools = expectedToolNames.filter(
      name => !injectedTools.some(t => t.name === name)
    );

    if (missingTools.length > 0) {
      this.logger.error('MCP tools not injected into thread', {
        threadId: thread.id,
        missingTools,
        injectedTools: injectedTools.map(t => t.name),
        mcpServers: await this.mcpManager.getServerList(),
      });

      // Retry injection
      await this.retryMcpToolInjection(thread, mcpTools);
    }

    return thread;
  }

  private async retryMcpToolInjection(thread: Thread, tools: McpToolDef[]): Promise<void> {
    for (const tool of tools) {
      const toolName = `mcp__${tool.name}`;
      if (!thread.session_meta.dynamic_tools.some(t => t.name === toolName)) {
        thread.session_meta.dynamic_tools.push({
          name: toolName,
          description: tool.description,
          parameters: tool.parameters,
          source: 'mcp',
        });
      }
    }
  }
}

Fix 2: MCP Manager Ready Signal (Important)

// In McpManager — emit ready signal when all servers are loaded
class McpManager {
  private readyPromise: Promise<void> | null = null;
  private servers: Map<string, McpServerConnection> = new Map();

  async waitForReady(): Promise<void> {
    if (!this.readyPromise) {
      this.readyPromise = this.initializeAllServers();
    }
    return this.readyPromise;
  }