@aashari/boilerplate-mcp-server
Version:
TypeScript Model Context Protocol (MCP) server boilerplate providing IP lookup tools/resources. Includes CLI support and extensible structure for connecting AI systems (LLMs) to external data sources like ip-api.com. Ideal template for creating new MCP in
86 lines (85 loc) • 4.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const zod_1 = require("zod");
const logger_util_js_1 = require("../utils/logger.util.js");
const vendor_ip_api_com_types_js_1 = require("./vendor.ip-api.com.types.js");
const error_util_js_1 = require("../utils/error.util.js");
const transport_util_js_1 = require("../utils/transport.util.js");
// Create a contextualized logger for this file
const serviceLogger = logger_util_js_1.Logger.forContext('services/vendor.ip-api.com.service.ts');
// Log service initialization
serviceLogger.debug('IP API service initialized');
/**
* @namespace VendorIpApiService
* @description Service layer for interacting directly with the ip-api.com vendor API.
* Responsible for constructing API requests based on provided parameters
* and handling the raw response from the `fetchIpApi` utility.
*/
/**
* @function get
* @description Fetches details for a specific IP address or the current device's IP from ip-api.com.
* It uses the `fetchIpApi` utility and handles the specific success/failure status returned by ip-api.com.
* @memberof VendorIpApiService
* @param {string} [ipAddress] - Optional IP address to look up. If omitted, fetches details for the current device's public IP.
* @param {IPApiRequestOptions} [options={}] - Optional request options for the ip-api.com service, such as `useHttps`, `fields`, and `lang`.
* @returns {Promise<IPDetail>} A promise that resolves to the detailed IP information if the API call is successful.
* @throws {McpError} Throws an `McpError` (specifically `ApiError` or `UnexpectedError`) if:
* - The `fetchIpApi` call fails (network error, non-2xx response).
* - The ip-api.com response status is not 'success'.
* - An unexpected error occurs during processing.
* @example
* // Get basic details for 8.8.8.8
* const details = await get('8.8.8.8');
* // Get extended details using HTTPS
* const extendedDetails = await get('1.1.1.1', { useHttps: true, fields: [...] });
*/
async function get(ipAddress, options = {}) {
const methodLogger = logger_util_js_1.Logger.forContext('services/vendor.ip-api.com.service.ts', 'get');
methodLogger.debug(`Calling IP API for IP: ${ipAddress || 'current'}`);
try {
// Make the API call with correctly typed response
// Use a more specific type here since we know the API returns at least status + potential message
const rawData = await (0, transport_util_js_1.fetchIpApi)(ipAddress || '', {
useHttps: options.useHttps,
fields: options.fields,
lang: options.lang,
});
// First check API-level success/failure before Zod validation
// This avoids unnecessary validation errors for known API errors
if (rawData.status !== 'success') {
// Handle specific ip-api.com error responses
if (rawData.message) {
if (rawData.message.includes('private range')) {
throw (0, error_util_js_1.createApiError)(`Private IP addresses are not supported: ${rawData.message}`, 400, rawData);
}
else if (rawData.message.includes('reserved range')) {
throw (0, error_util_js_1.createApiError)(`Reserved IP addresses are not supported: ${rawData.message}`, 400, rawData);
}
}
throw (0, error_util_js_1.createApiError)(`IP API error: ${rawData.message || 'Unknown error'}`, 400, // Use 400 for client errors from ip-api.com
rawData);
}
// Now parse with Zod for successful responses
// Validate with Zod schema and return
const validatedData = vendor_ip_api_com_types_js_1.IPDetailSchema.parse(rawData);
methodLogger.debug(`Received and validated successful data from IP API`);
return validatedData;
}
catch (error) {
methodLogger.error(`Service error fetching IP data`, error);
// Handle Zod validation errors
if (error instanceof zod_1.z.ZodError) {
throw (0, error_util_js_1.createApiError)(`API response validation failed: ${error.errors
.map((e) => `${e.path.join('.')}: ${e.message}`)
.join(', ')}`, 500, // Use 500 for validation errors as it's a server-side issue
error);
}
// Rethrow other McpErrors
if (error instanceof error_util_js_1.McpError) {
throw error;
}
// Wrap any other unexpected errors
throw (0, error_util_js_1.createUnexpectedError)('Unexpected service error while fetching IP data', error);
}
}
exports.default = { get };