// ToolPick client used by the stdio MCP server (and the npm package once publishing resumes). Free tier: the hosted
// preview (match count + top 3 service names with price and payers; 10/min per IP). URLs, live checks, best pick and
// market reports: paid API over x402 (see paid.js).
import { withDeadline } from './deadline.js';
export const API_BASE = 'https://api.youraitools.co/toolpick';
export const FALLBACK_API_BASE = 'https://toolpick.agentgate.workers.dev';
export const PAID_URL = `${API_BASE}/v1/pick`;
export const PRICES = { lite: '$0.003', pick: '$0.01', market: '$0.02' };
export const DISCLAIMER = 'Rankings use public x402 index data and index-reported 30-day usage; listings are self-described by sellers and are not endorsements. Check terms before paying.';
export const FULL_DETAILS = `URLs and usage for the top 5: ${API_BASE}/v1/lite?need=<task> (${PRICES.lite}); top 10 with live 402 checks and a best verified pick: /v1/pick (${PRICES.pick}); market report: /v1/market (${PRICES.market}). USDC over x402 or a prepaid credits key.`;
const NETS = ['any', 'base', 'solana', 'polygon', 'arbitrum', 'optimism', 'ethereum', 'avalanche'];
/** Validate arguments locally (no network). Returns { need, max_price?, network? } or { error }. */
export function toQuery(args = {}, { needRequired = true } = {}) {
const n = args.need ?? args.query ?? args.task;
const out = {};
if (n === undefined || n === '') { if (needRequired) return { error: '"need" is required: the capability you need, e.g. "web search"' }; }
else if (typeof n !== 'string' || n.trim().length < 2 || n.length > 200) return { error: '"need" must be a string of 2-200 characters' };
else out.need = n.trim();
if (args.max_price !== undefined && args.max_price !== '') { const p = Number(args.max_price); if (!Number.isFinite(p) || p < 0 || p > 100) return { error: '"max_price" must be a USD number from 0 to 100' }; out.max_price = p; }
if (args.network !== undefined && args.network !== '') { const v = String(args.network).toLowerCase(); if (!NETS.includes(v)) return { error: `"network" must be one of ${NETS.join(', ')}` }; out.network = v; }
return out;
}
/** Free hosted preview. Returns the preview JSON or { error, message }. */
export async function preview(q, { fetchImpl = globalThis.fetch, apiBase = API_BASE, ms = 30000 } = {}) {
try {
const res = await withDeadline(fetchImpl(`${apiBase.replace(/\/+$/, '')}/v1/preview?${new URLSearchParams(q)}`, { headers: { accept: 'application/json', 'user-agent': 'toolpick-mcp' } }), ms, 'ToolPick preview');
const j = await res.json().catch(() => null);
if (!res.ok) return { error: j?.error || `http_${res.status}`, message: j?.message || `ToolPick preview answered HTTP ${res.status}` };
return j;
} catch (e) { return { error: 'network', message: String(e?.message || e).slice(0, 200) }; }
}
agentgate-source: code mirror on Cloudflare Pages. All repos