Skip to main content
Glama

atlas-mcp-server

#!/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(url, { responseType: "text", validateStatus: (status) => status >= 200 && status < 300, }); const contentType = response.headers["content-type"] || 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..."); return yaml.load(data) as object; } else if (lowerContentType?.includes("json")) { console.log("Parsing content as JSON based on Content-Type..."); return JSON.parse(data); } else { console.log( "Content-Type is ambiguous or missing. Attempting to parse as YAML first...", ); try { const parsedYaml = yaml.load(data) as object; // 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); 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); 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> { 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: any) { if (error.code === "ENOENT") { console.log(`Output directory not found. Creating: ${outputDirAbsolute}`); await fs.mkdir(outputDirAbsolute, { recursive: true }); } else { console.error( `Error accessing output directory ${outputDirAbsolute}: ${error.message}. 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."); } 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/atlas-mcp-server'

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