Skip to main content
Glama
errors.ts6.17 kB
/** * Custom error classes for the Jira MCP server. * Provides structured error handling with proper error hierarchies. * @module utils/errors */ import type { ZodError } from 'zod'; /** * Base error class for all Jira MCP errors. * Extends the native Error class with additional context. */ export class JiraMcpError extends Error { /** Error code for programmatic handling */ public readonly code: string; /** Additional context about the error */ public readonly context?: Record<string, unknown>; constructor( message: string, code: string = 'JIRA_MCP_ERROR', context?: Record<string, unknown> ) { super(message); this.name = 'JiraMcpError'; this.code = code; this.context = context; Error.captureStackTrace(this, this.constructor); } /** * Converts the error to a JSON-serializable object. */ toJSON(): Record<string, unknown> { return { name: this.name, message: this.message, code: this.code, context: this.context, }; } } /** * Error thrown when Jira API requests fail. */ export class JiraApiError extends JiraMcpError { /** HTTP status code from the API response */ public readonly statusCode: number; /** Raw response body from the API */ public readonly responseBody?: unknown; constructor( message: string, statusCode: number, responseBody?: unknown, context?: Record<string, unknown> ) { super(message, 'JIRA_API_ERROR', context); this.name = 'JiraApiError'; this.statusCode = statusCode; this.responseBody = responseBody; } /** * Creates an error from an HTTP response. */ static async fromResponse( response: Response, context?: Record<string, unknown> ): Promise<JiraApiError> { let body: unknown; try { body = await response.json(); } catch { body = await response.text().catch(() => 'Unable to read response body'); } const message = JiraApiError.extractMessage(body, response.statusText); return new JiraApiError(message, response.status, body, context); } /** * Extracts a human-readable message from the API response. */ private static extractMessage(body: unknown, fallback: string): string { if (typeof body === 'object' && body !== null) { const obj = body as Record<string, unknown>; if ( typeof obj['errorMessages'] === 'object' && Array.isArray(obj['errorMessages']) ) { return (obj['errorMessages'] as string[]).join(', '); } if (typeof obj['message'] === 'string') { return obj['message']; } if (typeof obj['error'] === 'string') { return obj['error']; } } return fallback; } override toJSON(): Record<string, unknown> { return { ...super.toJSON(), statusCode: this.statusCode, responseBody: this.responseBody, }; } } /** * Error thrown when input validation fails. */ export class ValidationError extends JiraMcpError { /** Validation errors by field */ public readonly errors: Record<string, string[]>; constructor( message: string, errors: Record<string, string[]>, context?: Record<string, unknown> ) { super(message, 'VALIDATION_ERROR', context); this.name = 'ValidationError'; this.errors = errors; } /** * Creates a validation error from Zod errors. */ static fromZodError(zodError: ZodError): ValidationError { const errors: Record<string, string[]> = {}; for (const issue of zodError.issues) { const path = issue.path.join('.') || 'root'; if (!errors[path]) { errors[path] = []; } errors[path].push(issue.message); } return new ValidationError('Validation failed', errors); } override toJSON(): Record<string, unknown> { return { ...super.toJSON(), errors: this.errors, }; } } /** * Error thrown when authentication fails. */ export class AuthenticationError extends JiraMcpError { constructor(message: string = 'Authentication failed') { super(message, 'AUTHENTICATION_ERROR'); this.name = 'AuthenticationError'; } } /** * Error thrown when rate limiting is exceeded. */ export class RateLimitError extends JiraMcpError { /** Time in milliseconds until the rate limit resets */ public readonly retryAfter: number; constructor( message: string = 'Rate limit exceeded', retryAfter: number = 60000 ) { super(message, 'RATE_LIMIT_ERROR', { retryAfter }); this.name = 'RateLimitError'; this.retryAfter = retryAfter; } override toJSON(): Record<string, unknown> { return { ...super.toJSON(), retryAfter: this.retryAfter, }; } } /** * Error thrown when a resource is not found. */ export class NotFoundError extends JiraMcpError { /** Type of resource that was not found */ public readonly resourceType: string; /** Identifier of the resource */ public readonly resourceId: string; constructor(resourceType: string, resourceId: string) { super(`${resourceType} not found: ${resourceId}`, 'NOT_FOUND_ERROR', { resourceType, resourceId, }); this.name = 'NotFoundError'; this.resourceType = resourceType; this.resourceId = resourceId; } } /** * Error thrown when configuration is invalid or missing. */ export class ConfigurationError extends JiraMcpError { constructor(message: string) { super(message, 'CONFIGURATION_ERROR'); this.name = 'ConfigurationError'; } } /** * Type guard to check if an error is a JiraMcpError. */ export function isJiraMcpError(error: unknown): error is JiraMcpError { return error instanceof JiraMcpError; } /** * Formats an error for MCP response. */ export function formatErrorForMcp(error: unknown): { content: string; isError: true; } { if (isJiraMcpError(error)) { return { content: `Error [${error.code}]: ${error.message}`, isError: true, }; } if (error instanceof Error) { return { content: `Error: ${error.message}`, isError: true, }; } return { content: `Unknown error: ${String(error)}`, isError: true, }; }

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/icy-r/jira-mcp'

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