# How to Handle MCP Connection Errors in Codex: Transport and Tool Error Management

> Handle MCP connection errors in Codex by logging to stderr and returning structured tool-error objects. Learn robust error management for LLM recovery.

- Repository: [Composio/awesome-codex-skills](https://github.com/composiohq/awesome-codex-skills)
- Tags: how-to-guide
- Published: 2026-04-26

---

**Model Context Protocol (MCP) connection errors in Codex must be caught at both the transport and tool layers, logged to stderr, and returned as structured tool-error objects to ensure LLMs can recover gracefully without crashing the session.**

When building Codex skills that rely on external Model Context Protocol (MCP) servers, network failures and configuration mismatches are inevitable. The ComposioHQ/awesome-codex-skills repository provides architectural guidelines for handling these errors deterministically across stdio, HTTP, and SSE transports. Understanding how to properly catch transport-level failures and convert them into MCP-compatible error payloads ensures your tools remain robust and debuggable.

## Understanding MCP Connection Error Layers

Connection errors originate at two distinct architectural layers, each requiring different handling strategies.

### Transport-to-Server Connection Failures

The first layer involves the connection between the MCP client and the MCP server itself. According to the evaluation documentation in [`mcp-builder/reference/evaluation.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/evaluation.md) (lines 78-86), these failures typically stem from incorrect command-line parameters, unreachable URLs, missing ports, or invalid TLS configurations. When the transport (stdio, HTTP, or SSE) cannot establish a connection, the client—such as [`scripts/evaluation.py`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/scripts/evaluation.py)—receives a transport-level failure before any tool execution begins.

### Tool-to-External Service Errors

The second layer occurs when an MCP tool attempts to reach an external API. These failures include DNS resolution failures, authentication errors, network partitions, and timeout conditions. As documented in [`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md) (lines 62-67 and 78-84), tools must catch these exceptions internally and return structured error objects rather than allowing raw exceptions to propagate as JSON-RPC errors.

## Architectural Best Practices for Error Handling

Implementing robust error handling requires following specific patterns established in the MCP server reference guides.

### Select the Appropriate Transport

Choose transport protocols based on your deployment topology. Stdio is optimal for local subprocesses, while HTTP or SSE transports suit remote services. In Python, initialize the server with explicit transport parameters: `mcp.run(transport="sse", port=8000)` as shown in [`mcp-builder/reference/python_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/python_mcp_server.md) (lines 64-68). This selection determines how connection failures manifest and which retry strategies apply.

### Implement Explicit Timeouts and Retries

Network transience requires defensive coding. Configure HTTP clients with sensible timeouts—such as `httpx.Timeout(10.0, read=10.0)` in Python or `timeout: 8000` in axios for Node.js—and wrap external calls in retry loops with exponential backoff. This prevents transient glitches from terminating tool execution.

### Centralize Error Handling Logic

Create utility functions that translate library-specific exceptions into MCP-compatible error payloads. Both the Python and Node server guides in [`mcp-builder/reference/python_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/python_mcp_server.md) (lines 78-81) and [`mcp-builder/reference/node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/node_mcp_server.md) (lines 88-89) recommend this centralization pattern. This ensures consistent error formatting across all tools in your MCP server.

### Return Structured Tool Errors

Errors must conform to the JSON-RPC specification by populating the `result.error` field with a clear, user-friendly message. According to [`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md) (lines 88-94), you must **not** expose stack traces or internal details. Instead, return objects with standardized codes (such as `-32001`) and descriptive messages that allow the LLM to react programmatically—either retrying the request or prompting the user for corrected credentials.

### Log to stderr, Not stdout

To prevent corrupting the MCP transport protocol (particularly critical for stdio), log all error diagnostics to **stderr**. The best practices document (lines 91-95) explicitly warns against writing error information to stdout, as this interferes with JSON-RPC message framing.

### Clean Up Resources on Failure

Always release connections and file handles even when errors occur. Use `async with` context managers or `try…finally` blocks as demonstrated in [`mcp-builder/reference/python_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/python_mcp_server.md) (lines 96-98). Additionally, implement the `app_lifespan` manager to handle global resource cleanup during server shutdown (lines 30-35).

## Code Examples for Robust Connection Handling

### Python MCP Tool with Timeout Handling

The following pattern uses `httpx` with explicit timeouts and centralized error formatting:

```python
import httpx
import sys
from mcp.server.fastmcp import FastMCP, Context
from pydantic import BaseModel

mcp = FastMCP("github_mcp")


class GithubRepo(BaseModel):
    name: str
    html_url: str
    description: str | None = None


def _format_error(exc: Exception) -> dict:
    """Convert any exception into a MCP‑compatible error payload."""
    return {
        "code": -32001,
        "message": f"Failed to contact GitHub: {str(exc)}",
    }


@mcp.tool()
async def get_repo(owner: str, repo: str, ctx: Context) -> GithubRepo | dict:
    """
    Fetch a public GitHub repository.
    Returns a GithubRepo model on success, otherwise a structured error dict.
    """
    url = f"https://api.github.com/repos/{owner}/{repo}"
    timeout = httpx.Timeout(10.0, read=10.0)

    try:
        async with httpx.AsyncClient(timeout=timeout) as client:
            resp = await client.get(url)
            resp.raise_for_status()
    except (httpx.RequestError, httpx.HTTPStatusError) as exc:
        # Log to stderr (won’t interfere with stdio transport)

        print(_format_error(exc), file=sys.stderr)
        return _format_error(exc)

    data = resp.json()
    return GithubRepo(**data)

```

This example illustrates catching both network-level (`RequestError`) and HTTP-level (`HTTPStatusError`) exceptions, logging to stderr, and returning a structured error dictionary rather than raising an unhandled exception.

### Node.js MCP Tool with Retry Logic

For TypeScript implementations, implement exponential backoff and type-safe error conversion:

```typescript
import { FastMCP, Context } from "mcp";
import axios, { AxiosError } from "axios";

const mcp = new FastMCP("linear_mcp");

function formatError(err: AxiosError) {
  return {
    code: -32001,
    message: `Linear API error: ${err.message}`,
  };
}

async function fetchWithRetry<T>(url: string, attempts = 3): Promise<T> {
  let delay = 200;
  for (let i = 0; i < attempts; i++) {
    try {
      const resp = await axios.get<T>(url, { timeout: 8000 });
      return resp.data;
    } catch (e) {
      if (i === attempts - 1) throw e;
      await new Promise((r) => setTimeout(r, delay));
      delay *= 2;
    }
  }
  throw new Error("Unreachable");
}

@mcp.tool()
async function getIssue(
  issueId: string,
  ctx: Context
): Promise<{
  title: string;
  status: string;
} | ReturnType<typeof formatError>> {
  const url = `https://api.linear.app/issues/${issueId}`;

  try {
    const data = await fetchWithRetry<{ title: string; status: { name: string } }>(url);
    return {
      title: data.title,
      status: data.status.name,
    };
  } catch (e) {
    console.error(formatError(e as AxiosError));
    return formatError(e as AxiosError);
  }
}

```

The `ReturnType<typeof formatError>` annotation ensures TypeScript recognizes the error shape, allowing the LLM to understand the possible return values deterministically.

### Stdio Transport Validation Script

Before deploying, verify the MCP server launches correctly without transport-level errors:

```bash
#!/usr/bin/env bash

# Verify MCP server launches correctly in stdio mode

python my_mcp_server.py --help >/dev/null 2>stderr.log
if grep -q "error" stderr.log; then
  echo "MCP server startup error – see stderr.log"
  exit 1
fi
echo "MCP server ready"

```

This sanity check, referenced in [`mcp-builder/reference/evaluation.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/evaluation.md) (lines 78-86), detects configuration errors before the evaluation harness attempts to connect.

## Summary

- **Distinguish error layers**: Handle transport connection failures (stdio/HTTP/SSE) separately from tool execution errors (external API calls).
- **Log to stderr**: Never write error diagnostics to stdout, as this corrupts the JSON-RPC protocol stream.
- **Return structured payloads**: Catch exceptions and return objects with standardized error codes (`-32001`) and descriptive messages in the `result.error` field.
- **Use defensive networking**: Implement explicit timeouts (10s default) and exponential backoff retries for transient failures.
- **Centralize logic**: Create utility functions to convert library-specific exceptions into MCP-compatible formats, as recommended in both Python and Node server guides.
- **Clean up resources**: Use `async with` or `try…finally` blocks and implement `app_lifespan` managers to release connections on failure or shutdown.

## Frequently Asked Questions

### What are the most common causes of MCP connection errors in Codex?

The most frequent causes include incorrect command-line arguments or paths when using stdio transport, unreachable URLs or firewall-blocked ports for HTTP/SSE transports, and missing authentication tokens. According to [`mcp-builder/reference/evaluation.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/evaluation.md), you should verify the server command, check URL accessibility, and confirm required API keys are set before investigating code-level issues.

### How should MCP tools handle timeouts from external APIs?

Tools must implement explicit timeout configurations—such as `httpx.Timeout(10.0)` in Python or `{ timeout: 8000 }` in axios—and wrap calls in retry loops with exponential backoff. When timeouts occur, catch the specific exception, log details to stderr, and return a structured error object with code `-32001` rather than allowing the exception to propagate as a JSON-RPC transport error.

### Why must MCP errors be logged to stderr instead of stdout?

MCP servers using stdio transport communicate with clients via JSON-RPC messages over stdout. Writing error text to stdout corrupts the protocol framing and causes parse errors. The best practices in [`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md) (lines 91-95) mandate that all diagnostic logging and error traces be directed to stderr to keep the transport channel clean.

### How do I validate MCP server connectivity before deploying to Codex?

Use a pre-flight validation script that launches the server in stdio mode and captures stderr output. As shown in the evaluation guide ([`mcp-builder/reference/evaluation.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/evaluation.md) lines 78-86), check for startup errors by running commands like `python my_mcp_server.py --help` and inspecting stderr.log. This catches configuration errors early without requiring the full evaluation harness.