This document describes how errors are handled throughout the MCP server tool system, including both regular tools and embedded agent tools.
ApiError (base class)
├─ ApiClientError (4xx - user errors, NOT sent to Sentry)
│ ├─ ApiPermissionError (403)
│ ├─ ApiNotFoundError (404)
│ ├─ ApiValidationError (400, 422)
│ ├─ ApiAuthenticationError (401)
│ └─ ApiRateLimitError (429)
└─ ApiServerError (5xx - system errors, SENT to Sentry)
Key Method:
ApiClientError.toUserMessage()- Returns"API error (status): message"- For 404s with generic messages, adds: "Please verify that the organization, project, or resource ID is correct and that you have access to it."
- For 404s with specific messages, adds: "Please verify the parameters are correct."
UserInputError- User-facing error for validation failures- Parameter validation failures
- Any user-correctable error
ConfigurationError- Missing/invalid configurationLLMProviderError- LLM provider availability issues (e.g., region restrictions)- OpenAI rejecting requests from unsupported regions
- Provider service availability issues that cannot be resolved by retrying
APICallError- Errors from LLM provider API calls (OpenAI, Anthropic, etc.)- 4xx errors (account issues, rate limits, invalid keys) → Converted to
LLMProviderError, NOT sent to Sentry - 5xx errors (server errors) → System errors, SENT to Sentry
- 4xx errors (account issues, rate limits, invalid keys) → Converted to
Conversion Flow:
callEmbeddedAgentconverts user-facingAPICallError(4xx) →LLMProviderErrorimmediately after the AI SDK call- Defensive handling in
handleAgentToolErrorandformatErrorForUserfor any that slip through
User-Facing Errors (Should NOT create Sentry issues):
- All
ApiClientErrorsubclasses UserInputErrorConfigurationErrorLLMProviderErrorAPICallErrorwith 4xx status codes (converted toLLMProviderError)
System Errors (Should be captured by Sentry):
ApiServerErrorAPICallErrorwith 5xx status codes- Network failures
- Unexpected runtime errors
API errors should bubble up naturally to the appropriate handler. The API client throws properly typed errors that are caught at the right level:
- In MCP tools → Bubble up to MCP server wrapper →
formatErrorForUser - In embedded agent tools → Caught by
agentTool→ Formatted for AI
The API client uses a factory pattern (createApiError) to create properly typed errors:
- 4xx →
ApiClientErrorsubclass (ApiPermissionError, ApiNotFoundError, etc.) - 5xx →
ApiServerError - Each error type has specific behaviors and helper methods
🚨 NEVER return untrusted error messages to AI agents - this creates prompt injection vulnerabilities.
In our system, we ONLY return trusted error messages from:
- Sentry API responses (trusted - Sentry controls these messages)
- Our own validation errors (
UserInputError- we control the message content) - Pre-formatted system messages (hardcoded error templates we control)
Why this matters:
- AI agents receive error messages as part of their context
- Malicious error messages could contain prompt injection attacks
- Untrusted input could manipulate agent behavior or extract sensitive information
What we trust:
- Sentry's API error messages (via
ApiClientError.toUserMessage()) - Our own
UserInputErrormessages (application-controlled) - System-generated error templates with Event IDs
What we DON'T trust:
- User-provided input in error scenarios (never directly returned to agents)
- Third-party API error messages (would need sanitization)
- Database error messages (could contain sensitive schema information)
- UserInputError → Log to
console.warn()in wrapAgentToolExecute (for Sentry logging, not as exception) - ApiClientError → Log to
console.warn()in wrapAgentToolExecute (for Sentry logging, not as exception) - ApiServerError/System errors (5xx) → Let bubble up to be captured with
captureException()
When using Cloudflare with Sentry's consoleLoggingIntegration:
console.warn()andconsole.log()→ Recorded and sent to Sentry as logsconsole.error()→ Also recorded, but useconsole.warn()for expected errorscaptureException()→ Creates Sentry issue immediately
Tools exposed to MCP clients call the API directly and let errors bubble up naturally:
// In tool handler - just call the API directly:
const result = await apiService.someMethod({ organizationSlug });
// No try/catch needed - errors bubble up to MCP server wrapperWhat happens:
- API client throws typed errors via
createApiErrorfactory:- 4xx →
ApiClientErrorsubclass (ApiPermissionError, ApiNotFoundError, etc.) - 5xx →
ApiServerError
- 4xx →
- Errors bubble up naturally to MCP server wrapper
formatErrorForUserhandles formatting:ApiClientError→ "Input Error" message withtoUserMessage(), NOT logged to SentryApiServerError→ "Error" message with Event ID, logged to SentryUserInputError→ "Input Error" message, NOT logged to Sentry
Tools used by AI agents within other tools use agentTool() which returns structured responses:
import { agentTool } from "../../internal/agents/tools/utils";
return agentTool({
description: "Tool description",
parameters: z.object({ ... }),
execute: async (params) => {
// Just call the API directly - no error handling needed
const data = await apiService.someMethod(params);
return formatResult(data);
}
});What happens:
- API client throws
ApiClientErrororApiServerError agentToolcatches ALL API errors and returns structured responses:- Success:
{ result: <data> } - UserInputError:
{ error: "Input Error: {message}. You may be able to resolve this by addressing the concern and trying again." } - ApiClientError:
{ error: "Input Error: API error (404): Project not found. Please verify the parameters are correct. You may be able to resolve this by addressing the concern and trying again." } - ApiServerError:
{ error: "Server Error (502): Bad Gateway. Event ID: abc123def456. This is a system error that cannot be resolved by retrying." }
- Success:
- Other errors (unexpected) → Re-thrown to parent tool
Key Benefits:
- Structured responses: AI agents receive consistent
{error?, result?}objects instead of thrown errors - Better error handling: Agents can check for
errorproperty and handle failures gracefully - Retry logic: Agents can analyze error messages and determine if retry is worthwhile
- Type safety: Return types are preserved while error handling is abstracted
1. User calls search_events tool
2. search_events uses AI agent with datasetAttributesTool
3. datasetAttributesTool calls fetchCustomAttributes()
4. fetchCustomAttributes calls apiService.listTraceItemAttributes() directly
5. API returns 403 "no multi-project access"
6. API client creates ApiPermissionError via createApiError factory and throws
7. fetchCustomAttributes lets it bubble up (no try/catch)
8. agentTool catches ApiClientError (specifically ApiPermissionError)
9. Logs to console.warn() for Sentry logging
10. Returns structured response:
```
{
error: "Input Error: API error (403): You do not have access to query across multiple projects. Please select a project for your query. You may be able to resolve this by addressing the concern and trying again."
}
```
11. AI agent receives the structured response and can check the error property
12. AI agent analyzes error message and retries with a specific project
1. User calls get_sentry_resource for an issue
2. Tool calls apiService.getIssue() directly (no withApiErrorHandling)
3. API returns 502 Bad Gateway
4. API client creates ApiServerError via createApiError factory and throws
5. Error bubbles up naturally to MCP server wrapper
6. formatErrorForUser handles ApiServerError, logs to Sentry with captureException
7. User receives formatted error response with Event ID
- Call API methods directly and let errors bubble up naturally
- Use
agentTool()for embedded agent tools - Let typed errors (ApiClientError, ApiServerError) bubble up
- Include helpful context in error messages
- Rely on the error hierarchy for proper handling
- Check for
errorproperty in agent tool responses - SECURITY: Only return trusted error messages to AI agents
- Don't wrap API calls in try/catch unless adding value
- Don't use
withApiErrorHandlinganymore (deprecated) - Don't use the old
wrapAgentToolExecutefunction (useagentToolinstead) - Don't use
logIssue()for expected API errors (4xx) - Don't use
captureException()for UserInputError or ApiClientError - Don't create Sentry issues for user-facing errors
- SECURITY: NEVER pass untrusted error messages to AI agents - risk of prompt injection
✅ SAFE - These are trusted and can be returned to agents:
// Sentry API errors (controlled by Sentry)
return { error: `Input Error: ${apiError.toUserMessage()}. You may be able to resolve this...` };
// Our own validation errors (controlled by our code)
throw new UserInputError("Invalid organization slug format");
// System-generated templates (controlled by our code)
return { error: `Server Error (${status}): ${message}. Event ID: ${eventId}...` };❌ UNSAFE - These could enable prompt injection:
// User input directly in error (NEVER do this)
return { error: `Invalid input: ${userProvidedValue}` }; // 🚨 DANGEROUS
// Third-party API errors without validation (NEVER do this)
return { error: externalApiResponse.error }; // 🚨 DANGEROUS
// Database errors (could leak schema info)
return { error: sqlError.message }; // 🚨 DANGEROUSAPI Call
↓
createApiError Factory
├─ 4xx → ApiClientError subclass (with toUserMessage())
└─ 5xx → ApiServerError
↓
Thrown directly to tool (no withApiErrorHandling)
↓
In Embedded Agent Tool?
├─ Yes → agentTool
│ ├─ UserInputError → Returns { error: "Input Error: ..." }
│ ├─ ApiClientError → Returns { error: "Input Error: ..." } with toUserMessage()
│ ├─ ApiServerError → Returns { error: "Server Error (5xx): ..." } + Event ID (logged to Sentry)
│ └─ Other (unexpected) → Re-throw
│ ↓
│ AI agent receives structured {error?, result?} response
│ ↓
│ AI agent checks for error property and handles accordingly
└─ No → MCP Server Wrapper → formatErrorForUser
├─ UserInputError → "**Input Error**" formatted
├─ ApiClientError → "**Input Error**" with toUserMessage()
├─ ApiServerError → "**Error**" + Event ID (logged to Sentry)
└─ Other → Captured by Sentry
When using Cloudflare Workers with Sentry integration:
console.error()is captured as breadcrumbs (not as issues)- Use for debugging information that should be attached to real errors
- Don't use for expected error conditions
-
Call the API directly without wrappers:
// Just call the API - errors bubble up naturally const result = await apiService.someMethod({ organizationSlug });
-
Let errors bubble up to the MCP server wrapper - don't add try/catch unless you're adding value
-
The MCP server will automatically:
- Format errors via
formatErrorForUser - Log ApiServerError to Sentry with captureException
- Return formatted error to MCP client
- Format errors via
-
Use
agentTool()instead of the regulartool()function:return agentTool({ description: "Tool description", parameters: z.object({ ... }), execute: async (params) => { // Your tool implementation - return the result directly const data = await apiService.someMethod(params); return formatResult(data); } });
-
Inside the tool, call the API directly:
// No error handling needed - agentTool handles it automatically const data = await apiService.someMethod(params);
-
The wrapper will automatically:
- Return
{ result: <data> }on success - Return
{ error: "formatted message" }on failure - Log UserInputError/ApiClientError to console.warn for Sentry logging
- Include Event IDs for ApiServerError in error messages
- Return
- UserInputError to Agent:
{ error: "Input Error: {message}. You may be able to resolve this by addressing the concern and trying again." } - LLMProviderError to Agent:
{ error: "AI Provider Error: {message}. This is a service availability issue that cannot be resolved by retrying." } - ApiClientError to Agent:
{ error: "Input Error: {toUserMessage()}. You may be able to resolve this by addressing the concern and trying again." } - ApiServerError to Agent:
{ error: "Server Error (5xx): {message}. Event ID: {eventId}. This is a system error that cannot be resolved by retrying." } - LLMProviderError to MCP User: Formatted with "AI Provider Error" header
- ApiClientError to MCP User: Formatted with "Input Error" header and toUserMessage()
- ApiServerError to MCP User: Formatted with "Error" header + Event ID (logged to Sentry)
When testing tools, verify:
- 404 errors include helpful hints via toUserMessage():
- Generic messages get detailed help about checking org/project/resource IDs
- Specific messages get brief parameter verification hint
- 403 errors are returned to agents as formatted markdown
- 5xx errors are captured by Sentry with Event IDs
- Network errors bubble up appropriately
- UserInputErrors have clear, actionable messages
- ApiClientError in agent tools returns formatted markdown with "Input Error" header