Skip to Content

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.

TypeScript
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.

TypeScript
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 execution).

TypeScript
import { ResourceError } from 'arcade-mcp-server'; throw new ResourceError('Cannot read resource: config://settings');

PromptError

Error in prompt management ( prompts, not execution).

TypeScript
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.

TypeScript
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.

TypeScript
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 from the before it can proceed.

TypeScript
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 errors, use RetryableToolError (transient failures) or FatalToolError (unrecoverable). Use ContextRequiredToolError when the AI needs to ask the for clarification.

UpstreamError

External API or service failure. canRetry is true for 5xx and 429, false otherwise.

TypeScript
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.

TypeScript
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 errors use an options object pattern. Required fields are marked.

TypeScript
// 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; });
OptionPurpose
developerMessageDebug info (not shown to AI)
extraStructured metadata for telemetry/adapters
causeES2022 error chaining for stack traces

All errors also expose these read-only properties:

PropertyTypeDescription
canRetrybooleanWhether this error is retryable
kindErrorKindClassification for telemetry
statusCodenumber | undefinedHTTP status code equivalent

SDK-Internal Errors

These errors are thrown by the SDK, not by code. They appear in error messages and logs to help you debug. You don’t need to import or throw them directly.

ErrorWhen Thrown
ToolDefinitionErrorTool definition is invalid
ToolInputSchemaErrorInput schema is malformed
ToolOutputSchemaErrorOutput schema is malformed
ToolInputErrorInput doesn’t match schema at runtime
ToolOutputErrorOutput can’t be serialized
ToolkitLoadErrorToolkit fails to import (missing deps, syntax error)

Error Hierarchy

All errors extend from MCPError:

TEXT
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 TypeUse when…HTTP Analogy
MCPContextErrorUser/input caused the error4xx errors
MCPRuntimeErrorServer/infrastructure caused the error5xx errors

ErrorKind

Each error has a kind property for telemetry and logging:

TypeScript
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:

TypeScript
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 -level errors, extend 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

TypeScript
// ✅ 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

TypeScript
// ❌ 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:

TypeScript
// ❌ 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:

TypeScript
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:

TypeScript
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 } } }
Last updated on

Errors - Arcade MCP TypeScript Reference | Arcade Docs