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.
throw new Error("Database connection failed");
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;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TIMEOUT);
try {
const resp = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
} 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
text: "ECONNREFUSED 127.0.0.1:5432"
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
text: "⏳ Rate limited. Try again in ${retryAfter} seconds."
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
};
Complete Error Handler Pattern
async function safeCall(fn, errorContext) {
try {
return await fn();
} catch (err) {
console.error(`[${errorContext}]`, err.message);
return {
content: [{
type: "text",
text: `❌ An unexpected error occurred while ${errorContext}. ` +
`Please try again or check the tool logs for details.`
}],
isError: true
};
}
}
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
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:
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