Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch keyword (matched against name, description, slug)
categoryNoFilter by category: compliance, validation, data-extraction, developer-tools, web3, security, domain-intel, recruiting, sales, legal, text
offsetNoNumber of results to skip (for pagination). Default: 0

Implementation Reference

  • 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,
              ),
            },
          ],
        };
      },
    );

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/strale-io/strale'

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