What is a cold email MCP server? The cold-email MCP server for coding agents
A hosted, streamable-HTTP MCP server exposing 28 intent-level cold-email infrastructure tools, scoped by per-tenant bearer token and live in production. This page documents the exact tool surface — verified against the live tools/list response, not aspirational.
What this MCP server does
Most cold-email MCP servers in the wild re-export a vendor's full API surface — a well-known example ships 100+ tools. This one is a curated, high-level facade: 28 intents that hide domain-registrar, mailbox-vendor, and sequencing-engine plumbing behind calls an agent can reason about in one context window. Content generation — the offer, the subject lines, the sequence body — stays your agent's job; this server owns infrastructure provisioning, sequencing, isolation, and compliance guardrails. Five of the 28 (get_dashboard, configure_dashboard, label_thread, list_campaigns, activity) are the same facade the optional human dashboard at /app uses — parity by design, never dashboard-only state. For how that tool count compares to larger vendor-wrapper MCP servers, see Smartlead vs Instantly MCP tool coverage.
How to add it
Streamable HTTP, JSON-RPC 2.0 (initialize, tools/list, tools/call), one config line, no OAuth dance. Get a token first via POST /signup (see the setup guide), then add:
{
"mcpServers": {
"agent-cold-email": {
"url": "https://api.coldrig.dev/mcp",
"headers": {
"Authorization": "Bearer <your-token>"
}
}
}
}
The endpoint resolves your token fresh on every call — there is no session or cache that could leak another tenant's data. The same tool names and same per-tenant auth are also reachable as plain REST (see openapi.yaml) if your agent talks HTTP directly instead of MCP.
The 28 tools — exact schemas
Verified directly against the live tools/list response and openapi.yaml. Two additional helpers described in the design spec (write_sequence, suggest_domains) are not implemented — don't assume they exist.
setup_infrastructure
Buy branded lookalike domains, provision mailboxes, and start the warmup ramp. Domain purchase, DNS, provisioning, and warmup run before the response, not in the background — a `provisioning` field in the response names any work still owed; poll infrastructure_status or re-call this tool to progress it. Resend the same idempotencyKey (or omit it identically) to retry safely — the call converges onto what's already provisioned rather than buying again; a distinct idempotencyKey provisions additional infrastructure instead.
{
brand: string (1-200 chars),
primaryDomain: string (3-253 chars),
domains: integer (1-20),
inboxesEach: integer (1-10),
persona: string (1-200 chars),
physicalAddress: string (1-500 chars), // CAN-SPAM footer address
senderIdentity: string (1-200 chars), // legal sender identity, captured at setup
quoteOnly?: boolean, // true previews the new count + price, provisions nothing
registerDomains?: boolean, // omit = leave prior consent unchanged; true = opt in (requires registrant); false = revoke
registrant?: { firstName, lastName, email, phone, addressLine1, city, state, country, postalCode, organization? }
}
// the first 7 fields are required; quoteOnly/registerDomains/registrant are optional
infrastructure_status
Provisioning + warmup progress, per-mailbox health, and send-readiness. No parameters.
launch_campaign
Create and activate a campaign against a lead list. The caller supplies the offer and sequence step content — this platform does not generate outreach copy.
{
name: string (1-200 chars),
offer: string (1-2000 chars),
leads: [ { email: string, firstName: string (1-200 chars), company?: string } ], // 1-5000 items
sequence: [ { step: integer (>=1), subject: string (1-300 chars),
body: string (1-20000 chars), delayDays: integer (0-60) } ], // 1-10 items
timezone: string, // default "UTC"
sendWindow: { startHour: integer (0-23), endHour: integer (0-23) }, // default 0-23
stopOnReply: boolean // default true
}
campaign_results
Sends, replies, bounces, and complaints for one campaign.
{ campaignId: string }
list_campaigns
List every campaign for the tenant with id, name, status, and event counts (sent/reply/bounce/...) — no per-campaign lookup needed. No parameters.
metrics
Account-wide outcome totals across all campaigns (sent, reply, bounce, complaint, unsubscribe, failed, soft_bounce). No parameters. For warmup/deliverability health, use infrastructure_status.
activity
Unified, chronological activity feed merging campaign events (sent/reply/bounce/...) with deliverability control-loop actions (pause/throttle/...).
{
limit?: integer (1-200), // default 50
cursor?: string,
kind?: "event" | "deliverability" // omit for every kind
}
inbox
Unified reply inbox across all mailboxes for the tenant. No parameters.
thread
Full message history for one thread.
{ threadId: string }
reply
Send a reply on an existing thread.
{ threadId: string, body: string (1-20000 chars) }
mark
Mark a thread read, unread, or archived.
{ threadId: string, status: "read" | "unread" | "archived" }
label_thread
Set (or, with label: null, clear) a triage label on an inbox thread — the same labels the dashboard UI shows as chips.
{ threadId: string, label?: string (1-100 chars) | null } // default null
pause
Pause one campaign.
{ campaignId: string }
pause_all
Pause every campaign for the tenant. No parameters.
account
Usage, billing state, quota, and what the AI deliverability control loop has done (paused/throttled mailboxes, burning domains, auto-replacements, recent actions). No parameters.
remove_mailboxes
Downgrade: release your N newest live mailboxes now and lower the billed quantity. acknowledged must be true — the release is immediate for provisioning, but there is no mid-cycle credit; the lower price takes effect at your next renewal (minimum 5 mailboxes / $99). To add mailboxes instead, use setup_infrastructure or configure_byo_domain's request_managed_mailboxes.
{
count: integer (1-60),
acknowledged: true
}
get_dashboard
List every saved dashboard view (id, name, isDefault, rev, editedBy) or, with id, fetch one view's full layout + rev. The agent-controlled counterpart to the optional human dashboard at /app.
{ id?: string (1-200 chars) } // omit to list every view
configure_dashboard
Create, update, promote-to-default, or delete a dashboard saved view. update requires the rev you last read; a stale rev returns a structured conflict (currentRev + currentLayout) so you can rebase and retry.
{
action: "create" | "update" | "promote" | "delete",
id?: string, // required for update/promote/delete
name?: string (1-200 chars), // required for create; optional rename on update
rev?: integer, // required for update (rev-CAS)
layout?: { schemaVersion: 1, widgets: [...] }, // required for create/update
note?: string (0-2000 chars)
}
get_webhooks
List every outbound webhook subscription, or, with id, fetch one subscription plus its recent delivery and attempt log. Secrets are never returned on reads — shown once at create/rotate.
{ id?: string (1-200 chars) } // omit to list every subscription
configure_webhook
Create, update, or delete an outbound webhook subscription — push reply, bounce, soft_bounce, complaint, and unsubscribe events (HMAC-SHA256 signed, header X-Coldrig-Signature) to your own HTTPS endpoint. URLs must be https and resolve to a public host.
{
action: "create" | "update" | "delete",
id?: string, // required for update/delete
url?: string, // required for create; optional on update
eventTypes?: ("reply" | "bounce" | "soft_bounce" | "complaint" | "unsubscribe")[], // required for create; 1-5 items
secret?: string (16-200 chars), // omit on create to auto-generate; set to rotate
active?: boolean // default true
}
get_byo_domains
List your bring-your-own domains, or, with id, fetch one domain's full intake detail (pre-flight scan, abuse verdict, consent status). byoStatus progresses pending_kyc|pending_consent|pending_dns → active (or rejected/abandoned).
{ id?: string (1-200 chars) } // omit to list every domain
configure_byo_domain
Register or advance a BYO domain intake (SPEC.md §20). Registration, the pre-flight scan, and consent tracking are live today; real DNS delegation, live reputation checks, and platform-provisioned mailboxes on your own domain are self-serve and automatic once you've gone live, with mailbox send-authorization completing on our side.
{
action: "register" | "poll_dns" | "acknowledge_consent" | "request_managed_mailboxes" | "connect_mailbox",
domain?: string (3-253 chars), // required for register
domainRelationship?: "fresh_standalone" | "subdomain_of_primary" | "is_primary", // required for register
id?: string, // required for poll_dns/acknowledge_consent/request_managed_mailboxes/connect_mailbox
acknowledged?: true, // required (must be true) for acknowledge_consent
count?: integer (1-10), // required for request_managed_mailboxes
personaSlug?: string (1-50 chars), // optional for request_managed_mailboxes
email?: string, // required for connect_mailbox
transport?: { kind: "smtp", host, port, secure, user, pass }
| { kind: "gmail_api", clientId, clientSecret, refreshToken }
| { kind: "ms_graph", mode: "delegated" | "app_only", tenantId, clientId, clientSecret, refreshToken? }
// required for connect_mailbox — declares a mailbox connection YOU already hold; not this platform's own send transport
}
No pricing is published yet for BYO-connected mailboxes specifically, and a burned BYO domain hard-pauses rather than auto-replacing (unlike a burned lookalike domain) — see the BYO domain page for the full risk model.
suppress_lead
Permanently suppress an email address tenant-wide — every current and future campaign — the manual/free-text "stop emailing me" path for opt-outs the strict typed-unsubscribe matcher misses. Cancels every pending send and marks every campaign-lead row for this email suppressed. There is no un-suppress tool.
{
email: string (email),
reason?: "manual", // the only value this tool honestly claims — bounce/complaint/unsubscribe are recorded automatically elsewhere
note?: string (0-2000 chars) // accepted for schema symmetry, not persisted
}
update_lead
Record what you learned about a contact — their reply, your triage — as a durable, contact-level disposition keyed by email, visible across every campaign that lists them. A partial patch: only the fields you pass are changed.
{
email: string (email),
interestStatus?: "none" | "interested" | "meeting_booked" | "not_now" | "not_interested" | "bad_fit" | "out_of_office" | "wrong_person",
notes?: string (0-5000 chars),
tags?: string[] (0-50 items, each 1-100 chars)
}
// at least one of interestStatus, notes, tags is required
list_leads
List/export leads with their contact-level disposition, cursor-paginated. This is the export surface — paginate to dump the full book of business as JSON (no separate CSV endpoint).
{
campaign?: string (1-200 chars),
interestStatus?: "none" | "interested" | "meeting_booked" | "not_now" | "not_interested" | "bad_fit" | "out_of_office" | "wrong_person",
suppressed?: boolean,
replied?: boolean,
cursor?: string (1-500 chars),
limit?: integer (1-200) // default 50
}
Returns { leads[], nextCursor }; each row: leadId, email, firstName, company, campaignId, campaignName, globalStatus, interestStatus, notes, tags, suppressed, lastEventType, lastEventTs, createdAt.
list_messages
List this tenant's system + operator messages (a retryable setup step, a credential going live, an operator notice), cursor-paginated. Unacked messages sort first (newest first within that group), then acked ones (also newest first). infrastructure_status also inlines the newest 5 unacked messages for a quick glance — this is the full paginated surface.
{
cursor?: string (1-500 chars),
limit?: integer (1-200) // default 50
}
Returns { messages[], nextCursor }; each message: id, kind, severity, body, actionHint, source (system|operator), createdAt, readAt (null until acked).
ack_message
Acknowledge a message by id (from list_messages or infrastructure_status's messages[]) — sets it read so it stops surfacing as unacked. Idempotent: acking an already-acked id returns success with no second effect, never an error. 404 if the id doesn't exist for this tenant.
{ messageId: string }
contact_operator
Reach a human operator — files a support ticket and notifies the operator. Works in every account state a tenant token still authenticates in, including dunning-suspended, canceling and canceled (an admin-terminated abuse account is the one exception — its token 401s at auth). The reply arrives as a message on this account (list_messages / infrastructure_status's messages[] — there is no separate reply-fetch call). Retrying with the identical body and urgency within an hour is safe: same ticketId, no second alert — re-sending the same text at a higher urgency is an escalation and files a new ticket. needs_human also bypasses the ~10-minute alert throttle. Rate-limited to 5 calls/hour per tenant.
{
body: string (1-2000 chars),
urgency?: "normal" | "needs_human" // default "normal"
}
Returns { ticketId, note }.
The no-signup CLI demo
npx agent-cold-email demo mints a demo tenant, provisions sample infrastructure, and runs the accelerated sandbox pipeline end to end (warmup, sends, replies, bounces, stop-on-reply) in one command — no signup or token-wrangling required. The npm CLI is live at v0.2.1. The same 28 tools are available over the hosted MCP endpoint or directly over HTTP (quickstart). The demo path stays sandboxed; real sending is live in production, and going live is self-serve — call POST /checkout, pay, and mailbox send-authorization completes on our side.
Auth model
One bearer token per tenant, minted by the unauthenticated POST /signup bootstrap call. Every other tool call requires Authorization: Bearer <token> and resolves server-side to exactly one tenant's isolated state — there is no cross-tenant data access at any layer.
Related
- Run your entire cold email operation with Claude Code — the flagship end-to-end lifecycle guide, plus a reusable Claude Code Skill.
- Run your entire cold email operation with Cursor — exact mcp.json config and Cursor Rules.
- Run your entire cold email operation with Codex — exact config.toml and native AGENTS.md context.
- How to run cold email with your AI coding agent — the full step-by-step walkthrough.
- 28 intent-level tools vs larger vendor MCP surfaces — lifecycle coverage and control tradeoffs.
- Smartlead vs Instantly vs agent-cold-email.
- Docs — quickstart, CLI usage, auth model.
- openapi.yaml — the same 28 intents as REST.
- MCP server card — machine-readable transport + tool list for registry scans.