/** * Custom error types for MCP UI operations. * Provides structured errors with context and recovery hints. */ export interface McpUiErrorContext { server?: string; tool?: string; uri?: string; session?: string; [key: string]: unknown; } /** * Base error class for MCP UI errors. */ export class McpUiError extends Error { readonly code: string; readonly context: McpUiErrorContext; readonly recoveryHint?: string; readonly cause?: Error; constructor( message: string, options: { code: string; context?: McpUiErrorContext; recoveryHint?: string; cause?: Error; } ) { super(message); this.name = 'McpUiError'; this.code = options.code; this.context = options.context ?? {}; this.recoveryHint = options.recoveryHint; this.cause = options.cause; // Maintain proper stack trace if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } toJSON(): Record { return { name: this.name, code: this.code, message: this.message, context: this.context, recoveryHint: this.recoveryHint, stack: this.stack, }; } } /** * Error fetching a UI resource from the MCP server. */ export class ResourceFetchError extends McpUiError { constructor(uri: string, reason: string, options?: { server?: string; cause?: Error }) { super(`Failed to fetch UI resource "${uri}": ${reason}`, { code: 'RESOURCE_FETCH_ERROR', context: { uri, server: options?.server }, recoveryHint: 'Check that the MCP server is connected and the resource URI is valid.', cause: options?.cause, }); this.name = 'ResourceFetchError'; } } /** * Error parsing or validating UI resource content. */ export class ResourceParseError extends McpUiError { constructor(uri: string, reason: string, options?: { server?: string; mimeType?: string }) { super(`Invalid UI resource "${uri}": ${reason}`, { code: 'RESOURCE_PARSE_ERROR', context: { uri, server: options?.server, mimeType: options?.mimeType }, recoveryHint: 'Ensure the resource returns valid HTML with the correct MIME type.', }); this.name = 'ResourceParseError'; } } /** * Error connecting to the AppBridge. */ export class BridgeConnectionError extends McpUiError { constructor(reason: string, options?: { session?: string; cause?: Error }) { super(`AppBridge connection failed: ${reason}`, { code: 'BRIDGE_CONNECTION_ERROR', context: { session: options?.session }, recoveryHint: 'Check browser console for detailed errors. The iframe may have failed to load.', cause: options?.cause, }); this.name = 'BridgeConnectionError'; } } /** * Error related to user consent for tool calls. */ export class ConsentError extends McpUiError { readonly denied: boolean; constructor(server: string, options: { denied?: boolean; requiresApproval?: boolean }) { const message = options.denied ? `Tool calls for "${server}" were denied for this session` : `Tool call approval required for "${server}"`; super(message, { code: options.denied ? 'CONSENT_DENIED' : 'CONSENT_REQUIRED', context: { server }, recoveryHint: options.denied ? 'The user denied tool access. Start a new session to try again.' : 'Prompt the user for consent before calling tools.', }); this.name = 'ConsentError'; this.denied = options.denied ?? false; } } /** * Error with UI server session management. */ export class SessionError extends McpUiError { constructor(reason: string, options?: { session?: string; cause?: Error }) { super(`Session error: ${reason}`, { code: 'SESSION_ERROR', context: { session: options?.session }, recoveryHint: 'The session may have expired or been closed. Try opening the UI again.', cause: options?.cause, }); this.name = 'SessionError'; } } /** * Error starting or operating the UI server. */ export class ServerError extends McpUiError { constructor(reason: string, options?: { port?: number; cause?: Error }) { super(`UI server error: ${reason}`, { code: 'SERVER_ERROR', context: { port: options?.port }, recoveryHint: 'Check if the port is available. Another process may be using it.', cause: options?.cause, }); this.name = 'ServerError'; } } /** * Error communicating with the MCP server. */ export class McpServerError extends McpUiError { constructor(server: string, reason: string, options?: { tool?: string; cause?: Error }) { super(`MCP server "${server}" error: ${reason}`, { code: 'MCP_SERVER_ERROR', context: { server, tool: options?.tool }, recoveryHint: 'Check that the MCP server is running and responsive.', cause: options?.cause, }); this.name = 'McpServerError'; } } /** * Wrap an unknown error into an McpUiError. */ export function wrapError(error: unknown, context?: McpUiErrorContext): McpUiError { if (error instanceof McpUiError) { // Merge contexts return new McpUiError(error.message, { code: error.code, context: { ...error.context, ...context }, recoveryHint: error.recoveryHint, cause: error.cause, }); } const cause = error instanceof Error ? error : undefined; const message = error instanceof Error ? error.message : String(error); return new McpUiError(message, { code: 'UNKNOWN_ERROR', context, cause, }); } /** * Check if an error is a specific MCP UI error type. */ export function isErrorCode(error: unknown, code: string): boolean { return error instanceof McpUiError && error.code === code; }