Mailchimp via MCP: draft, test, and send campaigns; manage audiences and subscribers; pull reports.
Runs on your machine from its package; the steps are below.
At most: moves money, runs code or commands, deletes data, or sends messages for you.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y @cyanheads/mailchimp-mcp-server
MCP client config
{
"mcpServers": {
"mailchimp": {
"command": "npx",
"args": [
"-y",
"@cyanheads/mailchimp-mcp-server"
]
}
}
}Run
npx -y @cyanheads/mailchimp-mcp-server
MCP client config
{
"mcpServers": {
"mailchimp": {
"command": "npx",
"args": [
"-y",
"@cyanheads/mailchimp-mcp-server"
]
}
}
}Draft, test, and send Mailchimp campaigns straight from your MCP client — with audience management, subscriber CRUD, and post-send analytics behind safe-by-default send gates. STDIO or Streamable HTTP.
Mailchimp campaign management. Draft, test, and send email campaigns, manage audiences and subscribers, and review post-send analytics, with optional local image and template directories for authoring.
| Tool | Description |
|---|---|
mailchimp_account | Account profile, plan, data center, and the Chimp Chatter activity feed |
mailchimp_audiences | Read, create, and update audiences (lists), with per-audience analytics and signup-form config |
mailchimp_audience_overview | One-call audience digest: info, stats, growth history, top email clients, merge-field schema |
mailchimp_subscribers | Subscriber reads, updates, archive, tags, notes, and engagement history |
mailchimp_upsert_subscriber | Add or update one subscriber idempotently, with status, merge fields, tags, and a note |
mailchimp_find_subscriber | Locate a subscriber by email in one audience or across the account |
mailchimp_import_subscribers | Batch add or update up to 500 subscribers per call, pending by default |
mailchimp_segments | Segment CRUD (saved, static, fuzzy), member listing, and batch add/remove |
mailchimp_merge_fields | Read, create, and update custom subscriber attributes |
mailchimp_campaigns | Campaign records, content, send checklist, and RSS/resend controls |
mailchimp_send_campaign | Compose a campaign and leave it as a draft, test it, send it, or schedule it in one call |
mailchimp_replicate_campaign | Duplicate a campaign with overrides, then draft, test, send, or schedule |
mailchimp_reports | Campaign reports: the index, one report, or one of ten report dimensions |
mailchimp_campaign_report | Post-send digest: headline metrics plus top links, locations, and unsubscribes |
mailchimp_templates | Mailchimp-hosted templates; reads work on free plans, writes need a paid plan |
mailchimp_files | File Manager uploads, listing, renames, and deletes on Mailchimp's CDN |
mailchimp_search | Search members or campaigns across the account |
mailchimp_assets (conditional: MAILCHIMP_ASSETS_DIR) | Inspect and pre-warm uploads for @assets/<path> references in campaign HTML |
mailchimp_local_templates (conditional: MAILCHIMP_TEMPLATES_DIR) | Author and render local .eta templates, the template write path on free plans |
mailchimp_playbook | Procedural playbooks merged with live account state; advice only |
| Resource | Description |
|---|---|
mailchimp://account | Account snapshot: profile, plan, data center, total subscribers |
mailchimp://audiences/{audienceId} | Audience snapshot: name, contact, stats, double opt-in status |
mailchimp://campaigns/{campaignId} | Campaign snapshot: status, settings, recipients |
mailchimp://campaigns/{campaignId}/report | Post-send headline metrics for a campaign |
All resource data is also reachable through tools; audiences and campaigns are listed with each tool's list operation.
| Prompt | Description |
|---|---|
newsletter_from_source | Compose an editorial newsletter from a URL or brief, then walk it through draft, test, and send |
Design reference: docs/email-design-playbook.md.
mailchimp_account tooloperation: info returns profile, plan, data center, total subscribers, and industry benchmarks; operation: activity-feed returns Chimp Chatter events, paged by count (max 100, default 20) and offsetnote naming the likely causesmailchimp_audiences toollist, get, create, update, the analytics reads list-activity, list-growth, list-clients, list-abuse-reports, list-locations, and get-signup-forms / customize-signup-forms (takes signupFormConfig); every operation except list and create needs audienceIdcreate requires name, contact (company, address1, city, state, zip, country), permissionReminder, and campaignDefaults (fromName, fromEmail, language)mailchimp_audience_overview toolaudienceId plus growthMonths (1–36, default 12); returns info, stats, contact, campaign defaults, growth history, top email clients, and the merge-field schemanotes[] explains empty growth or email-client arrays, separating "no data yet" from zero engagementmailchimp_subscribers toolaudienceId on every call and email on every operation except list (filterable by status); operations: list, get, update, archive, list-tags, set-tags, list-notes / add-note / update-note / delete-note (by noteId), and list-activity / list-events / list-goalslist email stays unfiltered. update to subscribed asks for confirmation when resuming a subscription; already-subscribed members are left as they areset-tags is declarative: tags becomes the full active set and every other tag is removed unless named in preserveTags (static-segment membership is stored as a tag). Returns tagsAdded, tagsRemoved, and tagsActivemailchimp_upsert_subscriber toolaudienceId, email, and status required; optional mergeFields, tags + preserveTags (same declarative sync as set-tags), note, vip, language; updateExistingStatus: false applies status to new records onlyisNew, the resulting status, tagsAdded / tagsRemoved / tagsFinal, noteAttached, and webUrlstatus: "pending" uses Mailchimp's double opt-in flow. New or resumed subscribed status asks for confirmation of documented consent before anything is written; declining writes nothing. Already-subscribed updates and updateExistingStatus: false keep the existing status without askingmailchimp_find_subscriber toolemail accepts an address or free-form search fragment, including a fragment without @, scoped to one audienceId or, when omitted, every audience on the account (echoed as searchedAcross)exactMatches and fuzzyMatches with merge fields and stats; includeTags (default true) adds each match's tags at one extra call per matchmailchimp_import_subscribers toolaudienceId and 1–500 subscribers rows per call; status defaults to pending (double opt-in) and a per-row status overrides it; updateExisting defaults to false, so existing records are skippedsubscribed needs one confirmation before the whole batch writes; declining writes nothing. Pending-only imports need nonetotalCreated, totalUpdated, errorCount, and per-row succeeded and failed entries; incomplete addresses fail as invalid_email, and a batch with no valid rows makes no upstream requestmailchimp_segments toolaudienceId on every call and segmentId on every operation except list (filterable by type: saved, static, fuzzy) and create; create / update take name, staticEmails, or saved-segment optionsbatch-update-members applies membersToAdd / membersToRemove to a static segment and returns added, removed, and per-email errorsmailchimp_forbiddenmailchimp_merge_fields toollist, get, create, update; create requires name, tag (10 characters max), and type; get / update take mergeIdoptions carries type-specific config: choices for dropdown / radio, date_format for date / birthday, phone_format for phonemailchimp_campaigns toollist (filter by status, listId, sinceSendTime / beforeSendTime), get, create (type of regular / plaintext / rss, plus recipients and settings), update, replicate, get-content, set-content, get-checklist, cancel-send, create-resend, pause-rss, resume-rss; all but list and create take campaignIdset-content accepts html, plainText, templateId + templateSections, archiveContent, url, or localTemplate (exclusive with html and templateId); get-checklist returns isReady and typed itemsget-content accepts variant: html | plainText | archiveHtml | all (default all); empty bodies are omittedmailchimp_send_campaign or mailchimp_replicate_campaignmailchimp_send_campaign toolaudienceId, subject, fromName, replyTo, and content (at least one of html, plainText, templateId, localTemplate) required; mode is draft (default), test (needs testEmails, max 50), send, or schedule (needs scheduleTime at least 15 minutes out)campaignId, the effective mode, status, recipientCount, webUrl, and non-blocking checklistWarnings; blocking checklist errors throw pre_send_checklist_failed outside draft, and cleanupOnError (default true) deletes the draft after a mid-flow failuresend and schedule require confirmSend: true, then a single-use confirmation prompt before any campaign is created; declining leaves a draft and sets cancelledByUser. Changed content or a replay asks again, and creation is never retried after an ambiguous upstream failuremailchimp_replicate_campaign toolsourceCampaignId plus optional subjectOverride, previewTextOverride, fromNameOverride, replyToOverride, titleOverride, audienceOverride, segmentOverride, and contentOverride; mode, testEmails, scheduleTime, and cleanupOnError work as in mailchimp_send_campaigncampaignId, overridesApplied[], and the same status, checklist, and cancelledByUser fieldsconfirmSend: true plus confirmation-prompt gate for send and schedulemailchimp_reports toollist (filter by type, sinceSendTime / beforeSendTime), get, or slice with a campaignId and a dimension: abuse-reports, advice, click-details (one URL via linkId), open-details (one member via subscriberHash), domain-performance, eepurl, email-activity, locations, sent-to, unsubscribedslice returns rows shaped by the dimension plus totalItems; get throws campaign_not_sent for a campaign with no sendmailchimp_campaign_report toolcampaignId plus includeTopN (1–100, default 10) rows per slicedelivery (delivered, bounces by type, abuse reports), engagement (opens, clicks, unsubscribes), topClickedLinks, topLocations, recentUnsubscribes, and industryBenchmarks when Mailchimp has them; throws campaign_not_sent for a campaign with no sendmailchimp_templates toollist (filter by type of user / base / gallery, category, folderId), get, get-default-content, create, update, and delete; update accepts only name, html, and folderId, so per-section edits go through a campaign's templateSectionsbase and user templates work on free plans; writes and gallery reads need a paid plan and fail as mailchimp_forbidden. mailchimp_local_templates is the authoring path on every planmailchimp_files toollist (filter by type, folderId, sinceCreatedAt / beforeCreatedAt), get, upload (name with extension plus base64 fileData, no data: prefix), update (rename, or move with folderId, 0 for root), delete, and read-only list-folders / get-folder; uploads cap at 1 MB per image and 10 MB per other file, and .webp / .avif are not acceptedupload and get return fullSizeUrl, the public CDN URL to embed in campaign HTML; list adds totalFileSizeInBytesmailchimp_search toolscope (members or campaigns) and query; audienceId narrows a member search; includeTopN (1–100, default 10) caps each result arrayexact and fuzzy with upstream totalExact / totalFuzzy; campaigns as matches with a snippet and totalMatches; an empty result carries a notemailchimp_assets toollist, info (by relPath; returns sha256 and any cached upload), sync (uploads uncached files ahead of a send; returns uploaded, cached, and skipped with reasons), and clear-cache@assets/<path> references in content passed to mailchimp_send_campaign, mailchimp_replicate_campaign, or mailchimp_campaigns set-content upload to File Manager and are rewritten to CDN URLs. Uploads are cached by SHA-256 in <assetsDir>/.mailchimp-cache.json; oversize files and paths outside the directory are rejected before uploadMAILCHIMP_ASSETS_DIR is setmailchimp_local_templates toollist, get (source plus parsed metadata), render-preview (name plus vars; returns HTML, sends nothing), and seed-from-mailchimp (writes Mailchimp template mailchimpTemplateId to disk as name).eta files with optional YAML frontmatter for subject, previewText, and vars; a declared var left unsupplied fails the render. Campaign tools render one via content.localTemplate + content.localTemplateVars, exclusive with html and templateId. Example templatesMAILCHIMP_TEMPLATES_DIR is setmailchimp_playbook tooltopic: send, post-send-review, deliverability, list-hygiene, onboarding, subscriber-triage, or design-campaign; audienceId for send / list-hygiene / design-campaign, campaignId for post-send-review, email for subscriber-triageinstructions tuned to live numbers, a liveState snapshot, and nextToolSuggestions with pre-filled inputs; makes no writesmailchimp://account resourceapplication/json with profile, plan, data center, total subscribers, and fetchedAtmailchimp_account operation: infomailchimp://audiences/{audienceId} resourceaudienceId comes from mailchimp_audiences operation: listmailchimp://campaigns/{campaignId} resourcecampaignId comes from mailchimp_campaigns operation: listmailchimp://campaigns/{campaignId}/report resourcenewsletter_from_source promptsource (URL or free-form brief) required; audienceId and seasonalContext optionalmailchimp_playbook (topic: design-campaign) into an HTML draft, then draft, test, and send via mailchimp_send_campaignBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Mailchimp-specific:
-<dc> suffix, with retries, a process-wide concurrency limit (MAILCHIMP_CONCURRENCY_LIMIT), and a cooldown after a 429draft, send / schedule need confirmSend: true plus a confirmation prompt, and new or resumed subscribed subscriptions need confirmation too; imports default to pendingarchive is the strongest subscriber removal@assets/<path> files upload and rewrite at send time, and .eta templates render locally, which covers free plans where the Templates API is read-onlycount (max 1000) and offsetAgent-friendly output:
note / notes[] on empty activity feeds, audience overviews, and searches say whether data is missing or genuinely zeromailchimp_import_subscribers and mailchimp_segments batch-update-members return per-row successes and errors instead of failing the batchoperation, dimension, and mode say which fields are populated; isNew and cancelledByUser report what actually happenedcampaign_not_sent, pre_send_checklist_failed, mailchimp_forbidden, …) carries a recovery hint for the caller's next moveAdd the following to your MCP client configuration file. See docs/api-key.md for how to generate a Mailchimp API key.
{
"mcpServers": {
"mailchimp-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/mailchimp-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"MAILCHIMP_API_KEY": "your-key-with-dc-suffix-e.g.-us22"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"mailchimp-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/mailchimp-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"MAILCHIMP_API_KEY": "your-key-with-dc-suffix-e.g.-us22"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"mailchimp-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "MAILCHIMP_API_KEY=your-key-with-dc-suffix-e.g.-us22",
"ghcr.io/cyanheads/mailchimp-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 MAILCHIMP_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcp
-dc suffix (e.g. -us22) identifies your data center and is parsed at startup.git clone https://github.com/cyanheads/mailchimp-mcp-server.git
cd mailchimp-mcp-server
bun install
cp .env.example .env
# edit .env and set MAILCHIMP_API_KEY
| Variable | Description | Default |
|---|---|---|
MAILCHIMP_API_KEY | Required. Marketing API key, including the -dc suffix (e.g. abc…-us22). | none |
MAILCHIMP_BASE_URL | API base URL override, for mock servers or tests. | https://{dc}.api.mailchimp.com/3.0 |
MAILCHIMP_TIMEOUT_MS | Per-request timeout, in ms. | 60000 |
MAILCHIMP_MAX_RETRIES | Retry attempts for transient upstream failures (0–10). | 3 |
MAILCHIMP_CONCURRENCY_LIMIT | Max in-flight upstream requests across the process (1–10). | 4 |
MAILCHIMP_ASSETS_DIR | Absolute path to a local assets directory. Enables mailchimp_assets and @assets/<path> uploads in campaign HTML. | none |
MAILCHIMP_TEMPLATES_DIR | Absolute path to a local templates directory. Enables mailchimp_local_templates and content.localTemplate on campaign tools. | none |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | Session mode; confirmations need stateful, and stateless refuses HTTP startup (stdio is unaffected). | stateful |
MCP_REQUEST_STATE_KEY | Optional sealing key, at least 32 bytes. Confirmations are single-use and tied to one process, so a restart or another instance asks again. | none |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <app-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry. | false |
OTEL_EXPORTER_OTLP_ENDPOINT | Base URL for traces and metrics. | none |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | OTLP log endpoint; log export is off unless this is set. | none |
LOG_TOOL_FAILURE_PAYLOADS | Log failed-call arguments and results, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). Secrets inside free-form values remain visible. | false |
See .env.example for the full list of optional overrides.
Watch mode (transport via MCP_TRANSPORT_TYPE):
bun run dev # stdio (default)
MCP_TRANSPORT_TYPE=http bun run dev # http
Build and run:
bun run rebuild
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t mailchimp-mcp-server .
docker run --rm -e MAILCHIMP_API_KEY=your-key-us22 -p 3010:3010 mailchimp-mcp-server
The Dockerfile defaults to HTTP transport, stateful session mode, and logs to /var/log/mailchimp-mcp-server. OpenTelemetry peer dependencies are installed by default; build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point: registers tools, resources, and the prompt, and initializes services. |
src/config | MAILCHIMP_* environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) plus shared helpers for template rendering, asset rewriting, and send confirmation. |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). |
src/services/mailchimp | Mailchimp API client: HTTP, request pacing, retries, response normalization. |
src/services/assets | Local assets: discovery, hashing, upload cache, @assets/ rewriting. |
src/services/templates | Local templates: Eta rendering, frontmatter parsing, seeding from Mailchimp. |
templates/ | Example .eta templates; point MAILCHIMP_TEMPLATES_DIR here to try them. |
docs/ | API key guide and the email design playbook. |
tests/ | Vitest suites for config, services, tools, and the prompt. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped loggingsrc/mcp-server/*/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.