MCP server
Connect an external agent to SendSets' focused, policy-controlled tool surface.
SendSets exposes the same service-layer tools over MCP and REST. An internal LLM
provider is not required. The endpoint uses streamable HTTP at /v1/mcp and
accepts OAuth 2.1 access tokens or static ssk_ API keys.
claude mcp add --transport http sendsets http://localhost:8080/v1/mcpOr configure a static key:
{
"mcpServers": {
"sendsets": {
"type": "http",
"url": "http://localhost:8080/v1/mcp",
"headers": { "Authorization": "Bearer ssk_..." }
}
}
}Hosted endpoint and packaged installs
On SendSets Cloud the endpoint is https://api.sendsetsapi.com/v1/mcp:
claude mcp add --transport http sendsets https://api.sendsetsapi.com/v1/mcpThe same server is published in the official MCP Registry as io.github.AddisonHoff/sendsets, so registry-backed clients and catalogs can find it by name. github.com/AddisonHoff/sendsets packages it with the SendSets skills for clients that install bundles:
| Client | Command |
|---|---|
| Claude Code | /plugin marketplace add AddisonHoff/sendsets, then /plugin install sendsets@sendsets |
| Codex | codex plugin marketplace add AddisonHoff/sendsets, then codex plugin add sendsets@sendsets |
| Gemini CLI | gemini extensions install https://github.com/AddisonHoff/sendsets |
| Any agent with the skills CLI | npx skills add AddisonHoff/sendsets (skills only) |
The skills are send-cold-email (send a sequence to a lead list), sendsets (propose and run product-led campaigns) and sendsets-cli (the full CLI reference).
Send tools
Every tool in tools/list carries a title and MCP annotations: read tools are readOnlyHint, send tools and every write that modifies or removes data are destructiveHint (only purely additive create_* and add_* tools are not), and tools that reach outside the workspace (mail, the web, DNS, webhook receivers) are openWorldHint. The endpoint accepts POST only; a GET answers 405 because the server does not open an event stream.
Send-class tools are present in tools/list. Scopes decide whether the caller
can see and call a tool; a credential-bound policy independently decides whether
the action may execute autonomously.
| Tool | Scope | Capability |
|---|---|---|
create_run | WRITE_RUNS | none for draft; campaign.launch for autonomous launch |
get_run, list_runs | READ_RUNS | — |
cancel_run | WRITE_RUNS | — |
start_campaign | EXECUTE_RUNS | campaign.launch |
stop_campaign | WRITE_RUNS | — |
compose_email | SEND_DIRECT_EMAIL | email.compose |
send_reply | SEND_INBOX_REPLY | inbox.reply |
The successful result is always structured:
{
"result": "executed | awaiting_approval | blocked",
"reasons": ["policy does not grant email.compose"],
"approval_url": "http://localhost:5173/app/approvals/..."
}awaiting_approval means a JWT-authenticated human can review the request for
30 minutes. Approval tools are intentionally absent from MCP and REST tool
discovery. A structurally unsafe request is blocked; approval cannot override
missing opt-out, suppression, mailbox health, or deliverability checks.
The registry also exposes focused read/write tools for campaigns, sequences,
leads, mailboxes, warmup, inbox, analytics, suppressions, and webhooks. CRM,
forms, automations, advisor, and other legacy engines are absent unless the
instance operator explicitly sets SENDSETS_ENABLE_EXTENDED_SURFACES=true.
Query your data with SQL
Four read-only tools let an agent answer any question about the workspace by writing its own SQL, instead of being limited to the reports a tool happens to offer:
| Tool | What it does |
|---|---|
sql_schema | Lists the tables, or describes one table's columns, types, and meaning |
sql_examples | 26 worked queries for common outbound questions, to adapt rather than write from scratch |
sql_estimate | Plans a query without running it: estimated cost and rows, and whether it fits the ceiling |
sql_query | Runs one query and returns typed columns and rows as JSON |
The tables are views over this workspace's data, named directly with no schema prefix:
| Area | Tables |
|---|---|
| Campaigns | campaigns, campaign_steps, ab_variants, campaign_leads, campaign_logs |
| Sends and outcomes | email_sends (one row per email step, with opens, clicks, reply class, bounces), send_ledger (the mailbox and A/B arm behind each send), link_clicks |
| Contacts | contacts (with job title, seniority, department, company domain, and outreach status), categories, contact_categories, segments, suppressions |
| Infrastructure | mailboxes, mailbox_errors, warmup_daily, warmup_health |
| Outcomes | meetings, product_events, goals, conversions, people, attribution_credits, deals, deal_stages |
Mailbox credentials, message bodies, and anything encrypted are not in any table.
What keeps a query inside its workspace, and harmless:
- One read. Only a single
SELECT(orWITH ... SELECT) is accepted. Writes, data-modifyingWITH,SELECT INTO, settings, server files, advisory locks, large objects, and SQL run from a string are refused before anything runs, with the reason. - Its own rows only. Each query gets fresh views with the workspace id written into them, and runs as a database role that can read nothing else: not the underlying tables, not another workspace's views, not other connections. The views are security barriers, so even an error message cannot carry another workspace's row.
- Nothing persists. The transaction is always rolled back.
- Bounded. 30 seconds per query, a planner-cost ceiling (
sql_estimateshows where a query sits), at most 10,000 rows (200 by default) and 4 MB per result, and a few queries at a time per server.
The tools need both READ_ANALYTICS and READ_CONTACTS (view_analytics and view_contacts for a signed-in member).
{"tool": "sql_query", "arguments": {"sql": "SELECT m.email, count(*) AS sent, round(100.0 * count(*) FILTER (WHERE s.bounced_at IS NOT NULL) / count(*), 1) AS bounce_pct FROM send_ledger l JOIN email_sends s USING (campaign_id, contact_id, step_id) JOIN mailboxes m ON m.id = l.mailbox_id WHERE l.confirmed_at >= now() - interval '7 days' GROUP BY m.email ORDER BY bounce_pct DESC LIMIT 5"}}SQL only reads. To change data, an agent uses the typed tools listed in Reading and changing each table, which validate, audit, and keep the dashboard live. sql_bulk_contacts lets a query choose the contacts and a typed action change them, with a dry run first.
The dashboard's Ask Claude buttons, on the Acquisition Outbound and Copy tabs, on Mailboxes, and in the Lead database, copy a ready question for that screen with instructions to answer it through these tools. Paste it into Claude once this server is connected.
OAuth discovery
Unauthenticated requests return the standard protected-resource challenge. Clients discover authorization metadata from:
/.well-known/oauth-protected-resource/.well-known/oauth-authorization-server
OAuth credentials use sscid_, sscs_, ssat_, ssrt_, and ssac_
prefixes. Old credential formats are not accepted.
