Skip to main content
Glama
shahlaukik

Money Manager MCP Server

by shahlaukik
index.ts9.88 kB
/** * Error categories for the Money Manager MCP server */ export enum ErrorCategory { NETWORK = "NETWORK", API = "API", VALIDATION = "VALIDATION", SESSION = "SESSION", FILE = "FILE", INTERNAL = "INTERNAL", } /** * Base error interface for MCP errors */ export interface McpErrorDetails { code: string; category: ErrorCategory; message: string; details?: Record<string, unknown>; retryable: boolean; } /** * Base class for all Money Manager MCP errors */ export class McpError extends Error implements McpErrorDetails { public readonly code: string; public readonly category: ErrorCategory; public readonly details?: Record<string, unknown>; public readonly retryable: boolean; constructor( code: string, category: ErrorCategory, message: string, retryable: boolean = false, details?: Record<string, unknown>, ) { super(message); this.name = "McpError"; this.code = code; this.category = category; this.retryable = retryable; this.details = details; // Maintains proper stack trace for where error was thrown (V8 engines) const ErrorWithCapture = Error as typeof Error & { captureStackTrace?: ( targetObject: object, constructorOpt?: (...args: unknown[]) => unknown, ) => void; }; if (ErrorWithCapture.captureStackTrace) { ErrorWithCapture.captureStackTrace(this, McpError); } } /** * Converts the error to a JSON-serializable object */ toJSON(): McpErrorDetails { return { code: this.code, category: this.category, message: this.message, details: this.details, retryable: this.retryable, }; } } /** * Network-related errors (connection failures, timeouts) */ export class NetworkError extends McpError { constructor(message: string, details?: Record<string, unknown>) { super("NETWORK_ERROR", ErrorCategory.NETWORK, message, true, details); this.name = "NetworkError"; } static timeout(url: string, timeoutMs: number, hint?: string): NetworkError { const message = `Request to ${url} timed out after ${timeoutMs}ms`; return new NetworkError(message, { url, timeoutMs, errorType: "TIMEOUT", hint, }); } /** * Creates a timeout error with a specific hint for transaction_list queries */ static timeoutForTransactionList( url: string, timeoutMs: number, ): NetworkError { return NetworkError.timeout( url, timeoutMs, "Note: The Money Manager server may hang when querying date ranges with no transactions. " + "This is a known server-side limitation. Try a date range that has recorded transactions.", ); } static connectionRefused(url: string): NetworkError { return new NetworkError(`Connection refused to ${url}`, { url, errorType: "CONNECTION_REFUSED", }); } static unreachable(url: string, originalError?: string): NetworkError { return new NetworkError( `Cannot connect to Money Manager server at ${url}`, { url, originalError, errorType: "UNREACHABLE", }, ); } } /** * API-related errors (server returned an error response) */ export class APIError extends McpError { public readonly statusCode?: number; constructor( message: string, statusCode?: number, details?: Record<string, unknown>, ) { const retryable = statusCode !== undefined && statusCode >= 500; super("API_ERROR", ErrorCategory.API, message, retryable, { ...details, statusCode, }); this.name = "APIError"; this.statusCode = statusCode; } static notFound(resource: string, id?: string): APIError { const message = id ? `${resource} with ID '${id}' not found` : `${resource} not found`; return new APIError(message, 404, { resource, id }); } static serverError(message: string, statusCode: number = 500): APIError { return new APIError(`Server error: ${message}`, statusCode); } static badRequest(message: string): APIError { return new APIError(`Bad request: ${message}`, 400); } static fromStatusCode(statusCode: number, message?: string): APIError { const defaultMessages: Record<number, string> = { 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 500: "Internal Server Error", 502: "Bad Gateway", 503: "Service Unavailable", }; const errorMessage = message ?? defaultMessages[statusCode] ?? "Unknown Error"; return new APIError(errorMessage, statusCode); } } /** * Validation errors (input validation failures) */ export class ValidationError extends McpError { public readonly field?: string; public readonly expected?: string; public readonly received?: unknown; constructor( message: string, field?: string, expected?: string, received?: unknown, ) { super("VALIDATION_ERROR", ErrorCategory.VALIDATION, message, false, { field, expected, received, }); this.name = "ValidationError"; this.field = field; this.expected = expected; this.received = received; } static invalidField( field: string, expected: string, received: unknown, ): ValidationError { return new ValidationError( `Invalid value for '${field}': expected ${expected}, received ${JSON.stringify(received)}`, field, expected, received, ); } static requiredField(field: string): ValidationError { return new ValidationError( `Required field '${field}' is missing`, field, "required", undefined, ); } static invalidFormat( field: string, format: string, value: string, ): ValidationError { return new ValidationError( `Invalid format for '${field}': expected ${format}`, field, format, value, ); } static fromZodError(zodError: { errors: Array<{ path: (string | number)[]; message: string }>; }): ValidationError { const firstError = zodError.errors[0]; if (!firstError) { return new ValidationError("Validation failed"); } const field = firstError.path.join("."); return new ValidationError( `Validation failed for '${field}': ${firstError.message}`, field, ); } } /** * Session-related errors (authentication issues) */ export class SessionError extends McpError { constructor(message: string, details?: Record<string, unknown>) { super("SESSION_ERROR", ErrorCategory.SESSION, message, true, details); this.name = "SessionError"; } static expired(): SessionError { return new SessionError("Session has expired. Please reconnect."); } static invalid(): SessionError { return new SessionError("Invalid session. Please reconnect."); } static unauthorized(): SessionError { return new SessionError( "Unauthorized access. Please check your credentials.", ); } } /** * File system errors (backup/export operations) */ export class FileError extends McpError { public readonly filePath?: string; constructor( message: string, filePath?: string, details?: Record<string, unknown>, ) { super("FILE_ERROR", ErrorCategory.FILE, message, false, { ...details, filePath, }); this.name = "FileError"; this.filePath = filePath; } static readFailed(filePath: string, originalError?: string): FileError { return new FileError(`Cannot read file at '${filePath}'`, filePath, { originalError, operation: "read", }); } static writeFailed(filePath: string, originalError?: string): FileError { return new FileError(`Cannot write file to '${filePath}'`, filePath, { originalError, operation: "write", }); } static notFound(filePath: string): FileError { return new FileError(`File not found: '${filePath}'`, filePath, { operation: "access", }); } static permissionDenied(filePath: string): FileError { return new FileError( `Permission denied for file: '${filePath}'`, filePath, { operation: "access", }, ); } } /** * Internal errors (unexpected errors) */ export class InternalError extends McpError { constructor(message: string, details?: Record<string, unknown>) { super("INTERNAL_ERROR", ErrorCategory.INTERNAL, message, false, details); this.name = "InternalError"; } static unexpected(originalError?: Error): InternalError { return new InternalError( "An unexpected error occurred", originalError ? { originalError: originalError.message, stack: originalError.stack, } : undefined, ); } static notImplemented(feature: string): InternalError { return new InternalError(`Feature '${feature}' is not implemented`, { feature, }); } } /** * Type guard to check if an error is an McpError */ export function isMcpError(error: unknown): error is McpError { return error instanceof McpError; } /** * Wraps an unknown error into an appropriate McpError */ export function wrapError(error: unknown): McpError { if (isMcpError(error)) { return error; } if (error instanceof Error) { // Check for common network error codes const errorWithCode = error as Error & { code?: string }; if (errorWithCode.code === "ECONNREFUSED") { return NetworkError.connectionRefused("unknown"); } if ( errorWithCode.code === "ETIMEDOUT" || errorWithCode.code === "ECONNABORTED" ) { return NetworkError.timeout("unknown", 0); } if (errorWithCode.code === "ENOTFOUND") { return NetworkError.unreachable("unknown", error.message); } return InternalError.unexpected(error); } return new InternalError(String(error)); }

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/shahlaukik/money-manager-mcp'

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