/**
* Custom error classes for HN MCP Server
*/
import type { ErrorContext, MCPToolResponse } from '../types/mcp.js';
/** Base error class with MCP conversion */
export class HNError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly context?: ErrorContext
) {
super(message);
this.name = 'HNError';
Error.captureStackTrace(this, this.constructor);
}
/** Convert error to MCP-formatted response */
toMCPError(): MCPToolResponse {
const contextStr = this.context ? `\nContext: ${JSON.stringify(this.context, null, 2)}` : '';
return {
content: [
{
type: 'text',
text: `Error: ${this.message}\nCode: ${this.code}${contextStr}`,
},
],
isError: true,
};
}
}
/** HN Algolia API returned an error */
export class HNAPIError extends HNError {
constructor(
message: string,
public readonly statusCode: number,
public readonly response?: unknown,
context?: ErrorContext
) {
super(message, 'HN_API_ERROR', {
...context,
statusCode,
response,
});
this.name = 'HNAPIError';
}
}
/** Rate limit exceeded */
export class RateLimitError extends HNError {
constructor(
public readonly current: number,
public readonly limit: number,
public readonly resetTime?: Date
) {
const resetStr = resetTime ? ` Resets at ${resetTime.toISOString()}` : '';
super(`Rate limit exceeded: ${current}/${limit} requests per hour.${resetStr}`, 'RATE_LIMIT_EXCEEDED', {
current,
limit,
resetTime: resetTime?.toISOString(),
});
this.name = 'RateLimitError';
}
}
/** Invalid input parameters */
export class ValidationError extends HNError {
constructor(
message: string,
public readonly field: string,
public readonly value: unknown,
context?: ErrorContext
) {
super(message, 'VALIDATION_ERROR', {
...context,
field,
value,
});
this.name = 'ValidationError';
}
}
/** Network request failed */
export class NetworkError extends HNError {
constructor(message: string, cause?: Error, context?: ErrorContext) {
super(message, 'NETWORK_ERROR', {
...context,
cause: cause?.message,
});
this.name = 'NetworkError';
if (cause) {
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
}
}
}
/** Item not found (404) */
export class ItemNotFoundError extends HNAPIError {
public override readonly code: string;
constructor(itemType: 'story' | 'comment' | 'user', identifier: string | number, context?: ErrorContext) {
super(`${itemType} not found: ${identifier}`, 404, undefined, {
...context,
itemType,
identifier,
});
this.name = 'ItemNotFoundError';
// Override the code for ItemNotFoundError
this.code = 'ITEM_NOT_FOUND';
}
}