openai/codex · error

codex_apps MCP server is unavailable

Error message

codex_apps MCP server is unavailable

What it means

read_resource_for_call resolves which app tool produced a widget resource (by call_id in resource_origins), then reads it through the current binding of the reserved codex_apps MCP server. current_binding_for_call returned None: the hosted apps server has no live connection right now (Apps not enabled in this session, the server dropped by a config replace, or its connection failed), so the resource read cannot proceed.

Source

Thrown at codex-rs/codex-mcp/src/runtime.rs:234

            .restore_checkpoint(checkpoint);
    }

    /// Reads a widget through the current binding of the app tool that produced it.
    pub async fn read_resource_for_call(
        &self,
        thread_id: ThreadId,
        call_id: &str,
        uri: &str,
    ) -> anyhow::Result<ReadResourceResult> {
        let origin = self
            .resource_origins
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .find(call_id)?;
        let binding = self
            .current_binding_for_call(crate::CODEX_APPS_MCP_SERVER_NAME)
            .await
            .ok_or_else(|| anyhow::anyhow!("codex_apps MCP server is unavailable"))?;

        origin.read(&binding, thread_id, uri).await
    }

    pub async fn new(input: McpRuntimeInput) -> Self {
        let runtime = Self::empty(input.config.prefix_mcp_tool_names);
        runtime.replace(input).await;
        runtime
    }

    /// Reconciles configured servers and publishes their immutable runtime snapshot.
    pub async fn replace(&self, input: McpRuntimeInput) {
        let current = self.current.load_full();
        let mut reconnect = McpReconnectGuard {
            pending: &self.reconnect_pending,
            claimed: self.reconnect_pending.swap(false, Ordering::AcqRel),
        };
        self.publish(

View on GitHub (pinned to 339751715c)

Solutions

  1. Ensure the Apps/codex_apps feature and its hosted server are enabled, then restart the session so a live binding exists
  2. Re-run the originating tool call: a fresh call registers a new origin and binding for the read
  3. If the read is a best-effort UI refresh, degrade gracefully (render stale/placeholder content) instead of propagating the error

Example fix

// before
let result = runtime.read_resource_for_call(thread_id, call_id, uri).await?;

// after: degrade when the apps server is unavailable
let result = match runtime.read_resource_for_call(thread_id.clone(), call_id, uri).await {
    Ok(r) => r,
    Err(err) if err.to_string().contains("codex_apps MCP server is unavailable") => return render_stale(call_id),
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: try-catch

Try / catch

match runtime.read_resource_for_call(thread_id, call_id, uri).await {
    Ok(result) => { /* render */ }
    Err(err) if err.to_string().contains("codex_apps MCP server is unavailable") => { /* stale or placeholder render; optionally retry after the next runtime replace */ }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Reading a widget resource for an older tool call (e.g. re-rendering an app widget from thread history) after the MCP runtime replaced connections without a codex_apps server, when the Apps feature is disabled, or while the codex_apps connection is down or restarting.

Common situations: Resuming a thread whose widgets came from apps now disabled or reconfigured; enterprise config removing apps mid-session; transient connection drop during a resource read.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/8968bb7258062532. Report an issue: GitHub.