MCP Server
Connect any MCP-compatible agent runtime to the Molted Email API with the @molted/mcp server package.
The @molted/mcp package exposes the Molted Email API as discoverable tools for any runtime that supports the Model Context Protocol. Claude Desktop, Cursor, VS Code Copilot, Windsurf, and any other MCP client can use it to send email, manage threads, classify inbound messages, and more.
Installation
npx -y @molted/mcpOr install globally:
npm install -g @molted/mcpEnvironment variables
| Variable | Required | Default | Description |
|---|---|---|---|
MOLTED_API_KEY | Yes | - | Your Molted API key (mm_live_* or mm_test_*) |
MOLTED_API_URL | No | https://api.molted.email | API base URL |
Setup
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"molted": {
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}Cursor
Add to .cursor/mcp.json in your project:
{
"mcpServers": {
"molted": {
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}VS Code Copilot
Add to .vscode/mcp.json in your project:
{
"servers": {
"molted": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"molted": {
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}HTTP transport
For cloud agents or server-side use, run in HTTP mode:
MOLTED_API_KEY=mm_live_... molted-mcp --transport http --port 3100The HTTP transport is stateless -- each request creates a fresh MCP session. This works well for serverless deployments and load-balanced environments.
Tools
The MCP server exposes 44 tools across eight groups (budget, threads, sending, intelligence, governance, analytics, journeys and segments, coordination), plus 8 resources and 5 prompts.
Budget
check_budget
Check remaining send capacity including daily/hourly limits and bounce/complaint budget. Call this before planning a campaign or batch send to verify you have capacity.
Parameters: None
Returns: { daily: { used, remaining }, hourly: { used, remaining }, negativeBudget: { bounces, complaints } }
Inbox and threads
read_inbox
List threads in a mailbox with optional filtering.
| Parameter | Type | Required | Description |
|---|---|---|---|
mailbox_id | string | No | Filter by mailbox UUID |
status | string | No | Filter by status: open, waiting, resolved, escalated |
limit | number | No | Max results (1-50, default 25) |
Returns: { items: [{ id, contactEmail, subject, status, messageCount, lastMessageAt }], total }
get_thread
Get a full thread with all messages, intent classification, and SLA status.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID |
Returns: { thread: { id, status, contactEmail, ... }, envelopedMessages: [...] }
reply
Reply to a thread. Supports two modes:
- Shorthand: Provide
body(plain text) orhtml-- uses the_defaulttemplate with an auto-generated dedupe key. - Template: Provide
template_id,payload, anddedupe_keyfor custom templates.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID to reply to |
body | string | No | Plain text reply body (shorthand mode) |
html | string | No | HTML reply body (shorthand mode) |
subject | string | No | Override reply subject |
template_id | string | No | Template ID (template mode) |
payload | object | No | Template payload (template mode) |
dedupe_key | string | No | Deduplication key |
send_reason | string | No | Reason for sending (audit trail) |
Returns: { status: "queued" | "sent", requestId }
archive
Archive a resolved thread, moving it out of the active inbox.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID |
Returns: { archived: 1 }
update_thread
Update a thread's status, metadata, or assignment.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID |
status | string | No | New status: open, waiting, resolved, escalated |
metadata | object | No | Metadata to set |
assigned_agent_id | string | No | Agent to assign to |
Returns: The updated thread object.
approve
Approve a pending send in the approval queue (human-in-the-loop).
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID with pending approval |
reason | string | Yes | Reason for approving (audit trail) |
Returns: Approval confirmation.
Sending
send_email
Send a policy-checked email. Supports shorthand (body/html) and template modes.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email |
body | string | No | Plain text body (shorthand) |
html | string | No | HTML body (shorthand) |
subject | string | No | Subject line (shorthand) |
template_id | string | No | Template ID |
payload | object | No | Template payload |
dedupe_key | string | No | Deduplication key |
send_reason | string | No | Reason for sending |
mailbox_id | string | No | Mailbox to send from |
idempotency_key | string | No | Idempotency key for safe retries |
Returns: { status: "queued" | "blocked" | "pending_approval", requestId }
Policy blocks are returned as successful results with the block reason so your agent can reason about alternatives.
draft_email
Draft an email with AI-assisted composition and policy pre-check.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email |
context | object | No | Background context for draft generation |
Returns: { draftCandidates: [...], policyPreCheck: { allowed, reason }, contactContext }
dry_run
Simulate a send without dispatching. Preview the policy decision.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email |
template_id | string | Yes | Template to simulate |
dedupe_key | string | No | Deduplication key |
payload | object | No | Template payload |
mailbox_id | string | No | Mailbox to simulate from |
Returns: { wouldAllow: boolean, reason?, cooldownExpiresAt?, simulation: true }
batch_send
Send to multiple recipients with per-recipient policy evaluation. Max 500 recipients per batch.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | Yes | Template for all recipients |
sends | array | Yes | Array of { to, dedupe_key, payload?, send_reason? } |
mailbox_id | string | No | Mailbox to send from |
Returns: { batchId, total, queued, blocked, results: [{ recipientEmail, requestId, status, reason? }] }
schedule_followup
Schedule a delayed follow-up email that auto-cancels if the recipient replies.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact to follow up with |
thread_request_id | string | Yes | Request ID from the initial send |
delay_minutes | number | Yes | Delay before sending (1-43200) |
cancel_on_reply | boolean | No | Auto-cancel on reply |
trigger_conditions | object | No | Custom trigger conditions |
Returns: { followupId, status, scheduledAt }
Intelligence
classify_inbound
Classify an inbound email's intent.
| Parameter | Type | Required | Description |
|---|---|---|---|
subject | string | Yes | Email subject line |
body_text | string | Yes | Plain text body |
Returns: { intent, confidence, suggestedAction, flags?, safetyVerdict? }
Intent categories: interested, objection, not_now, support, billing, legal, security, out_of_office.
next_best_action
Get a recommendation for what to do next with a contact based on timeline, fatigue, and intent signals.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact email address |
Returns: { recommendation, reasoning, contactSummary: { email, lastSendAt, isSuppressed } }
Recommendations: reply, wait, nudge, stop, escalate.
get_context
Get full contact timeline including sends, replies, journeys, engagement history, and suppression status.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact email address |
Returns: { timeline: { sends, journeyRuns, inboundMessages }, suppressionStatus, activeIncidents, lastClassification }
batch_classify
Classify multiple inbound emails in one call.
| Parameter | Type | Required | Description |
|---|---|---|---|
messages | array | Yes | Array of { subject, body_text } |
Returns: { results: [{ intent, confidence, suggestedAction }] } in the same order as inputs.
batch_next_action
Get next-best-action recommendations for multiple contacts.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_emails | array | Yes | Array of email addresses |
Returns: { results: [{ contactEmail, recommendation, reasoning }] }
Governance and outcomes
add_suppression
Suppress a contact or domain so no further emails are sent to them. Provide either recipient_email (email-level) or domain (domain-level).
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | No | Email address to suppress |
domain | string | No | Domain to suppress (e.g. example.com) |
scope | string | No | Scope: tenant or campaign (platform-wide global scope is admin-only) |
reason_code | string | No | Reason code: complaint, hard_bounce, manual_dnc, legal_request, role_account, domain_suppressed, no_engagement |
source | string | No | Source of the suppression |
Returns: The created suppression record.
check_suppression
Check if an email address or domain is currently suppressed. Call this before sending to verify the recipient is not on a suppression list.
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | No | Email to check |
domain | string | No | Domain to check |
Returns: Suppression records if found, empty array if not suppressed.
remove_suppression
Remove a suppression so the contact or domain can receive emails again.
| Parameter | Type | Required | Description |
|---|---|---|---|
suppression_id | string | No | UUID of the email suppression to remove |
domain | string | No | Domain suppression to remove |
Returns: { deleted: true }
check_consent
Check consent status for a contact (GDPR/CCPA compliance).
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | Yes | Email address to check |
Returns: { hasConsent: boolean, basis: string }
record_consent
Record consent for GDPR/CCPA compliance.
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | Yes | Email address |
basis | string | Yes | Legal basis: explicit_opt_in, legitimate_interest, contractual, legal_obligation |
source | string | No | Where consent was given (e.g. signup_form) |
jurisdiction | string | No | Legal jurisdiction (e.g. GDPR, CCPA) |
granted_at | string | No | ISO 8601 timestamp when consent was granted |
revoked_at | string | No | ISO 8601 timestamp when consent was revoked |
Returns: The consent record.
record_outcome
Record a business outcome (conversion, revenue, engagement) linked to email touches. Molted automatically attributes the outcome to recent email interactions.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact email |
event_type | string | Yes | activation, trial_conversion, meeting_booked, deal_closed, upsell, or custom |
event_name | string | Yes | Event name (e.g. purchase, demo_booked) |
revenue | number | No | Revenue amount in cents |
metadata | object | No | Additional metadata |
occurred_at | string | No | ISO 8601 timestamp (defaults to now) |
Returns: The ingested outcome record with attribution data.
outcome_dashboard
Get a revenue attribution summary showing how email campaigns contributed to business outcomes.
Parameters: None
Returns: { totalRevenue, conversions, topCampaigns }
journey_impact
Analyze which journeys had the most impact on business outcomes.
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | No | Attribution model: first_touch, last_touch, or linear (default last_touch) |
Returns: Journeys ranked by outcome contribution.
Analytics
fatigue_score
Get the fatigue score (0-100) for a contact. Measures how "tired" a recipient is of your emails based on send frequency, bounces, complaints, reply rate, and days since last engagement.
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The contact email address to check |
Returns: { contactEmail, fatigueScore, factors: { sendFrequency, bounceCount, complaintCount, replyRate, daysSinceLastEngagement }, recommendation }
Thresholds: Above 70 = stop sending. Above 40 = reduce frequency. Below 40 = safe.
send_velocity
Get your recent send volume and velocity trends. Use this to detect if you are ramping up too fast.
Parameters: None
Returns: { lastHour, last24Hours, last7Days, hourlyTrend: [{ hour, count }] }
deliverability
Get email deliverability metrics: delivery rate, bounce rate, and complaint rate.
| Parameter | Type | Required | Description |
|---|---|---|---|
period | string | No | Time period: 24h, 7d, or 30d (default 7d) |
Returns: { period, totalSent, delivered, bounced, complained, deliveryRate, bounceRate, complaintRate }
check_reputation
Check the reputation status of a specific mailbox.
| Parameter | Type | Required | Description |
|---|---|---|---|
mailbox_id | string | Yes | Mailbox UUID |
Returns: { id, mailbox_id, bounce_rate, complaint_rate, total_sends, total_bounces, total_complaints, status, last_calculated_at }
Status values: healthy, warning, paused. A paused mailbox blocks all sends until reputation recovers.
Journeys and segments
create_journey
Create a multi-step email journey (onboarding, nurture, re-engagement). Journeys start in "draft" status.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the journey |
trigger_event | string | Yes | Event name that triggers enrollment (e.g. "signup") |
trigger_conditions | object | No | Optional filter conditions on the event payload |
Returns: { id }
activate_journey
Activate a draft journey or change its status.
| Parameter | Type | Required | Description |
|---|---|---|---|
journey_id | string | Yes | Journey UUID |
status | string | No | Target status: active, paused, or archived (default active) |
Returns: { updated: true }
trigger_journey
Enroll a contact into a journey by ingesting a trigger event. The event is processed asynchronously.
| Parameter | Type | Required | Description |
|---|---|---|---|
event_name | string | Yes | Event name matching a journey's trigger_event |
contact_email | string | Yes | Contact email to enroll |
payload | object | No | Event properties available to journey steps |
Returns: { eventId }
journey_status
Check a journey's configuration, status, and steps.
| Parameter | Type | Required | Description |
|---|---|---|---|
journey_id | string | Yes | Journey UUID |
Returns: { id, name, status, trigger_event, trigger_conditions, steps: [{ id, step_order, step_type, config }] }
add_journey_step
Append or replace a step in an existing journey. Call repeatedly after create_journey to build a sequence, then activate_journey.
| Parameter | Type | Required | Description |
|---|---|---|---|
journey_id | string | Yes | Journey UUID |
step_order | number | Yes | Execution order (1-based). Steps run in ascending order. |
step_type | string | Yes | send, delay, branch, or end |
config | object | No | Step configuration — shape depends on step_type (see below) |
config per step_type:
send:{ templateId, mailboxId?, payload?, dedupeKeyPrefix?, sendReason? }delay:{ delayMinutes }(the worker only reads this spelling — notminutesordelayMs)branch:{ conditions: [{ field, operator, value, nextStepOrder }], defaultNextStepOrder? }end:{}
Returns: { id, step_order, step_type, config }
update_journey_step
Update an existing step. All fields are optional — supply only what you want to change.
| Parameter | Type | Required | Description |
|---|---|---|---|
journey_id | string | Yes | Journey UUID |
step_id | string | Yes | Journey-step UUID |
step_order | number | No | New execution order (1-based) |
step_type | string | No | Change type (rare — usually keep the type and only update config) |
config | object | No | New configuration — same shape as add_journey_step.config. Passing config replaces the previous value (not a shallow merge). |
Returns: updated step.
create_segment
Define an audience segment with AND/OR filter criteria on contact fields, metadata, and behavioral data.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Segment display name |
filter_group | object | Yes | { logic: "and"|"or", filters: [{ type, field, operator, value }] } |
Filter types: contact_field, metadata, account_field, firmographic, behavioral
Operators: eq, neq, gt, gte, lt, lte, contains, not_contains, in, not_in, exists, not_exists, between
Returns: { id, name, filter_group, status }
compute_segment
Queue an asynchronous recomputation of segment membership for all contacts.
| Parameter | Type | Required | Description |
|---|---|---|---|
segment_id | string | Yes | Segment UUID |
Returns: { queued: true, segmentId }
check_membership
Check if a specific contact is a member of a segment. Evaluates live against current data.
| Parameter | Type | Required | Description |
|---|---|---|---|
segment_id | string | Yes | Segment UUID |
contact_email | string | Yes | Contact email to check |
Returns: { isMember: boolean, evaluatedLive: true }
Coordination
Multi-agent coordination tools that prevent simultaneous messaging and enable consensus decisions. Agents must register before using leases, heartbeats, or consensus.
register_agent
Register this agent with the coordination system. If an agent with the same name already exists, its heartbeat is refreshed.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_name | string | Yes | Unique name for this agent within the tenant |
agent_role | string | No | Role of the agent (e.g. sdr, support, marketing) |
Returns: { id, agentName, agentRole, lastHeartbeat, registeredAt }
heartbeat
Signal liveness to the coordination system. Agents that stop sending heartbeats have their leases auto-expired. Call periodically (every 1-2 minutes) while holding leases.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id | string | No | Agent UUID (provide either agent_id or agent_name) |
agent_name | string | No | Agent name (provide either agent_id or agent_name) |
Returns: { ok: true }
acquire_lease
Claim exclusive access to a contact. Prevents other agents from messaging the same contact simultaneously. If another agent already holds the lease, returns a conflict response with the current lease holder details.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id | string | No | Agent UUID (provide either agent_id or agent_name) |
agent_name | string | No | Agent name (provide either agent_id or agent_name) |
contact_email | string | Yes | Email address of the contact to claim |
intent | string | No | Intent for contacting (e.g. outbound_sales, support). Default general |
duration_minutes | number | No | Lease duration in minutes. Default 30 |
Returns: { id, agentId, contactEmail, intent, expiresAt } on success, or { conflict: true, leaseHolder: { agentId, agentName } } if already held.
release_lease
Release a contact lease when done. Frees the contact for other agents to claim.
| Parameter | Type | Required | Description |
|---|---|---|---|
lease_id | string | Yes | UUID of the lease to release |
Returns: { released: true }
list_leases
List all active contact leases in the tenant. Shows which agents hold leases on which contacts, with expiry times.
Parameters: None
Returns: Array of { id, agentId, contactEmail, intent, expiresAt }
create_consensus
Propose a high-risk action for multi-agent consensus. Other agents can vote to approve or reject. Resolved automatically when a majority of active agents have voted.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id | string | No | Proposing agent UUID (provide either agent_id or agent_name) |
agent_name | string | No | Proposing agent name (provide either agent_id or agent_name) |
action | string | Yes | The proposed action (e.g. send_sensitive_email, delete_contact) |
contact_email | string | Yes | Email of the contact this action affects |
reason | string | Yes | Why this action is being proposed |
timeout_minutes | number | No | Time to wait for votes before expiring. Default 5 |
Returns: { id, status, action, contactEmail, reason, timeoutAt }
get_consensus
Get the current state of a consensus request including votes cast and status.
| Parameter | Type | Required | Description |
|---|---|---|---|
consensus_id | string | Yes | Consensus request UUID |
Returns: { id, status, action, contactEmail, reason, timeoutAt }
Status values: pending, approved, rejected, expired.
vote
Cast a vote on a multi-agent consensus request. Each agent can only vote once per request.
| Parameter | Type | Required | Description |
|---|---|---|---|
consensus_id | string | Yes | Consensus request UUID |
agent_id | string | Yes | Voting agent UUID |
vote | string | Yes | approve or reject |
reason | string | No | Reason for the vote |
Returns: { id, vote, consensusStatus }
Resources
Resources provide read-only context that agents can pull into their working memory without calling tools. Access them via resources/list and resources/read in any MCP client.
| URI | Description |
|---|---|
molted://mailboxes | All mailboxes with address, reputation, autonomy, and send limits |
molted://mailboxes/{id}/config | Full mailbox configuration: rules, SLA, alerts, autonomy level |
molted://templates | Available email templates with variable schemas |
molted://templates/{id} | Specific template with version history |
molted://segments | Audience segments with filter definitions |
molted://journeys | Active email journeys with step definitions |
molted://policy-rules | Current policy rules and thresholds |
molted://domains | Verified sending domains with DNS and warmup status |
Resources return JSON data from the Molted API. Template URIs ({id} parameters) auto-populate from the list callback, so MCP clients that support resource templates will show available mailboxes and templates as completions.
Prompts
Prompts are pre-built multi-step workflows that agents can invoke. Each prompt returns a message sequence that guides the agent through chaining multiple tools together.
draft-followup
Given a thread, draft an appropriate follow-up email. Reads the thread, checks budget, and composes a reply matching the conversation tone.
| Argument | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID to follow up on |
mailbox_id | string | No | Mailbox UUID to send from |
triage-inbox
Process unread threads: classify intent, recommend action, and auto-archive resolved/spam/OOO threads.
| Argument | Type | Required | Description |
|---|---|---|---|
mailbox_id | string | No | Mailbox UUID to triage (omit for all) |
max_threads | string | No | Maximum threads to process (default 20) |
pre-send-check
Before sending an email, verify it is safe: check suppression status, consent, and remaining budget. Returns a GO / NO-GO recommendation.
| Argument | Type | Required | Description |
|---|---|---|---|
to_email | string | Yes | Recipient email to validate |
mailbox_id | string | No | Mailbox UUID to send from |
investigate-bounce
Investigate why an email bounced and recommend corrective action. Automatically suppresses hard bounces.
| Argument | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID of the bounced email |
mailbox_id | string | No | Mailbox UUID |
campaign-readiness
Check if conditions are right to send a campaign to a segment: budget capacity, template readiness, domain health, and negative-signal budget.
| Argument | Type | Required | Description |
|---|---|---|---|
segment_id | string | Yes | Segment UUID |
mailbox_id | string | No | Mailbox UUID |
template_id | string | No | Template UUID |
Error handling
The MCP server maps API responses to structured tool results:
| HTTP Status | Behavior |
|---|---|
| 2xx | Success with parsed JSON data |
| 429 | Rate limited -- includes retry timing |
| 403 | Insufficient scope -- key lacks required permissions |
| 401 | Authentication error -- invalid or expired key |
| 404 | Resource not found |
| 5xx | Server error |
Policy blocks (e.g., suppressed contact, rate limit exceeded) are returned as successful results with the block reason. This lets your agent reason about why the send was blocked and decide on alternatives rather than treating it as an error.
Scoped keys
API keys can be scoped to specific mailboxes. When using a scoped key, all tools automatically operate within that mailbox's context. This is useful for multi-tenant setups where each agent should only access its own mailbox. See Authentication for details on key scoping.