MCP server
The MCP server exposes the API as tools, so an agent can run senders, audiences, campaigns and the inbox the way the console does. It speaks streamable HTTP, is stateless and authenticates with an API key.
| URL | https://api.getoutvoy.com/mcp |
|---|---|
| Transport | Streamable HTTP, stateless: POST only, plain JSON responses, no session id |
| Auth | Authorization: Bearer ov_... (API keys only) |
Create an API key in the console under Agents & API first. The agent acts as your workspace: it can do what the API allows with that key, and nothing more. Console sessions are refused here; use a key.
Claude Code
claude mcp add --transport http outvoy https://api.getoutvoy.com/mcp \
--header "Authorization: Bearer ov_..."
Add --scope user to make it available in every project. Run /mcp inside Claude Code to check the connection.
Claude Desktop
Claude Desktop starts local servers, so bridge the remote one with mcp-remote (it needs Node.js for npx). Open Settings → Developer → Edit Config, add this to claude_desktop_config.json and restart Claude:
{
"mcpServers": {
"outvoy": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.getoutvoy.com/mcp",
"--header",
"Authorization:${OUTVOY_AUTH}"
],
"env": { "OUTVOY_AUTH": "Bearer ov_..." }
}
}
}
The header goes through an environment variable because Claude Desktop splits arguments on spaces.
Cursor
Add this to ~/.cursor/mcp.json, or to .cursor/mcp.json in a project:
{
"mcpServers": {
"outvoy": {
"url": "https://api.getoutvoy.com/mcp",
"headers": { "Authorization": "Bearer ov_..." }
}
}
}
Other clients
Any MCP client that supports streamable HTTP and custom headers works the same way: the URL above and an Authorization header. GET and DELETE on /mcp answer 405. A 401 never carries a WWW-Authenticate header, so clients don't fall into an OAuth discovery loop; if a call fails with 401, check the key.
curl https://api.getoutvoy.com/mcp \
-H "Authorization: Bearer $OUTVOY_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "whoami", "arguments": {} } }'
{
"result": {
"content": [
{
"type": "text",
"text": "{\"data\":{\"apiKey\":{\"id\":\"key_gLG1KlUD\",\"name\":\"Claude Code\"},\"workspace\":{\"id\":\"org_dzfEWWtX\",\"name\":\"Acme\",\"plan\":\"growth\",\"planName\":\"Growth\",\"limits\":{\"linkedinSenders\":3,\"includedLinkedinSenders\":3,\"extraLinkedinSenders\":0,\"sandboxSenders\":5,\"enrollmentsPerMonth\":3000,\"liveCampaigns\":20},\"billing\":{\"source\":\"subscription\",\"status\":\"trialing\",\"trialEndsAt\":\"2026-11-04T14:46:02.690Z\",\"renewsAt\":\"2026-11-04T14:46:02.690Z\"},\"usage\":{...}}}}"
}
]
},
"jsonrpc": "2.0",
"id": 1
}
A tool answers with the same { data, meta } JSON the REST endpoint returns, as text content. A refused call is a tool result with isError: true and the same { errorCode, message, details? } the REST API answers as its text, never a JSON-RPC error. Arguments a tool doesn't know are refused (422 UNPROCESSABLE_CONTENT, naming the field in details.issues), so a misspelled argument fails loudly instead of being ignored.
Tools
Each description states the domain rules (for example, that a launch enrolls every eligible lead, or that messages only go to connected people), so the agent doesn't have to discover them by failing. On initialize the server also sends instructions: start with whoami, rehearse with a sandbox sender, always preview before launching. Tools that act on LinkedIn or delete things carry destructiveHint, so well-behaved clients ask before running them.
| Tool | What it does |
|---|---|
| whoami | Workspace, plan, plan limits, billing state and current usage. A good first call. |
| rename_workspace | Rename the workspace. |
| get_license | The plan and the active lifetime licence, if any. Redeeming one is human-only. |
| get_billing | Plan source (subscription, licence or none), trial end, next charge, extra LinkedIn accounts. Starting the trial or buying accounts is human-only. |
| list_senders | Every sender with status, limits, today's effective limits and usage, and working hours. |
| get_sender | One sender, with why it is in its status. |
| create_sandbox_sender | Create a simulated LinkedIn sender, active at once, for rehearsing. |
| get_connect_link | A hosted page URL the account owner opens to connect (or reconnect) a real LinkedIn account. |
| wait_for_connection | Wait until the human finishes connecting through that link; call again if it timed out. |
| update_sender | Change a sender's name, limits, working hours or warm-up. |
| pause_sender | Stop a sender from acting until resumed. |
| resume_sender | Resume a paused sender. |
| disconnect_sender | Disconnect a sender for good and free its plan slot; a LinkedIn sender's session is removed from our provider. Destructive. |
| search_parameters | Look up the ids search filters need (LOCATION, INDUSTRY, COMPANY, SCHOOL). |
| search_people | One page of LinkedIn people from filters or a pasted search URL. Saves nothing. |
| create_list | Create an empty list. |
| list_lists | Every list with its lead count. |
| get_list | One list. |
| update_list | Rename a list or change its description. |
| delete_list | Delete a list; its leads stay in the workspace. Destructive. |
| import_to_list | Fill a list in the background from a search or a post's engagers (up to 1,000). |
| get_import | An import's status and progress. |
| list_imports | A list's imports, newest first, with status and progress. |
| add_leads_to_list | Add up to 1,000 people by profile URL, with names and custom fields. |
| remove_leads_from_list | Remove leads from a list; they stay in the workspace. Destructive. |
| list_leads | Page through leads, optionally one list's or matching a search. |
| get_lead | One lead with its profile data and fields. |
| update_lead_fields | Merge custom fields into a lead. |
| delete_lead | Erase a person who asked to be forgotten: stops their sequences, deletes their data, redacts past events, blocklists their profile by default. Destructive. |
| list_blocklist | The workspace blocklist. |
| add_to_blocklist | Keep a profile or a company out of every campaign. |
| remove_from_blocklist | Remove one blocklist entry. Destructive. |
| create_campaign | Create a draft campaign: senders, lists, steps and settings. |
| list_campaigns | Every campaign with its status and funnel. |
| get_campaign | One campaign with its sequence, settings and funnel. |
| update_campaign | Rename at any time; change senders, lists, steps or settings while draft or paused. |
| add_leads_to_campaign | Add up to 100 people straight to a campaign, each with its own fields. |
| preview_campaign | Render every message for every lead and list every issue. Sends nothing. |
| launch_campaign | Enroll the eligible leads and start acting on LinkedIn. Destructive. |
| pause_campaign | Pause a running campaign. |
| resume_campaign | Resume a paused campaign. |
| archive_campaign | Stop a campaign for good; history stays. Destructive. |
| delete_campaign | Delete a draft (a campaign that ran is archived). Destructive. |
| get_campaign_stats | The funnel overall, per step and per sender. |
| list_enrollments | The campaign's leads with status, next action time and waitingReason. |
| get_enrollment | One enrollment with its full action history and rendered texts. |
| update_enrollment_fields | Set fields for one lead in one campaign (they override the lead's). |
| stop_enrollment | Stop one lead's sequence. Destructive. |
| list_conversations | The inbox, newest activity first; unread: true finds new replies. |
| get_conversation | Every message of a conversation. Marks it read. |
| send_message | Reply from the sender that owns the conversation. Destructive. |
| list_events | Read the event feed from a cursor, filtered by type. |
| create_webhook | Register an endpoint for signed event deliveries; returns its secret once. |
| list_webhooks | Every webhook endpoint with its event types and failure streak. |
| get_webhook | One webhook endpoint. |
| update_webhook | Change URL, description or event types, or enable/disable it. |
| delete_webhook | Delete an endpoint and its pending retries. Destructive. |
| rotate_webhook_secret | Issue a new signing secret; the old one stops at once. Destructive. |
| test_webhook | Send a signed webhook.test ping now and return the attempt. |
| list_webhook_deliveries | An endpoint's delivery log, newest first. |
Minting or revoking API keys and redeeming a licence have no tools on purpose: they need a person signed in to the console.
Example prompts
- “Create a sandbox sender called Lena and run a test campaign against 10 people from a search for heads of RevOps in Germany. Show me the preview first.”
- “Import everyone who reacted to or commented on this post into a list called Webinar follow-up: <post URL>.”
- “Draft a three-step campaign for the RevOps DE list: visit, invite with a note under 200 characters that mentions their company, then a message two days after they accept. Preview it and fix every issue before you ask me to launch.”
- “Which leads in RevOps DE are waiting, and why?”
- “Summarize every reply from the last 24 hours and draft an answer for each. Don't send anything yet.”
- “Block everyone at Globex from every campaign.”
Start with a sandbox sender. It runs the same engine on a fast clock, so you can watch the agent run a whole campaign, replies included, before it touches a real account.