sayintel
Docs

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 only

Tools

26 tools. Every one returns the same envelope as REST, and each bound tool has a matching HTTP endpoint in the REST reference.

add_conference_urlwriteREST endpoint

Add a new conference URL to a campaign. Must be a public https URL — private/internal hostnames are rejected.

campaign_statsreadREST endpoint

Funnel stats for a single campaign.

create_campaignwriteREST endpoint

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 }.

create_webhookwriteREST endpoint

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_webhookwriteREST endpoint

Delete a webhook endpoint and its pending deliveries.

get_campaignreadREST endpoint

Get full campaign config: positioning, ICP, pitch, CTA, conference URLs, totals.

get_credit_balancereadREST endpoint

Return the user's current credit balance and the last 20 ledger entries.

get_draft_qareadREST endpoint

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.

get_run_statusreadREST endpoint

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.

get_speaker_dossierreadREST endpoint

Full dossier on a speaker: bio, talk, enrichment, AI research signals, drafted messages.

list_campaignsreadREST endpoint

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_leadsreadREST endpoint

List intent-based leads (LinkedIn signals) for a campaign with intent_enabled=true. Paginated: pass next_cursor while has_more is true.

list_messagesreadREST endpoint

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.

list_qa_codesreadREST endpoint

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.

list_webhook_deliveriesreadREST endpoint

Inspect recent webhook deliveries (event, status, attempts, response status, error) so a failing integration is debuggable without guessing.

list_webhooksreadREST endpoint

List the caller's registered webhook endpoints with their subscribed events, active flag and recent failure count. Secrets are never returned.

mark_message_sentwriteREST endpoint

Mark a drafted message as sent (records sent_at = now). Idempotent unless force=true.

push_approvedsendREST endpoint

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.

request_rewritewriteREST endpoint

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_speakersreadREST endpoint

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.

set_intent_lead_selectedwriteREST endpoint

Mark an intent lead as selected (true) or excluded (false).

set_speaker_reply_statuswriteREST endpoint

Update reply status for a speaker. Allowed: replied, booked, no_reply, or null to clear.

set_speaker_selectedwriteREST endpoint

Mark a speaker as selected (true) or excluded (false) from the outbound list.

start_campaign_runwriteREST endpoint

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.

update_messagewriteREST endpoint

Edit drafted message content. Cannot edit a message after it is sent.

whoamireadREST endpoint

Return the SayIntel user id and granted scopes for this MCP token.