PTENES
MODULE 3.8

⚠️ Error Handling

Learn to handle failures gracefully — validation, try/except, timeouts, and logging.

6
Topics
25
Minutes
Intermediate
Level
Practical
Type
1

⚠️ Types of Errors in MCP

MCP servers are subject to various types of failures. Understanding each error category is the first step toward handling them properly. A good server isn't one that never fails—it's one that fails in a predictable, informative, and recoverable.

💎 Core Concept

Errors in an MCP server fall into four main categories: input errors (invalid parameters, incorrect types, values outside the expected range); execution errors (code bugs, division by zero, missing dictionary key); network errors (external API down, connection timeout, DNS unresolved); and permission errors (file access denied, token expired, invalid credentials). Each type requires a different handling strategy.

📊 Data and Research

Software engineering studies show that more than 60% of production incidents are caused by inadequate error handling—not by the errors themselves. In MCP servers, this is even more critical because the AI model depends on clear error messages to inform the user about what happened and suggest next steps. A raw stack trace doesn't help anyone.

📋 Error Categories
Input Errors

Invalid parameters, incorrect types, values outside the range. Detectable before running the main logic. Prevented through validation.

Runtime Errors

Bugs in the code, KeyError, IndexError, division by zero. They occur during processing. Prevent them with try/except and tests.

Network Errors

API down, timeout, DNS resolution failure. Unpredictable and transient. Handled with retries and clear messages.

Permission Errors

Access denied, token expired, invalid credentials. These require action from the user or admin. Clear message about what’s missing.

2

🛡️ Input Validation

The first line of defense against errors is input validation. Before processing any data, check that the parameters are correct—their type, format, range, and presence. Never trust incoming data. Return clear messages when something is wrong so the model can give the user useful information.

💎 Core Concept

Robust validation at the start of the tool prevents cascading errors:

@mcp.tool()
async def search_user(id: int) -> str:
# Validate type and range
if id <= 0:
return "Error: ID must be a positive number."
if id > 999999:
return "Error: ID is outside the valid range (1-999999)."
# Now process...
❌ Avoid
  • •Blindly trust the data received
  • •Validate only the type, ignoring range and format
  • •Generic error messages ("Internal error")
  • •Leave validation until the end of processing
✅ Do
  • •Validate all parameters at the start of the function
  • •Check type, range, format, and presence
  • •Return a clear message explaining what’s wrong
  • •Include the expected value in the error message
🚨 Alert

Never expose sensitive data in error messages. Avoid returning server file paths, environment variable names, database connection strings, or any information that could be used to exploit the system. Error messages should be useful to the user, not an attacker.

3

🔄 Try/Except in Tools

Even with perfect validation, runtime errors can happen. The structure try/except is your safety net—it catches exceptions before they bring down the server and lets you return useful error messages instead of incomprehensible stack traces.

💎 Core Concept

Wrap the main logic in try/except with specific exceptions:

@mcp.tool()
async def process_data(data: str) -> str:
try:
result = json.loads(data)
value = result["key"]
return f"Value found: {valor}"
except json.JSONDecodeError:
return "Error: Data is not valid JSON."
except KeyError the e:
return f"Error: key {e} not found in the data."
except Exception the e:
return f"Unexpected error: {type(e).__name__}."
💡 Practical Tip

Always catch specific exceptions before the generic one. Start with the most likely errors (ValueError, KeyError, json.JSONDecodeError) and end with except Exception as the final safety net. Never use except: without a type—this catches even KeyboardInterrupt and SystemExit.

❌ Avoid
  • •except: without an exception type
  • •Return the full stack trace to the model
  • •Silence errors with pass
  • •A single generic except for everything
✅ Do
  • •Catch specific exceptions first
  • •Return a readable, useful error message
  • •Log the full error (see topic 6)
  • •Generic Exception as the final fallback
4

📡 Network Errors

When your tools make HTTP requests to external APIs, network failures are inevitable. The API server may be unavailable, the connection may time out, or the response may have an error status. Each type of network failure requires specific handling with messages that help the model tell the user what happened.

💎 Core Concept

With httpx, the main network errors are:

  • •httpx.ConnectError — couldn't connect to the server (DNS, closed port, unreachable host)
  • •httpx.TimeoutException — connection or read timed out
  • •httpx.HTTPStatusError — the server responded with a 4xx or 5xx status

Handle each one separately to give the model precise messages.

📋 Network Error Types and How to Handle Them
ConnectError

Server is unreachable. Message: "Could not connect to service X. Check whether the service is online." Retry may help if the issue is temporary.

TimeoutException

Timed out. Message: "The request to service X took longer than expected." Consider increasing the timeout or retrying with backoff.

HTTPStatusError

Error response. 404 = resource not found, 401/403 = unauthorized, 500 = external server error. Adapt the message to the status code.

🚨 Alert

Be careful with infinite retries. If the API is down, retrying without limits will make your tool hang. Use retries with exponential backoff and a maximum number of attempts (e.g., 3 attempts with waits of 1s, 2s, and 4s). If they all fail, return a clear message saying the service is temporarily unavailable.

5

⏱️ Timeouts

Configuring timeouts is essential for MCP servers. The AI model waits for the tool's response before continuing—if the tool gets stuck on an HTTP request without a timeout, the model waits indefinitely. Always set time limits for operations that may take a while.

💎 Core Concept

Always configure a timeout for HTTP requests:

async with httpx.AsyncClient(timeout=10.0) the client:
response = await client.get(url)

The value timeout=10.0 means the request will be canceled if it takes more than 10 seconds. For fast APIs, 5–10 seconds is enough. For heavy operations, 30 seconds or more may be necessary—but always set a limit.

💡 Practical Tip

You can configure granular timeouts with httpx.Timeout(connect=5.0, read=10.0, write=5.0, pool=5.0). This allows different limits for connection, reading, writing, and the pool. In most cases, timeout=10.0 as a single value is enough and easier to maintain.

❌ Avoid
  • •HTTP requests without a timeout (the default may be infinite)
  • •Excessively long timeouts (60s+) without justification
  • •Ignore TimeoutException and let the server hang
  • •Different timeout for each tool with no standard
✅ Do
  • •Set a timeout for every HTTP request
  • •Reasonable values: 5-15s for standard APIs
  • •Catch TimeoutException with a clear message
  • •Standardize timeouts as project constants
6

📋 Logging and Debugging

Error messages returned to the model should be simple and useful. But you, the developer, need complete details to diagnose problems. That’s where logging comes in: recording detailed errors to stderr without interfering with the MCP protocol, which uses stdout.

💎 Core Concept

Configure logging to record detailed errors in stderr:

import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler()] # stderr by default
)
logger = logging.getLogger("mcp-server")
# Inside a tool:
except Exception the e:
logger.error(f"Error in buscar_usuario: {e}", exc_info=True)
return "Error fetching user. Try again."

O exc_info=True includes the full stack trace in the log — essential for debugging. The MCP protocol uses stdout for communication, so logs on stderr don’t interfere.

💡 Practical Tip

Use appropriate log levels: DEBUG for development details, INFO for normal operations (tool called, parameters received), WARNING for suspicious but non-critical situations, ERROR for failures that prevent operation. In production, set the level to INFO; in development, use DEBUG.

📊 Data and Research

The MCP Inspector displays server logs in its interface, making debugging during development easier. Structured logs (with a timestamp, level, and logger name) are 3x faster to diagnose than scattered print statements, according to software engineering studies. Invest time in setting up proper logging from the start—you’ll be glad you did when the first production bug appears.

📝 Module Summary

  • ✓ MCP errors fall into four categories: input, runtime, network, and permission.
  • ✓ Input validation is the first line of defense—check type, range, and format before processing.
  • ✓ A try/except with specific exceptions catches errors and returns helpful messages instead of stack traces.
  • ✓ Network errors (ConnectError, TimeoutException, HTTPStatusError) need to be handled individually.
  • ✓ Timeouts are required for HTTP requests — the model can't wait forever.
  • ✓ Logging to stderr preserves the MCP protocol and provides essential details for debugging.

Next Module: 3.9 — Testing with MCP Inspector