Skip to main content
Glama
Vojtaupan

instantly-ai-mcp

by Vojtaupan

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.

  • #5UNVERIFIABLE 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

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Vojtaupan/instantly-ai-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server