set_custom_domain
Configure a custom domain for QR code short URLs (Pro required). All new QR codes will use your branded domain after setting DNS CNAME. Pass null to remove.
Instructions
Set a custom domain for your QR code short URLs (Pro plan required). When set, all new QR codes will use https://your-domain.com/r/... instead of the default URL. You must configure DNS (CNAME) to point to the QR Agent server. Pass domain=null to remove the custom domain.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| domain | Yes | Your custom domain without protocol (e.g. 'qr.mybrand.com'). Pass null to remove. |
Implementation Reference
- packages/mcp/src/tools.ts:793-813 (handler)MCP tool handler for 'set_custom_domain'. Defines description, inputSchema (zod validation for nullable domain string), and handler that calls the HTTP API: PUT /api/domain (with body {domain}) to set, or DELETE /api/domain to remove.
set_custom_domain: { description: "Set a custom domain for your QR code short URLs (Pro plan required). When set, all new QR codes will use https://your-domain.com/r/... instead of the default URL. You must configure DNS (CNAME) to point to the QR Agent server. Pass domain=null to remove the custom domain.", inputSchema: z.object({ domain: z .string() .nullable() .describe( "Your custom domain without protocol (e.g. 'qr.mybrand.com'). Pass null to remove." ), }), handler: async (input: { domain: string | null }) => { if (input.domain === null) { return apiRequest("/api/domain", { method: "DELETE" }); } return apiRequest("/api/domain", { method: "PUT", body: { domain: input.domain }, }); }, }, - packages/mcp/src/tools.ts:796-803 (schema)Input schema for set_custom_domain tool. Uses zod to define a nullable string 'domain' (e.g. 'qr.mybrand.com') which can also be null to remove the custom domain.
inputSchema: z.object({ domain: z .string() .nullable() .describe( "Your custom domain without protocol (e.g. 'qr.mybrand.com'). Pass null to remove." ), }), - src/modules/auth/auth.service.ts:143-148 (handler)Core database function setCustomDomain that updates the customDomain field on an apiKeys row by keyId using SQLite/Drizzle ORM.
export function setCustomDomain(keyId: number, domain: string | null): void { db.update(apiKeys) .set({ customDomain: domain }) .where(eq(apiKeys.id, keyId)) .run(); } - packages/mcp/src/tools.ts:1-8 (registration)The tools object where set_custom_domain (and all other MCP tools) is registered/exported. Each tool has description, inputSchema, and handler.
import { z } from "zod"; import { apiRequest } from "./api-client.js"; /** * MCP tool definitions for QR Agent Core. * Each tool calls the production HTTP API. */ export const tools = { - HTTP API route handler for PUT /api/domain (called by the MCP tool). Validates input, enforces Pro plan gate, checks domain format/uniqueness, calls setCustomDomain(), and checks DNS status.
// PUT /api/domain — set custom domain (Pro only) app.put( "/", { schema: { tags: ["Custom Domain"], summary: "Set your custom domain", description: "Configure a custom domain for your QR code short URLs. Pro plan required. The domain must be unique across all users.", body: { type: "object", required: ["domain"], properties: { domain: { type: "string", description: "Your custom domain without protocol (e.g. 'qr.mybrand.com').", }, }, }, response: { 200: { type: "object", properties: { custom_domain: { type: "string" }, dns_status: { type: "string" }, hint: { type: "string" }, }, }, }, }, }, async (request, reply) => { // Pro-only gate if (request.plan !== "pro") { return sendError(reply, 403, { error: "Custom domains require a Pro plan.", code: "PRO_REQUIRED", hint: "Upgrade to Pro ($19/month) to use custom domains. Use the upgrade_to_pro tool or POST /api/stripe/checkout.", }); } const { domain } = request.body as { domain: string }; // Basic validation: no protocol, no path, no whitespace const cleaned = domain.trim().toLowerCase(); if ( !cleaned || cleaned.includes("://") || cleaned.includes("/") || cleaned.includes(" ") ) { return sendError(reply, 400, { error: "Invalid domain format.", code: "INVALID_DOMAIN", hint: "Provide a bare domain without protocol or path (e.g. 'qr.mybrand.com', not 'https://qr.mybrand.com/').", }); } // Must contain at least one dot if (!cleaned.includes(".")) { return sendError(reply, 400, { error: "Invalid domain format.", code: "INVALID_DOMAIN", hint: "Provide a fully qualified domain name with at least one dot (e.g. 'qr.mybrand.com').", }); } // Uniqueness check if (isCustomDomainTaken(cleaned, request.apiKeyId)) { return sendError(reply, 409, { error: `Domain "${cleaned}" is already claimed by another user.`, code: "DOMAIN_ALREADY_TAKEN", hint: "Choose a different subdomain or contact support if you believe this is an error.", }); } setCustomDomain(request.apiKeyId, cleaned); const dnsStatus = await checkDnsStatus(cleaned); return { custom_domain: cleaned, dns_status: dnsStatus, hint: dnsStatus === "active" ? `Domain ${cleaned} is active. New QR codes will use https://${cleaned}/r/...` : `Domain ${cleaned} saved. DNS is pending — add a CNAME record pointing to your server. Use GET /api/domain to re-check status.`, }; } );