HonkIO MCP Server
Model Context Protocol (MCP) server for HonkIO, the Canadian SMS API. It lets an AI agent send SMS, run phone verification, manage Canadian numbers, and handle CASL compliance through natural language.
41 tools, 4 resources, and 5 guided prompts, all backed by the live HonkIO REST API.
Requirements
- Node.js 20 or newer
- A HonkIO account and API key
1. Get an API key
Sign in at https://honkio.ca/dashboard/keys and copy a key:
mk_test_… | Test | Messages are simulated as delivered. No SMS is sent and nothing is charged. |
mk_live_… | Live | Real SMS, real Canadian numbers, real charges against your balance. |
Start with a test key. Every tool works in test mode, so an agent can exercise the whole surface for free. Switch to a live key only when you want real delivery.
Live sending also requires that the account owner has completed phone verification, and that you have provisioned at least one number to send from.
2. Connect your AI tool
There is nothing to install. npx fetches the server on first use and caches it.
In every example below, put your own key in HONKIO_API_KEY.
Claude Code
From inside your project:
claude mcp add honkio \
--env HONKIO_API_KEY=mk_test_YOUR_KEY_HERE \
-- npx -y @honkio/mcp
Or commit a .mcp.json at the project root to share it with your team (keep the real key out of git — see Keeping your key out of git):
{
"mcpServers": {
"honkio": {
"command": "npx",
"args": ["-y", "@honkio/mcp"],
"env": { "HONKIO_API_KEY": "mk_test_YOUR_KEY_HERE" }
}
}
}
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"honkio": {
"command": "npx",
"args": ["-y", "@honkio/mcp"],
"env": { "HONKIO_API_KEY": "mk_test_YOUR_KEY_HERE" }
}
}
}
Restart Claude Desktop afterwards.
Cursor
Edit .cursor/mcp.json in your project, or ~/.cursor/mcp.json globally:
{
"mcpServers": {
"honkio": {
"command": "npx",
"args": ["-y", "@honkio/mcp"],
"env": { "HONKIO_API_KEY": "mk_test_YOUR_KEY_HERE" }
}
}
}
VS Code (GitHub Copilot)
Edit .vscode/mcp.json in your workspace:
{
"servers": {
"honkio": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@honkio/mcp"],
"env": { "HONKIO_API_KEY": "mk_test_YOUR_KEY_HERE" }
}
}
}
3. Confirm it works
Ask your agent:
List my HonkIO phone numbers.
- A list (or an empty list) means you are connected.
Error [UNAUTHORIZED]: Invalid or missing API key. means the key is wrong, missing, or has been revoked.
To check the server outside any AI tool:
HONKIO_API_KEY=mk_test_YOUR_KEY npx -y @honkio/mcp
It should start and wait silently on stdin — an MCP server speaks JSON-RPC over stdio, so no output is the healthy state. Press Ctrl-C to exit. If it exits immediately with an error, the message tells you what is wrong.
Environment variables
HONKIO_API_KEY | Yes | Your API key (mk_live_… or mk_test_…). The server refuses to start without it. |
HONKIO_API_URL | No | Override the API base URL. Defaults to https://api.honkio.ca. Only needed for self-hosting or local development. |
Keeping your key out of git
.mcp.json and .vscode/mcp.json are usually committed. An API key pasted into one is a live credential in your repository history. Either keep those files untracked, or reference an environment variable your shell already exports and add the file to .gitignore.
If a key does leak, revoke it immediately at https://honkio.ca/dashboard/keys — a revoked key stops working at once.
Tools
Messages
send_sms | Send an SMS from one of your numbers. Enforces CASL consent unless overridden. Pass idempotency_key when retrying. A reserved exchange (555-XXXX, N11, test codes) comes back 201 FAILED with error_code: RESERVED_DESTINATION and is billed. |
list_messages | List messages, filterable by number, status, direction and date. |
get_message | Full detail for one message, including delivery status and cost. |
Both directions are billed: sending an SMS charges per segment, and receiving one on a provisioned number charges a flat per-message cost — get_pricing returns both figures. Inbound charges happen automatically whenever someone texts your number, independent of any tool call.
Phone verification (OTP)
start_verification | Send a one-time code to a phone number. |
check_verification | Submit a code to verify it. |
get_verification | Status of a single verification attempt. |
list_verifications | List verification attempts. |
On a live key, verification is billed per message segment plus a per-verification surcharge — get_pricing returns the current figure. On a test key nothing is sent and nothing is charged, and the code is always 000000, padded to the requested length. Every verification carries a mode of LIVE or TEST, so you never have to infer which happened.
Pricing
get_pricing | Current per-segment outbound SMS cost, per-segment inbound SMS cost, verification upcharge, phone-number first-month/monthly rent, and the one-time activation fee, in CAD cents. |
Prices are set at runtime and change without a release. Read them rather than hardcoding them.
Phone numbers
search_phone_numbers | Search available Canadian numbers by area code. |
provision_phone_number | Purchase a number. Charges the first month plus a one-time activation fee, then monthly rent. |
list_phone_numbers | List the numbers on your account. |
get_phone_number | Detail for one number. |
release_phone_number | Release a number and stop its recurring rent. |
CASL compliance
record_consent | Record express or implied consent. |
list_consents | List consent records. |
check_consent | Check whether a number has valid consent. |
revoke_consent | Revoke consent for a number. |
record_opt_out | Record an opt-out manually. |
list_opt_outs | List opt-out records. |
DNCL — not available yet
check_dncl | Returns 501 DNCL_COMING_SOON. |
batch_check_dncl | Returns 501 DNCL_COMING_SOON. |
CRTC Do Not Call List checking is not live. dncl_exemptions on send_sms is accepted but not yet acted upon.
Webhooks
create_webhook | Register an endpoint for delivery receipts and inbound messages. |
list_webhooks | List registered webhooks. |
update_webhook | Change a webhook's URL or subscribed events. |
delete_webhook | Remove a webhook. |
list_webhook_deliveries | Delivery attempts for a webhook, for debugging. |
list_webhook_dead_letters | Deliveries that exhausted their retries. |
replay_webhook_dead_letter | Retry a dead-lettered delivery. |
discard_webhook_dead_letter | Drop a dead-lettered delivery. |
reactivate_webhook | Re-enable a webhook disabled by repeated failures. |
Sending limits
get_send_limit | Daily cap (rolling 24 h), usage, the per-recipient cap (recipient_rate_per_hour / recipient_rate_per_day), probation status, any active pause, and past volume requests. |
request_send_limit | File a "request a higher volume" for staff review once probation has ended. |
get_topup_allowance | How much credit can be added right now under the balance and 30-day top-up caps. |
New accounts can send 250 live messages per rolling 24 hours; 30 days after the first live send they can request more. Identical messages to many recipients, per-number rate, link shorteners, and unusually high opt-out or failure rates are also limited — see honkio.ca/docs#limits. Refused sends return DAILY_LIMIT_REACHED, FANOUT_LIMIT_REACHED, NUMBER_RATE_LIMITED, RECIPIENT_RATE_LIMITED (30 an hour / 100 a day to one number), SENDING_PAUSED, UNDELIVERABLE_NUMBER (three consecutive carrier failures list a number for 90 days) or LINK_SHORTENER_BLOCKED with details. A send to a reserved exchange (555-XXXX, N11, carrier test codes) is not refused: it comes back 201 as a billed FAILED row with error_code: RESERVED_DESTINATION.
Account and keys
whoami | Identify the account the API key belongs to — id, name, balance, status. |
get_account | Account detail and credit balance. |
update_account | Update the account name. |
get_usage | Usage for a billing period, including live delivery health (delivery: delivered, failed, undelivered, pending, failureRatePct; byNumber: the same per sending number). |
create_api_key | Issue a new live or test key. |
rotate_api_key | Replace a key, invalidating the old value. |
revoke_api_key | Revoke a key immediately. |
account_id is optional on all of these. Omit it and the server resolves your account from the API key, so "show my usage for this month" just works. Pass one explicitly only if you are deliberately targeting a different account. Use whoami if you want to see the id itself.
PIPEDA
request_erasure | Execute a right-to-erasure request for a phone number. |
Resources
Readable by MCP clients that support resources:
honkio://messages | 20 most recent messages |
honkio://phone-numbers | Your provisioned numbers |
honkio://consents | Active CASL consent records |
honkio://webhooks | Registered webhook endpoints |
Prompts
Guided multi-step workflows:
send_compliant_sms | Check consent, then send |
provision_canadian_number | Search, then purchase |
setup_webhooks | Register event notifications |
compliance_audit | Full compliance check on a number |
handle_erasure_request | Process a PIPEDA erasure request |
Things to try
Find me an available Toronto (416) number and tell me what it costs before buying anything.
Check whether +1514XXXXXXX has valid CASL consent, and if it does, text them that
their appointment is confirmed for 2pm tomorrow.
Start a phone verification for +1604XXXXXXX, then check the code 123456 against it.
Set up a webhook at https://myapp.ca/webhooks/honkio for delivery receipts and
inbound messages.
Show me any webhook deliveries that failed and ended up in the dead-letter queue.
Spending money by accident
Two tools move real money on a live key:
provision_phone_number charges the first month's rent plus a one-time activation fee, together, and starts monthly rent. Rent keeps accruing until you call release_phone_number; the activation fee is not refunded on release.
send_sms and start_verification charge per SMS part against your balance, counted the way the carrier splits the body (typographic quotes and dashes are smart-encoded to GSM-7; emoji force Unicode parts). The charge is settled to the carrier's part count after the send. A send the carrier rejects outright costs nothing; a message the carrier accepts but cannot deliver keeps its charge, and so does a send to a reserved exchange (555-XXXX and similar), which is blocked here and returned as a FAILED row. A body over 10 parts is refused with 422 MESSAGE_TOO_LONG before any charge. start_verification adds a per-verification surcharge on top. get_pricing tells you what each costs right now.
A third charge is not tool-triggered at all: inbound SMS is billed per segment the moment a carrier delivers it to one of your provisioned numbers, whether or not you ever call a tool. Traffic to a number you provisioned draws down your balance on its own (STOP/START/HELP replies are not charged), and a received message is debited even when the balance cannot cover it — the balance goes negative and sends are frozen until the next top-up.
Agents act on instructions that can be vaguer than you intended. If you are exploring, use a test key — the tools behave the same and charge nothing.
A test-mode send still comes back with status DELIVERED, because it simulates a successful delivery. That is not a claim that a phone received anything. The mode field on the response — LIVE or TEST — is the one that tells you whether an SMS actually left the building.
Canadian compliance
- CASL — commercial messages need consent on record. Use
record_consent before sending; send_sms enforces it unless you pass skip_consent_check. Express consent does not expire; implied consent expires two years after the last transaction.
- DNCL — CRTC Do Not Call List checking is not available yet and is not enforced.
- PIPEDA — customer data is stored in Canada (
ca-central-1). Request processing currently runs on infrastructure outside Canada, so data crosses the border in transit; see the privacy policy for the full disclosure. Use request_erasure for right-to-erasure requests.
Troubleshooting
Error [UNAUTHORIZED] | Key is wrong, revoked, or HONKIO_API_KEY never reached the server. Check the env block in your config. |
| Tools do not appear in the agent | The client did not start the server. Restart the client, and check that npx is on its PATH — GUI apps do not always inherit your shell's PATH. |
| First start is slow, or times out once | npx downloads the package on first use. Run npx -y @honkio/mcp once in a terminal to warm the cache, then restart your client. |
npm ERR! 404 @honkio/mcp | Usually an npm registry override or a private proxy. Check npm config get registry. |
Error [PAYMENT_REQUIRED] | The account needs an initial top-up before live use. |
Error [ACCOUNT_NOT_VERIFIED] | Live sending needs the account owner's phone verified. Do it in the dashboard. |
501 DNCL_COMING_SOON | Expected. DNCL checking is not live yet. |
| Server starts then exits silently | That is normal when nothing is attached to stdin. It only means something is wrong if it prints an error. |
Development
To work on the server rather than just use it:
git clone https://github.com/jeffcaldwellca/honkio
cd honkio/mcp
npm install
npm run dev
npm run typecheck
npm run build
Point a local checkout at your own API with HONKIO_API_URL=http://localhost:3000, and at a local build by using node /path/to/honkio/mcp/dist/index.js as the command in your client config instead of npx.
dist/ is gitignored and the published tarball is built from it, so prepublishOnly rebuilds on every npm publish — never publish without letting it run.
License
MIT