register_domain
Register a domain name using USDC payment via x402. Automatic async provisioning handles domain setup after registration.
Instructions
Register a new domain name. Requires USDC payment via x402. Handles async provisioning automatically.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| domain | Yes | Domain to register (e.g., 'myproject.com') | |
| years | No | Registration period in years (1-10, default: 1) |
Implementation Reference
- src/tools/register.ts:15-75 (handler)The registerDomain function is the handler for the register_domain tool. It validates the private key, ensures auth, POSTs to /domains/register, polls async jobs on 202, and formats the response.
export async function registerDomain( client: BloomfilterClient, params: { domain: string; years?: number }, ): Promise<McpToolResult> { const keyError = client.requiresPrivateKey(); if (keyError) return keyError; try { await client.ensureAuth(); const response = await client.http.post<RegistrationResponse>( "/domains/register", { domain: params.domain, years: params.years ?? 1 }, { headers: client.getAuthHeaders() }, ); let result = response.data; // Async provisioning — poll until complete if (response.status === 202 && result.jobId) { console.error(`[bloomfilter-mcp] Registration queued (job ${result.jobId}), polling...`); const jobResult = await client.pollJobStatus(result.jobId); if (jobResult.result) { result = jobResult.result as RegistrationResponse; } } // Format the result const lines = [`Domain registered: ${result.domain}`, `Status: ${result.status}`]; if (result.expiresAt) { lines.push(`Expires: ${result.expiresAt}`); } if (result.payment) { lines.push(`Cost: $${result.payment.amountUsd}`); if (result.payment.txHash) { lines.push(`Transaction: ${result.payment.txHash}`); } lines.push(`Network: ${result.payment.network}`); } if (result.dnsRecords && result.dnsRecords.length > 0) { lines.push(""); lines.push("DNS Records:"); for (const record of result.dnsRecords) { lines.push(` ${record.type} ${record.host} \u2192 ${record.value}`); } } if (result.warnings && result.warnings.length > 0) { lines.push(""); lines.push("Warnings:"); for (const warning of result.warnings) { lines.push(` \u26a0 ${warning}`); } } return { content: [{ type: "text", text: lines.join("\n") }] }; } catch (error) { return formatToolError(error); } } - src/types.ts:86-100 (schema)RegistrationResponse type — defines the shape of the API response for domain registration, including domain, status, expiresAt, payment details, DNS records, and warnings.
export interface RegistrationResponse { domain: string; status: string; registeredAt?: string; expiresAt?: string; jobId?: string; payment?: { amountUsd: string; txHash?: string; network: string; settled: boolean; }; dnsRecords?: Array<{ type: string; host: string; value: string }>; warnings?: string[]; } - src/index.ts:117-132 (registration)The register_domain tool is registered with the MCP server on line 118-132, defining the tool name, description, Zod schema params (domain string, optional years int 1-10), and delegating to registerDomain().
// 4. register_domain server.tool( "register_domain", "Register a new domain name. Requires USDC payment via x402. Handles async provisioning automatically.", { domain: z.string().describe("Domain to register (e.g., 'myproject.com')"), years: z .number() .int() .min(1) .max(10) .optional() .describe("Registration period in years (1-10, default: 1)"), }, async (params) => registerDomain(client, params), ); - src/client.ts:225-261 (helper)pollJobStatus is a helper used by registerDomain for async provisioning — polls GET /domains/status/:jobId until completed or failed, with timeout.
async function pollJobStatus(jobId: string): Promise<JobStatusResponse> { const startTime = Date.now(); while (Date.now() - startTime < JOB_TIMEOUT_MS) { // Re-check auth on each poll — long polling loops can outlast token expiry await ensureAuth(); const { data } = await httpClient.get<JobStatusResponse>(`/domains/status/${jobId}`, { headers: getAuthHeaders(), }); if (data.status === "completed") { return data; } if (data.status === "failed") { throw new Error(data.error ?? `Job ${jobId} failed: domain provisioning was unsuccessful`); } // Wait before polling again await new Promise((resolve) => setTimeout(resolve, JOB_POLL_INTERVAL_MS)); } throw new Error( `Job ${jobId} timed out after ${JOB_TIMEOUT_MS / 1000}s. ` + "The domain may still be provisioning — check status later with get_domain_info.", ); } return { http: httpClient, ensureAuth, getAuthHeaders, requiresPrivateKey, pollJobStatus, }; } - src/client.ts:206-221 (helper)requiresPrivateKey is called at the top of registerDomain to check if a private key is configured; returns an error result if missing.
function requiresPrivateKey(): McpToolResult | null { if (config.privateKey) return null; return { content: [ { type: "text", text: "Error: BLOOMFILTER_PRIVATE_KEY is required for this operation. " + "Set it as an environment variable to enable domain registration, " + "renewal, DNS management, and account access.\n\n" + "Example: BLOOMFILTER_PRIVATE_KEY=0x... bloomfilter-mcp", }, ], isError: true, }; }