ActiveCampaign MCP
# ohneben's ActiveCampaign MCP
[](https://buymeacoffee.com/ohneben)
---
#### License & checks
[](https://github.com/ohneben/ActiveCampaign-MCP/actions/workflows/ci.yml)
[](./LICENSE.md)
#### MCP registries
[](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.ohneben%2Factivecampaign-mcp/versions/latest)
[](https://mcpservers.org/servers/ohneben/activecampaign-mcp)
[](https://glama.ai/mcp/servers/ohneben/ActiveCampaign-MCP)
**The most complete ActiveCampaign MCP server there is.** Run your entire
[ActiveCampaign](https://www.activecampaign.com/) account in plain language from
**Claude**, **Cursor**, or any other [MCP](https://modelcontextprotocol.io) client.
This [Model Context Protocol](https://modelcontextprotocol.io) server exposes the
**whole ActiveCampaign account API โ ~325 tools** โ covering the v3 REST API plus
the **SMS**, **WhatsApp** and async **Segments** APIs, generated straight from
ActiveCampaign's own OpenAPI definitions. Every tool is **safety-categorized**
(๐ข read-only / ๐ก write / ๐ด destructive) so your assistant knows what an action
does *before* it calls it. It runs over **stdio** (Claude Desktop and other local
launchers) or **Streamable HTTP** (hosted in Docker), and ships with retries,
client-side rate limiting tuned to ActiveCampaign's 5 req/s cap, and request
timeouts so it holds up against a live account.
## Why you'll want this
Some MCP servers just forward a slice of the API. This one is built to be **safe
to hand to an LLM**, **complete**, and **easy to run for real**:
| What you get | Why it matters |
| --- | --- |
| **~325 tools โ the full account API** | Complete coverage of contacts, deals, accounts, lists, tags, custom fields & objects, ecommerce (deep data), campaigns, messages, automations, segments, webhooks, tasks, calendars, site/event tracking, SMS and WhatsApp โ nothing hand-picked or left behind. Other servers stop at ~60. |
| **Curated safety categories** ๐ข / ๐ก / ๐ด | Not naive "GET = safe": `contact sync` is flagged as an **upsert**, a segment-match `POST` as a **read-only query**, tagging a contact as a **link**, and `POST /sms/broadcasts` as **sends messages** โ so the model knows the real blast radius. |
| **Machine-readable MCP annotations** (`readOnlyHint`, `destructiveHint`) | Hosts that honor annotations (Claude included) can auto-trust reads and demand confirmation before anything destructive. |
| **Read-only mode & resource filtering** | Expose only ๐ข read-only tools (`ACTIVECAMPAIGN_READ_ONLY=true`), or narrow to just the groups you use (`ACTIVECAMPAIGN_INCLUDE_GROUPS=contacts,deals,lists`). Tailor the surface per deployment. |
| **Pagination made first-class** | `limit`, `offset`, `orders[โฆ]` and `filters[โฆ]` are injected onto every list endpoint as typed inputs, so the assistant can page, sort and filter without guessing ActiveCampaign's bracket syntax. |
| **Automatic retries with backoff** | Transient `429` / `5xx` responses are retried with jittered exponential backoff, honoring the server's `Retry-After` header. |
| **Built-in rate limiting** | Self-throttles under ActiveCampaign's **5 requests / second** account cap so a burst of tool calls never trips a `429`. |
| **Response-size guard** | Optionally cap huge list responses (`ACTIVECAMPAIGN_MAX_RESPONSE_CHARS`) so one call can't blow the model's context window. |
| **GraphQL passthrough** | A bonus tool for ActiveCampaign's Ecommerce GraphQL API when you want to shape exactly the fields you need in one round-trip. |
| **Two transports: stdio *and* Streamable HTTP** | Use it locally in Claude Desktop, or run one always-on server that any number of MCP clients reach over HTTP. |
| **Docker + docker-compose, health check, auto-restart** | Production-style deployment out of the box: `docker compose up` and it stays up. |
| **Optional bearer-token auth** on the HTTP endpoint | Put the server behind a shared secret the moment it's reachable beyond localhost. |
| **Your token never reaches the model** | The `Api-Token` lives in the server's environment and is injected on every request โ the assistant only ever sees tool inputs and API responses. |
| **Drop-in spec updates** | ActiveCampaign ships newer OpenAPI JSON? Replace the file in `spec/` and rebuild โ new endpoints become new tools automatically, no code changes. |
## How it compares
There are a few ways to reach ActiveCampaign from an AI assistant today. Here's how
this server stacks up against the alternatives:
| | **This server** | Official AC remote MCP | HighLiuk `mcp-server-activecampaign` | `mcp-activecampaign` (PyPI) | CData MCP |
|---|:---:|:---:|:---:|:---:|:---:|
| Approx. tools | **~325** | curated subset | 58 | ~65 | 3 (generic SQL) |
| Full v3 REST coverage | โ
| โ | โ core CRM | โ broad CRM | โ |
| SMS ยท WhatsApp ยท Segments | โ
| โ | โ | โ | โ |
| Ecommerce deep data + GraphQL | โ
| โ | โ | โ | โ |
| Reads **and** writes | โ
| โ
| โ
| โ
| โ read-only |
| Self-hosted on your own infra | โ
| โ vendor cloud | โ
| โ
| โ
|
| Connects directly to the AC API | โ
| โ
| โ
| โ
| โ via JDBC driver |
| `stdio` transport | โ
| โ | โ
| โ
| โ
|
| Streamable-HTTP transport | โ
| โ
| โ | โ | โ |
| Docker + compose + health check | โ
| n/a | โ | โ | โ |
| Curated ๐ข / ๐ก / ๐ด safety categories | โ
| โ | โ partial | โ | n/a |
| `readOnlyHint` / `destructiveHint` | โ
| โ | โ | โ | โ |
| Read-only mode + group filtering | โ
| โ | โ | โ | always read-only |
| Rate limiting + auto-retry (`429`/`5xx`) | โ
| โ | โ | โ | โ |
| Language | TypeScript | โ | TypeScript | Python | Java |
| License | MIT | proprietary | MIT | MIT | MIT |
<sub>โ
= yes ยท โ = partial / not documented ยท โ = no. Compiled from each project's
public documentation; this is an unofficial project, not affiliated with
ActiveCampaign or the projects listed. Tool counts are approximate and move as APIs
evolve.</sub>
**The short version:** the official remote MCP is a great managed on-ramp but runs
in ActiveCampaign's cloud on a curated slice of the API. The community servers are
solid but top out around 60 tools of core CRM, stdio-only, without a deployment
story. This one gives you **the entire account API**, **both transports**, a
**Docker deployment**, and **safety guardrails** โ self-hosted, on your token, MIT.
## What you can do
Once it's connected, ask your assistant things like:
- "Find the contact jane@example.com and show her tags, lists and recent activity."
- "Create a deal for Acme Corp worth $12,000 in the 'Sales' pipeline, stage 'Qualified'."
- "List contacts added this month who opened the last campaign but haven't been tagged 'engaged'."
- "Add the tag 'webinar-2026' to every contact on list 5."
- "Show total ecommerce revenue and the top 10 orders from last week."
- "Create a segment of customers in Germany and count how many match."
- "Draft an SMS broadcast to the 'VIP' list โ but don't send it until I confirm."
Tools are generated automatically from ActiveCampaign's specs and grouped into
๐ข read-only, ๐ก write and ๐ด destructive โ so a well-behaved host can treat each
group differently.
## How it works
```
Claude / Cursor / any MCP client โโMCPโโโบ this server โโHTTPSโโโบ ActiveCampaign API (your account)
```
The server parses the bundled OpenAPI definitions into MCP tools (resolving
`$ref`s and guarding against recursive schemas), tags each with a curated safety
category, and injects your `Api-Token` header on every outgoing request. Your
token stays in the server's environment โ the model never sees or handles it.
## Requirements
- An **ActiveCampaign account with API access** โ your **API URL** and **API
token** from **Settings โ Developer**. See
[Get your API credentials](#get-your-api-credentials).
- **Docker** (Docker Desktop on macOS/Windows) for the quick start below โ or
**Node.js โฅ 18** to [run from source](#run-from-source-stdio-no-docker).
## Quick start (Docker)
**1. Add your credentials.** Copy the example config and fill it in:
```bash
cp .env.example .env
# edit .env โ set ACTIVECAMPAIGN_API_URL and ACTIVECAMPAIGN_API_TOKEN
# โ set MCP_AUTH_TOKEN to a long random string if reachable beyond localhost
```
**2. Start the server:**
```bash
docker compose up -d --build
```
The bundled `docker-compose.yml` binds to `127.0.0.1:8765` only, so the server is
reachable from your machine but not the network.
**3. Confirm it's running:**
```bash
curl -s http://localhost:8765/health
# โ {"status":"ok","server":"activecampaign-mcp","tools":325}
```
**4. Connect your MCP client.** The MCP endpoint is `http://localhost:8765/mcp`.
- **Claude Desktop** โ add a **custom connector** (Settings โ Connectors) pointing
at the URL, or bridge it locally with
[`mcp-remote`](https://www.npmjs.com/package/mcp-remote). Add this under
`mcpServers` in your config, then fully quit and reopen the app:
```json
{
"mcpServers": {
"activecampaign": {
"command": "npx",
"args": [
"mcp-remote",
"http://localhost:8765/mcp",
"--header", "Authorization: Bearer YOUR_MCP_AUTH_TOKEN"
]
}
}
}
```
(Drop the `--header` line if you left `MCP_AUTH_TOKEN` empty.)
- **Claude Code** โ one command:
```bash
claude mcp add --transport http activecampaign http://localhost:8765/mcp
```
- **Claude Cowork** โ shares Claude Code's MCP config, so the command above makes
the tools available there too.
### Prefer a prebuilt image?
Every push to `main` publishes a ready-to-run image to the GitHub Container
Registry, so you can skip the local build entirely:
```bash
docker run -d --name activecampaign-mcp -p 127.0.0.1:8765:8765 --env-file .env \
ghcr.io/ohneben/activecampaign-mcp:latest
```
## Get your API credentials
1. Log in to ActiveCampaign and open **Settings โ Developer**.
2. Copy your **API URL** โ `ACTIVECAMPAIGN_API_URL`. It looks like
`https://your-account.api-us1.com/api/3`. **Use the exact URL shown** โ not all
accounts are on `api-us1`, and custom domains differ.
3. Copy your **API Key** โ `ACTIVECAMPAIGN_API_TOKEN`.
Put both in `.env`. The server injects the token on every request, so your
assistant never sees it.
## Configuration
Everything is set in `.env` (copied from `.env.example`):
| Variable | Required | Default | Description |
|---|---|---|---|
| `ACTIVECAMPAIGN_API_URL` | โ
| โ | Your account's API URL, incl. `/api/3` |
| `ACTIVECAMPAIGN_API_TOKEN` | โ
| โ | Your API token (`Api-Token` header) |
| `MCP_TRANSPORT` | โ | `stdio` | `stdio` or `http` (the Docker image defaults to `http`) |
| `PORT` | โ | `8765` | HTTP listen port |
| `HOST` | โ | `0.0.0.0` | HTTP bind address |
| `MCP_HTTP_PATH` | โ | `/mcp` | HTTP MCP route |
| `MCP_AUTH_TOKEN` | โ | _(off)_ | Require `Authorization: Bearer <token>` on `/mcp` |
| `ACTIVECAMPAIGN_MAX_REQUESTS` | โ | `4` | Client-side requests per window (`0` disables throttling) |
| `ACTIVECAMPAIGN_RATE_WINDOW_MS` | โ | `1000` | Rate-limit window in ms (default: 4 req/s) |
| `ACTIVECAMPAIGN_MAX_RETRIES` | โ | `3` | Retries on `429` / `5xx` / network errors |
| `ACTIVECAMPAIGN_TIMEOUT_MS` | โ | `30000` | Per-attempt request timeout |
| `ACTIVECAMPAIGN_READ_ONLY` | โ | `false` | Expose only ๐ข read-only tools |
| `ACTIVECAMPAIGN_INCLUDE_GROUPS` | โ | _(all)_ | Only expose these resource groups (comma-separated) |
| `ACTIVECAMPAIGN_EXCLUDE_GROUPS` | โ | _(none)_ | Hide these resource groups |
| `ACTIVECAMPAIGN_ENABLE_GRAPHQL` | โ | `true` | Expose the Ecommerce GraphQL passthrough tool |
| `ACTIVECAMPAIGN_GRAPHQL_URL` | โ | _(derived)_ | Override the GraphQL endpoint |
| `ACTIVECAMPAIGN_MAX_RESPONSE_CHARS` | โ | `0` | Truncate responses longer than N chars (`0` = never) |
| `ACTIVECAMPAIGN_SPEC_DIR` | โ | _(bundled)_ | Load OpenAPI files from a different directory |
After changing `.env`, reload with `docker compose up -d --force-recreate`.
Run `npm run list-tools` (no credentials needed) to print the full catalog, the
per-category counts, and the list of resource-group keys you can filter on.
## Tool safety categories
Each tool's description starts with one of these banners and carries the matching
[MCP annotations](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations):
| Banner | Count | `readOnlyHint` | `destructiveHint` | Meaning |
|---|:---:|:---:|:---:|---|
| ๐ข **READ-ONLY** | 155 | `true` | `false` | `GET` โ fetches data only. Safe. |
| ๐ข **READ-ONLY ยท query** | 7 | `true` | `false` | A `POST` that *searches/reports* (segment match, metrics export) โ changes no data. |
| ๐ก **WRITE ยท creates data** | 51 | `false` | `false` | `POST` โ creates a record (not idempotent; may duplicate). |
| ๐ก **WRITE ยท creates or updates** | 1 | `false` | `false` | Idempotent upsert (`contact sync`). |
| ๐ก **WRITE ยท updates data** | 52 | `false` | `false` | `PUT` / `PATCH` โ modifies a record in place. |
| ๐ก **WRITE ยท links records** | 6 | `false` | `false` | Associates records (tag a contact, add to a list/automation). Reversible. |
| ๐ก **WRITE ยท unlinks records** | 5 | `false` | `false` | Removes an association (untag, remove from list). Reversible. |
| ๐ก **WRITE ยท sends messages** | 3 | `false` | `false` | Sends/schedules an outbound SMS or WhatsApp message. |
| ๐ก **GraphQL** | 1 | `false` | `false` | Arbitrary GraphQL document (may contain mutations). |
| ๐ด **DESTRUCTIVE ยท deletes data** | 42 | `false` | `true` | `DELETE` โ removes a record. Confirm first. |
| ๐ด **DESTRUCTIVE ยท bulk delete** | 2 | `false` | `true` | Deletes many records at once. Confirm first. |
That's **162 read-only ยท 119 write ยท 44 destructive = 325 tools.** Hosts that
respect annotations (Claude included) can require confirmation for
`destructiveHint` tools and trust `readOnlyHint` tools automatically. Prefer to
lock it down further? Set `ACTIVECAMPAIGN_READ_ONLY=true` to expose *only* the 162
read-only tools.
<details>
<summary><strong>Coverage by area (๐ข read / ๐ก write / ๐ด destructive)</strong></summary>
| Area | ๐ข Read | ๐ก Write | ๐ด Delete | Tools |
|---|:---:|:---:|:---:|:---:|
| Custom Fields & Objects | 18 | 25 | 9 | **52** |
| Deals & Pipelines | 19 | 23 | 9 | **51** |
| Contacts | 30 | 11 | 2 | **43** |
| Campaigns & Messaging | 15 | 10 | 4 | **29** |
| Admin & Delivery | 12 | 8 | 6 | **26** |
| Accounts | 8 | 13 | 4 | **25** |
| Segments | 16 | 3 | 1 | **20** |
| Ecommerce (Deep Data) | 9 | 6 | 3 | **18** |
| SMS | 12 | 4 | 1 | **17** |
| Lists, Tags & Forms | 8 | 5 | 2 | **15** |
| Site & Event Tracking | 7 | 5 | 2 | **14** |
| WhatsApp | 7 | 5 | 1 | **13** |
| Automations | 1 | 0 | 0 | **1** |
| GraphQL passthrough | โ | 1 | โ | **1** |
| **Total** | **162** | **119** | **44** | **325** |
</details>
## Run from source (stdio, no Docker)
Prefer the classic stdio mode for Claude Desktop? Build it locally:
```bash
npm install
npm run build
```
Then point Claude Desktop at the compiled entrypoint in
`claude_desktop_config.json`:
```json
{
"mcpServers": {
"activecampaign": {
"command": "node",
"args": ["/ABSOLUTE/PATH/ActiveCampaign-MCP/dist/index.js"],
"env": {
"MCP_TRANSPORT": "stdio",
"ACTIVECAMPAIGN_API_URL": "https://your-account.api-us1.com/api/3",
"ACTIVECAMPAIGN_API_TOKEN": "your-api-token"
}
}
}
}
```
## Keeping the specs current
The bundled files under `spec/` are the source of truth for the tools:
| File | API | Auth header |
|---|---|---|
| `v3.json` | Core v3 REST API | `Api-Token` |
| `sms.json` | SMS Broadcast API | `Api-Token` |
| `whatsapp.json` | WhatsApp channel API | `Token` (also sends `Api-Token`) |
| `segments-crud.json`, `segments-match-one.json`, `segments-match-all-some.json` | Async Segments API | `Api-Token` |
To refresh against a newer API version, drop the updated OpenAPI JSON in place (or
point `ACTIVECAMPAIGN_SPEC_DIR` at a directory using the same filenames) and
rebuild. New paths become new tools automatically โ no code changes needed.
## Development
```bash
npm install
npm run build # compile TypeScript โ dist/
npm test # run the Vitest suite
npm run list-tools # print the categorized tool catalog (no credentials needed)
```
CI builds and tests every push across Node 20 and 22; pushes to `main` also
publish a Docker image to the GitHub Container Registry.
## Notes & conventions
- **Transports**: `MCP_TRANSPORT=stdio` (default) for local launchers;
`MCP_TRANSPORT=http` for the always-on Streamable-HTTP server the Docker image
runs.
- **Paging / sorting / filtering**: list tools accept `limit` (max 100), `offset`,
`orders` (`{"email":"ASC"}`) and `filters` (`{"name":"ecom"}`). For very large
contact exports ActiveCampaign recommends `orders={"id":"ASC"}` + `id_greater`.
- **Rate limit**: ActiveCampaign allows 5 requests / second per account (shared
across every key); the server self-throttles at `ACTIVECAMPAIGN_MAX_REQUESTS`
per `ACTIVECAMPAIGN_RATE_WINDOW_MS` (default 4 / 1 s) and retries any `429` it
still receives, honoring `Retry-After`.
- **Request bodies**: write tools take a `body` argument; its schema is resolved
from the spec and shown to the model (e.g. create-contact expects
`{"contact": {โฆ}}`).
## Security
- Your API token lives only in `.env`, which is git-ignored. **Never commit real
secrets.** The token grants full account access โ if it leaks, rotate it in
**ActiveCampaign โ Settings โ Developer**.
- The HTTP endpoint is unauthenticated by default (fine on localhost). To expose
it beyond your machine, set `MCP_AUTH_TOKEN` and send it as an
`Authorization: Bearer <token>` header โ ideally behind TLS.
- Delete and **send** (SMS/WhatsApp) tools carry the right annotations so a
well-behaved host prompts before acting โ keep that confirmation on, or run in
`ACTIVECAMPAIGN_READ_ONLY=true` mode.
See [SECURITY.md](./SECURITY.md) for the full policy and how to report a
vulnerability.
## Credits & license
An unofficial community integration for
[ActiveCampaign](https://www.activecampaign.com/); not affiliated with or endorsed
by ActiveCampaign. Built on the [Model Context Protocol](https://modelcontextprotocol.io).
Tools are generated from ActiveCampaign's public OpenAPI definitions. Licensed
under the [MIT License](./LICENSE.md).
TDQS
Scored across 325 tools
Tool names frequently collide or use vague verbs (e.g., `saved_responses`, `local_events`, `forms_1`), and `retrieve_a_savedresponse` is actually a destructive delete. Variants like `list_all_calendar_feeds` vs `list_all_calendar_feeds_1` and multiple count-history endpoints are nearly indistinguishable, so agents will frequently select the wrong tool.
Naming follows no consistent pattern: `get_`, `list_`, `retrieve_`, `create_`, `add_`, `edit_`, `set_`, `remove_`, and `delete_` are used interchangeably, with arbitrary suffixes like `_1` and `_new`. Some tools are bare nouns (`brandings`, `saved_responses`, `local_events`), and one tool (`retrieve_a_savedresponse`) is misnamed as a read but actually deletes.
Exposing 325 tools is far beyond what an agent can sensibly navigate; this is a raw API surface dump rather than a curated tool set. Even broad platforms typically need only a fraction of this count.
The surface is extensive across contacts, deals, ecommerce, segments, and custom objects, but common resources lack full CRUD: there is no delete campaign, no update list, no create/delete branding, and forms/templates are read-only. These gaps will cause agent failures when attempting standard lifecycle operations.