Mailchimp via MCP: draft, test, and send campaigns; manage audiences and subscribers; pull reports.
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 over the Mailchimp Marketing API v3. Draft, test, and send email campaigns, manage audiences and subscribers, and review post-send analytics from any MCP client. Runs as a stdio process or a local Streamable HTTP server.
| Tool | Description |
|---|---|
mailchimp_account | Account profile, plan, data center, and the Chimp Chatter activity feed. |
mailchimp_audiences | Manage audiences (lists) — read, create/update, per-audience analytics, signup-form config. No delete. |
mailchimp_audience_overview | One-call audience health digest: info, stats, growth history, top email clients, merge-field schema. |
mailchimp_subscribers | Subscriber CRUD + tags/notes/activity. archive is the strongest delete available. |
mailchimp_upsert_subscriber | Add or update a subscriber idempotently with status, merge fields, tags, and an optional note. |
mailchimp_find_subscriber | Locate a subscriber by email in one audience or across the account. |
mailchimp_import_subscribers | Batch add/update subscribers (capped at 500/call). Status defaults to pending. |
mailchimp_segments | CRUD for audience segments (saved, static, fuzzy) plus member listing and batch add/remove. |
mailchimp_merge_fields | Read + create/update custom subscriber attributes. No delete. |
mailchimp_campaigns | Campaign record management: list/get/create/update, replicate, content, checklist, RSS/resend controls. |
mailchimp_send_campaign | Compose and send (or schedule/test) a campaign in one call. |
mailchimp_replicate_campaign | Duplicate a campaign with optional overrides, then draft/test/send/schedule. |
mailchimp_reports | Campaign reports — generic slicer across ten dimensions. |
mailchimp_campaign_report | Post-send analytics digest — headline metrics plus top-N slices in one response. |
mailchimp_templates | Mailchimp-hosted templates: reads work on free; writes require a paid plan. |
mailchimp_files | File Manager (Content Studio) — upload, list, fetch, rename, delete files on Mailchimp's CDN. |
mailchimp_search | Global search across members or campaigns. |
mailchimp_assets (conditional — set MAILCHIMP_ASSETS_DIR) | Local-assets surface — inspect and pre-warm uploads for @assets/<path> references in campaign HTML. |
mailchimp_local_templates (conditional — set MAILCHIMP_TEMPLATES_DIR) | Author and render local .eta templates — the write path for templates on free-tier Mailchimp. |
mailchimp_playbook | Structured procedural playbook merged with live account state. Advice-only. |
| Resource | Description |
|---|---|
mailchimp://account | Account info 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 summary. |
mailchimp://campaigns/{campaignId}/report | Post-send campaign report headline metrics. |
All resource data is also reachable via tools. Large collections (audiences, campaigns) are not exposed as resources — use the list operation on the corresponding tool instead.
| Prompt | Description |
|---|---|
newsletter_from_source | Compose a monthly editorial newsletter from a URL or brief, chaining into mailchimp_playbook and the draft → test → send flow. |
Design reference: docs/email-design-playbook.md.
mailchimp_account tooloperation: info returns profile, plan, data center, and total subscribers; operation: activity-feed returns the Chimp Chatter event streamactivity-feed pages via count (max 100, default 20) and offset; each item carries type, timestamp, and a human-readable descriptionnote explaining likely causes instead of a bare empty arraymailchimp_audiences toollist/get/create/update manage audience records; create requires name, contact (company/address1/city/state/zip/country), permissionReminder, and campaignDefaults (fromName/fromEmail/language)list-activity, list-growth, list-clients, list-abuse-reports, list-locationsget-signup-forms/customize-signup-forms manage hosted/embedded signup-form header, content sections, and CSScount caps at 1000 per pagemailchimp_audience_overview toolgrowthMonths of growth history (1–36, default 12), top email clients, and the full merge-field schemanotes[] distinguishes "no data yet" (new audience, no sends) from a genuine empty-engagement signalmailchimp_subscribers toollist/get/update, plus archive (removes from the active audience, preserves the record so the email can resubscribe)set-tags is declarative — the provided set becomes the full active tag list; anything not included is removed unless named in preserveTagsset-tags can silently drop segment membershiplist-notes/add-note/update-note/delete-note manage CRM-style notes; list-activity/list-events/list-goals are engagement readsmailchimp_upsert_subscriber toolpreserveTags protects named tags (including static-segment names) from removalstatus: 'pending' triggers Mailchimp's double opt-in email; 'subscribed' requires documented consentupdateExistingStatus: false applies status only to newly-created subscribers, leaving existing ones untouchedmailchimp_find_subscriber toolaudienceId) or every audience on the account by email, returning separate exactMatches/fuzzyMatches arraysincludeTags: true (default) adds the full active tag list at one extra call per matchmailchimp_import_subscribers toolstatus defaults to pending (double opt-in) to prevent accidental mass-sends; a per-row status overrides the top-level defaultupdateExisting: false (default) skips rows that already exist instead of overwriting themmailchimp_segments toollist-members pages current segment membershipbatch-update-members adds/removes many subscribers from a static segment in one call — not reversible in one shotdelete is exposed here (unlike audiences/merge-fields), since removing a segment doesn't destroy subscriber datamailchimp_merge_fields toollist/get/create/update for custom subscriber attributes (merge tags such as FNAME, limited to 10 characters)options carries type-specific config: choices for dropdown/radio, date_format for date/birthday, phone_format for phonemailchimp_campaigns toollist/get/create/update/replicate for campaign records; get-content/set-content manage the HTML/plaintext payloadget-checklist runs Mailchimp's send-readiness checklist without sending; cancel-send aborts an in-flight sendcreate-resend builds a resend-to-non-openers draft; pause-rss/resume-rss control RSS-driven campaignssend/send-test/schedule/delete — use mailchimp_send_campaign or mailchimp_replicate_campaign for dispatch (checklist-validated, gated on confirmSend: true); delete would destroy report history on a sent campaignset-content accepts html, plainText, templateId + templateSections, an archive payload, a fetch url, or localTemplate (mutually exclusive with html/templateId)mailchimp_send_campaign toolmode defaults to draft; send/schedule require confirmSend: true plus a re-entrant confirmation round before any campaign mutationpre_send_checklist_failed before dispatch; cleanupOnError: true (default) deletes the orphaned draft on any mid-flow failurescheduleTime must be at least 15 minutes in the future; testEmails caps at 50 recipientsmailchimp_replicate_campaign toolconfirmSend: true plus re-entrant confirmation and cleanupOnError semantics as mailchimp_send_campaignoverridesApplied[] in the output lists which overrides actually took effectmailchimp_reports toollist/get are report-index reads; slice pulls one dimension via dimension (abuse-reports, advice, click-details, open-details, domain-performance, eepurl, email-activity, locations, sent-to, unsubscribed)click-details drills into one URL with linkId; open-details drills into one member with subscriberHashget/slice throw campaign_not_sent when the campaign has no send yetmailchimp_campaign_reportmailchimp_campaign_report toolincludeTopN, 1–100, default 10)campaign_not_sent if the campaign hasn't been dispatched yetmailchimp_templates toollist/get/get-default-content are reads that work on free for base/user template types; create/update/delete are paid-tier writes regardless of typegallery (drag-and-drop) is read-gated to paid plans tooname/html/folderId; per-section overrides at send time go through mailchimp_campaigns (set-content) or mailchimp_send_campaign's templateSectionsmailchimp_local_templates for authoring — it works on every plan tiermailchimp_files toolupload/list/get/update/delete manage Mailchimp File Manager (Content Studio); works on every plan tier including freefileData is base64 with no data: prefixfullSizeUrl is the public CDN URL to embed in campaign HTMLupdate with folderId: 0 moves a file to root; folder CRUD isn't exposed — use the Mailchimp UI or upload to rootmailchimp_search toolscope: members matches across all audiences (or one via audienceId); scope: campaigns matches subject/title/preview/archive textexact and fuzzy arrays with separate upstream totalsincludeTopN results (1–100, default 10); use mailchimp_find_subscriber for full subscriber detail and tagsmailchimp_assets toolMAILCHIMP_ASSETS_DIR is setlist/info/sync/clear-cache inspect and pre-warm the local-assets pipeline; most workflows never call this directly@assets/<path> references in campaign HTML auto-upload via mailchimp_send_campaign, mailchimp_replicate_campaign, or mailchimp_campaigns set-content — hash → upload cache misses → cache sha256 → fileId/URL at <assetsDir>/.mailchimp-cache.json → rewrite to the CDN URL.., absolute paths) is rejected; deleting the cache file forces re-upload on next referencemailchimp_local_templates toolMAILCHIMP_TEMPLATES_DIR is setlist/get/render-preview/seed-from-mailchimp manage .eta template files (Eta v4 — partials, conditionals, loops) with optional YAML frontmatter (subject, previewText, vars) or a legacy <name>.meta.yaml sidecar/templates API is read-onlyvars, every declared name must be present in render-preview's vars input or the render fails — undeclared lookups fall back to an empty stringseed-from-mailchimp bootstraps a local template from an existing Mailchimp base/user template by IDcontent.localTemplate + content.localTemplateVars; mutually exclusive with html/templateIdmailchimp_playbook tooltopic selects a procedural playbook: send, post-send-review, deliverability, list-hygiene, onboarding, subscriber-triage, design-campaigninstructions tailored to live account/audience state, a liveState snapshot, and nextToolSuggestions with pre-filled argumentsdesign-campaign includes the editorial-design reference (palette, typography, layout, graphics via CDN) tailored by audience size and engagementmailchimp://account resourceapplication/json — profile, plan, data center, total subscribers, fetchedAt timestampmailchimp_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 (optional — feeds live engagement state into design-campaign), seasonalContext (optional)mailchimp_playbook (topic: design-campaign) for audience-aware design guidance, then walks draft → test → 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 on the API keyMAILCHIMP_CONCURRENCY_LIMIT)Agent-friendly output:
notes/note fields explain empty results (e.g. "no growth history yet" vs. zero engagement)mailchimp_import_subscribers and mailchimp_segments batch operations return per-row succeeded/failed with Mailchimp error codes instead of failing the whole requestoperation/dimension fields on multi-operation tools tell the caller which optional fields are populatedrecovery string describing 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. Mailchimp Marketing API key including -dc suffix (e.g. abc…-us22). | — |
MAILCHIMP_BASE_URL | Override API base URL (for mock servers or tests). | https://{dc}.api.mailchimp.com/3.0 |
MAILCHIMP_TIMEOUT_MS | Per-request timeout in milliseconds. | 60000 |
MAILCHIMP_MAX_RETRIES | Max retry attempts for transient upstream failures (0-10). | 3 |
MAILCHIMP_CONCURRENCY_LIMIT | Max in-flight upstream requests across all tools in this process (1-10). | 4 |
MAILCHIMP_ASSETS_DIR | Absolute path to a local assets directory. Enables mailchimp_assets and auto-upload of @assets/<path> references in campaign HTML. Node-only. | unset |
MAILCHIMP_TEMPLATES_DIR | Absolute path to a local templates directory. Enables mailchimp_local_templates and content.localTemplate on campaign tools. Node-only. | unset |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | auto, stateful, or stateless; auto resolves to stateful. HTTP requires stateful for campaign confirmation and rejects a stateless override. Stdio is unaffected. | stateful |
MCP_HTTP_HOST | HTTP server hostname. | 127.0.0.1 |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_HTTP_ENDPOINT_PATH | MCP endpoint path. | /mcp |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Set MAILCHIMP_ASSETS_DIR to enable a local-image workflow on top of Mailchimp's File Manager. Drop image files into the directory, reference them in HTML as @assets/<relative-path>, and the server uploads + rewrites at send time.
export MAILCHIMP_ASSETS_DIR=/Users/me/Pictures/email-assets
Then in a campaign:
<img src="@assets/hero.png" alt="Hero">
<a href="@assets/whitepaper.pdf">Download</a>
When mailchimp_send_campaign (or mailchimp_campaigns set-content / mailchimp_replicate_campaign contentOverride) sees these references, it:
mailchimp_files tool surface.sha256 → file_id + URL at <assetsDir>/.mailchimp-cache.json (atomic writes; safe to delete to force re-upload).@assets/<path> to the public CDN URL before passing content upstream.The mailchimp_assets tool exposes list, info, sync (pre-warm), and clear-cache for direct inspection — most workflows don't need it.
Caveats:
mailchimp_files tool description. WebP and AVIF are NOT in the allowlist — convert to PNG/JPG.../ and absolute paths throw Forbidden).mailchimp_assets tool is Node-only.Set MAILCHIMP_TEMPLATES_DIR to enable a local-template authoring workflow on top of Eta (v4 — fast, ESM-native, supports partials/conditionals/loops). This is the canonical write path for templates on free-tier Mailchimp accounts, where the upstream /templates API is read-only.
export MAILCHIMP_TEMPLATES_DIR=/Users/me/email-templates
email-templates/
welcome.eta # body + optional YAML frontmatter
newsletter.eta
partials/
header.eta
footer.eta
Template (welcome.eta) — YAML frontmatter on top, Eta body below:
---
subject: "Welcome to {{brand}}"
previewText: "Onboarding starts here"
vars:
- firstName
- brand
---
<%~ include('partials/header', it) %>
<h1>Hello <%= it.firstName %></h1>
<p>Welcome to <%= it.brand %>.</p>
<img src="@assets/hero.png" alt="Hero">
Frontmatter and its fields are optional. When vars: is present, every listed variable must be supplied; undeclared variable lookups render as an empty string.
Sidecar fallback (legacy): prior to v0.3.1, meta lived in a separate
<name>.meta.yamlfile next to the body. That form still works for backward compatibility — if a.etahas no frontmatter, the loader falls back to reading the sidecar. Frontmatter takes precedence when both exist.
Reference from any campaign tool:
{
"audienceId": "abc123",
"subject": "Welcome to Acme",
"fromName": "Casey",
"replyTo": "casey@acme.com",
"content": {
"localTemplate": "welcome",
"localTemplateVars": { "firstName": "Sam", "brand": "Acme" }
},
"mode": "draft"
}
The render pipeline:
welcome.eta with it = { firstName: 'Sam', brand: 'Acme' }.@assets/hero.png is uploaded to Mailchimp File Manager and rewritten to a CDN URL.set-content.The mailchimp_local_templates tool exposes list, get, render-preview (returns HTML without sending), and seed-from-mailchimp (reads a Mailchimp base/user template by ID and writes it to disk as a starting point — useful on free where you can read but not write upstream).
The templates/ directory holds working examples — point MAILCHIMP_TEMPLATES_DIR at it directly to try them, or copy them into your own dir as a starting point:
| Template | What it shows |
|---|---|
welcome.eta | Minimal body — frontmatter declaring subject / previewText / vars, <%= it.firstName %> interpolation, <% if %> conditional CTA block |
redden-gardens-april-2026.eta | Full inline-styled HTML newsletter. Demonstrates the recommended split: Mailchimp merge tags (*|FNAME|*) for per-recipient personalization on real list sends, Eta vars (volume / issue / monthYear / URLs) for list-wide constants substituted at template-render time |
Caveats:
localTemplate is mutually exclusive with html and templateId on the same content block.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. Stateful HTTP preserves campaign confirmation for 2025-era clients. 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/prompts and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Eighteen always-on tools plus two conditional local-workspace tools. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Four snapshot resources. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). Newsletter starter prompt. |
src/services/mailchimp | Mailchimp client wrapper — HTTP plumbing, retries, normalization, typed surface. |
templates/ | Example .eta templates (frontmatter + Eta syntax) — point MAILCHIMP_TEMPLATES_DIR here to try them. |
tests/ | Vitest coverage for configuration, services, tool workflows, output formatting, framework contracts, and regressions. |
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.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/mailchimp-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-mailchimp-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/mailchimp-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/mailchimp-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.