search_domains
Search domain name availability and pricing across specified TLDs. Provide a domain name and optional TLD list to receive availability status and cost for each TLD.
Instructions
Search for available domain names. Returns availability and pricing for each TLD.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Domain name to search for (e.g., 'myproject', 'coolstartup') | |
| tlds | No | Comma-separated TLDs to check (e.g., 'com,io,xyz'). Defaults to com,net,org,io,xyz |
Implementation Reference
- src/tools/search.ts:12-46 (handler)Main handler for the search_domains tool. Calls GET /domains/search?query=...&tlds=... via the Bloomfilter client, maps results to formatted text (checkmark/cross icons with pricing), and returns MCP tool result.
export async function searchDomains( client: BloomfilterClient, params: { query: string; tlds?: string }, ): Promise<McpToolResult> { try { const { data } = await client.http.get<SearchResponse>("/domains/search", { params: { query: params.query, ...(params.tlds && { tlds: params.tlds }) }, }); if (!data.results || data.results.length === 0) { return { content: [{ type: "text", text: `No results found for "${params.query}".` }], }; } const lines = data.results.map((r) => { if (!r.available) { return ` \u274c ${r.domain} \u2014 unavailable`; } const price = r.priceUsd ? `$${r.priceUsd}` : r.priceCents ? `$${(r.priceCents / 100).toFixed(2)}` : "price unavailable"; const premium = r.premium ? " (premium)" : ""; return ` \u2705 ${r.domain} \u2014 ${price}/yr${premium}`; }); const text = `Domain search results for "${params.query}":\n\n${lines.join("\n")}`; return { content: [{ type: "text", text }] }; } catch (error) { return formatToolError(error); } } - src/tools/search.ts:8-10 (schema)Imports types: BloomfilterClient from client.ts, formatToolError for error handling, McpToolResult and SearchResponse from types.ts.
import type { BloomfilterClient } from "../client.js"; import { formatToolError } from "../client.js"; import type { McpToolResult, SearchResponse } from "../types.js"; - src/types.ts:50-61 (schema)SearchResultDomain and SearchResponse interfaces define the API response shape consumed by searchDomains.
export interface SearchResultDomain { domain: string; available: boolean; premium: boolean; priceCents?: number; priceUsd?: string; } export interface SearchResponse { query: string; results: SearchResultDomain[]; } - src/index.ts:78-92 (registration)Registration of search_domains tool on the MCP server with Zod schema for query (required string) and tlds (optional string). Delegates to searchDomains handler.
// 1. search_domains server.tool( "search_domains", "Search for available domain names. Returns availability and pricing for each TLD.", { query: z.string().describe("Domain name to search for (e.g., 'myproject', 'coolstartup')"), tlds: z .string() .optional() .describe( "Comma-separated TLDs to check (e.g., 'com,io,xyz'). Defaults to com,net,org,io,xyz", ), }, async (params) => searchDomains(client, params), ); - src/client.ts:274-383 (helper)formatToolError helper used by searchDomains to convert any error (axios, rate-limit, network, timeout) into a consistent MCP error result.
export function formatToolError(error: unknown, apiUrl?: string): McpToolResult { if (axios.isAxiosError(error)) { // Rate limited if (error.response?.status === 429) { const data = error.response.data as Record<string, unknown> | undefined; const message = (data?.message as string) ?? "Too many requests"; return { content: [{ type: "text", text: `Rate limited: ${message}. Please wait before retrying.` }], isError: true, }; } // API error response if (error.response?.data) { const data = error.response.data as Record<string, unknown>; const status = error.response.status; // x402 payment responses — two cases: // 1. Initial 402 with accepts array (no x402 wrapper, or wrapper disabled) // 2. Retry 402 after x402 wrapper tried to pay but settlement failed if (status === 402) { if (data.accepts) { // Case 1: Initial 402 with payment requirements const accepts = data.accepts as Array<Record<string, unknown>>; const amount = accepts[0]?.price ?? accepts[0]?.amount; const description = (data.resource as Record<string, unknown> | undefined)?.description; const detail = description ? `${description} requires payment of ${amount} USDC` : `Payment of ${amount} USDC required`; return { content: [ { type: "text", text: `Payment required: ${detail}. Ensure your wallet has sufficient USDC balance.`, }, ], isError: true, }; } // Case 2: Payment was attempted but failed (insufficient balance, settlement error, etc.) const serverMsg = (data.message as string) ?? (data.error as string) ?? (data.detail as string); const detail = serverMsg ?? "payment was attempted but could not be settled on-chain"; return { content: [ { type: "text", text: `Payment failed: ${detail}. Check that your wallet has sufficient USDC balance on Base.`, }, ], isError: true, }; } const code = (data.code as string) ?? `HTTP ${status}`; const message = (data.message as string) ?? (data.error as string) ?? (data.detail as string) ?? error.response.statusText ?? `Request failed with status ${status}`; return { content: [{ type: "text", text: `Error [${code}]: ${message}` }], isError: true, }; } // Network/connection error if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") { const url = apiUrl ?? error.config?.baseURL ?? "unknown"; return { content: [ { type: "text", text: `Failed to connect to Bloomfilter API at ${url}. ` + "Check that the API is running and BLOOMFILTER_API_URL is correct.", }, ], isError: true, }; } // Timeout if (error.code === "ECONNABORTED" || error.code === "ERR_CANCELED") { return { content: [ { type: "text", text: "Request timed out. The Bloomfilter API may be slow or unreachable.", }, ], isError: true, }; } // Generic axios error return { content: [{ type: "text", text: `Request failed: ${error.message}` }], isError: true, }; } // Non-axios error const message = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error: ${message}` }], isError: true, }; }