import { MCPApp, ResourceError } from 'arcade-mcp';import { z } from 'zod';const app = new MCPApp({ name: 'file-service' });app.tool('readFile', { input: z.object({ path: z.string() }), handler: async ({ input }) => { try { return await Bun.file(input.path).text(); } catch { throw new ResourceError(`Cannot read file: ${input.path}`); } },});
PromptError
Error in prompt management.
TypeScript
import { PromptError } from 'arcade-mcp';// Thrown when a prompt is not found or invalidthrow new PromptError('Prompt template not found: greeting');
UpstreamError
External API or service failure. Typically thrown by error adapters.
TypeScript
import { UpstreamError } from 'arcade-mcp';throw new UpstreamError('Slack API error: channel_not_found', { statusCode: 404, // required});
UpstreamRateLimitError
Rate limit from an external API. Includes retry information.
TypeScript
import { UpstreamRateLimitError } from 'arcade-mcp';throw new UpstreamRateLimitError('Rate limited by Slack', { retryAfterMs: 60_000, // required});
You rarely throw UpstreamError manually. Use error adapters to automatically translate SDK errors.
Retry-Aware Errors
These errors include metadata that helps AI orchestrators decide whether and how to retry.
RetryableToolError
The operation failed but can be retried, optionally with guidance for the AI.
TypeScript
import { RetryableToolError } from 'arcade-mcp';// Simple retrythrow new RetryableToolError('Service temporarily unavailable');// With retry delaythrow new RetryableToolError('Rate limited', { retryAfterMs: 5000,});// With guidance for the AI on how to retry differentlythrow new RetryableToolError('Search returned no results', { additionalPromptContent: 'Try broader search terms or check spelling.',});
FatalToolError
Unrecoverable error — the AI should not retry this operation.
TypeScript
import { FatalToolError } from 'arcade-mcp';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 context from the user before it can proceed.
TypeScript
import { ContextRequiredToolError } from 'arcade-mcp';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 tool errors, use RetryableToolError (transient failures) or FatalToolError (unrecoverable).
Use ContextRequiredToolError when the AI needs to ask the user for clarification.
Constructor Signatures
All tool errors use an options object pattern. Required fields are marked.
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
TypeScript
// ✅ Good: Specific error typethrow new NotFoundError('User not found');// ✅ Good: Retryable with guidancethrow new RetryableToolError('Search returned no results', { additionalPromptContent: 'Try broader search terms.',});
Don’t: Expose Internals
TypeScript
// ❌ Bad: Leaks implementation detailsthrow new Error(`Database error: ${dbError.stack}`);// ❌ Bad: Leaks SQLthrow new Error(`Query failed: SELECT * FROM users WHERE...`);
Error Messages for AI Clients
Write actionable messages that help AI clients understand what went wrong:
TypeScript
// ❌ Vaguethrow new RetryableToolError('Invalid input');// ✅ Actionable with contextthrow 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:
TypeScript
import { RetryableToolError } from 'arcade-mcp';import { z } from 'zod';app.tool('fetchWeather', { input: z.object({ city: z.string() }), handler: async ({ input }) => { try { return await weatherApi.get(input.city); } catch (error) { // Wrap with context and preserve the original error for debugging throw new RetryableToolError( `Failed to fetch weather for ${input.city}. Please try again.`, { cause: error } // Preserves stack trace for debugging ); } },});
Error Handling in Middleware
TypeScript
import { Middleware, MCPError, type MiddlewareContext, type CallNext,} from 'arcade-mcp';class ErrorEnrichmentMiddleware extends Middleware { async onCallTool(context: MiddlewareContext, next: CallNext) { try { return await next(context); } catch (error) { if (error instanceof MCPError) { // Log structured error info console.error({ type: error.name, message: error.message, tool: context.message.params?.name, session: context.session?.id, }); } throw error; // Re-throw for ErrorHandlingMiddleware } }}