GemSuite-MCP
by PV-Bhat
Verified
- src
- utils
/**
* Type guard for Axios errors
*/
function isAxiosError(error: any): boolean {
return error && error.isAxiosError === true;
}
import axios from 'axios';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { MODELS } from '../config/models.js';
/**
* Custom error class for Gemini API errors
*/
export class GeminiError extends Error {
/** HTTP status code (if applicable) */
status?: number;
/** Error code for categorization */
code: string;
/** Whether the error is a rate limit error */
isRateLimit: boolean;
/** Whether the error is retriable */
isRetriable: boolean;
/**
* Creates a new GeminiError
* @param message Error message
* @param status HTTP status code (if applicable)
* @param code Error code
* @param isRateLimit Whether it's a rate limit error
* @param isRetriable Whether the error is retriable
*/
constructor(
message: string,
status?: number,
code = 'UNKNOWN_ERROR',
isRateLimit = false,
isRetriable = false
) {
super(message);
this.name = 'GeminiError';
this.status = status;
this.code = code;
this.isRateLimit = isRateLimit;
this.isRetriable = isRetriable;
}
}
/**
* Categorizes and handles API errors
* @param error The error to handle
* @param modelId The model ID that was being used
* @returns A standardized GeminiError
*/
export function handleApiError(error: unknown, modelId?: string): GeminiError {
// Get model display name if available
let modelDisplay = 'Gemini API';
if (modelId && MODELS[modelId]) {
modelDisplay = MODELS[modelId].displayName;
}
// Handle Axios errors (API errors)
if (isAxiosError(error) && (error as any).response) {
const status = (error as any).response.status;
const data = (error as any).response.data;
switch (status) {
case 400:
return new GeminiError(
`Invalid request to ${modelDisplay}: ${getErrorMessage(data)}`,
status,
'INVALID_REQUEST',
false,
false
);
case 401:
return new GeminiError(
`Unauthorized: Check your Gemini API key`,
status,
'UNAUTHORIZED',
false,
false
);
case 403:
return new GeminiError(
`Forbidden: You don't have access to ${modelDisplay}`,
status,
'FORBIDDEN',
false,
false
);
case 404:
return new GeminiError(
`Model not found: ${modelDisplay}`,
status,
'NOT_FOUND',
false,
false
);
case 429:
return new GeminiError(
`Rate limit exceeded for ${modelDisplay}. Please try again later.`,
status,
'RATE_LIMIT_EXCEEDED',
true,
true
);
case 500:
case 501:
case 502:
case 503:
return new GeminiError(
`${modelDisplay} server error: ${getErrorMessage(data)}. Please try again later.`,
status,
'SERVER_ERROR',
false,
true
);
default:
return new GeminiError(
`${modelDisplay} API error: ${getErrorMessage(data)}`,
status,
'API_ERROR',
false,
status >= 500 // Server errors are retriable
);
}
}
// Handle GeminiError that might be passed through
if (error instanceof GeminiError) {
return error;
}
// Handle general Error objects
if (error instanceof Error) {
return new GeminiError(
error.message,
undefined,
'UNKNOWN_ERROR',
error.message.includes('rate limit'),
error.message.includes('rate limit')
);
}
// Handle completely unknown errors
return new GeminiError(
`Unknown error occurred with ${modelDisplay}`,
undefined,
'UNKNOWN_ERROR',
false,
false
);
}
/**
* Formats an error response for the MCP protocol
* @param error The error to format
* @returns Formatted error response for MCP
*/
export function formatErrorResponse(error: unknown): any {
const geminiError = error instanceof GeminiError
? error
: handleApiError(error);
return {
content: [
{
type: 'text',
text: geminiError.message,
},
],
isError: true,
metadata: {
errorCode: geminiError.code,
status: geminiError.status,
isRetriable: geminiError.isRetriable
}
};
}
/**
* Converts an API error to an MCP error
* @param error The error to convert
* @returns An MCP error
*/
export function toMcpError(error: unknown): McpError {
const geminiError = error instanceof GeminiError
? error
: handleApiError(error);
// Map Gemini error codes to MCP error codes
let mcpErrorCode: ErrorCode;
switch (geminiError.code) {
case 'INVALID_REQUEST':
mcpErrorCode = ErrorCode.InvalidParams;
break;
case 'UNAUTHORIZED':
case 'FORBIDDEN':
mcpErrorCode = ErrorCode.InvalidParams;
break;
case 'NOT_FOUND':
mcpErrorCode = ErrorCode.MethodNotFound;
break;
case 'RATE_LIMIT_EXCEEDED':
mcpErrorCode = ErrorCode.InternalError;
break;
case 'SERVER_ERROR':
mcpErrorCode = ErrorCode.InternalError;
break;
default:
mcpErrorCode = ErrorCode.InternalError;
}
return new McpError(mcpErrorCode, geminiError.message);
}
/**
* Extracts error message from API response data
* @param data API response data
* @returns Extracted error message or default message
*/
function getErrorMessage(data: any): string {
if (data?.error?.message) {
return data.error.message;
}
if (data?.error) {
return JSON.stringify(data.error);
}
return 'Unknown error';
}