@porkbunllc/mcp-server
Version:
Official Porkbun MCP server, built and maintained by Porkbun — exposes the Porkbun v3 API as tools for AI agents (ChatGPT, Claude, Cursor, etc.)
92 lines (91 loc) • 8.25 kB
JavaScript
import { FUNDING_LOCAL, PAY_DIRECT_PARAMS, PAY_DIRECT_OPENING, PAY_WITH_CARD_NOTE, PAY_WITH_USDC_NOTE } from "./tools.js";
/** Replace the named keys anywhere in a tool result. Returns a new value. */
export function redact(value, keys, note) {
if (Array.isArray(value))
return value.map((v) => redact(v, keys, note));
if (value && typeof value === "object") {
const out = {};
for (const [k, v] of Object.entries(value)) {
out[k] = keys.includes(k) ? note : redact(v, keys, note);
}
return out;
}
return value;
}
// Where the full server and its setup live, for the restricted paths to point at.
const FULL = "the full Porkbun server, which the user can add themselves as a custom connector: https://mcp.porkbun.com/mcp (setup: https://porkbun.com/mcp)";
// A hosted connection is always a live key, so the sandbox tools never apply.
const SANDBOX = ["create_sandbox_key", "sandbox_topup", "sandbox_reset", "sandbox_trigger_webhook"];
const TOPUPS = ["top_up_account_credit", "configure_auto_topup", "top_up_with_usdc", "get_usdc_topup_status", "top_up_with_card_mpp"];
const PURCHASES = ["register_domain", "renew_domain", "transfer_domain", "buy_closeout", "create_hosting", "create_mailbox", "renew_mailbox"];
// Tools whose whole output is a secret: the certificate private key, a
// WordPress application password, and a webhook signing secret (create and
// rotate exist only to hand one out). Other webhook tools stay, with the
// secret field redacted (redactKeys below). OpenAI's app review asks that tool responses
// exclude auth secrets, so the directory listings' path leaves them out. They stay
// on /mcp, where the user added the server themselves.
const SECRETS = ["get_ssl_bundle", "create_wp_credentials", "create_webhook", "rotate_webhook_secret", "setup_mailbox", "retrieve_api_key"];
// Takes a domain-transfer authorization (EPP) code, a credential. No transfer can
// be started on the directory path anyway (transfer_domain is a purchase).
const CREDENTIAL_INPUTS = ["update_transfer_auth_code", "set_email_password"];
// Money movement and future charges, left out of the directory path to be safe:
// cancel_transfer refunds a paid order, and update_auto_renew turning renewal on
// authorises charges at expiry (close to "initiating a subscription").
const MONEY_ADJACENT = ["cancel_transfer", "update_auto_renew"];
// Invoice tools (0.39.0) are read-only, but held off the directory path while
// the ChatGPT review of it is still open: its tool list is what was submitted.
// Move them onto /mcp/no-purchases once that review has concluded.
const HELD_FOR_REVIEW = ["list_invoices", "get_invoice", "get_invoice_pdf", "send_phone_verification_code", "confirm_phone_verification", "send_email_verification_code", "confirm_email_verification", "list_email_forwards", "create_email_forward", "delete_email_forward", "list_mailboxes", "request_api_key", "search_aftermarket"];
const FUNDING_NO_TOPUPS = "**How a purchase is paid.** From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. On this connection purchases are paid from credit only. If the credit is short, the call fails with `INSUFFICIENT_FUNDS` carrying `cost`, `balance` and `shortfall` (`dry_run: true` reports the same without charging). Adding credit is the account holder's step: tell them the exact shortfall and that they can add it with **buy account credit** at https://porkbun.com/account/credit, then retry this exact call once they say it is done. Auto top-up, which they can set in their API settings (https://porkbun.com/account/api), refills the balance from their saved card on its own next time. Money parameters are integer cents: state amounts to the user in dollars (`cost_cents: 1108` is $11.08).";
export const PROFILES = [
{
path: "/mcp",
exclude: new Set(SANDBOX),
},
{
path: "/mcp/no-topups",
exclude: new Set([...SANDBOX, ...TOPUPS]),
funding: FUNDING_NO_TOPUPS,
omitParams: PAY_DIRECT_PARAMS,
note: "On this connection, adding account credit and changing auto top-up are done by the account holder (https://porkbun.com/account/credit and https://porkbun.com/account/api); the top-up tools named above are not available here.",
instructions: "This is the directory version of Porkbun's connector. To follow the directory's rules it cannot add money to the account: there are no top-up tools (charging a saved card for credit, or changing auto top-up). Everything else works, including buying with credit already on the account. When the user wants credit added, tell them the amount and that they can add it on porkbun.com; if they want an assistant that can top up for them, that is " + FULL + ".",
},
{
path: "/mcp/no-purchases",
exclude: new Set([...SANDBOX, ...TOPUPS, ...PURCHASES, ...SECRETS, ...CREDENTIAL_INPUTS, ...MONEY_ADJACENT, ...HELD_FOR_REVIEW]),
redactKeys: ["secret"],
redactNote: "On this connection the webhook signing secret is not returned: the `secret` field holds a note instead. The user sees or rotates the secret in their Porkbun API settings (https://porkbun.com/account/api).",
// No purchase redirects here: OpenAI supports physical-goods commerce only, so
// this path neither buys nor tells anyone where to buy (no "complete it on
// porkbun.com", no pointer to the full server in purchase contexts).
note: "Some tools named above are not part of this app. Buying anything (registering, renewing or transferring domains, closeouts, new hosting, account credit) is not available in this app: if asked, say so, without directing the user anywhere to buy. SSL certificate keys, WordPress application passwords and webhook signing secrets are not returned here; the user can get them from their Porkbun account settings.",
instructions: "This is the app-directory version of Porkbun's connector. It does not buy anything: registering, renewing or transferring domains, buying closeouts, starting hosting and adding account credit are not available in this app. You can still share availability and prices. If the user asks to buy, say that purchasing isn't available in this app; do not direct them to a website, checkout or other connector to buy. It also returns no secrets: webhook details are shown with the signing secret hidden, and SSL certificate keys, WordPress application passwords and webhook signing secrets are not available here (the user can find them in their Porkbun account settings). Everything else works: availability and pricing, DNS, nameservers, forwarding, glue, DNSSEC, restore points, hosting sites, webhooks and Cloudflare.",
overrides: {
get_balance: "Get the available account credit balance for the authenticated Porkbun account, in cents (integer) and as a display string (e.g. `$12.34`). Read-only and informational on this connection, which cannot buy anything.",
get_auto_topup: "Read the account's auto top-up configuration: whether it is on, the balance threshold that triggers it, the amount it adds, and whether a payment method is on file (`paymentMethodOnFile`). Read-only: this connection cannot change it or charge anything.",
},
},
];
/** The description a tool gets under a profile. */
export function describeFor(profile, name, description) {
if (profile.overrides?.[name])
return profile.overrides[name];
let d = description;
if (profile.funding && d.includes(FUNDING_LOCAL))
d = d.replace(FUNDING_LOCAL, profile.funding);
if (profile.omitParams?.some((p) => PAY_DIRECT_PARAMS.includes(p))) {
d = d.split(PAY_WITH_USDC_NOTE).join("").split(PAY_WITH_CARD_NOTE).join("").split(PAY_DIRECT_OPENING).join("");
}
if (profile.redactNote && profile.redactKeys?.some((k) => new RegExp(`\\b${k}\\b`, "i").test(d))) {
d += `\n\n${profile.redactNote}`;
}
if (profile.note) {
for (const missing of profile.exclude) {
if (new RegExp(`\\b${missing}\\b`).test(d)) {
d += `\n\n${profile.note}`;
break;
}
}
}
return d;
}