Run your Porkbun account from your AI assistant: find
and register domains, edit DNS and DNSSEC, publish a static site, transfer a
domain in or move its DNS to Cloudflare, set up mailboxes and webhooks, and
read invoices. The tools are Porkbun's own
(@porkbunllc/mcp-server),
plus a zone file export, with safety around them.
Every tool that spends money, commits to a charge, changes a domain's owner
or hands out a secret is money: your client asks you first, and a charge
over the account's monthly API limit is refused before it reaches Porkbun.
You need a Porkbun account and an API key pair (pk1_... and sk1_...),
each half in a file, with API access turned on for the domains it should
manage. Purchases are paid from account credit, or by your card through
Stripe Link. To save a file (a certificate, an invoice PDF, a zone file, a
mailbox password), the server also needs a directory to save into.
What you can ask
- "Which of my domains expire in the next three months, and which of those have auto-renew off?"
- "Is talonbakery.com available? If it costs less than $15 a year, register it."
- "Back up the DNS of example.com, then point shop.example.com at 203.0.113.10."
- "Publish the site in ./dist to example.com's static hosting."
- "If I moved example.org to my Cloudflare account, which DNS records would be dropped?"
- "Save last month's Porkbun invoice as a PDF."
Install
Install in VS Code Install in Cursor Download for Claude Desktop
Note
@foreverworks/mcp-porkbun is not on npm yet: the buttons and the npx commands below work once it is published. Until then, run it from a clone.
With Claude Code:
claude mcp add porkbun -e PORKBUN_API_KEY_FILE="<path to API key file>" -e PORKBUN_SECRET_API_KEY_FILE="<path to Secret API key file>" -- npx -y @foreverworks/mcp-porkbun
Claude Desktop (Settings, Developer, Edit Config) and most other clients read the same command as JSON:
{
"mcpServers": {
"porkbun": {
"command": "npx",
"args": ["-y", "@foreverworks/mcp-porkbun"],
"env": {
"PORKBUN_API_KEY_FILE": "<path to API key file>",
"PORKBUN_SECRET_API_KEY_FILE": "<path to Secret API key file>"
}
}
}
}
Each runs the published package on Node 22.19 or newer. VS Code and the Claude Desktop bundle ask for each setting as they add the server; leave an optional one empty. Cursor reads each from your environment instead (${env:PORKBUN_API_KEY_FILE} and so on), so set them before you start Cursor. The commands above set only what is required; Configuration has the rest.
From a clone
git clone https://github.com/foreverworks/mcps.git && cd mcps
npm ci && npm run build
claude mcp add porkbun -e PORKBUN_API_KEY_FILE="<path to API key file>" -e PORKBUN_SECRET_API_KEY_FILE="<path to Secret API key file>" -- node "$PWD/servers/porkbun/dist/index.js"
In a JSON configuration, the command is node and its one argument the full path of servers/porkbun/dist/index.js. To share one running copy between several clients over HTTP, see docs/deploying.md.
Configuration
| Variable | Required | Meaning |
|---|---|---|
PORKBUN_API_KEY_FILE |
yes, or PORKBUN_API_KEY |
File holding the API key (pk1_...). A file that is named but missing or empty is a config failure, never a fallback. |
PORKBUN_SECRET_API_KEY_FILE |
yes, or PORKBUN_SECRET_API_KEY |
File holding the secret API key (sk1_...). |
PORKBUN_API_KEY, PORKBUN_SECRET_API_KEY |
no | The keys themselves, for a local stdio run (the names Porkbun's own server reads). |
PORKBUN_OUTBOX_DIR |
for the tools that save a file | Where files are saved, with mode 0640 (the server's user and the directory's group). No default: unset, get_ssl_bundle, get_invoice_pdf, export_dns_zone, and create_mailbox or setup_mailbox without a password answer config; every other tool works. |
PORKBUN_BASE_URL, PORKBUN_DOCS_BASE |
no | Read by Porkbun's package: the API base (default https://api.porkbun.com/api/json/v3) and where the documentation tools fetch from (default https://porkbun.com). Leave them unset. |
PORKBUN_X402_PRIVATE_KEY, PORKBUN_X402_MAX_CENTS |
no | Read by Porkbun's package to pay in USDC, which this server does not offer. Never set them. |
PORKBUN_TOOLSETS |
no | The toolsets to load, separated by commas; unset, every one (Toolsets). |
The keys are read on the first call that needs them, so the documentation
tools (read_doc, search_docs, list_doc_topics), get_pricing and
mock_call work without them. The keys are never logged or returned: the
audit line carries no arguments or results.
A deployment that limits egress sets
MCP_EGRESS_ALLOW=api.porkbun.com,porkbun.com. deploy_site and
import_dns_records read files from MCP_FILE_ROOTS. Both, and the other
variables every server shares, are in
docs/configuration.md.
Getting the API key pair
- Sign in at porkbun.com and open API Access.
- Create a key for this server alone, so you can restrict and revoke it on its own. Porkbun shows the secret key once: copy both halves now.
- Optionally restrict the key (there, or later with the gear icon next to it): Allowed IPs, Allowed domains, and Permissions (Full access, Read-only, Manage, but no purchases, or custom scopes). A call outside them fails and changes nothing.
- Save each half in a file readable only by you (
chmod 600), and pointPORKBUN_API_KEY_FILEandPORKBUN_SECRET_API_KEY_FILEat them. - Turn on API access for the domains: Opt In All Domains on the same page, or each domain's API Access under Domain Management. Without it, every call on that domain fails.
Toolsets
Every tool definition the server hands your client travels with each request
the client makes, and Porkbun's are long. PORKBUN_TOOLSETS names the
toolsets to load, separated by commas. The tools of the others are not there
at all, and the server's instructions tell the assistant which toolsets are
missing, so it can tell you instead of looking for a way around. Unset, empty
or all, every toolset is loaded, as before.
| Toolset | What it loads |
|---|---|
domains |
Availability and prices, registering and renewing, auto-renew, contacts. |
dns |
DNS records, restore points, scanning, importing and exporting a zone, nameservers, preflight_domain, and the free TLS certificate (get_ssl_bundle). |
dnssec |
DS records at the registry, with preflight_domain. |
glue |
Glue records, for nameservers named under the domain they serve. |
forwards |
URL forwarding and email forwarding. |
hosting |
Static hosting and Cloud for WordPress: plans, deploy_site, files, WordPress passwords. |
mailboxes |
Email hosting mailboxes: buying, setting up, renewing, passwords. |
cloudflare |
Moving DNS to your own Cloudflare account, and the zone, records and proxy there. |
transfers |
Transferring a domain in, with check_domain for its price and scan_dns_records and import_dns_records for its zone. |
marketplace |
Names other people sell: Porkbun Marketplace, the aftermarket, closeouts. |
webhooks |
Webhook endpoints, their secrets and their deliveries. |
account |
Top-ups and auto top-up, verifying the account's email and phone, invoices. |
docs |
Porkbun's API documentation, and mock_call's example answers. |
ping, get_balance and get_api_settings are always loaded: ping is
where you start and where a failure is diagnosed, and every purchase, in five
of the toolsets, is paid from the balance and counts against the monthly API
spend cap.
Whatever is loaded, a purchase is still money and still checked against the
caps before it is sent. A name that is not a toolset stops the server at
start, with a message listing the ones there are. src/mcp/toolsets.ts
places each tool by name, and a version of Porkbun's package with a tool it
does not place fails the tests and will not start. Measured with
scripts/measure-tokens.ts on 2026-10-23 (GPT-4 tokenizer), the tool list is
113 tools and about 44,200 tokens with every toolset, 23 tools and 8,800 with
dns,forwards, and 25 tools and 12,300 with domains,account.
To load DNS and forwarding only:
{
"mcpServers": {
"porkbun": {
"command": "npx",
"args": ["-y", "@foreverworks/mcp-porkbun"],
"env": {
"PORKBUN_TOOLSETS": "dns,forwards",
"PORKBUN_API_KEY_FILE": "/path/to/porkbun-api-key",
"PORKBUN_SECRET_API_KEY_FILE": "/path/to/porkbun-secret-api-key"
}
}
}
}
A toolset removes tools; MCP_MAX_ACCESS=read keeps every tool listed and
refuses the writes.
First run
- Ask your assistant to run Porkbun's
ping. It checks the connection and the key pair, and shows the key's scopes. - Ask for the API spend settings (
get_api_settings) and the balance (get_balance): what the account may still spend this month, and the credit a purchase is paid from. - Purchases and top-ups need a verified email and phone. If a call answers
VERIFICATION_REQUIRED, the assistant sends each code (send_email_verification_code,send_phone_verification_code) and you read it back to confirm.
Tools
read changes nothing; write changes the account, its DNS or its hosting, but spends nothing and hands out no secret; money spends or commits money, changes a domain's owner, or hands out a secret, and your client asks you before each call.
113 tools: 54 read, 39 write, 20 money, generated from the running server
because they follow Porkbun's package, every toolset loaded
(Toolsets says which load together). Each row is the first sentence of the
tool's description: Porkbun's, amended where this server differs, or this
server's own for deploy_site, get_ssl_bundle, get_invoice_pdf and
export_dns_zone.
| Tool | Access | What it does |
|---|---|---|
buy_closeout |
money | Spends account credit, or pays directly by card (pay_with_card). Buys a closeout at its current price and claims the name. |
cancel_transfer |
money | Cancels a paid inbound transfer and refunds the order. Confirm with the user first. |
check_domain |
read | Check whether a single domain is available for registration and what it costs. |
check_domains |
read | Check up to 25 domains for availability and price in ONE call. |
configure_auto_topup |
money | Set the top-up amount, and/or turn auto top-up on or off. |
confirm_email_verification |
write | Verify the account's email address with the code from the verification email (send_email_verification_code, or any verification email from the last 3 days). |
confirm_phone_verification |
write | Verify the account's phone number with the code the user received from send_phone_verification_code. |
connect_domains_to_cloudflare |
write | Queue domains to move into the customer's own Cloudflare account: Porkbun creates the zone there, copies over the DNS records it holds, and repoints the registry nameservers at Cloudflare. |
create_cloudflare_record |
write | Create a DNS record in a domain's Cloudflare zone. |
create_dns_record |
write | Create a DNS record on a domain in the authenticated account. |
create_dnssec_record |
write | Submit a DNSSEC DS record to the registry for a domain. |
create_email_forward |
write | Forward an address at the domain (e.g. |
create_glue_record |
write | Create a glue record for a host on a domain. |
create_hosting |
money | Provision hosting for a domain in the account: Secure Static Hosting OR Cloud for WordPress (a managed WordPress site), chosen by sku. |
create_mailbox |
money | Spends account credit, or pays directly by card (pay_with_card). Buy one email hosting mailbox ($36.00 a year, renewing automatically; the current price is price in list_mailboxes) and set it up in the same call. |
create_url_forward |
write | Add a URL forwarding rule for a domain. |
create_webhook |
money | Register a webhook endpoint. |
create_wp_credentials |
money | CLOUD FOR WORDPRESS ONLY. |
delete_cloudflare_record |
write | Delete a DNS record from a domain's Cloudflare zone. |
delete_dns_record |
write | Delete a single DNS record by its numeric record_id (obtained from list_dns_records). |
delete_dnssec_record |
write | Remove a DNSSEC DS record from the registry for a domain, identified by key tag. |
delete_email_forward |
write | Delete one email forward, named by its address and forward_to (from list_email_forwards; an address can forward to several mailboxes, so both are needed). |
delete_glue_record |
write | Delete a glue record by host on a domain. |
delete_hosting |
write | Deprovision (cancel) Secure Static Hosting for a domain; teardown is scheduled by Porkbun. |
delete_hosting_file |
write | Delete a file or directory at path in a domain's Secure Static Hosting space. |
delete_url_forward |
write | Delete a URL forwarding rule by its id (obtained from list_url_forwards). |
delete_webhook |
write | Delete a webhook endpoint by id. |
delete_wp_credentials |
write | CLOUD FOR WORDPRESS ONLY. |
deploy_site |
write | Upload static files to a domain's Secure Static Hosting. |
diff_dns_restore_point |
read | Compare a DNS restore point with the live zone before changing anything. |
disconnect_cloudflare |
write | Remove this account's stored Cloudflare connection. |
edit_cloudflare_record |
write | Update a DNS record in a domain's Cloudflare zone. |
export_dns_zone |
read | Save a domain's DNS records as a BIND zone file (RFC 1035) in this server's outbox: every record list_dns_records returns, under $ORIGIN <domain>., host names absolute, MX and SRV with their priority, TXT text quoted in 255-byte strings, each record's notes as a comment. |
get_api_settings |
read | Get the account's API spend controls and the limits actually enforced, in cents. |
get_auto_topup |
read | Read the account's auto top-up configuration: whether it is on, the balance threshold that triggers it, the amount added, whether a payment method is actually on file, and effectiveAmount: what top_up_account_credit would charge right now. |
get_balance |
read | Get the available account credit balance for the authenticated Porkbun account. |
get_closeout |
read | Get one closeout plus totalPrice: the binding amount, which is the closeout price plus the registration year that comes with it. |
get_cloudflare_connection |
read | Check whether this Porkbun account is connected to a Cloudflare account, and which one. |
get_cloudflare_domain_status |
read | Get the Cloudflare move status for ONE domain, including the Cloudflare zone id once created and the nameservers Porkbun replaced (kept so the move can be undone). |
get_cloudflare_queue |
read | List every Cloudflare move this account has requested, with per-domain status and message. |
get_cloudflare_records |
read | List a domain's LIVE DNS records as Cloudflare holds them, each with its orange-cloud (proxied) state and whether it is proxiable at all. |
get_cloudflare_zone |
read | Get what CLOUDFLARE currently says about a domain's zone (status, paused, its nameservers, activation date) rather than what Porkbun's queue row remembers. |
get_cloudflare_zone_settings |
read | Read the Cloudflare zone settings that matter after a move: ssl mode, always_use_https, automatic_https_rewrites, min_tls_version, development_mode, cache_level. |
get_contacts |
read | Get the four contacts (registrant, admin, tech, billing) for a domain in the authenticated account, with their current field values (name, organization, address, phone, email). |
get_domain |
read | Get the metadata for a single domain in the authenticated account: status, TLD, create date, expire date, security lock, WHOIS privacy, auto-renew, API access opt-in, and (optionally) labels. |
get_hosting |
read | Get Secure Static Hosting status for a domain (plan, server, trial, expiry, auto-renew), or null if the domain has no hosting. |
get_invoice |
read | Get one invoice as data: who it is billed to, how it was paid (card brand and last four only), each line with its product, term, resulting domain expiry and status, and the gross, refunded and net totals in cents. |
get_invoice_pdf |
read | Save an invoice PDF, the same document as Download PDF on porkbun.com, in this server's outbox. |
get_nameservers |
read | Get the current nameservers configured for a domain in the authenticated account. |
get_pricing |
read | Get current Porkbun pricing: registration, renewal and transfer prices per TLD in USD. |
get_registration_requirements |
read | Get a TLD's registration requirements as JSON Schema. |
get_ssl_bundle |
money | Save a domain's free Porkbun-issued TLS certificate and its private key in this server's outbox, instead of returning the key. |
get_transfer_setup |
read | Report where a held inbound transfer is and what it is waiting on: whether it is held at PENDINGDNS, whether its DNS zone exists, how many records it holds, what the domain currently delegates to, and the next step. |
get_transfer_status |
read | Get the status of a specific inbound transfer for a domain. |
get_webhook |
read | Fetch a single webhook endpoint by its numeric id, including its signing secret and delivery health (consecutive failures, last success/failure) (its secret is withheld on this server). |
get_webhook_delivery |
read | Fetch a single webhook delivery by id, including the full JSON payload that was (or will be) sent and its delivery status. |
get_webhook_event_types |
read | List the event types you can subscribe a webhook endpoint to. |
import_dns_records |
write | Create many DNS records on a Porkbun domain in one call: the companion to scan_dns_records for keeping a transferred domain working. |
list_cloudflare_inventory |
read | List every domain in the account with its Cloudflare eligibility: state is eligible / warn / blocked / connected / inprogress, each with a human-readable reason (DNSSEC live, custom nameservers, uses Porkbun hosting, etc). |
list_dns_records |
read | List all DNS records for a domain in the authenticated account. |
list_dns_restore_points |
read | List the saved versions of a domain's DNS zone, newest first. |
list_dnssec_records |
read | List the DNSSEC DS records currently submitted to the registry for a domain. |
list_doc_topics |
read | List the available Porkbun API documentation topics. |
list_domains |
read | List domains in the authenticated Porkbun account. |
list_email_forwards |
read | List the email forwards on a domain: each address at the domain and the forwardTo mailbox it is delivered to (one address can forward to several). |
list_glue_records |
read | List glue records for a domain. |
list_hosting_files |
read | List file/directory names under an optional path in a domain's Secure Static Hosting space. |
list_hosting_plans |
read | List the hosting plans provisionable via the API, with price (cents: pass as acknowledged_cost_cents to create_hosting), interval, trial length, and features. |
list_invoices |
read | List the account's invoices (one per order), newest first, with each one's date, state (PAID, PARTIALLY_REFUNDED, REFUNDED or UNPAID), net total in cents and the domains on it. |
list_mailboxes |
read | List a domain's email hosting mailboxes (real inboxes at the domain, read over IMAP/POP or webmail), with status, address, whether each is the free trial, and expires. |
list_marketplace |
read | Browse domains for sale on Porkbun Marketplace: names other Porkbun customers listed themselves, not new registrations. |
list_transfers |
read | List all in-progress and recent inbound domain transfers for the authenticated account. |
list_url_forwards |
read | List all URL forwarding rules configured for a domain. |
list_webhook_deliveries |
read | List recent webhook delivery attempts (newest first), across all endpoints or filtered to one. |
list_webhooks |
read | List the webhook endpoints registered on the authenticated account (its secret is withheld on this server). |
list_wp_credentials |
read | CLOUD FOR WORDPRESS ONLY. |
make_hosting_dir |
write | Create a directory (and any missing parent directories) at path in a domain's Secure Static Hosting space. |
mock_call |
read | Get a schema-accurate EXAMPLE response for any API endpoint with NO credentials: nothing to set up. |
ping |
read | Verify the Porkbun API connection and credentials. |
preflight_domain |
read | Ask whether a change is about to break a domain, BEFORE making it. |
prepare_transfer |
write | Create the Porkbun DNS zone for a domain whose inbound transfer is held, so records can be added before the domain moves. |
preview_cloudflare_move |
read | Show exactly which DNS records would be copied into Cloudflare for a domain, and which would be dropped, WITHOUT queueing anything. |
read_doc |
read | Read a Porkbun documentation page as Markdown. |
register_domain |
money | Spends account credit, or pays directly by card (pay_with_card). Registers a new domain on the authenticated Porkbun account. |
renew_domain |
money | Spends account credit, or pays directly by card (pay_with_card). Renews an existing domain in the authenticated account. |
renew_mailbox |
money | Spends account credit, or pays directly by card (pay_with_card). Add a year to an email hosting mailbox now, at its renewalPrice from list_mailboxes ($36.00, or a legacy price for mailboxes that still qualify). |
resend_webhook |
write | Re-queue a past webhook delivery to its endpoint. |
restore_dns_zone |
write | Put a domain's DNS zone back to a saved restore point. |
retry_cloudflare_domain |
write | Re-queue a domain whose Cloudflare move failed or was skipped. |
rollback_cloudflare_domain |
write | UNDO a completed Cloudflare move: point the domain's nameservers back at Porkbun, restoring the DNS Porkbun still holds. |
rotate_webhook_secret |
money | Generate a new signing secret for a webhook endpoint and return the endpoint with the new secret. |
scan_dns_records |
read | Discover the DNS records a domain currently publishes by querying its live authoritative nameservers. |
search_aftermarket |
read | Find registered domains that are FOR SALE on the aftermarket: Afternic, Sedo, Atom, NameBright and DAX listings and auctions, plus Porkbun Marketplace, merged to the lowest price per name. |
search_closeouts |
read | Search expired-domain closeouts: names that did not sell at auction and are now offered at a fixed price that descends on a schedule. |
search_docs |
read | Keyword-search the full Porkbun API reference and return the most relevant sections (endpoints/topics) as Markdown. |
send_email_verification_code |
write | Email a verification code to the email address on the user's Porkbun account, to clear VERIFICATION_REQUIRED (purchases and top-ups need a verified email and phone). |
send_phone_verification_code |
write | Text a verification code to the phone number on the user's Porkbun account, to clear VERIFICATION_REQUIRED (purchases and top-ups need a verified phone and email, and new accounts are not asked to verify the phone at signup). |
set_cloudflare_proxy |
write | Turn the Cloudflare proxy (orange cloud) on or off for a domain's DNS records. |
set_cloudflare_zone_settings |
write | Change Cloudflare zone settings for a moved domain. |
set_email_password |
money | Change the password of an email hosting mailbox (an active one, from list_mailboxes). |
setup_mailbox |
money | Give a waiting mailbox (already paid for, or the free trial that comes with a domain registration) its address and password. |
start_transfer |
money | Release a held inbound transfer to the registry. |
test_webhook |
write | Send a webhook.test event to an endpoint to confirm it's reachable and that signature verification works. |
top_up_account_credit |
money | Charges the user's saved payment method (amount_cents is integer cents: 803 charges $8.03; tell the user the dollar figure first) and adds the money to their Porkbun account credit immediately. |
top_up_with_card_mpp |
money | Get a payment link that adds account credit when paid with the user's CARD over MPP (Machine Payments Protocol), using a Stripe Shared Payment Token from their agent wallet (Stripe's Link Agent Wallet). |
transfer_domain |
money | Spends account credit, or pays directly by card (pay_with_card). Initiates a transfer of an external domain into Porkbun. |
update_auto_renew |
money | Turn auto-renewal on or off for a domain in the authenticated account. |
update_contacts |
money | Edit a domain's contacts. |
update_dns_record |
write | Update an existing DNS record by its numeric record_id (obtained from list_dns_records). |
update_glue_record |
write | Update the IP addresses associated with an existing glue record. |
update_nameservers |
write | Replace the nameservers for a domain in the authenticated account. |
update_transfer_auth_code |
write | Replace the authorization code on an inbound transfer that stalled because the code was wrong, and re-queue it: instead of cancelling, refunding and resubmitting. |
update_webhook |
write | Update a webhook endpoint. |
How a tool is classified
src/mcp/policy.ts derives the class where that is safe: a tool Porkbun
marks readOnlyHint: true is read. Only MONEY and WRITE are kept by
hand, each tool named once, and a tool without readOnlyHint in neither stops
the server at startup, so a new Porkbun tool that might spend is looked at
before it ships. Email and phone verification is write: the code reaches you,
never the assistant. export_dns_zone is read: it lists records and writes a
file.
Left out
The four sandbox tools (create_sandbox_key, sandbox_topup,
sandbox_reset, sandbox_trigger_webhook) answer only to a sandbox key, and
this server holds a live one; Porkbun's own live-key profile leaves them out
too. Four more are left out on purpose (src/infra/vendor/offer.ts), with
every purchase's pay_with_usdc and usdc_checkout_id arguments: a USDC
payment needs a wallet key in the server's environment, or a checkout paid
where this server cannot see it, and an API key handed to the assistant is a
leaked key.
| Tool | Why it is not offered |
|---|---|
top_up_with_usdc |
It adds credit in USDC on Base over x402, a crypto payment this server does not make. |
get_usdc_topup_status |
It reads a USDC checkout, which nothing here opens. |
request_api_key |
Its answer (requestToken, codeVerifier) is all anyone needs to collect the new live key pair. |
retrieve_api_key |
It hands the assistant a live API key pair. |
Money
Every money tool is marked so in tools/list, and your client asks you to
approve each call (Claude Code does, in every permission mode). With
MCP_CONFIRM=elicit the server asks you itself instead, in one line naming
the action, the domain and the dollars, computed from the integer cents in the
arguments without dividing. A dry run is confirmed too, its line starting
DRY RUN, no charge:. For example:
porkbun: register example.com for $11.08 from Porkbun account credit
porkbun: turn auto top-up ON: charge the saved card $50.00 whenever credit drops below $10.00
porkbun: DRY RUN, no charge: provision hosting PIXIESECURESTATICM2 on example.com at $3.00 per period, auto-renewing
When a call gives two amounts that disagree (cost_cents and its legacy
cost), the line says so, and Porkbun's package refuses the call before
anything is sent.
| Tools | Why they are money |
|---|---|
register_domain, renew_domain, buy_closeout, transfer_domain |
Spend account credit, or your card through Stripe Link. |
create_mailbox, renew_mailbox |
Buy a year of an email mailbox, renewing automatically. |
create_hosting |
A subscription: a 15-day trial on a domain's first provision, then the plan's price at every renewal; a re-provision charges at once. |
top_up_account_credit, top_up_with_card_mpp |
Charge the saved card now, or open a link that charges your card through Stripe Link once you approve it. |
configure_auto_topup |
Lets the card be charged whenever the balance runs low. |
update_auto_renew |
On, a charge at every expiry; off, the domain lapses. |
start_transfer, cancel_transfer |
Release a paid, held transfer (the domain leaves its registrar), or withdraw one and refund its order. |
update_contacts |
A registrant change makes a new owner of record. |
get_ssl_bundle, setup_mailbox |
Save a private key or a generated mailbox password in the outbox, where whoever reads it holds the secret. |
create_wp_credentials |
Mints a WordPress application password, shown once; the administrator role can run code. |
set_email_password |
Every mail app signed in with the old password stops. |
create_webhook, rotate_webhook_secret |
Hand out an endpoint's signing secret; a rotation stops the receiver's old one from verifying. |
The monthly caps, checked first
Porkbun caps an account's API purchases (a spend limit) and the credit added
over the API (a top-up limit) per calendar month. Each is the account
holder's own figure, $100 when they set none, or no cap at all. Porkbun
refuses a call over a cap only once it arrives, so before register_domain,
renew_domain, transfer_domain, buy_closeout, create_mailbox,
renew_mailbox, top_up_account_credit and top_up_with_card_mpp this
server reads the caps (get_api_settings) and refuses a charge over what is
left:
{"error": {"kind": "over_limit", "code": "MONTHLY_SPEND_LIMIT_EXCEEDED", "cap": "spend",
"message": "register_domain would charge $11.08, more than the $5.00 left of this month's monthly API spend limit ($100.00, Porkbun's default for an account that set none; $95.00 spent so far); nothing was sent to Porkbun",
"figures": {"currency": "USD", "charge": "11.08", "limit": "100.00", "used": "95.00", "remaining": "5.00", "source": "default"}}}
code is what Porkbun would have answered. The amount checked comes from the
same argument as the dollars in the confirmation line
(src/domain/charges.ts), so what you approve is what is checked. Within the
cap, the call goes to Porkbun as given, and Porkbun's own check still runs.
Left to Porkbun: a dry run (which reports the cap itself), create_hosting (a
first provision is a free trial, a re-provision is charged, and the arguments
do not say which), a top-up without amount_cents, and configure_auto_topup
and update_auto_renew, which charge later. Caps that cannot be read refuse
the charge rather than guess.
Secrets in results
Outside money tools, every secret field in a result is replaced by a note:
the fields Porkbun hides on its own app-directory connection (redactKeys in
its profiles.js, today secret). That matters for list_webhooks and
get_webhook, which Porkbun marks read-only but which return each endpoint's
signing secret. A secret leaves this server only through a call your client
asked you to approve; the account holder also sees it at
porkbun.com/account/api. A password you pass in a call goes through the
conversation: set_email_password needs one, while create_mailbox and
setup_mailbox can let Porkbun make it. With MCP_CONFIRM=none nobody is
asked, and a deployment that sets it accepts that.
Troubleshooting
config: "no Porkbun API key pair is configured for this server". Set
PORKBUN_API_KEY_FILE and PORKBUN_SECRET_API_KEY_FILE
(Getting the API key pair).
The server will not start: PORKBUN_TOOLSETS names "...", which is not a toolset. Fix the name; the message lists the ones there are
(Toolsets).
The assistant says a tool is not on this server. Its toolset is not in
PORKBUN_TOOLSETS: add it, or unset the variable to load every one.
config naming PORKBUN_OUTBOX_DIR. The tool saves a file and has
nowhere to put it: set the variable to a directory the server may write. A
mailbox call that would generate a password is refused before anything is
bought, since Porkbun shows that password once.
api on one domain while the others work. The domain has no API access:
turn it on under Domain Management, or Opt In All Domains on the API page.
api with IP_NOT_ALLOWED, DOMAIN_NOT_ALLOWED or
INSUFFICIENT_SCOPE. The key's own restrictions refused the call. Change
them at porkbun.com/account/api.
api with INSUFFICIENT_FUNDS. The credit is short by the amount in the
message. Repeat the call with pay_with_card: true, or after a top-up
(top_up_account_credit, or
porkbun.com/account/credit).
api with VERIFICATION_REQUIRED. Verify the account's email and phone
(First run, step 3).
over_limit. Nothing was sent. Only the account holder can raise the
limit, at porkbun.com/account/api; until
then, spend less or wait for the next calendar month.
input or refused. This server's own check (a DS digest of the wrong
length, a DKIM key that does not parse), or Porkbun's package (two amounts
that disagree), refused the arguments. The message names what to change;
nothing was sent.
bad_bundle. The private key Porkbun sent is not the certificate's, so
nothing was saved. Try later; if it persists, download the bundle at
porkbun.com (Domain Management, SSL) and check it.
drift. Porkbun answered without a field this server reads (a bundle's
privatekey, an invoice's contentBase64, the caps). Nothing was saved or
charged; please report it.
network means Porkbun did not answer: retry in a minute. egress
means a host outside MCP_EGRESS_ALLOW, which needs api.porkbun.com and
porkbun.com.
How it works
Wrapped, not ported
Every other server in this repository models its API by hand or from a published schema. This one wraps Porkbun's package instead, on purpose:
- Porkbun owns the API. Its tool descriptions carry its own rules (the
exact price a registration must echo, how a held transfer avoids an
outage, what
INSUFFICIENT_FUNDSmeans). A port would be a second copy, always a little behind. - It moves fast. On npm, 0.22.0 to 0.52.0 took seven weeks.
- What it lacks is around the tools: where the keys come from, who may call, what is logged, what a client must confirm, how a failure reads.
So the names, descriptions, input schemas, annotations and handlers are
Porkbun's, read from the pinned package at startup (src/infra/vendor.ts).
The package ships no type declarations, so each export used is parsed: a
version that changes a shape stops the server rather than run on a guess.
Results are Porkbun's JSON; a failure is
{ "error": { kind, message, remedy, code?, status? } }, sorted from
Porkbun's error text in src/infra/porkbunPort/vendorErrors.ts. Porkbun's
server instructions are sent at initialize unchanged.
Files instead of typed bytes
Ten Porkbun tools are wrapped (WRAPPED in src/mcp/register.ts), each
checking at startup that Porkbun's tool still takes what the wrapper sends.
Four keep bytes out of the model's hands:
deploy_site: Porkbun's tool takes each file as base64 in the arguments, where one wrong character publishes a broken asset unnoticed. Here a file is{ path, local_path }(inside the server's file roots) or{ path, content_base64, sha256 }(written by a script, the hash checked), at most 10 MB. The answer addssent_files, each file's sha256 and size as sent; a file that cannot be read or does not match uploads nothing.get_ssl_bundle: the private key must belong to the leaf certificate, and both are saved as<domain>.fullchain.pemand<domain>.privkey.pem(insave_dir, a folder of the outbox, if given). The answer gives each file's path, size and sha256, and the certificate's subject, names, issuer, validity and fingerprint.get_invoice_pdf: Porkbun's base64, tens of thousands of characters, is decoded strictly, must start like a PDF and match the size Porkbun states, and is saved under Porkbun's file name. Porkbun's other fields stay, the 15-minutedownloadUrlamong them.create_mailboxandsetup_mailbox: a password Porkbun generates is saved as<address>.password, and the answer'spasswordsays where, never what. Move it to a password manager and delete the file.
The certificate, the invoice and a zone file are linked in the result (a
resource_link), so a client can open them without reading the JSON; a
mailbox password deliberately is not.
Checks before sending
create_dnssec_record: a DS record that does not match the zone's key breaks the domain for every validating resolver. Givendnskey(the line a signer prints), the DS is computed here (RFC 4034 5.1.4), after cheap checks of the key: an ECDSA key must be on its curve, an Ed25519 or Ed448 key the right length, an RSA modulus 512 to 4096 bits. Typed DS fields must agree, and so must Porkbun's fourkeyData*fields. Without a key, the fields are checked for shape (key tag, algorithm, digest type, a hex digest of 40, 64 or 96 characters). Tested against the examples in RFC 4034, 4509 and 6605.- DKIM and SPF, in
create_dns_record,update_dns_recordand every record ofimport_dns_records: av=DKIM1record must hold a key that parses (RSA of 1024 bits or more, or a 32-byte ed25519 key; an emptyp=revokes). Av=spf1record must have no obvious mistake: an unknown mechanism, a bad address or prefix, an include without a domain, a term afterall, a secondv=spf1orredirect=, more than ten lookups. import_dns_recordsalso takesrecords_file(a JSON array) orzone_file(below) instead of records typed inline, parsed with Porkbun's own record schema; the answer adds the file's sha256, size and count.update_nameserverschecks each name (RFC 1123, fully qualified, not an IP address, no duplicates) and answers withnameservers: { before, after }.
Zone files
export_dns_zone saves what list_dns_records returns as <domain>.zone
(RFC 1035) and answers with its path, size, sha256 and a count per type: a
backup before a large change, a zone to compare over time or move to another
DNS host. Owners are relative to $ORIGIN <domain>., and there is no SOA
(Porkbun serves its own). The same records always give the same bytes, so two
exports compare with diff.
import_dns_records with zone_file reads such a file, or another host's
export: $ORIGIN, $TTL with units, @, relative names, a blank owner,
parentheses, comments and escaped quoted strings. SOA, NS and DNSSEC signing
data are set aside and listed in zone_file.ignored. Nothing is imported in
part: the whole file is refused, with every problem and its line, for a name
outside the domain, a type Porkbun cannot hold (it takes A, AAAA, CNAME,
ALIAS, MX, TXT, SRV, CAA, TLSA, SSHFP, HTTPS and SVCB), a class other than
IN, $INCLUDE, $GENERATE or a line that does not parse.
Updating the pinned version
package.json pins "@porkbunllc/mcp-server": "0.52.0" exactly. It also
brings @x402/core, @x402/evm and viem, loaded only to sign a USDC
payment this server never asks for. Porkbun keeps no changelog: each version
is a commit whose subject starts with the version, in
oborseth/Porkbun-MCP.
- Read those commits and the published diff
(
npm diff --diff=@porkbunllc/mcp-server@0.52.0 --diff=@porkbunllc/mcp-server@<new>), above all every new or changed description: a tool that spends, charges a card or returns a secret goes inMONEY, whatever its annotations say. A new way to pay, or a tool that hands out a key, is a question foroffer.ts; a version that offers USDC in words it does not recognise stops the server at startup. npm install -w @foreverworks/mcp-porkbun --save-exact @porkbunllc/mcp-server@<new>npm run check, thennpx tsx scripts/check-docs.ts --write porkbun. The policy test fails on an unclassified tool, a stale name, or Porkbun's own list of purchase, top-up and secret tools no longer matching the money class;vendorErrors.test.tsfails on a reworded error message.
npx tsx scripts/smoke.ts drives the built server with your key pair,
read-only: ping, list_domains and get_balance, and no write or money
tool, not even as a dry run.
Limitations
- Results are relayed, not modelled. A field Porkbun drops is missing
from the result, except in
get_ssl_bundle,get_invoice_pdfand the caps read before a charge, where it isdrift. - The checks catch a lost character, not every changed one. A changed
character inside an RSA key (DKIM, DNSKEY) still parses as some key, while
an ECDSA key almost always falls off its curve. Prefer
dnskey, which removes the typed digest altogether. The DS is computed for SHA-1, SHA-256 and SHA-384; a GOST or SM3 digest is checked for shape only. - SPF is checked as written. An
includeis not followed, and nothing here queries DNS. Other record types are Porkbun's to validate, and an import driven by a scan imports what Porkbun found without this server seeing it. - Zone files. CAA, TLSA, SSHFP, HTTPS and SVCB are kept as written. ALIAS, which Porkbun exports, is not a type every DNS server reads. Notes become comments on export and are not read back.
- One live key pair. The sandbox tools need Porkbun's own server with a sandbox key. USDC payments and API key minting are not offered: pay from credit or by card, and make keys at porkbun.com/account/api.
Other servers for Porkbun
Searched 2026-10-15. Porkbun publishes its own, and it is the one this server runs:
@porkbunllc/mcp-server(com.porkbun/mcpin the MCP Registry), by Porkbun: stdio, with the key pair in environment variables. Porkbun also hosts it over Streamable HTTP, where an OAuth sign-in issues the client its own API key (scoped or revocable), with narrower endpoints that leave out purchases or top-ups. The account itself adds a monthly spend limit, dry runs, and keys limited by IP, domain and permission.
Community servers cover parts of the same API:
porkbun-mcpon PyPI: domains, DNS, DNSSEC and SSL, read-only unless a variable allows writes.porkbun-mcpon npm: read-only by default.porkbun-mcp-serveron GitHub.
This one adds what Porkbun's server leaves to its client: every tool classified and each money call confirmed in one line (Money), the monthly caps checked before a charge is sent, secrets kept out of results (Secrets in results), files by path instead of base64 a model types (Files instead of typed bytes), payloads checked (Checks before sending), zone file export and import (Zone files), and a service of its own: keys in files, and over HTTP a token per client and an audit line per call.
In return, Porkbun's hosted endpoint gives each connection its own key, which with the account's spend limit is a finer grant than this server's one key pair.