.codex-plugin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited .codex-plugin (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/mailchimp-mcp-server</h1> <p><b>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.</b> <div>18 Tools (+2 conditional) • 4 Resources • 1 Prompt</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
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 <sub>tool</sub>operation: 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 <sub>tool</sub>list, 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 <sub>tool</sub>audienceId 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 <sub>tool</sub>audienceId 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 <sub>tool</sub>audienceId, 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 <sub>tool</sub>email 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 <sub>tool</sub>audienceId 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 <sub>tool</sub>audienceId 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 <sub>tool</sub>list, 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 <sub>tool</sub>list (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 <sub>tool</sub>audienceId, 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 <sub>tool</sub>sourceCampaignId 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 <sub>tool</sub>list (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 <sub>tool</sub>campaignId 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 <sub>tool</sub>list (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 <sub>tool</sub>list (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 <sub>tool</sub>scope (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 <sub>tool</sub>list, 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 <sub>tool</sub>list, 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 <sub>tool</sub>topic: 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 <sub>resource</sub>application/json with profile, plan, data center, total subscribers, and fetchedAtmailchimp_account operation: infomailchimp://audiences/{audienceId} <sub>resource</sub>audienceId comes from mailchimp_audiences operation: listmailchimp://campaigns/{campaignId} <sub>resource</sub>campaignId comes from mailchimp_campaigns operation: listmailchimp://campaigns/{campaignId}/report <sub>resource</sub>newsletter_from_source <sub>prompt</sub>source (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.gitcd mailchimp-mcp-serverbun installcp .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.
MCP_TRANSPORT_TYPE): bun run dev # stdio (default)
MCP_TRANSPORT_TYPE=http bun run dev # http bun run rebuild
bun run start:stdio
# or
bun run start:http bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against specdocker build -t mailchimp-mcp-server .
docker run --rm -e MAILCHIMP_API_KEY=your-key-us22 -p 3010:3010 mailchimp-mcp-serverThe 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 testApache-2.0 — see LICENSE for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.