instantly-ai-mcp

instantly-ai-mcp

Enables AI assistants to interact with the Instantly.ai v2 API to manage campaigns, leads, accounts, replies, blocklist, and webhooks, with tools organized into read, write, and dangerous safety tiers.

Category
访问服务器

README

instantly-ai-mcp

An MCP server for the Instantly.ai v2 REST API that encodes what the API actually does instead of trusting what its docs say. Every quirk below was reproduced against the live API, not copied from a changelog or a forum post, and stays checked: npm run verify-gotchas re-probes the live account on demand and flags any claim whose real-world behaviour has drifted from what's documented here (see Why this table is machine-checked - it's a manual check, not part of CI).

The gotchas

This table is the reason the repo exists. Every server built against this API eventually rediscovers these the hard way — usually by staring at an error that reads like the wrong thing. Captured live on 2026-08-21; see Why this table is machine-checked for how it stays honest.

# Claim Verdict
1 Cloudflare rejects the Python-urllib User-Agent with 403 error code: 1010, which reads exactly like an API-key scope failure but is not HOLDS
2 DELETE rejects any request carrying a body or a Content-Type header (body must be null) HOLDS
3 POST /leads/list silently ignores campaign_ids; the working filter is singular campaign HOLDS
4 GET /campaigns/analytics?id= is silently ignored REFUTED
5 The unfiltered GET /campaigns/analytics omits draft campaigns entirely HOLDS
6 The campaign timezone field is a restricted enum: only America/Dawson, America/Chicago, America/Detroit UNVERIFIABLE by the read-only probe
7 Webhook event_type is narrower than the docs: auto_reply_received and link_clicked are documented but rejected with 400 UNVERIFIABLE by the read-only probe
8 Reads are not internally consistent — /leads/list and /campaigns/analytics can contradict each other UNVERIFIABLE (intermittent by nature)

Notes on the interesting rows:

  • #3 — the live probe sent campaign_ids: [id] and got back 5 leads, all 5 belonging to other campaigns. The parameter isn't just ignored, it's silently a no-op filter; the singular campaign parameter is what actually scopes the query. list_leads verifies every returned lead's own campaign field for exactly this reason and warns instead of trusting the filter.
  • #4 — this was recorded HOLDS on 2026-08-17 and flipped to REFUTED on 2026-08-21. ?id= now correctly filters analytics to the single campaign. See below for why that flip is the whole point of this repo.
  • #5 — UNVERIFIABLE on 2026-08-17 (no draft campaign existed in the account to test against), then confirmed HOLDS by the live integration suite (INSTANTLY_LIVE_TEST=1), which creates a throwaway draft campaign and confirms the unfiltered /campaigns/analytics omits it. The HOLDS above is verified that way, not by verify-gotchas's read-only probe: that probe returns UNVERIFIABLE whenever no draft campaign already exists in the account (it never creates one), so running it against a no-draft account is expected to say "could not re-check", not contradict this row. list_campaigns reads from GET /campaigns for this reason — that endpoint does include drafts.
  • #6, #7, #8 are UNVERIFIABLE by the read-only probe on principle, not by accident: #6 and #7 would require a live write (creating a campaign / webhook) that the probe script deliberately never performs against a real account, and #8 is an intermittent read-consistency issue that can't be forced on demand. UNVERIFIABLE is a real, honest outcome here — see below.

Why this table is machine-checked

A hand-maintained quirk list rots. Claim #4 above is the proof: it was recorded as HOLDS on 2026-08-17 and refuted four days later, on 2026-08-21, when Instantly apparently fixed the ?id= parameter server-side. Four days is not a long tail — it's how fast an undocumented API can move under a written-down assumption.

npm run verify-gotchas re-runs every claim's probe against the live API and prints a five-column table (#, Claim, Verdict, Observed, Last checked) — a superset of the three-column summary above, carrying the live probe's raw evidence and the date it ran. That is not the same shape as the table above; don't expect a byte-for-byte match.

Each claim also carries a documented expected verdict (HOLDS for #1–#3 and #5, REFUTED for #4, UNVERIFIABLE for #6–#8) — the currently-documented state, i.e. what this README says today. The script exits non-zero only when a probe's actual verdict has genuinely changed from that expectation (e.g. a documented HOLDS comes back REFUTED), and prints exactly which claim drifted and in which direction. Re-confirming an already-documented REFUTED claim (like #4) is not drift and does not fail the run — only a new change does.

UNVERIFIABLE is a real outcome the script reports honestly, not a failure it papers over, and it never counts as drift in either direction. Some claims genuinely can't be checked by a safe, read-only, non-destructive probe (see #6–#8 above); the script says so rather than guessing or skipping silently. #5 is the clearest case: its documented HOLDS comes from the live integration suite, not this probe, so the probe coming back UNVERIFIABLE (no draft campaign exists right now) is reported as "could not re-check" — not a failure.

INSTANTLY_API_KEY=your-key npm run verify-gotchas

verify-gotchas is run manually, not wired into CI — check .github/workflows/ci.yml, it only runs build, typecheck, and test. That's a deliberate choice, not an oversight: CI has no live API key (the script self-skips cleanly without one, printing a message and exiting 0 — see the top of scripts/verify-gotchas.ts — so it would be a silent no-op there anyway), and this script exists to touch a real account's read endpoints, which a repo's CI has no business doing unattended. Run it locally against your own account when you want a fresh read.

Install

{
  "mcpServers": {
    "instantly": {
      "command": "npx",
      "args": ["-y", "instantly-ai-mcp"],
      "env": { "INSTANTLY_API_KEY": "your-v2-api-key" }
    }
  }
}

Get a v2 API key from Instantly's dashboard under Settings → Integrations → API. Requires Node 20+.

The safety model

Tools are grouped into three tiers, gated by environment variables. A disabled tier is not registered with the MCP server at all — a model talking to this server literally cannot see or attempt a tool it isn't allowed to use, this isn't a runtime permission check that a clever prompt could talk its way around.

Tier Enabled by Tools Behaviour
Read always on 6 tools Read-only. readOnlyHint: true.
Write INSTANTLY_MCP_WRITE=1 5 tools Creates/updates data, but nothing irreversible.
Dangerous INSTANTLY_MCP_WRITE=1 and INSTANTLY_MCP_ALLOW_DANGEROUS=1 4 tools Sends real email, activates campaigns, deletes data.

The dangerous tier requires both flags on purpose: turning on routine writes (uploading leads, blocklisting an address) never silently also enables campaign activation, sending, or deletion. Those four tools additionally carry MCP's destructiveHint: true annotation — a hint that a compliant client may act on (for example by prompting the user for confirmation) even when the tier is enabled. It is client-enforced behaviour, not a guarantee this server makes: a client that ignores the hint will call the tool without any extra confirmation step.

Tools

Read (always registered)

  • list_campaigns — list every campaign including drafts, with numeric status decoded.
  • list_accounts — list connected sending mailboxes with warmup score, status, and daily limit.
  • campaign_state — cross-check one campaign's state across three independent endpoints and report where they disagree, rather than picking a winner. The lead-list reading is page-scoped (one page, limit 100); a full page is reported plainly as page-limited, never as an Instantly disagreement.
  • list_leads — list a campaign's leads, filtered by the singular campaign parameter, with a warning if any returned lead's own campaign field disagrees. Reads one page (default limit 100); pageLimited in the result tells you when more leads may exist beyond it.
  • find_lead — find one lead by email via the search parameter; the correct second opinion when list_leads looks wrong. search is fuzzy, so the row is returned only when its own address matches the one asked for - a near-match is reported as null, never as the lead.
  • list_replies — list received replies with quoted-thread/signature stripped and interest status decoded.

Write (INSTANTLY_MCP_WRITE=1)

  • add_leads — upload leads to a campaign, verified by diff (not by count) across two independent read paths. On a campaign with more than 100 leads, the verification read is page-limited too - the result's pageLimited and note fields say so.
  • blocklist_address — blocklist one full email address; structurally refuses bare domains.
  • update_lead — patch a lead's fields.
  • create_campaign — create a campaign as a draft (never sends); validates the timezone enum before any network call.
  • create_webhook — create a webhook subscription; validates the event-type enum before any network call.

Dangerous (INSTANTLY_MCP_WRITE=1 and INSTANTLY_MCP_ALLOW_DANGEROUS=1)

  • set_campaign_status — activate or pause a campaign; activating starts sending real email immediately.
  • send_reply — send a real, unrecallable reply to a lead. Plain text is HTML-escaped and line-broken for the html body rather than pasted in raw; pass html yourself to override.
  • delete_lead — permanently delete a lead.
  • delete_campaign — permanently delete a campaign and its history.

Known limitations

list_replies strips the quoted original thread and signature from each reply (src/reply-text.ts). It is deliberately conservative: on ambiguous input it leaves the quote in rather than risk deleting real text. Every remaining edge below therefore fails in the SAFE direction - a quoted thread survives into the returned text, which is noise, rather than a sentence being deleted, which is lost data:

  • An attribution naming only a weekday, e.g. On Tuesday ... wrote:, carries none of the date/time signal the stripper requires, so it is not stripped.
  • An attribution naming a lowercase sender with no address, e.g. ... at 8:22 AM, john wrote:, fails the sender-shape check (a real sender reads as an address, a capitalised name, or a pronoun) and is not stripped.
  • A body that is entirely a signature (-- on the first non-blank line, with nothing before it) is returned whole, delimiter included, rather than emptied.

Two over-strips found during the build did delete real prospect text: a body beginning with -- was emptied completely, and prose shaped like On May 5 reasons you wrote: ... was misread as a quoted-thread marker and cut. Both were fixed before the first release and are covered by the offline suite (test/reply-text.test.ts, "Fix round 4").

There is still no tool that returns a reply's raw, unstripped body.text. If a reply from list_replies reads suspiciously short, check it in the Instantly dashboard before concluding the prospect said less than they did.

Prior art

An existing package, instantly-mcp by bcharleson, covers similar ground and was last published 2025-06-17. As of 2026-08-21, its npm latest tag points at 1.0.5 while its next tag carries 3.0.5-1 — so a plain npx instantly-mcp installs a much older build than the package's own newest published code (dist tags can change after this was written; re-check npm view instantly-mcp dist-tags for the current state). This is stated factually, not as a knock: instantly-ai-mcp is an independent, unaffiliated project with a different focus (the gotchas table and its self-verification) rather than a fork or a replacement.

Testing

The fixture suite (npm test) runs entirely offline against mocked clients and needs no API key. A separate live integration suite, gated behind INSTANTLY_LIVE_TEST=1 (and a real INSTANTLY_API_KEY), exercises the real API — but it only ever creates, reads, and deletes its own throwaway draft campaign (named zz-instantly-ai-mcp-throwaway-<timestamp>), never an existing campaign or lead, and never activates or sends anything. It self-skips whenever the flag or key is absent, which is always true in CI.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选