Skip to main content
Glama

Git MCP Server

fetch-openapi-spec.ts9.3 kB
#!/usr/bin/env node /** * @fileoverview Fetches an OpenAPI specification (YAML/JSON) from a URL, * parses it, and saves it locally in both YAML and JSON formats. * @module scripts/fetch-openapi-spec * Includes fallback logic for common OpenAPI file names (openapi.yaml, openapi.json). * Ensures output paths are within the project directory for security. * * @example * // Fetch spec and save to docs/api/my_api.yaml and docs/api/my_api.json * // ts-node --esm scripts/fetch-openapi-spec.ts https://api.example.com/v1 docs/api/my_api * * @example * // Fetch spec from a direct file URL * // ts-node --esm scripts/fetch-openapi-spec.ts https://petstore3.swagger.io/api/v3/openapi.json docs/api/petstore_v3 */ import axios, { AxiosError } from 'axios'; import fs from 'fs/promises'; import yaml from 'js-yaml'; import path from 'path'; const projectRoot = process.cwd(); const args = process.argv.slice(2); const helpFlag = args.includes('--help'); const urlArg = args[0]; const outputBaseArg = args[1]; if (helpFlag || !urlArg || !outputBaseArg) { console.log(` Fetch OpenAPI Specification Script Usage: ts-node --esm scripts/fetch-openapi-spec.ts <url> <output-base-path> [--help] Arguments: <url> Base URL or direct URL to the OpenAPI spec (YAML/JSON). <output-base-path> Base path for output files (relative to project root), e.g., 'docs/api/my_api'. Will generate .yaml and .json. --help Show this help message. Example: ts-node --esm scripts/fetch-openapi-spec.ts https://petstore3.swagger.io/api/v3 docs/api/petstore_v3 `); process.exit(helpFlag ? 0 : 1); } const outputBasePathAbsolute = path.resolve(projectRoot, outputBaseArg); const yamlOutputPath = `${outputBasePathAbsolute}.yaml`; const jsonOutputPath = `${outputBasePathAbsolute}.json`; const outputDirAbsolute = path.dirname(outputBasePathAbsolute); // Security Check: Ensure output paths are within project root if ( !outputDirAbsolute.startsWith(projectRoot + path.sep) || !yamlOutputPath.startsWith(projectRoot + path.sep) || !jsonOutputPath.startsWith(projectRoot + path.sep) ) { console.error( `Error: Output path "${outputBaseArg}" resolves outside the project directory. Aborting.`, ); process.exit(1); } /** * Attempts to fetch content from a given URL. * @param url - The URL to fetch data from. * @returns A promise resolving to an object with data and content type, or null if fetch fails. */ async function tryFetch( url: string, ): Promise<{ data: string; contentType: string | null } | null> { try { console.log(`Attempting to fetch from: ${url}`); const response = await axios.get<string>(url, { responseType: 'text', validateStatus: (status) => status >= 200 && status < 300, }); const rawContentType = (response.headers as Record<string, unknown>)[ 'content-type' ]; const contentType = typeof rawContentType === 'string' ? rawContentType : null; console.log( `Successfully fetched (Status: ${response.status}, Content-Type: ${contentType || 'N/A'})`, ); return { data: response.data, contentType }; } catch (error) { let status = 'Unknown'; if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; status = axiosError.response ? String(axiosError.response.status) : 'Network Error'; } console.warn(`Failed to fetch from ${url} (Status: ${status})`); return null; } } /** * Parses fetched data as YAML or JSON, attempting to infer from content type or by trying both. * @param data - The raw string data fetched from the URL. * @param contentType - The content type header from the HTTP response, if available. * @returns The parsed OpenAPI specification as an object, or null if parsing fails. */ function parseSpec(data: string, contentType: string | null): object | null { try { const lowerContentType = contentType?.toLowerCase(); if ( lowerContentType?.includes('yaml') || lowerContentType?.includes('yml') ) { console.log('Parsing content as YAML based on Content-Type...'); const parsed = yaml.load(data); if (parsed && typeof parsed === 'object') return parsed; return null; } else if (lowerContentType?.includes('json')) { console.log('Parsing content as JSON based on Content-Type...'); const parsed = JSON.parse(data) as unknown; if (parsed && typeof parsed === 'object') return parsed; return null; } else { console.log( 'Content-Type is ambiguous or missing. Attempting to parse as YAML first...', ); try { const parsedYaml = yaml.load(data); // Basic validation: check if it's a non-null object. if (parsedYaml && typeof parsedYaml === 'object') { console.log('Successfully parsed as YAML.'); return parsedYaml; } } catch (_yamlError) { console.log('YAML parsing failed. Attempting to parse as JSON...'); try { const parsedJson = JSON.parse(data) as unknown; if (parsedJson && typeof parsedJson === 'object') { console.log('Successfully parsed as JSON.'); return parsedJson; } } catch (_jsonError) { console.warn( 'Could not parse content as YAML or JSON after attempting both.', ); return null; } } // If YAML parsing resulted in a non-object (e.g. string, number) but didn't throw console.warn( 'Content parsed as YAML but was not a valid object structure. Trying JSON.', ); try { const parsedJson = JSON.parse(data) as unknown; if (parsedJson && typeof parsedJson === 'object') { console.log( 'Successfully parsed as JSON on second attempt for non-object YAML.', ); return parsedJson; } } catch (_jsonError) { console.warn( 'Could not parse content as YAML or JSON after attempting both.', ); return null; } } } catch (parseError) { console.error( `Error parsing specification: ${parseError instanceof Error ? parseError.message : String(parseError)}`, ); } return null; } /** * Main orchestrator function. Fetches the OpenAPI spec from the provided URL (with fallbacks), * parses it, and saves it to the specified output paths in both YAML and JSON formats. */ async function fetchAndProcessSpec(): Promise<void> { if (!urlArg) { console.error('URL argument is missing.'); process.exit(1); } let fetchedResult: { data: string; contentType: string | null } | null = null; const potentialUrls: string[] = [urlArg]; if ( !urlArg.endsWith('.yaml') && !urlArg.endsWith('.yml') && !urlArg.endsWith('.json') ) { const urlWithoutTrailingSlash = urlArg.endsWith('/') ? urlArg.slice(0, -1) : urlArg; potentialUrls.push(`${urlWithoutTrailingSlash}/openapi.yaml`); potentialUrls.push(`${urlWithoutTrailingSlash}/openapi.json`); } for (const url of potentialUrls) { fetchedResult = await tryFetch(url); if (fetchedResult) break; } if (!fetchedResult) { console.error( `Error: Failed to fetch specification from all attempted URLs: ${potentialUrls.join(', ')}. Aborting.`, ); process.exit(1); } const openapiSpec = parseSpec(fetchedResult.data, fetchedResult.contentType); if (!openapiSpec || typeof openapiSpec !== 'object') { console.error( 'Error: Failed to parse specification content or content is not a valid object. Aborting.', ); process.exit(1); } try { await fs.access(outputDirAbsolute); } catch (error: unknown) { if ( error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT' ) { console.log(`Output directory not found. Creating: ${outputDirAbsolute}`); await fs.mkdir(outputDirAbsolute, { recursive: true }); } else { const errorMessage = error instanceof Error ? error.message : String(error); console.error( `Error accessing output directory ${outputDirAbsolute}: ${errorMessage}. Aborting.`, ); process.exit(1); } } try { console.log(`Saving YAML specification to: ${yamlOutputPath}`); await fs.writeFile(yamlOutputPath, yaml.dump(openapiSpec), 'utf8'); console.log(`Successfully saved YAML specification.`); } catch (error) { console.error( `Error saving YAML to ${yamlOutputPath}: ${error instanceof Error ? error.message : String(error)}. Aborting.`, ); process.exit(1); } try { console.log(`Saving JSON specification to: ${jsonOutputPath}`); await fs.writeFile( jsonOutputPath, JSON.stringify(openapiSpec, null, 2), 'utf8', ); console.log(`Successfully saved JSON specification.`); } catch (error) { console.error( `Error saving JSON to ${jsonOutputPath}: ${error instanceof Error ? error.message : String(error)}. Aborting.`, ); process.exit(1); } console.log('OpenAPI specification processed and saved successfully.'); } // Intentionally not awaiting; internal try/catch handles errors. void fetchAndProcessSpec();

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

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