strale_search
Search Strale's API capabilities for KYC compliance, data validation, company registries, domain intelligence, Web3 data, and other business functions. Find specific tools by name, description, or category.
Instructions
Search Strale's 250+ API capabilities and bundled solutions. Covers: KYC & compliance (company verification, VAT validation, sanctions screening), data validation (IBAN, email, phone numbers), company registries (Nordic countries, US SEC EDGAR), domain & website intelligence (WHOIS, DNS, SSL certificates, trust scores, security headers, PageSpeed, tech stack detection), Web3 (live crypto prices, gas fees), and more (translation, invoice extraction, lead enrichment, AI Act assessment). Most under €0.10. Free to search.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search keyword (matched against name, description, slug) | |
| category | No | Filter by category: compliance, validation, data-extraction, developer-tools, web3, security, domain-intel, recruiting, sales, legal, text | |
| offset | No | Number of results to skip (for pagination). Default: 0 |
Implementation Reference
- packages/mcp-server/src/tools.ts:558-782 (handler)Implementation of the 'strale_search' tool, which searches capabilities and bundled solutions, first using a typeahead API and falling back to local filtering.
// Meta-tool: strale_search (works without API key) server.registerTool( "strale_search", { description: "Search Strale's 250+ API capabilities and bundled solutions. Covers: KYC & compliance (company verification, VAT validation, sanctions screening), data validation (IBAN, email, phone numbers), company registries (Nordic countries, US SEC EDGAR), domain & website intelligence (WHOIS, DNS, SSL certificates, trust scores, security headers, PageSpeed, tech stack detection), Web3 (live crypto prices, gas fees), and more (translation, invoice extraction, lead enrichment, AI Act assessment). Most under €0.10. Free to search.", inputSchema: z.object({ query: z .string() .describe( "Search keyword (matched against name, description, slug)", ), category: z .string() .optional() .describe( "Filter by category: compliance, validation, data-extraction, developer-tools, web3, security, domain-intel, recruiting, sales, legal, text", ), offset: z .number() .optional() .describe("Number of results to skip (for pagination). Default: 0"), }), }, async ({ query, category, offset }) => { const skip = offset ?? 0; // ─── Try the /v1/suggest/typeahead endpoint first ──────────────── // It has smarter ranking, solution deduplication, and geography awareness. // Fall back to local keyword matching if the API is unreachable. if (!category) { try { const url = `${opts.baseUrl}/v1/suggest/typeahead?q=${encodeURIComponent(query)}&limit=10`; const resp = await fetch(url, { headers: { Accept: "application/json" }, signal: AbortSignal.timeout(5000), }); if (resp.ok) { const data = (await resp.json()) as { results: Array<{ type: "solution" | "capability"; slug: string; name: string; description: string; category: string; price_cents: number | null; geography: string | null; sqs: number | null; sqs_label: string | null; is_free_tier?: boolean; step_count?: number; also_available_for?: string[]; }>; total: number; }; // Map typeahead results to the strale_search response format const results = data.results.map((r) => { const base: Record<string, unknown> = { type: r.type, slug: r.slug, name: r.name, description: r.description, category: r.category, geography: r.geography ?? "global", price: r.price_cents != null ? `€${(r.price_cents / 100).toFixed(2)}` : null, sqs: r.sqs ?? 0, sqs_label: r.sqs_label ?? "Pending", }; if (r.type === "solution") { if (r.step_count) base.step_count = r.step_count; if (r.also_available_for) base.also_available_for = r.also_available_for; } if (r.type === "capability") { // Enrich with input_fields from local capability data const cap = capabilities.find((c) => c.slug === r.slug); if (cap?.input_schema?.properties && Object.keys(cap.input_schema.properties).length > 0) { const required = new Set(cap.input_schema.required ?? []); const reqFields = Object.entries(cap.input_schema.properties) .filter(([key]) => required.has(key)) .map(([key, prop]) => `${key} (${prop.type ?? "any"})`); const optFields = Object.entries(cap.input_schema.properties) .filter(([key]) => !required.has(key)) .map(([key, prop]) => `${key} (${prop.type ?? "any"})`); const parts: string[] = []; if (reqFields.length > 0) parts.push(`Required: ${reqFields.join(", ")}`); if (optFields.length > 0) parts.push(`Optional: ${optFields.join(", ")}`); base.input_fields = parts.join(". ") || "No parameters"; } else { base.input_fields = "Accepts: task (string) — describe what you need in natural language"; } if (r.is_free_tier) base.is_free_tier = true; } return base; }); const page = results.slice(skip); return { content: [ { type: "text" as const, text: JSON.stringify( { query, category: null, total_matches: data.total, offset: skip, showing: page.length, has_more: data.total > skip + page.length, results: page, tip: "Use strale_execute to run any capability. Use strale_trust_profile for full quality breakdown and execution guidance.", }, null, 2, ), }, ], }; } } catch (err) { console.error("[strale_search] Typeahead API unavailable, falling back to local search:", err instanceof Error ? err.message : err); } } // ─── Fallback: local keyword matching ────────────────────────── // Used when: category filter is set (typeahead doesn't support it), // or when the typeahead endpoint is unreachable. const q = (query ?? "").toLowerCase(); const catFilter = category ? category.toLowerCase() : null; function localMatchScore(text: string): number { const tokens = q.split(/\s+/).filter((t) => t.length >= 2); if (tokens.length === 0) return text.toLowerCase().includes(q) ? 1 : 0; let score = 0; const lower = text.toLowerCase(); for (const token of tokens) { if (lower.includes(token)) score++; } return score; } // Match solutions const matchedSolutions = solutions .filter((s) => { if (catFilter && !s.category.toLowerCase().includes(catFilter)) return false; return localMatchScore(`${s.name} ${s.description} ${s.slug} ${s.category}`) > 0; }) .map((s) => ({ type: "solution" as const, slug: s.slug, name: s.name, description: s.description, category: s.category, price: `€${(s.price_cents / 100).toFixed(2)}`, geography: s.geography, step_count: s.step_count, capabilities: s.capabilities, sqs: s.sqs ?? 0, sqs_label: s.sqs_label ?? "Pending", })); // Match capabilities const matchedCaps = capabilities .filter((c) => { if (catFilter && !c.category.toLowerCase().includes(catFilter)) return false; return localMatchScore(`${c.name} ${c.description} ${c.slug} ${c.category}`) > 0; }) .map((c) => { let inputFields = "Accepts: task (string) — describe what you need in natural language"; const schema = c.input_schema; if (schema?.properties && Object.keys(schema.properties).length > 0) { const required = new Set(schema.required ?? []); const reqFields = Object.entries(schema.properties) .filter(([key]) => required.has(key)) .map(([key, prop]) => `${key} (${prop.type ?? "any"})`); const optFields = Object.entries(schema.properties) .filter(([key]) => !required.has(key)) .map(([key, prop]) => `${key} (${prop.type ?? "any"})`); const parts: string[] = []; if (reqFields.length > 0) parts.push(`Required: ${reqFields.join(", ")}`); if (optFields.length > 0) parts.push(`Optional: ${optFields.join(", ")}`); inputFields = parts.join(". ") || "No parameters"; } return { type: "capability" as const, slug: c.slug, name: c.name, description: c.description, category: c.category, geography: c.geography ?? "global", price: `€${(c.price_cents / 100).toFixed(2)}`, input_fields: inputFields, sqs: c.sqs ?? 0, sqs_label: c.sqs_label ?? "Pending", }; }); const combined = [...matchedSolutions, ...matchedCaps]; const page = combined.slice(skip, skip + 20); return { content: [ { type: "text" as const, text: JSON.stringify( { query, category: category ?? null, total_matches: combined.length, offset: skip, showing: page.length, has_more: skip + page.length < combined.length, results: page, tip: "Use strale_execute to run any capability. Use strale_trust_profile for full quality breakdown and execution guidance.", }, null, 2, ), }, ], }; }, );