/** * MCP resources: `hfmarketdata://status` (dataset inventory) and `hfmarketdata://limits` * (the three access levels — all free — + the caller's current headroom when the server exposes /v1/limits). */ import { HfmdClient, HfmdError } from './client.js'; /** The platform is entirely free; these are rate limits, not a price list. */ export const LIMITS = [ { access: 'keyless (per IP, low hourly limits)', window: '1 hour', requests: 30, rows: 100_000, max_rows_per_request: 5_000 }, { access: 'free API key (free account)', window: '1 minute', requests: 120, rows: 1_000_000, max_rows_per_request: 50_000 }, { access: 'higher limits (granted on request by e-mail to contact@spboucher.ai, also free)', window: '1 minute', requests: 600, rows: 10_000_000, max_rows_per_request: 200_000 }, ]; export interface ResourceDef { name: string; uri: string; title: string; description: string; mimeType: string; read: (client: HfmdClient) => Promise; } export const statusResource: ResourceDef = { name: 'status', uri: 'hfmarketdata://status', title: 'HF Market Data — dataset status', description: 'Live inventory of the data lake: number of symbols per asset class / timeframe / adjustment, futures roots, options quarters, last update.', mimeType: 'application/json', async read(client) { const r = await client.get('/v1/status'); return JSON.stringify({ base_url: client.baseUrl, fetched_ms: r.ms, ...(r.body as Record) }, null, 1); }, }; export const limitsResource: ResourceDef = { name: 'limits', uri: 'hfmarketdata://limits', title: 'HF Market Data — rate limits', description: 'The platform is free. Three access levels (keyless / free API key / higher limits on request) with their request and row quotas, whether this server runs with an API key, and the live headroom when /v1/limits is available.', mimeType: 'application/json', async read(client) { const out: Record = { mode: client.keyless ? 'keyless (low hourly limits — set HFMD_API_KEY, a free API key gives 120 req/min)' : 'api key configured', everything_is_free: true, limits: LIMITS, notes: [ 'Parquet responses count half the rows against the quota; bulk downloads are quota-exempt; 304 responses do not count.', 'Every response carries X-RateLimit-Limit-Requests / -Remaining-Requests / -Limit-Rows / -Remaining-Rows / -Reset; 429 adds Retry-After.', 'Create a free account at https://www.hfmarketdata.io/signup — keys look like hfmd_live_…. Higher limits: e-mail contact@spboucher.ai (also free). Details: https://www.hfmarketdata.io/limits', ], }; try { const r = await client.get('/v1/limits'); out.live = r.body; out.rate_headers = r.rate; } catch (e) { out.live = e instanceof HfmdError ? `unavailable (${e.status} ${e.code})` : 'unavailable'; } return JSON.stringify(out, null, 1); }, }; export const RESOURCES: ResourceDef[] = [statusResource, limitsResource];