Skip to main content
Glama
validate_endpoints.ts3.87 kB
/* Validate endpoints via GET/OPTIONS with pacing. Env: API_BASE, AUTH_*. */ import { loadLocalEnv } from './_load_env.js'; loadLocalEnv(); type Method = 'GET'|'POST'|'PUT'|'DELETE'|'OPTIONS'|'HEAD'; 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 BASE_DELAY = Number(process.env['PROBE_DELAY_MS'] ?? '1000'); if (!BASE) { console.error('Missing API_BASE'); process.exit(1); } function url(p: string): string { const b = BASE.endsWith('/') ? BASE.slice(0,-1) : BASE; const path = p.startsWith('/') ? p : `/${p}`; const u = `${b}${path}`; return applyAuth(u); } function applyAuth(u: string): string { if (AUTH_MODE === 'bearer') return addHeader(u); if (AUTH_MODE === 'header') return addHeader(u); if (AUTH_MODE === 'basic') return addHeader(u); if (AUTH_MODE === 'query') return addQuery(u); return u; } function addHeader(u: string): string { return u; } function addQuery(u: string): string { 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)}`; } } function baseHeaders(): Record<string,string> { const h: Record<string,string> = { 'Content-Type': 'application/json' }; if (AUTH_MODE === 'bearer') h['Authorization'] = `Bearer ${AUTH_TOKEN}`; if (AUTH_MODE === 'header') h[AUTH_HEADER] = AUTH_TOKEN; if (AUTH_MODE === 'basic') h['Authorization'] = `Basic ${AUTH_TOKEN}`; return h; } 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 sleep(ms: number): Promise<void> { return new Promise(r=>setTimeout(r,ms)); } const headers = baseHeaders(); const endpoints = [ { path: '/', methods: ['GET'] as Method[] }, { path: '/status', methods: ['GET'] as Method[] }, { path: '/health', methods: ['GET'] as Method[] } ]; async function probe(method: Method, p: string): Promise<{ status: number; allow?: string|null; retryAfter?: number }>{ const u = url(p); const res = await fetch(u, { method, headers } as any); return { status: res.status, allow: res.headers.get('allow'), retryAfter: parseRetryAfter(res.headers.get('retry-after')) }; } async function main(){ const results: any[] = []; let nextReady = Date.now(); let delay = BASE_DELAY; const MAX_DELAY = 15000; const MIN_DELAY = Math.max(300, BASE_DELAY); for (const e of endpoints){ const r: any = { path: e.path, checks: [] }; for (const m of e.methods){ const methodToUse: Method = m === 'GET' ? 'GET' : (m==='POST'||m==='PUT'||m==='DELETE') ? 'OPTIONS' : m; const now = Date.now(); if (now<nextReady) await sleep(nextReady-now); try { const { status, allow, retryAfter } = await probe(methodToUse, e.path); if (status===429) { if (typeof retryAfter==='number') { delay = Math.min(MAX_DELAY, retryAfter); } else { delay = Math.min(MAX_DELAY, Math.floor(delay * 1.8)); } nextReady = Date.now()+delay; } else { delay = Math.max(MIN_DELAY, Math.floor(delay * 0.9)); nextReady = Date.now()+delay; } r.checks.push({ method: m, probedWith: methodToUse, status, allow }); } catch (err) { r.checks.push({ method: m, error: String(err) }); } } results.push(r); } console.log(JSON.stringify({ base: BASE, results, 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