Errors
The SDK provides specific error types for different failure scenarios. Use these in your to return meaningful errors to AI clients.
Context Errors
Errors caused by input or missing resources. Extend MCPContextError.
NotFoundError
Requested resource doesn’t exist.
import { ArcadeMCP, NotFoundError } from 'arcade-mcp-server';
import { z } from 'zod';
const app = new ArcadeMCP({ name: 'user-service' });
app.tool('getUser', {
input: z.object({ id: z.string() }),
handler: async ({ input }) => {
const user = await db.findUser(input.id);
if (!user) {
throw new NotFoundError(`User ${input.id} not found`);
}
return user;
},
});AuthorizationError
lacks permission for the requested action.
import { AuthorizationError } from 'arcade-mcp-server';
// In your tool handler:
if (project.ownerEmail !== userEmail) {
throw new AuthorizationError('Only the owner can delete this project');
}ResourceError
Error in resource management ( resources, not ).
import { ResourceError } from 'arcade-mcp-server'; throw new ResourceError('Cannot read resource: config://settings');
PromptError
Error in prompt management (
import { PromptError } from 'arcade-mcp-server'; throw new PromptError('Prompt template not found: greeting');
Retry-Aware Errors
These errors include metadata that helps AI orchestrators decide whether and how to retry.
Don’t use ToolExecutionError directly — always use a specific subclass.
RetryableToolError
The operation failed but can be retried, optionally with guidance for the AI.
import { RetryableToolError } from 'arcade-mcp-server'; // Simple retry throw new RetryableToolError('Service temporarily unavailable'); // With retry delay throw new RetryableToolError('Rate limited', { retryAfterMs: 5000, }); // With guidance for the AI on how to retry differently throw new RetryableToolError('Search returned no results', { additionalPromptContent: 'Try broader search terms or check spelling.', });
FatalToolError
Unrecoverable error — the AI should not retry this operation.
import { FatalToolError } from 'arcade-mcp-server'; throw new FatalToolError('Account has been permanently deleted'); // With developer-only details (not shown to AI) throw new FatalToolError('Configuration error', { developerMessage: 'Missing required API key in environment', });
ContextRequiredToolError
The operation needs additional
import { ContextRequiredToolError } from 'arcade-mcp-server'; throw new ContextRequiredToolError('Multiple users found matching "John"', { additionalPromptContent: 'Please specify: John Smith (john@work.com) or John Doe (john@home.com)', // required });
For most RetryableToolError (transient failures) or FatalToolError (unrecoverable).
Use ContextRequiredToolError when the AI needs to ask the
UpstreamError
External API or service failure. canRetry is true for 5xx and 429, false otherwise.
import { UpstreamError } from 'arcade-mcp-server'; throw new UpstreamError('Slack API error: channel_not_found', { statusCode: 404, // required });
UpstreamRateLimitError
Rate limit from an external API. Extends UpstreamError with statusCode: 429 auto-set.
import { UpstreamRateLimitError } from 'arcade-mcp-server'; throw new UpstreamRateLimitError('Rate limited by Slack', { retryAfterMs: 60_000, // required — milliseconds until retry });
You rarely throw these manually. Use error adapters to automatically translate SDK errors.
Constructor Signatures
All
// RetryableToolError — all options are optional new RetryableToolError(message: string, options?: { developerMessage?: string; additionalPromptContent?: string; retryAfterMs?: number; extra?: Record<string, unknown>; cause?: unknown; }); // FatalToolError — all options are optional new FatalToolError(message: string, options?: { developerMessage?: string; extra?: Record<string, unknown>; cause?: unknown; }); // ContextRequiredToolError — additionalPromptContent is REQUIRED new ContextRequiredToolError(message: string, options: { additionalPromptContent: string; // required developerMessage?: string; extra?: Record<string, unknown>; cause?: unknown; }); // UpstreamError — statusCode is REQUIRED new UpstreamError(message: string, options: { statusCode: number; // required developerMessage?: string; extra?: Record<string, unknown>; cause?: unknown; }); // UpstreamRateLimitError — retryAfterMs is REQUIRED new UpstreamRateLimitError(message: string, options: { retryAfterMs: number; // required developerMessage?: string; extra?: Record<string, unknown>; cause?: unknown; });
| Option | Purpose |
|---|---|
developerMessage | Debug info (not shown to AI) |
extra | Structured metadata for telemetry/adapters |
cause | ES2022 error chaining for stack traces |
All
| Property | Type | Description |
|---|---|---|
canRetry | boolean | Whether this error is retryable |
kind | ErrorKind | Classification for telemetry |
statusCode | number | undefined | HTTP status code equivalent |
SDK-Internal Errors
These errors are thrown by the SDK, not by
| Error | When Thrown |
|---|---|
ToolDefinitionError | Tool definition is invalid |
ToolInputSchemaError | Input schema is malformed |
ToolOutputSchemaError | Output schema is malformed |
ToolInputError | Input doesn’t match schema at runtime |
ToolOutputError | Output can’t be serialized |
ToolkitLoadError | Toolkit fails to import (missing deps, syntax error) |
Error Hierarchy
All errors extend from MCPError:
MCPError (base) ├── MCPContextError (user/input caused the error) │ ├── AuthorizationError │ ├── NotFoundError │ ├── PromptError │ └── ResourceError └── MCPRuntimeError (server/infrastructure caused the error) ├── ProtocolError ├── TransportError └── ServerError ├── RequestError │ └── ServerRequestError ├── ResponseError ├── SessionError └── LifespanError ToolkitError (base for tool errors) └── ToolError └── ToolRuntimeError ├── RetryableToolError (canRetry: true) ├── FatalToolError (canRetry: false) ├── ContextRequiredToolError (canRetry: false) └── UpstreamError (canRetry: true for 5xx/429) └── UpstreamRateLimitError (canRetry: true)
SDK-internal errors (ToolDefinitionError, ToolInputError, etc.) are omitted.
See SDK-Internal Errors for the full list.
When to Use Each Category
| Error Type | Use when… | HTTP Analogy |
|---|---|---|
MCPContextError | User/input caused the error | 4xx errors |
MCPRuntimeError | Server/infrastructure caused the error | 5xx errors |
ErrorKind
Each error has a kind property for telemetry and logging:
import { ErrorKind } from 'arcade-mcp-server'; // Example: log errors by kind console.log(error.kind); // e.g., ErrorKind.TOOL_RUNTIME_RETRY
UpstreamError auto-sets kind from statusCode (401/403 → AUTH_ERROR, 404 → NOT_FOUND, 429 → RATE_LIMIT, 5xx → SERVER_ERROR).
Creating Custom Errors
Extend a specific error type — not ToolExecutionError directly:
import { FatalToolError } from 'arcade-mcp-server'; class QuotaExceededError extends FatalToolError { readonly limitType: string; constructor(message: string, limitType: string) { super(message, { extra: { limitType } }); this.name = 'QuotaExceededError'; this.limitType = limitType; } } throw new QuotaExceededError('Monthly API quota exceeded', 'api_calls');
For retryable errors, extend RetryableToolError. For MCPContextError.
Best Practices
Never expose internal error details to clients. The SDK’s ErrorHandlingMiddleware
masks stack traces by default. In development, set maskErrorDetails: false to debug.
Do: Use Specific Error Types
// ✅ Good: Specific error type throw new NotFoundError('User not found'); // ✅ Good: Retryable with guidance throw new RetryableToolError('Search returned no results', { additionalPromptContent: 'Try broader search terms.', });
Don’t: Expose Internals
// ❌ Bad: Leaks implementation details throw new Error(`Database error: ${dbError.stack}`); // ❌ Bad: Leaks SQL throw new Error(`Query failed: SELECT * FROM users WHERE...`);
Error Messages for AI Clients
Write actionable messages that help AI clients understand what went wrong:
// ❌ Vague throw new RetryableToolError('Invalid input'); // ✅ Actionable with context throw new RetryableToolError(`Invalid email: "${input.email}"`, { additionalPromptContent: 'Use format: user@domain.com', });
Wrapping External Errors
When calling external services, wrap their errors with cause chaining for debugging:
import { RetryableToolError } from 'arcade-mcp-server'; // In your tool handler: try { return await weatherApi.get(input.city); } catch (error) { throw new RetryableToolError( `Failed to fetch weather for ${input.city}. Please try again.`, { cause: error } // Preserves stack trace for debugging ); }
Error Handling in Middleware
Use middleware to log, enrich, or transform errors before they reach the client:
import { Middleware, MCPError, type MiddlewareContext, type CallNext } from 'arcade-mcp-server'; class ErrorLoggingMiddleware extends Middleware { async onCallTool(context: MiddlewareContext, next: CallNext) { try { return await next(context); } catch (error) { if (error instanceof MCPError) { console.error({ type: error.name, message: error.message }); } throw error; // Re-throw for ErrorHandlingMiddleware } } }