Skip to main content
Glama
fetch-with-retry.ts3.2 kB
import { exec } from "child_process"; import { promisify } from "util"; import { Logger } from "./logger.js"; const execAsync = promisify(exec); type RequestOptions = RequestInit & { /** * Force format of headers to be a record of strings, e.g. { "Authorization": "Bearer 123" } * * Avoids complexity of needing to deal with `instanceof Headers`, which is not supported in some environments. */ headers?: Record<string, string>; }; export async function fetchWithRetry<T>(url: string, options: RequestOptions = {}): Promise<T> { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`Fetch failed with status ${response.status}: ${response.statusText}`); } return (await response.json()) as T; } catch (fetchError: any) { Logger.log( `[fetchWithRetry] Initial fetch failed for ${url}: ${fetchError.message}. Likely a corporate proxy or SSL issue. Attempting curl fallback.`, ); const curlHeaders = formatHeadersForCurl(options.headers); // Most options here are to ensure stderr only contains errors, so we can use it to confidently check if an error occurred. // -s: Silent mode—no progress bar in stderr // -S: Show errors in stderr // --fail-with-body: curl errors with code 22, and outputs body of failed request, e.g. "Fetch failed with status 404" // -L: Follow redirects const curlCommand = `curl -s -S --fail-with-body -L ${curlHeaders.join(" ")} "${url}"`; try { // Fallback to curl for corporate networks that have proxies that sometimes block fetch Logger.log(`[fetchWithRetry] Executing curl command: ${curlCommand}`); const { stdout, stderr } = await execAsync(curlCommand); if (stderr) { // curl often outputs progress to stderr, so only treat as error if stdout is empty // or if stderr contains typical error keywords. if ( !stdout || stderr.toLowerCase().includes("error") || stderr.toLowerCase().includes("fail") ) { throw new Error(`Curl command failed with stderr: ${stderr}`); } Logger.log( `[fetchWithRetry] Curl command for ${url} produced stderr (but might be informational): ${stderr}`, ); } if (!stdout) { throw new Error("Curl command returned empty stdout."); } return JSON.parse(stdout) as T; } catch (curlError: any) { Logger.error(`[fetchWithRetry] Curl fallback also failed for ${url}: ${curlError.message}`); // Re-throw the original fetch error to give context about the initial failure // or throw a new error that wraps both, depending on desired error reporting. // For now, re-throwing the original as per the user example's spirit. throw fetchError; } } } /** * Converts HeadersInit to an array of curl header arguments. * @param headers Headers to convert. * @returns Array of strings, each a curl -H argument. */ function formatHeadersForCurl(headers: Record<string, string> | undefined): string[] { if (!headers) { return []; } return Object.entries(headers).map(([key, value]) => `-H "${key}: ${value}"`); }

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/nhn-commerce-fe/figma-mcp-ncds'

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