Vercel MCP Server

import { z } from "zod"; import { handleResponse } from "../utils/response.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { BASE_URL, DEFAULT_ACCESS_TOKEN } from "../config/constants.js"; export function registerDomainTools(server: McpServer) { // Check domain availability server.tool( "domain_check", "Check if a domain name is available for purchase", { name: z.string().describe("The name of the domain to check"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ name, teamId, slug }) => { const url = new URL(`${BASE_URL}/v4/domains/status`); url.searchParams.append("name", name); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain availability:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Get domain price server.tool( "domain_price", "Check the price to purchase a domain", { name: z.string().describe("The name of the domain to check price for"), type: z.enum(["new", "renewal", "transfer", "redemption"]).optional().describe("Domain status type to check price for"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ name, type, teamId, slug }) => { const url = new URL(`${BASE_URL}/v4/domains/price`); url.searchParams.append("name", name); if (type) url.searchParams.append("type", type); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain price:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Get domain config server.tool( "domain_config", "Get a Domain's configuration", { domain: z.string().describe("The name of the domain"), strict: z.boolean().optional().describe("When true, only include nameservers assigned directly to the domain"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ domain, strict, teamId, slug }) => { const url = new URL(`${BASE_URL}/v6/domains/${domain}/config`); if (strict !== undefined) url.searchParams.append("strict", String(strict)); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain configuration:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Get domain registry info server.tool( "domain_registry", "Get domain transfer info", { domain: z.string().describe("The domain name"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ domain, teamId, slug }) => { const url = new URL(`${BASE_URL}/v1/domains/${domain}/registry`); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain registry info:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Get single domain info server.tool( "domain_get", "Get information for a single domain", { domain: z.string().describe("The name of the domain"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ domain, teamId, slug }) => { const url = new URL(`${BASE_URL}/v5/domains/${domain}`); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain information:\n${JSON.stringify(data, null, 2)}` }] }; } ); // List domains server.tool( "domain_list", "List all domains", { limit: z.number().optional().describe("Maximum number of domains to list"), since: z.number().optional().describe("Get domains created after this timestamp"), until: z.number().optional().describe("Get domains created before this timestamp"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ limit, since, until, teamId, slug }) => { const url = new URL(`${BASE_URL}/v5/domains`); if (limit) url.searchParams.append("limit", String(limit)); if (since) url.searchParams.append("since", String(since)); if (until) url.searchParams.append("until", String(until)); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domains list:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Purchase domain server.tool( "domain_buy", "Purchase a domain", { name: z.string().describe("The domain name to purchase"), expectedPrice: z.number().optional().describe("The expected price for the purchase"), renew: z.boolean().optional().describe("Whether to auto-renew the domain"), country: z.string().describe("The country of the domain registrant"), orgName: z.string().optional().describe("The company name of the domain registrant"), firstName: z.string().describe("The first name of the domain registrant"), lastName: z.string().describe("The last name of the domain registrant"), address1: z.string().describe("The street address of the domain registrant"), city: z.string().describe("The city of the domain registrant"), state: z.string().describe("The state of the domain registrant"), postalCode: z.string().describe("The postal code of the domain registrant"), phone: z.string().describe("The phone number of the domain registrant"), email: z.string().email().describe("The email of the domain registrant"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ name, expectedPrice, renew, country, orgName, firstName, lastName, address1, city, state, postalCode, phone, email, teamId, slug }) => { const url = new URL(`${BASE_URL}/v5/domains/buy`); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` }, body: JSON.stringify({ name, expectedPrice, renew, country, orgName, firstName, lastName, address1, city, state, postalCode, phone, email }) }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain purchase result:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Register/transfer domain server.tool( "domain_register", "Register or transfer-in a domain", { method: z.enum(["add", "transfer-in"]).describe("The domain operation to perform"), name: z.string().describe("The domain name"), cdnEnabled: z.boolean().optional().describe("Whether to enable CDN"), zone: z.boolean().optional().describe("Whether to create a zone"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ method, name, cdnEnabled, zone, teamId, slug }) => { const url = new URL(`${BASE_URL}/v5/domains`); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` }, body: JSON.stringify({ method, name, cdnEnabled, zone }) }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain registration result:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Remove domain server.tool( "domain_remove", "Remove a domain", { domain: z.string().describe("The name of the domain to remove"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ domain, teamId, slug }) => { const url = new URL(`${BASE_URL}/v6/domains/${domain}`); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { method: "DELETE", headers: { Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` } }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain removal result:\n${JSON.stringify(data, null, 2)}` }] }; } ); // Update domain server.tool( "domain_update", "Update or move apex domain", { domain: z.string().describe("The domain name"), op: z.enum(["update", "move-out"]).describe("Operation type"), renew: z.boolean().optional().describe("Whether to auto-renew"), customNameservers: z.array(z.string()).optional().describe("Custom nameservers"), zone: z.boolean().optional().describe("Whether to create a zone"), teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"), slug: z.string().optional().describe("The Team slug to perform the request on behalf of") }, async ({ domain, op, renew, customNameservers, zone, teamId, slug }) => { const url = new URL(`${BASE_URL}/v3/domains/${domain}`); if (teamId) url.searchParams.append("teamId", teamId); if (slug) url.searchParams.append("slug", slug); const response = await fetch(url.toString(), { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}` }, body: JSON.stringify({ op, renew, customNameservers, zone }) }); const data = await handleResponse(response); return { content: [{ type: "text", text: `Domain update result:\n${JSON.stringify(data, null, 2)}` }] }; } ); }