smartleadai-mcp
An unofficial MCP server for Smartlead that exposes the SmartProspect API and core Smartlead operations, enabling agents to search for leads, manage campaigns, and control credit spending with configurable safety modes.
README
smartleadai-mcp
Unofficial MCP integration for Smartlead. This project is not affiliated with, endorsed by, or sponsored by Smartlead.ai.
A Model Context Protocol server for the Smartlead API, with complete SmartProspect coverage, for MCP-capable agents and clients.
191 tools covering 191 of the 194 unique endpoints in Smartlead's official API
reference, across all four Smartlead API hosts. Three endpoints are excluded
on purpose, each with a recorded reason — see
docs/endpoint-coverage.md.
That includes all 26 documented SmartProspect endpoints, the prospecting product other Smartlead MCP servers omit entirely.
Everything is built from Smartlead's public official documentation. Every route,
method, parameter name and limit is traceable to a documentation page listed in
docs/endpoint-coverage.md.
Table of contents
- Why this exists
- Requirements
- Installation
- Client configuration
- Environment variables
- Safety modes
- Tool reference
- The SmartProspect workflow
- Controlling credit spend
- Error handling
- Privacy and security
- Development
- Publishing checklist
- Official Smartlead documentation
- Known limitations
- Licence
Why this exists
Smartlead's SmartProspect family lives on a different API host from the rest
of the Smartlead API, and its most useful operations spend prospecting
credits. Existing third-party tooling either omits SmartProspect entirely or
targets routes that are not in Smartlead's current API reference (for example
POST /api/v1/verify-emails, which returns 404 Cannot POST /api/v1/verify-emails). This package:
- talks to all four documented Smartlead hosts, correctly and separately;
- never implements an undocumented route — there is no
verify_emailstool here; - treats credit spend as a privileged action that requires two independent approvals before any HTTP request is made;
- returns structured JSON envelopes instead of prose, so an agent can branch on the result.
Requirements
- Node.js 20.19 or newer. CI runs the full verification suite on 20.19 (the declared floor) and 22 on every push.
- A Smartlead API key with SmartProspect access.
Installation
Run it directly with npx (no install step):
SMARTLEAD_API_KEY=sl_your_key npx -y smartleadai-mcp
Or install it and use the smartleadai-mcp bin:
npm install -g smartleadai-mcp
SMARTLEAD_API_KEY=sl_your_key smartleadai-mcp
The server speaks MCP over stdio. Started by hand it will simply wait for a client on stdin; that is expected.
Command line
The same binary is an MCP server when run with no arguments, and a small helper CLI when given a subcommand.
smartleadai-mcp init # interactive setup: verify the key, print client config
smartleadai-mcp doctor # check configuration and validate the key
smartleadai-mcp config # print effective configuration (credential redacted)
smartleadai-mcp tools # list tools with their safety classification
smartleadai-mcp help
Start here:
npx -y smartleadai-mcp init
init verifies the key, asks which safety mode you want, then prints
ready-to-paste config for Claude Desktop, Claude Code and Hermes. It offers to
write a local .env (mode 0600) but never overwrites an existing key.
doctor diagnoses a broken setup:
✓ configuration valid
key sl_1************************9f2c
mode readonly (default — no writes, no credit spend)
credit spend disabled
✓ api key accepted by Smartlead
✓ tools 191 registered
Both validate the key against GET /countries?limit=1 — free, read-only, and
touching no contact data, so diagnosing a setup can never spend credits or pull
a prospect record. Keys are only ever shown as first-four/last-four.
Client configuration
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or
%APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"smartlead": {
"command": "npx",
"args": ["-y", "smartleadai-mcp"],
"env": {
"SMARTLEAD_API_KEY": "sl_your_key",
"SMARTLEAD_MCP_MODE": "readonly"
}
}
}
}
Hermes
Add this under mcp_servers in ~/.hermes/config.yaml (use
hermes config path to locate the active profile's file):
mcp_servers:
smartlead:
command: "npx"
args: ["-y", "smartleadai-mcp"]
env:
SMARTLEAD_API_KEY: "sl_your_key"
SMARTLEAD_MCP_MODE: "readonly"
SMARTLEAD_MCP_ALLOW_CREDIT_SPEND: "false"
Restart Hermes, then verify with hermes mcp test smartlead. Hermes filters the
subprocess environment, so the API key must be present in this server's env
mapping rather than merely exported in an unrelated shell.
Any other stdio MCP client
Launch the process with the API key in its environment and speak MCP over stdin/stdout:
{
"command": "npx",
"args": ["-y", "smartleadai-mcp"],
"transport": "stdio",
"env": { "SMARTLEAD_API_KEY": "sl_your_key" }
}
Programmatic use (for embedding in your own host):
import { createServer, loadConfig } from 'smartleadai-mcp';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const { server } = createServer(loadConfig());
await server.connect(new StdioServerTransport());
Environment variables
| Variable | Required | Default | Notes |
|---|---|---|---|
SMARTLEAD_API_KEY |
yes | — | Read from the environment only. It can never be passed as a tool argument. |
SMARTLEAD_CORE_BASE_URL |
no | https://server.smartlead.ai/api/v1 |
Core Smartlead host. |
SMARTLEAD_PROSPECT_BASE_URL |
no | https://prospect-api.smartlead.ai/api/v1/search-email-leads |
SmartProspect host. |
SMARTLEAD_DELIVERY_BASE_URL |
no | https://smartdelivery.smartlead.ai/api/v1 |
Smart Delivery host. |
SMARTLEAD_SENDERS_BASE_URL |
no | https://smart-senders.smartlead.ai/api/v1 |
Smart Senders host. |
SMARTLEAD_MCP_MODE |
no | readonly |
readonly | standard | unrestricted. |
SMARTLEAD_MCP_ALLOW_CREDIT_SPEND |
no | false |
Literal true/false. |
SMARTLEAD_MCP_ALLOW_SEND |
no | false |
Literal true/false. |
SMARTLEAD_MCP_ALLOW_DESTRUCTIVE |
no | false |
Literal true/false. |
SMARTLEAD_MCP_TIMEOUT_MS |
no | 30000 |
Per-request timeout, 1000–600000. |
SMARTLEAD_MCP_MAX_RETRIES |
no | 2 |
Extra attempts, 0–5. Applies to safe GETs only. |
SMARTLEAD_LIVE_TESTS |
no | false |
Development only; enables the opt-in read-only live test suite. |
Boolean flags accept only the literal strings true and false (case
insensitive). 1, yes and on are rejected so a typo can never silently
enable spending.
See .env.example.
Safety modes
readonly (default) |
standard |
unrestricted |
|
|---|---|---|---|
| Read-only operations | allowed | allowed | allowed |
| Remote mutations (saved searches, campaign drafts, lead import, block-list add) | blocked | allowed | allowed |
| Credit spending | blocked | needs env flag and confirm_credit_spend: true |
needs env flag and confirm_credit_spend: true |
| Sending / campaign activation | blocked | blocked | needs SMARTLEAD_MCP_ALLOW_SEND=true and confirm_send: true |
| Destructive operations | blocked | blocked | needs SMARTLEAD_MCP_ALLOW_DESTRUCTIVE=true and confirm_destructive: true |
| Lead import | blocked | needs confirm_import: true |
needs confirm_import: true |
Rules that hold in every mode:
- A confirmation field must be boolean
true."true",1and"yes"are rejected. There is no confirmation field that defaults totrue. - A blocked call is refused before any HTTP request is made, so a blocked credit-spending call costs nothing.
- Refusals come back as a normal structured envelope with
error.kind: "policy", a machine-readableerror.code, and anerror.requirementsarray telling the operator exactly what to change.
Tool reference
191 tools across four hosts. The full table would be unreadable here, so list them from the CLI instead — it prints each tool's safety classification:
smartleadai-mcp tools # all 191
smartleadai-mcp tools campaign # filter by substring
| Host | Base URL | Tools | Prefix |
|---|---|---|---|
| SmartProspect | prospect-api.smartlead.ai/api/v1/search-email-leads |
26 | smartprospect_ |
| Core | server.smartlead.ai/api/v1 |
131 | smartlead_ |
| Smart Delivery | smartdelivery.smartlead.ai/api/v1 |
27 | smartdelivery_ |
| Smart Senders | smart-senders.smartlead.ai/api/v1 |
7 | smartsenders_ |
By safety classification:
| Classification | Tools | Gate |
|---|---|---|
| Read-only | 113 | none — available in every mode |
| Remote mutation | 78 | standard mode or above |
| Sends email | 10 | unrestricted + ALLOW_SEND + confirm_send |
| Destructive | 15 | unrestricted + ALLOW_DESTRUCTIVE + confirm_destructive |
| Consumes credits | 2 | ALLOW_CREDIT_SPEND + confirm_credit_spend + preflight |
Classification is reviewed per endpoint, not inferred from the HTTP verb.
Smartlead serves 14 searches over POST — those are read-only. Several
DELETE and stop/suspend/block routes are suppression-increasing and are
deliberately not destructive, so the safe action is never harder to take
than the dangerous one.
Every tool returns the same envelope:
{
"ok": true,
"operation": "smartprospect_search_contacts",
"credit_spending": false,
"remote_mutation": false,
"data": { "list": [] },
"pagination": { "scroll_id": "…", "filter_id": 327105, "total_count": 16064669 },
"warnings": []
}
The SmartProspect workflow
SmartProspect separates searching (free) from revealing (paid). The tools mirror that split.
1. Inspect credits. Always first, always free.
// smartprospect_get_search_analytics
{}
// → data.availableCredits { available, total, used }, maxSingleFetchLimit, maxDailyFetchLimit
2. Build valid filter values. Free lookups: smartprospect_list_countries,
_list_states, _list_cities, _list_industries, _list_sub_industries,
_list_departments, _list_seniority_levels, _list_head_counts,
_list_revenue_ranges, _list_companies, _list_domains, _list_job_titles,
_list_keywords.
3. Search previews. Free. Returns a page of candidates plus the filter_id
you will need later, the total_count of matches, and a scroll_id for the
next page. Preview records are de-identified by default; set
include_full_records: true only when names and personal fields are needed.
// smartprospect_search_contacts
{
"limit": 25,
"title": ["Head of Growth"],
"country": ["United States"],
"companyHeadCount": ["25 - 100"],
"titleExactMatch": false
}
4. Review candidates. Page with scroll_id, narrow the filters, and — if
you want to avoid sending personal data to the model at all — pass
include_full_records: false to receive a de-identified summary.
Optionally persist the filter:
// smartprospect_save_search (standard mode or above)
{ "search_string": "US Heads of Growth, 25-100", "title": ["Head of Growth"], "country": ["United States"] }
5. Intentionally reveal selected contacts. This is the step that spends credits, and it is doubly gated.
// smartprospect_fetch_contacts (needs SMARTLEAD_MCP_ALLOW_CREDIT_SPEND=true)
{ "filter_id": 327105, "limit": 50, "visual_limit": 50, "confirm_credit_spend": true }
or, for a handful of named people you already know:
// smartprospect_find_emails (max 10 per call)
{
"contacts": [{ "firstName": "Ada", "lastName": "Lovelace", "companyDomain": "example.com" }],
"confirm_credit_spend": true
}
6. Retrieve contacts you already paid for. Free — never re-fetch.
// smartprospect_get_contacts
{ "filter_id": 327105, "limit": 100, "offset": 0, "verification_status": "valid" }
Use smartprospect_list_fetched_searches to find filters whose contacts have
already been revealed, and smartprospect_review_contacts to re-sync a filter's
metrics.
Controlling credit spend
Credit-consuming tools are gated at three independent layers:
- Process configuration.
SMARTLEAD_MCP_ALLOW_CREDIT_SPEND=truemust be set in the server's environment. Without it, the tool refuses and no HTTP request is made. - Per-call confirmation. The call must include
confirm_credit_spend: trueas a real boolean. - Credit preflight (
smartprospect_fetch_contactsonly). Before the paid request, the tool calls the freesearch-analyticsendpoint and compares the requested quantity againstavailableCredits.availableandmaxSingleFetchLimit. If the request exceeds either, it is rejected with an explanation — never silently reduced. The preflight result is returned indata.credit_preflight.
// Refused: env flag not set. No request was sent to Smartlead.
{
"ok": false,
"operation": "smartprospect_fetch_contacts",
"credit_spending": false,
"remote_mutation": false,
"data": null,
"pagination": null,
"warnings": [],
"error": {
"kind": "policy",
"code": "credit_spend_disabled",
"message": "This operation can consume SmartProspect credits and credit spending is disabled.",
"requirements": ["Set SMARTLEAD_MCP_ALLOW_CREDIT_SPEND=true and restart the MCP server."]
}
}
// Refused: request larger than the balance. Only the free preflight ran.
{
"ok": false,
"error": {
"kind": "refusal",
"code": "insufficient_credits",
"message": "Requested 900 contact(s) but only 100 SmartProspect credit(s) are available. The request was not sent and no credits were spent.",
"requirements": ["Reduce the request to 100 or fewer.", "Or top up SmartProspect credits in the Smartlead dashboard."]
}
}
Neither credit-consuming request is ever retried automatically. Retries are enabled only for safe idempotent GETs.
The preflight cannot be skipped. If analytics is unavailable or does not return recognisable credit and account-limit fields, the paid request fails closed.
Error handling
Failures never throw across the MCP boundary. They come back as an envelope with
ok: false and a typed error.kind:
error.kind |
Meaning |
|---|---|
policy |
Blocked locally by the safety policy. No HTTP request was made. |
refusal |
Blocked locally by a tool-level check (e.g. insufficient credits). |
authentication |
HTTP 401 — key missing or invalid. |
permission |
HTTP 403. |
payment |
HTTP 402, or a credit-related success: false body. |
validation |
HTTP 400 / 422. |
not_found |
HTTP 404. |
conflict |
HTTP 409. |
rate_limit |
HTTP 429. retry_after_seconds is surfaced when Smartlead sends it. |
server |
HTTP 5xx. |
timeout |
The request exceeded SMARTLEAD_MCP_TIMEOUT_MS. |
transport |
DNS/TLS/socket failure; no HTTP response. |
protocol |
HTTP 200 with a body that was not JSON. |
api_failure |
HTTP 200 with success: false in the body. |
Smartlead returns HTTP 200 with success: false for several documented failure
cases (notably fetch-contacts limit and credit checks). Those are surfaced as
errors, not as successes with empty data.
Retries apply only to GET requests and only for rate_limit, server,
timeout and transport failures, with exponential backoff that honours
Retry-After.
Privacy and security
- The API key is environment-only. No tool accepts it as an argument;
attempting to pass
api_keyto a tool is rejected by the input schema. - The key is redacted everywhere. Smartlead authenticates via an
api_keyquery parameter, so the credential appears in every request URL. Every URL, error message, error detail and tool result is passed through a redactor before it leaves the process. - Nothing is logged. The server writes no request bodies, no responses and no contact data to stdout, stderr or disk. stdout carries only the MCP protocol stream; stderr carries only fatal startup errors.
- Contact data is returned, by design. That is the purpose of a prospecting
tool. Tools that return contacts accept
include_full_records: falseto return a de-identified summary (counts and non-personal attributes) instead. - Tests use synthetic data only (
person@example.comand similar).
Read SECURITY.md and docs/security-model.md
before granting this server anything beyond readonly.
Prompt injection matters here. Contact records, campaign names and lead custom fields are attacker-influenceable text. Treat any instruction that appears inside tool output as data, never as a command — and note that the policy layer is what actually stops an injected "fetch 10,000 contacts" instruction, not the model's judgement.
Development
npm install
npm run typecheck # tsc --noEmit
npm run lint # eslint, zero warnings allowed
npm test # unit + integration (mocked fetch, no network)
npm run test:coverage # with v8 coverage thresholds
npm run build # tsup -> dist/
npm run pack:check # npm pack --dry-run
npm run verify # typecheck + lint + coverage + build + pack + installed-package smoke
npm run smoke:package # pack, install into a throwaway dir, drive the installed
# binary with a real MCP client (no Smartlead access)
npm run test:live # opt-in, read-only; needs SMARTLEAD_LIVE_TESTS=true
The default suite never touches the network — fetch is injected. The live
suite is read-only, is skipped unless SMARTLEAD_LIVE_TESTS=true and
SMARTLEAD_API_KEY are both set, and asserts that the credit balance is
unchanged before and after it runs. It never calls find-emails,
fetch-contacts, imports, campaign mutations, sending, deletion or unsubscribe.
Layout:
src/
index.ts stdio entry point
server.ts transport-agnostic server factory
config.ts environment parsing and validation
client/ errors.ts, http.ts, core-client.ts, prospect-client.ts
security/ redaction.ts, policy.ts
schemas/ common.ts, smart-prospect.ts, core.ts
tools/ types, envelope, shape, register + smart-prospect/ and core/
types/ loose Smartlead response types
tests/ unit/, integration/, live/, helpers/
docs/ endpoint-coverage.md, security-model.md, publishing.md
Adding an HTTP/Streamable HTTP transport later means adding a new entry point
that calls createServer() and attaches a different transport. No tool, schema
or client change is required.
Publishing checklist
Nothing here has been published. See docs/publishing.md
for the full procedure. Summary:
- Run
npm run verifyfor typecheck, lint, coverage, build, pack dry-run and a clean installed-package MCP smoke test.prepublishOnlyrepeats every check except the nested pack/install smoke, which npm cannot run recursively while already preparing a publish. - Confirm the packed file list contains only
dist/, the public Markdown documentation,.env.example, andpackage.json. - Confirm you are authenticated on npm. The name is unscoped, so no scope membership is needed — but nothing reserves it either until it is published.
- Tag, publish with
--access public(optionally--provenance), then run a post-publication smoke test from a clean directory.
Official Smartlead documentation
- Introduction — https://api.smartlead.ai/introduction
- Authentication — https://api.smartlead.ai/authentication
- Machine-readable index — https://api.smartlead.ai/llms.txt and https://api.smartlead.ai/llms-full.txt
- Rate limits — https://api.smartlead.ai/guides/rate-limits
- Error handling — https://api.smartlead.ai/guides/error-handling
- SmartProspect reference — https://api.smartlead.ai/api-reference/smart-prospect/search-contacts (and siblings)
Per-endpoint source pages, with the date each was checked, are listed in
docs/endpoint-coverage.md.
Known limitations
- Three documented endpoints are excluded.
email-accounts/add-smtpandadd-oauthrequire a mailbox password or OAuth refresh token in the request body; a tool argument is relayed through the model and on to the model provider, so no gate makes that safe — connect mailboxes in the Smartlead UI.campaigns/get-leads-history-bulkdocuments a curl with an opaque path segment that has no matching path parameter, so its route cannot be determined without guessing. Seedocs/endpoint-coverage.md. - Most tools are catalog-generated. The 39 hand-written tools encode every
documented range, enum and cross-field rule (such as the
id/filter_idXOR). The other 152 are generated from the documentation's parameter metadata: they validate names, types, presence, and any range or enum the docs stated explicitly, but they cannot express cross-field rules. Invalid combinations reach Smartlead and are rejected there. - This server can send email. With
unrestrictedmode plusSMARTLEAD_MCP_ALLOW_SEND=true, 10 tools can put mail in a real recipient's inbox, includingsmartlead_utilities_send_single_email. The gate stops accidents, not a determined agent that has been given the flag. fetch-contactselevated limit is unverifiable locally. Smartlead documents 1–10000 "or 30000 for some users" without exposing which applies. The schema accepts up to 30000 and warns above 10000; the account's realmaxSingleFetchLimitfrom the preflight is what is actually enforced.- Daily fetch limits are enforced from analytics. If the requested quantity
plus
leadsFoundTodayexceedsmaxDailyFetchLimit, the paid request is refused locally. - Undocumented maximums are guarded, not derived. A few lookup endpoints
document a default but no maximum; this package applies a client-side bound
(noted in
docs/endpoint-coverage.md) rather than inventing a documented one. - Rate-limit tiers are per account. Smartlead documents 60–120 requests per
minute depending on plan. This server does not throttle; it retries safe GETs
with backoff and surfaces
rate_limiterrors otherwise. - Response shapes are passed through. Smartlead's response envelopes vary
between endpoint families; tools unwrap the common
{ success, message, data }wrapper but do not otherwise normalise upstream field names. - Limited live verification. Independent review exercised search analytics, countries, and a one-result filtered contact search through the assembled MCP server. The account credit balance was unchanged. Mutations and paid endpoints remain mocks-only by design.
Licence
MIT — see LICENSE and THIRD_PARTY_NOTICES.md.
"Smartlead" and "SmartProspect" are trademarks of their respective owner. This project is not affiliated with, endorsed by, or sponsored by Smartlead.ai, and uses those names only to identify the API it integrates with.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。