UNPKG

whodis-mcp-server

Version:

Whodis MCP Server for checking the availability of domain names using WHOIS lookups.

164 lines (163 loc) 8.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getIpApiCredentials = getIpApiCredentials; exports.fetchIpApi = fetchIpApi; exports.fetchApi = fetchApi; const logger_util_js_1 = require("./logger.util.js"); const config_util_js_1 = require("./config.util.js"); const error_util_js_1 = require("./error.util.js"); // Create a contextualized logger for this file const transportLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts'); // Log transport utility initialization transportLogger.debug('Transport utility initialized'); /** * Retrieves IP API credentials from configuration. * Specifically checks for IPAPI_API_TOKEN. * @returns IpApiCredentials object containing the API token if found. */ function getIpApiCredentials() { const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'getIpApiCredentials'); const apiToken = config_util_js_1.config.get('IPAPI_API_TOKEN'); if (!apiToken) { methodLogger.debug('No IP API token found (IPAPI_API_TOKEN). Using free tier.'); return {}; // Return empty object if no token } else { methodLogger.debug('Using IP API token from configuration.'); return { apiToken }; } } /** * Fetches data specifically from the ip-api.com endpoint. * Handles URL construction, authentication (if token provided), and query parameters. * Relies on the generic fetchApi function for the actual HTTP request. * * @param path The specific IP address or path component (e.g., "8.8.8.8"). Empty string for current IP. * @param options Additional options like HTTP method, headers, body, and ip-api specific params. * @param options.useHttps - Use HTTPS (requires paid plan for ip-api.com). Defaults to false. * @param options.fields - Specific fields to request from ip-api.com. * @param options.lang - Language code for response data. * @returns The response data parsed as type T. * @throws {McpError} If the request fails, including network errors, API errors, or parsing issues. */ async function fetchIpApi(path, options = {}) { const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'fetchIpApi'); // Get credentials (token might be undefined) const credentials = getIpApiCredentials(); // Determine protocol based on options const protocol = options.useHttps ? 'https' : 'http'; const baseUrl = `${protocol}://ip-api.com/json`; // Format path for URL const normalizedPath = path ? `/${path}` : ''; let url = `${baseUrl}${normalizedPath}`; // Build query parameters const queryParams = new URLSearchParams(); // Add API token if present if (credentials.apiToken) { queryParams.set('key', credentials.apiToken); methodLogger.debug('API token added to query parameters.'); } // Add fields parameter if (options.fields?.length) { queryParams.set('fields', options.fields.join(',')); methodLogger.debug(`Requesting fields: ${options.fields.join(',')}`); } // Add language parameter if (options.lang) { queryParams.set('lang', options.lang); methodLogger.debug(`Requesting language: ${options.lang}`); } // Append query string if needed const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; } methodLogger.debug(`Constructed URL: ${url}`); // Delegate the actual fetch call to the generic fetchApi return fetchApi(url, { method: options.method, headers: options.headers, body: options.body, }); } /** * Generic and reusable function to fetch data from any API endpoint. * Handles standard HTTP request setup, response checking, basic error handling, and logging. * * @param url The full URL to fetch data from. * @param options Request options including method, headers, and body. * @returns The response data parsed as type T. * @throws {McpError} If the request fails, including network errors, non-OK HTTP status, or JSON parsing issues. */ async function fetchApi(url, options = {}) { const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'fetchApi'); // Prepare standard request options const requestOptions = { method: options.method || 'GET', headers: { // Standard headers, allow overrides via options.headers 'Content-Type': 'application/json', Accept: 'application/json', ...options.headers, }, body: options.body ? JSON.stringify(options.body) : undefined, }; methodLogger.debug(`Executing API call: ${requestOptions.method} ${url}`); const startTime = performance.now(); // Track performance try { const response = await fetch(url, requestOptions); const endTime = performance.now(); const duration = (endTime - startTime).toFixed(2); methodLogger.debug(`API call completed in ${duration}ms with status: ${response.status} ${response.statusText}`, { url, status: response.status }); // Check if the response status is OK (2xx) if (!response.ok) { const errorText = await response.text(); // Get error body for context methodLogger.error(`API error response (${response.status}):`, errorText); // Classify standard HTTP errors if (response.status === 401) { // Use createAuthInvalidError for consistency, even if ip-api uses keys throw (0, error_util_js_1.createAuthInvalidError)('Authentication failed. Check API token if required.'); } else if (response.status === 403) { // Use createAuthInvalidError or a more specific permission error if needed throw (0, error_util_js_1.createAuthInvalidError)('Permission denied for the requested resource.'); } else if (response.status === 404) { throw (0, error_util_js_1.createApiError)('Resource not found at the specified URL.', response.status, errorText); } else { // Generic API error for other non-2xx statuses throw (0, error_util_js_1.createApiError)(`API request failed with status ${response.status}: ${response.statusText}`, response.status, errorText); } } // Attempt to parse the response body as JSON try { const responseData = await response.json(); methodLogger.debug('Response body successfully parsed as JSON.'); // methodLogger.debug('Response Data:', responseData); // Uncomment for full response logging return responseData; } catch (parseError) { methodLogger.error('Failed to parse API response JSON:', parseError); // Throw a specific error for JSON parsing failure throw (0, error_util_js_1.createApiError)(`Failed to parse API response JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`, response.status, // Include original status for context parseError); } } catch (error) { const endTime = performance.now(); const duration = (endTime - startTime).toFixed(2); methodLogger.error(`API call failed after ${duration}ms for ${url}:`, error); // Rethrow if it's already an McpError (e.g., from status checks or parsing) if (error instanceof error_util_js_1.McpError) { throw error; } // Handle potential network errors (TypeError in fetch) if (error instanceof TypeError) { throw (0, error_util_js_1.createApiError)(`Network error during API call: ${error.message}`, undefined, // No specific HTTP status for network errors error); } // Wrap any other unexpected errors throw (0, error_util_js_1.createUnexpectedError)(`Unexpected error during API call: ${error instanceof Error ? error.message : String(error)}`, error); } }