Skip to main content
Glama
error-handler.ts8.3 kB
// Error handling utilities for MCP Sigmund import { ErrorDetails } from './types.js'; import { logError } from './logger.js'; export class MCPError extends Error { public readonly code: string; public readonly statusCode: number; public readonly details?: ErrorDetails; constructor( message: string, code: string, statusCode: number = 500, details?: ErrorDetails ) { super(message); this.name = 'MCPError'; this.code = code; this.statusCode = statusCode; this.details = details; } } export class DatabaseError extends MCPError { constructor(message: string, details?: ErrorDetails) { super(message, 'DATABASE_ERROR', 500, details); this.name = 'DatabaseError'; } } export class ValidationError extends MCPError { constructor(message: string, details?: ErrorDetails) { super(message, 'VALIDATION_ERROR', 400, details); this.name = 'ValidationError'; } } export class QueryError extends MCPError { constructor(message: string, details?: ErrorDetails) { super(message, 'QUERY_ERROR', 400, details); this.name = 'QueryError'; } } // Error handler for MCP tools export function handleMCPError( error: unknown, context: string ): { success: false; error: string; code: string; context: string; timestamp: string; } { logError(`MCP Error in ${context}`, 'error-handler', undefined, { message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, name: error instanceof Error ? error.name : 'UnknownError', timestamp: new Date().toISOString(), }); if (error instanceof MCPError) { return { success: false, error: error.message, code: error.code, context, timestamp: new Date().toISOString(), }; } if (error instanceof Error) { return { success: false, error: error.message, code: 'UNKNOWN_ERROR', context, timestamp: new Date().toISOString(), }; } return { success: false, error: String(error), code: 'UNKNOWN_ERROR', context, timestamp: new Date().toISOString(), }; } // Validation helpers export function validateDateRange(dateFrom?: string, dateTo?: string): void { if (dateFrom && !isValidDate(dateFrom)) { throw new ValidationError( `Invalid dateFrom format: ${dateFrom}. Expected YYYY-MM-DD` ); } if (dateTo && !isValidDate(dateTo)) { throw new ValidationError( `Invalid dateTo format: ${dateTo}. Expected YYYY-MM-DD` ); } if (dateFrom && dateTo && new Date(dateFrom) > new Date(dateTo)) { throw new ValidationError('dateFrom cannot be after dateTo'); } } export function validateLimit(limit?: number): void { if (limit !== undefined && (limit < 1 || limit > 10000)) { throw new ValidationError('Limit must be between 1 and 10000'); } } export function validateMonths(months?: number): void { if (months !== undefined && (months < 1 || months > 60)) { throw new ValidationError('Months must be between 1 and 60'); } } export function validateProvider(provider?: string): void { if (provider && typeof provider !== 'string') { throw new ValidationError('Provider must be a string'); } } // Utility functions function isValidDate(dateString: string): boolean { const regex = /^\d{4}-\d{2}-\d{2}$/; if (!regex.test(dateString)) return false; const date = new Date(dateString); return date instanceof Date && !isNaN(date.getTime()); } // Database error handling export function handleDatabaseError(error: unknown, operation: string): never { if (error instanceof Error) { if (error.message.includes('no such table')) { throw new DatabaseError(`Table not found for operation: ${operation}`, { message: error.message, timestamp: new Date().toISOString(), originalError: error.message, }); } if (error.message.includes('database is locked')) { throw new DatabaseError( `Database is locked for operation: ${operation}`, { message: error.message, timestamp: new Date().toISOString(), originalError: error.message, } ); } if (error.message.includes('disk I/O error')) { throw new DatabaseError( `Database I/O error for operation: ${operation}`, { message: error.message, timestamp: new Date().toISOString(), originalError: error.message, } ); } } throw new DatabaseError(`Database operation failed: ${operation}`, { message: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString(), originalError: error, }); } // Query validation export function validateBankingQuery(query: string): void { const validQueries = [ 'accounts', 'transactions', 'balance', 'overview', 'spending_analysis', 'cashflow_analysis', 'providers', ]; if (!validQueries.includes(query)) { throw new QueryError( `Invalid query: ${query}. Valid queries are: ${validQueries.join(', ')}` ); } } export function validateDataOperation(operation: string): void { const validOperations = [ 'get_stats', 'validate_data', 'export_data', 'cleanup_data', ]; if (!validOperations.includes(operation)) { throw new QueryError( `Invalid operation: ${operation}. Valid operations are: ${validOperations.join(', ')}` ); } } // Input sanitization functions export function sanitizeStringInput( input: string, maxLength: number = 255 ): string { if (typeof input !== 'string') { throw new ValidationError('Input must be a string'); } // Remove potentially dangerous characters const sanitized = input .replace(/[<>"'&]/g, '') // Remove HTML/XML characters .replace(/[;()]/g, '') // Remove SQL injection characters .trim() .substring(0, maxLength); return sanitized; } export function sanitizeNumericInput( input: unknown, min?: number, max?: number ): number { const num = Number(input); if (isNaN(num)) { throw new ValidationError('Input must be a valid number'); } if (min !== undefined && num < min) { throw new ValidationError(`Number must be at least ${min}`); } if (max !== undefined && num > max) { throw new ValidationError(`Number must be at most ${max}`); } return num; } export function sanitizeDateInput(input: string): string { if (typeof input !== 'string') { throw new ValidationError('Date input must be a string'); } // Validate date format (YYYY-MM-DD) const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(input)) { throw new ValidationError('Date must be in YYYY-MM-DD format'); } const date = new Date(input); if (isNaN(date.getTime())) { throw new ValidationError('Invalid date provided'); } return input; } export function sanitizeProviderId(input: string): string { const sanitized = sanitizeStringInput(input, 100); // Additional validation for provider IDs if (!/^[a-zA-Z0-9-_]+$/.test(sanitized)) { throw new ValidationError( 'Provider ID can only contain letters, numbers, hyphens, and underscores' ); } return sanitized; } export function sanitizeAccountId(input: string): string { const sanitized = sanitizeStringInput(input, 100); // Additional validation for account IDs if (!/^[a-zA-Z0-9-_]+$/.test(sanitized)) { throw new ValidationError( 'Account ID can only contain letters, numbers, hyphens, and underscores' ); } return sanitized; } export function sanitizeUserId(input: string): string { const sanitized = sanitizeStringInput(input, 100); // Additional validation for user IDs if (!/^[a-zA-Z0-9-_]+$/.test(sanitized)) { throw new ValidationError( 'User ID can only contain letters, numbers, hyphens, and underscores' ); } return sanitized; } export function sanitizeCategory(input: string): string { const sanitized = sanitizeStringInput(input, 100); // Additional validation for categories if (!/^[a-zA-Z0-9\s&-_]+$/.test(sanitized)) { throw new ValidationError( 'Category can only contain letters, numbers, spaces, ampersands, hyphens, and underscores' ); } return sanitized; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/radup/mcp-sigmund'

If you have feedback or need assistance with the MCP directory API, please join our Discord server