Skip to main content
Glama
discover_openapi.ts4.55 kB
/* Discover OpenAPI/Swagger docs and enumerate endpoints. Env: API_BASE, AUTH_*, OUTPUT (optional path), PROBE_DELAY_MS (default 800) */ import { loadLocalEnv } from './_load_env.js'; loadLocalEnv(); const BASE = process.env['API_BASE'] ?? ''; const AUTH_MODE = (process.env['AUTH_MODE'] ?? 'none').toLowerCase(); const AUTH_TOKEN = process.env['AUTH_TOKEN'] ?? ''; const AUTH_HEADER = process.env['AUTH_HEADER'] ?? 'Authorization'; const AUTH_QUERY_KEY = process.env['AUTH_QUERY_KEY'] ?? 'api_key'; const OUTPUT = process.env['OUTPUT'] ?? ''; const DELAY = Number(process.env['PROBE_DELAY_MS'] ?? '800'); if (!BASE) { console.error('Missing API_BASE'); process.exit(1); } function sleep(ms: number){ return new Promise(r=>setTimeout(r,ms)); } function parseRetryAfter(v: string | null): number | undefined { if (!v) return; const s = Number(v); if (Number.isFinite(s)) return s*1000; const ts = Date.parse(v); if (Number.isFinite(ts)) { const d=ts-Date.now(); return d>0?d:undefined; } } function joinUrl(base: string, path: string){ const b = base.endsWith('/')? base.slice(0,-1): base; const p = path.startsWith('/')? path: `/${path}`; return `${b}${p}`; } function applyAuth(u: string, headers: Record<string,string>): string { switch (AUTH_MODE) { case 'bearer': headers['Authorization'] = `Bearer ${AUTH_TOKEN}`; break; case 'header': headers[AUTH_HEADER] = AUTH_TOKEN; break; case 'basic': headers['Authorization'] = `Basic ${AUTH_TOKEN}`; break; case 'query': { try { const x = new URL(u); x.searchParams.set(AUTH_QUERY_KEY, AUTH_TOKEN); return x.toString(); } catch { return u + (u.includes('?')?'&':'?') + `${AUTH_QUERY_KEY}=${encodeURIComponent(AUTH_TOKEN)}`; } } default: break; } return u; } const candidates = [ '/openapi.json','/openapi.yaml','/openapi.yml', '/v3/api-docs','/v3/api-docs.yaml','/v3/openapi.json', '/swagger.json','/swagger/v1/swagger.json', '/.well-known/openapi.json','/api-docs','/docs/openapi.json' ]; async function fetchText(u: string): Promise<{ status: number; text?: string; ct?: string; retryAfter?: number }>{ const headers: Record<string,string> = { 'Accept': 'application/json, application/yaml, text/yaml, text/plain' }; const url = applyAuth(u, headers); const res = await fetch(url, { method: 'GET', headers } as any); const retryAfter = parseRetryAfter(res.headers.get('retry-after')); if (!res.ok) return { status: res.status, retryAfter }; const ct = res.headers.get('content-type') || ''; const text = await res.text(); return { status: res.status, text, ct, retryAfter }; } function parseSpec(text: string, ct?: string): any | undefined { try { return JSON.parse(text); } catch {} // naive YAML support if (ct && /yaml|yml/.test(ct)) return undefined; return undefined; } function listPaths(spec: any): { path: string; methods: string[] }[] { const out: { path: string; methods: string[] }[] = []; if (!spec || !spec.paths || typeof spec.paths !== 'object') return out; for (const [p, methods] of Object.entries<any>(spec.paths)) { const ms = Object.keys(methods).map(s=>s.toUpperCase()); out.push({ path: String(p), methods: ms }); } return out; } async function main(){ let nextReady = Date.now(); const tried: any[] = []; for (const c of candidates){ const now = Date.now(); if (now<nextReady) await sleep(nextReady-now); const u = joinUrl(BASE, c); const r = await fetchText(u); tried.push({ candidate: c, status: r.status }); if (r.status === 429 && typeof r.retryAfter === 'number') { nextReady = Date.now()+r.retryAfter; continue; } nextReady = Date.now()+DELAY; if (r.status >= 200 && r.status < 300 && r.text){ const spec = parseSpec(r.text, r.ct); if (spec){ const endpoints = listPaths(spec); const report = { base: BASE, discovered: c, endpoints, rawSpec: spec, timestamp: new Date().toISOString() }; if (OUTPUT){ const fs = await import('node:fs'); const path = await import('node:path'); const p = path.resolve(process.cwd(), OUTPUT); // @ts-ignore fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, JSON.stringify(report, null, 2)); console.log(p); } else { console.log(JSON.stringify(report, null, 2)); } return; } } } console.log(JSON.stringify({ base: BASE, discovered: null, tried, timestamp: new Date().toISOString() }, null, 2)); } main().catch((e)=>{ console.error(e); process.exit(1); });

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/ghively/API2MCP-creator'

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