MCP server.
sayintel speaks the Model Context Protocol over streamable HTTP. Connect an assistant once and it can run the whole conference-outbound loop: create a campaign, watch the pipeline, read drafts, curate. Nothing sends without a human tap.
Two doors
OAuth (connector clients: Claude, ChatGPT) https://sayintel.com/mcp The client registers itself, your human approves it, tools run as that user. Scopes: read + write. Bearer token (CLI and headless agents) https://sayintel.com/api/public/mcp Authorization: Bearer si_... Scopes: whatever the token was minted with, including send. Discovery https://sayintel.com/.well-known/mcp.json
Connect a client
# Claude Code
claude mcp add sayintel --transport http https://sayintel.com/api/public/mcp \
--header "Authorization: Bearer $SAYINTEL_TOKEN"
# Claude Desktop / claude.ai / ChatGPT connectors
# Add a custom connector pointing at https://sayintel.com/mcp
# and complete the sign-in prompt.
# Cursor (.cursor/mcp.json)
{
"mcpServers": {
"sayintel": {
"url": "https://sayintel.com/api/public/mcp",
"headers": { "Authorization": "Bearer si_..." }
}
}
}The recommended loop
create_campaign(conference_url) → campaign_id
get_run_status(campaign_id) → poll every 15-30s while the pipeline
scrapes, enriches, qualifies, drafts
search_speakers / get_speaker_dossier / list_messages
→ review what it produced
get_draft_qa(campaign_id, only_failing: true)
→ find what needs fixing
request_rewrite / update_message → curate
push_approved → send-scoped token onlyTools
26 tools. Every one returns the same envelope as REST, and each bound tool has a matching HTTP endpoint in the REST reference.
Add a new conference URL to a campaign. Must be a public https URL — private/internal hostnames are rejected.
Funnel stats for a single campaign.
Create a new outbound campaign, optionally attach a conference URL (public https only) and auto-start the pipeline. Seeds ICP, style memory, and sender defaults from the user's profile. Scraping runs asynchronously via cron; poll get_run_status to follow progress. Pass dry_run=true to get cost_preview without creating anything. Returns { campaign_id, run_started, cost_preview }.
Register an https endpoint to receive signed event callbacks. Events: run.started, run.completed, run.failed, drafts.ready, message.ready, message.approved, push.completed, or "*" for all. Returns the signing secret ONCE — deliveries carry `X-Sayintel-Signature: t=<unix>,v1=<hex hmac_sha256(secret, `${t}.${rawBody}`)>`. Reject deliveries older than 5 minutes.
Delete a webhook endpoint and its pending deliveries.
Get full campaign config: positioning, ICP, pitch, CTA, conference URLs, totals.
Return the user's current credit balance and the last 20 ledger entries.
Machine-readable QA state for drafts. Pass speaker_id alone to inspect one speaker, or campaign_id for the whole campaign. Returns per-message qa_score, qa_evaluated_at, hard-fail codes, actionable fix hints ({code, action, hint}), needs_human / needs_redo, approvable flag, plus per-speaker buckets (ready_to_send | needs_rewrite | needs_human | awaiting_qa | skipped | sent) and a rollup of the most common fail codes. Use this to decide what to rewrite (request_rewrite) before pushing. Filter with only_failing=true. Paginated: pass next_cursor while has_more is true.
Pipeline status for a campaign: latest run (stage, status, progress, error), live counts (speakers, drafts, signals), and per-URL scrape status. The pipeline is cron-driven, so progress arrives in batches; poll every 15-30 seconds while a run is active, no faster.
Full dossier on a speaker: bio, talk, enrichment, AI research signals, drafted messages.
List the user's outbound campaigns (conferences). Paginated: pass the returned next_cursor to fetch the next page while has_more is true.
List intent-based leads (LinkedIn signals) for a campaign with intent_enabled=true. Paginated: pass next_cursor while has_more is true.
List drafted/sent messages across a campaign, with QA fields (qa_score, qa_evaluated_at, needs_human, needs_redo). Filter by sent. Paginated: pass next_cursor while has_more is true.
Enumerate the machine-readable vocabulary this API answers with: every QA hard-fail code with its remediation action and hint, plus the envelope error codes and their HTTP statuses. Static reference — safe to cache. Use it to map a get_draft_qa hard_fail_code to the fix action without guessing.
Inspect recent webhook deliveries (event, status, attempts, response status, error) so a failing integration is debuggable without guessing.
List the caller's registered webhook endpoints with their subscribed events, active flag and recent failure count. Secrets are never returned.
Mark a drafted message as sent (records sent_at = now). Idempotent unless force=true.
Push every send-ready, not-yet-pushed sequence in a campaign to its wired HeyReach campaign (optionally narrowed to speaker_ids). Only already-approved sequences go: all three slots drafted, QA-evaluated and cleared, not flagged needs_human/needs_redo, LinkedIn URL present, freshness guard passed. Messages are marked sent only after HeyReach confirms, and a human still arms the HeyReach campaign itself. Pass dry_run=true to see how many sequences would go, without pushing. Requires the 'send' token scope.
Rewrite drafted messages per an instruction (e.g. 'shorter, drop the exclamation, lead with the talk'). Targets specific speakers via speaker_ids, specific drafts via message_ids (rewrites all slots for those speakers), or the whole campaign if neither is given. Rewrites pass the same hard-fail QA gate as fresh drafts; a rewrite that violates it is skipped, not saved. Pass dry_run=true to see cost_preview (model calls, no credits) before committing.
Search speakers across the user's campaigns. Filter by campaign_id, name (ilike), company (ilike), prequalify_verdict, reply_status, or selected. Paginated: pass next_cursor while has_more is true.
Mark an intent lead as selected (true) or excluded (false).
Update reply status for a speaker. Allowed: replied, booked, no_reply, or null to clear.
Mark a speaker as selected (true) or excluded (false) from the outbound list.
Start the full pipeline run for a campaign (prequalify, enrich, qualify, research, draft). Idempotent: if a run is already queued or running, returns it instead of stacking a second. Already-enriched speakers are not re-billed. Pass dry_run=true to get cost_preview (credits this run would spend) without starting anything. Poll get_run_status for progress.
Edit drafted message content. Cannot edit a message after it is sent.
Return the SayIntel user id and granted scopes for this MCP token.