Skip to main content
Glama
pubchemApiClient.ts4.88 kB
/** * @fileoverview A singleton service to interact with the PubChem PUG REST API. * This client handles URL construction, rate limiting, request execution, * and error handling for all PubChem API interactions. * @module src/services/pubchem/pubchemApiClient */ import { RateLimiter } from "limiter"; import { BaseErrorCode, McpError } from "../../types-global/errors.js"; import { fetchWithTimeout, logger, type RequestContext, } from "../../utils/index.js"; const PUBCHEM_API_BASE_URL = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"; const PUBCHEM_API_TIMEOUT_MS = 30000; // 30 seconds /** * A singleton class to manage all interactions with the PubChem PUG REST API. */ class PubChemApiClient { private static instance: PubChemApiClient; private readonly limiter: RateLimiter; /** * Private constructor to enforce the singleton pattern. * Initializes a rate limiter compliant with PubChem's policy (5 requests/sec). */ private constructor() { this.limiter = new RateLimiter({ tokensPerInterval: 5, interval: "second", }); logger.info( "PubChemApiClient initialized with a rate limit of 5 requests/second.", ); } /** * Retrieves the singleton instance of the PubChemApiClient. * @returns The singleton instance. */ public static getInstance(): PubChemApiClient { if (!PubChemApiClient.instance) { PubChemApiClient.instance = new PubChemApiClient(); } return PubChemApiClient.instance; } /** * Executes a request to the PubChem API for a JSON response, respecting the rate limit. * @param path - The API endpoint path (e.g., '/compound/cid/2244/property/MolecularFormula/JSON'). * @param context - The request context for tracing and logging. * @returns A promise that resolves with the JSON response from the API. * @throws {McpError} If the request fails, times out, or the API returns an error. */ public async get(path: string, context: RequestContext): Promise<any> { const response = await this.executeRequest(path, context); // Handle cases where the response is OK but there's no content if (response.status === 204) { return null; } return response.json(); } /** * Executes a request to the PubChem API for a binary blob response, respecting the rate limit. * @param path - The API endpoint path (e.g., '/compound/cid/2244/PNG'). * @param context - The request context for tracing and logging. * @returns A promise that resolves with an ArrayBuffer of the response body. * @throws {McpError} If the request fails or times out. */ public async getBlob( path: string, context: RequestContext, ): Promise<ArrayBuffer> { const response = await this.executeRequest(path, context); return response.arrayBuffer(); } /** * Private helper to execute a generic fetch request, handling rate limiting and basic error checking. * @param path - The API endpoint path. * @param context - The request context. * @returns A promise that resolves with the raw Response object. */ private async executeRequest( path: string, context: RequestContext, ): Promise<Response> { await this.limiter.removeTokens(1); const url = path.startsWith("http") ? path : `${PUBCHEM_API_BASE_URL}${path}`; logger.debug(`Executing PubChem API GET request`, { ...context, url }); const response = await fetchWithTimeout( url, PUBCHEM_API_TIMEOUT_MS, context, ); if (!response.ok) { const errorText = await response.text(); const pubChemStatus = response.headers.get("pubchem-pug-status-message") || "No status message"; logger.error("PubChem API request failed", { ...context, url, status: response.status, statusText: response.statusText, pubChemStatus, responseBody: errorText, }); // Map HTTP status codes to McpError codes let errorCode: BaseErrorCode; switch (response.status) { case 400: errorCode = BaseErrorCode.INVALID_INPUT; break; case 404: errorCode = BaseErrorCode.NOT_FOUND; break; case 405: errorCode = BaseErrorCode.METHOD_NOT_ALLOWED; break; case 503: errorCode = BaseErrorCode.SERVICE_UNAVAILABLE; break; case 504: errorCode = BaseErrorCode.GATEWAY_TIMEOUT; break; default: errorCode = BaseErrorCode.EXTERNAL_SERVICE_ERROR; } throw new McpError(errorCode, `PubChem API Error: ${pubChemStatus}`, { httpStatusCode: response.status, details: errorText, }); } return response; } } /** * Singleton instance of the PubChem API client. */ export const pubChemApiClient = PubChemApiClient.getInstance();

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/cyanheads/pubchem-mcp-server'

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