← Back to Blog
tutorialerror-handlingpatterns

Error Handling Patterns for MCP Tools

Design error responses that AI agents can understand and act on. Structured errors, retry hints, and graceful degradation.

·4 min read·xapable

Error Handling Patterns for MCP Tools

AI agents react to errors differently than humans. This guide covers error handling designed for agent consumption.

The Golden Rule

Always return structured content — never throw exceptions to the client.

// ❌ BAD — throws to MCP client, agent sees generic error
throw new Error("Database connection failed");

// ✅ GOOD — returns structured error the agent can understand
return {
  content: [{
    type: "text",
    text: "❌ Could not connect to the database. Please check your DATABASE_URL and ensure the server is running."
  }],
  isError: true
};

Error Categories

1. User Errors (4xx equivalents)

The agent made a mistake — wrong parameter, missing field.

if (!args.city) {
  return {
    content: [{
      type: "text",
      text: "❌ Missing required parameter: 'city'. Please provide a city name (e.g., 'London' or 'Tokyo')."
    }],
    isError: true
  };
}

2. Configuration Errors

The tool isn't set up correctly.

if (!process.env.API_KEY) {
  return {
    content: [{
      type: "text",
      text: "⚠️ API key not configured. Get a free key at https://service.com/keys, " +
            "then add API_KEY to your MCP client's env vars."
    }],
    isError: true
  };
}

3. External Service Errors

The API you depend on is down or rate-limited.

try {
  const resp = await fetch(`https://api.service.com/data?q=${query}`);
  if (resp.status === 429) {
    return {
      content: [{
        type: "text",
        text: "⏳ Rate limit reached. Please wait 60 seconds and try again."
      }],
      isError: true
    };
  }
  if (!resp.ok) {
    return {
      content: [{
        type: "text",
        text: `❌ The weather service is temporarily unavailable (status ${resp.status}). Please try again in a few minutes.`
      }],
      isError: true
    };
  }
} catch (err) {
  return {
    content: [{
      type: "text",
      text: "❌ Could not reach the weather service. Check your internet connection and try again."
    }],
    isError: true
  };
}

4. Timeout Errors

Operations that take too long.

const TIMEOUT = 10000; // 10 seconds

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TIMEOUT);

try {
  const resp = await fetch(url, { signal: controller.signal });
  clearTimeout(timeout);
  // ... process response
} catch (err) {
  if (err.name === "AbortError") {
    return {
      content: [{
        type: "text",
        text: "⏳ The request timed out after 10 seconds. Try narrowing your search or increasing the timeout."
      }],
      isError: true
    };
  }
}

Error Response Best Practices

1. Be Specific, Not Technical

// ❌ Too technical
text: "ECONNREFUSED 127.0.0.1:5432"

// ✅ User-facing
text: "❌ Could not connect to the database at localhost:5432. Is PostgreSQL running?"

2. Suggest Next Actions

text: "❌ No results found for '${query}'. Try:
" +
      "• Using fewer keywords
" +
      "• Checking for typos
" +
      "• Searching a broader term"

3. Use Consistent Patterns

Agents learn from patterns. Use consistent prefixes:

✅ Success messages
⚠️ Warnings (partial success)
❌ Errors (operation failed)
⏳ Timeouts / Rate limits
🔒 Authentication / Permission issues

4. Include Retry Information

// After a rate limit
text: "⏳ Rate limited. Try again in ${retryAfter} seconds."

// After a transient error
text: "❌ Temporary error. This usually resolves in a few minutes. Retry with the same parameters."

The Error Response Format

Always return this structure:

return {
  content: [{
    type: "text",
    text: "Your error message here"
  }],
  isError: true  // Signal to the MCP client that this is an error
};

Complete Error Handler Pattern

async function safeCall(fn, errorContext) {
  try {
    return await fn();
  } catch (err) {
    console.error(`[${errorContext}]`, err.message);

    // Don't expose raw errors to agents
    return {
      content: [{
        type: "text",
        text: `❌ An unexpected error occurred while ${errorContext}. ` +
              `Please try again or check the tool logs for details.`
      }],
      isError: true
    };
  }
}

// Usage
return await safeCall(
  () => fetchWeather(args.city),
  "fetching weather data"
);

Well-designed error handling makes your tool feel reliable and professional — even when things go wrong.

#MCP #Tutorial #ErrorHandling #mcpm #BestPractices