Tool catalog
The tools below are the ones worth knowing to get started. Unlike the CLI, the MCP server has no separate published reference — your MCP client's own tool list is the authoritative one, since it is read from the server when you connect. Each tool calls one endpoint in the API reference.
| create_profile_enrichment_job | Enrich a LinkedIn person profile. Takes `creditCapPerSync`, the per-sync credit limit on the source it creates (null = no limit; needs saveTrackedProfile true, because a cap needs a tracked source to sit on). Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. ⭐ IT CAN ALSO CREATE A POSTS-ONLY WATCH: `mode: "posts_only"` with `postsPerSync` (1-60) makes this source fetch NEW POSTS ONLY on its daily sync — no engagers, no leads, no enrichment — at ONE CREDIT PER NEW POST, never twice for the same post. It needs `confirmSpend`: without it the call is 409 `spend_confirmation_required` carrying `estimatedDailyMax` and `daysToExhaustAtCap` in `details`, and nothing is created. Ask which the person wants before creating anything — watching what somebody posts and finding the people who engage with them are different products at very different prices — and say the daily figure before sending confirmSpend. Switching an existing posts-only source TO engagers is a spend increase and needs confirmSpend; switching the other way does not. `enrichLeads: false` (with saveTrackedProfile true) keeps the source's leads RAW — captured and charged as before, never enriched, read with list_raw_leads. ⭐ CHOOSE WHAT THE FIRST SYNC COLLECTS: `firstSyncPosts` (1-50, the latest N posts) or `firstSyncDays` (1-90, the posts from the last N days, at most 50) replace the default of the latest 15 — the dashboard's Add Profile form calls this First sync. Only the FIRST sync reads them (every later sync checks the 4 newest posts, and a source that has already synced ignores them), every collected post's engagers are charged as usual and `creditCapPerSync` still bounds the run, so ask before choosing a large window. Needs saveTrackedProfile true; refused with mode "posts_only", whose first run takes its postsPerSync newest posts. |
| create_company_enrichment_job | Enrich a LinkedIn company page. Takes `creditCapPerSync`, the per-sync credit limit on the source it creates (null = no limit; needs saveTrackedProfile true, because a cap needs a tracked source to sit on). Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. ⭐ IT CAN ALSO CREATE A POSTS-ONLY WATCH: `mode: "posts_only"` with `postsPerSync` (1-60) makes this source fetch NEW POSTS ONLY on its daily sync — no engagers, no leads, no enrichment — at ONE CREDIT PER NEW POST, never twice for the same post. It needs `confirmSpend`: without it the call is 409 `spend_confirmation_required` carrying `estimatedDailyMax` and `daysToExhaustAtCap` in `details`, and nothing is created. Ask which the person wants before creating anything — watching what somebody posts and finding the people who engage with them are different products at very different prices — and say the daily figure before sending confirmSpend. Switching an existing posts-only source TO engagers is a spend increase and needs confirmSpend; switching the other way does not. `enrichLeads: false` (with saveTrackedProfile true) keeps the source's leads RAW — captured and charged as before, never enriched, read with list_raw_leads. ⭐ CHOOSE WHAT THE FIRST SYNC COLLECTS: `firstSyncPosts` (1-50, the latest N posts) or `firstSyncDays` (1-90, the posts from the last N days, at most 50) replace the default of the latest 15 — the dashboard's Add Profile form calls this First sync. Only the FIRST sync reads them (every later sync checks the 4 newest posts, and a source that has already synced ignores them), every collected post's engagers are charged as usual and `creditCapPerSync` still bounds the run, so ask before choosing a large window. Needs saveTrackedProfile true; refused with mode "posts_only", whose first run takes its postsPerSync newest posts. |
| untrack_profile | Stop tracking a profile — a soft delete. Nothing captured is erased, but its leads stop being served by default; list_leads and list_engagers read them back with includeInactive=true, and re-tracking revives the source everywhere. |
| untrack_company | Stop tracking a company page — a soft delete. Nothing captured is erased, but its leads stop being served by default; list_leads and list_engagers read them back with includeInactive=true, and re-tracking revives the source everywhere. |
| track_post | Track a single LinkedIn post by its permalink so its reactors and commenters become leads. Takes `creditCapPerSync`, the per-sync credit limit on the tracked post (null = no limit). Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. `enrichLeads: false` keeps the post's leads RAW (list_raw_leads). |
| track_keyword | Create a keyword search — sweep posts by keyword, optionally AI-filtered, and capture their engagers, the person who wrote each kept post (capturePostAuthors: an Author lead, one credit per new person like any engager; company-page authors are skipped and not charged), or both — four choices with defaults (mode engagers, captureEngagers true, captureReplies true, capturePostAuthors false), so ask rather than take them by omission; mode posts_only captures no people and is priced min(postsPerSync, creditCap). creditCap is the one limit on an engagers search. ⚠️ THE CUT-OVER IS SCHEDULED FOR 2026-11-01, AND THIS IS THE NOTICE OF IT. UNTIL 2026-11-01 an old-style create — one that omits the three scope fields and `confirmSpend` — still returns 201/200 and carries a `deprecations` entry naming exactly what to add. FROM 2026-11-01 the same request is refused: a missing scope field is `400` `scope_required` naming the field, and a scope that is stated but not confirmed is `409` `spend_confirmation_required` — the same refusal a request pinned to `contractVersion: "2026-11-01"` already gets today, which is how you test the new behaviour before the date. THE EXACT CHANGE IS FOUR ARGUMENTS: add creditCap: 100, captureMode: "depth", datePosted: "PAST_WEEK" and confirmSpend: true to the call. Those three values are what the dashboard's "Search scope" step pre-fills, offered so you can copy them DELIBERATELY — they are NOT server-side defaults and nothing is chosen for you, so send the caps you actually want, and send `confirmSpend` only once the person has heard the daily figure. THE VERSION STRING AND THE CUT-OVER DATE ARE THE SAME DAY, which they did not have to be: the version is what you PIN, the date is what happens to you if you do not. `enrichLeads: false` keeps the search's leads RAW — captured and charged as before, never enriched, read with list_raw_leads. |
| untrack_keyword | Stop a keyword search — sweeping and the daily charge stop; the leads already captured are kept and stay readable. The one kind that works this way. |
| update_profile | Change a tracked person’s PER-SYNC credit limit by LinkedIn username, without re-tracking them — the edit that queues no sync and charges nothing. Send any of `creditCapPerSync` (nullable: null removes the limit), `mode` (with `postsPerSync` for posts_only; switching a posts-only source back to engagers needs `confirmSpend: true`), `captureReplies` and `enrichLeads` — a call naming none is rejected, and `enrichLeads: false` keeps the person’s leads RAW (list_raw_leads). Not a keyword search’s `creditCap`, which is a per-RUN cap and belongs to update_keyword. Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. |
| update_company | The same edit for a tracked company page, by its LinkedIn username, with the same settings — `creditCapPerSync` (nullable), `mode` / `postsPerSync` / `confirmSpend`, `captureReplies` and `enrichLeads` (false keeps its leads RAW); a tracked post has none of these, because its identifier is a URN rather than a username — re-send track_post instead. Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. |
| update_keyword | Change a live keyword search's settings — caps, scope, AI filter, targeting, and who it captures — without re-creating it. Partial: what you omit is left alone. Raising what one day can cost (creditCap, or min(postsPerSync, creditCap) on posts_only) needs the person's confirmSpend; lowering it does not, and the refusal names the search. mode, captureReplies, captureEngagers and capturePostAuthors are editable here too: captureEngagers and capturePostAuthors choose the people who engaged, the person who wrote each kept post (an Author lead, one credit per new person like any engager; company-page authors are skipped and not charged), or both; turning engagers back on for an authors-only search needs confirmSpend. `enrichLeads: false` keeps the search's leads RAW (list_raw_leads) and needs no confirmSpend. |
| estimate_keyword_search | Price a keyword search before creating it — the daily maximum, the resolved caps, and whether these terms would resume a search the team already has. Creates nothing and charges nothing. |
| untrack_post | Stop tracking a post — a soft delete. Capture stops and nothing captured is erased, but its leads stop being served by default; list_leads and list_engagers read them back with includeInactive=true. |
| get_webhook_config | Get a tracked source's webhook configuration. |
| set_webhook_config | Configure a tracked source's webhook (URL, events, ICP-only). |
| get_keyword_webhook_config | Get a keyword search's webhook configuration — addressed by its source id, not a username. |
| set_keyword_webhook_config | Configure a keyword search's webhook (URL, events, ICP-only), by source id. |
| get_icp_config | Get a tracked source's ICP filter rules and match mode. |
| set_icp_config | Set a tracked source's ICP filter rules (which leads count as a match). Pass dryRun: true to PREVIEW instead: the rules are validated and evaluated against this source’s already-captured leads and NOTHING is written — no rules saved, no lead re-scored, no webhook fired — the reply carrying dryRun: true, counts (matching/notMatching) and a sample of up to ten matching leads. Run it before any rule change whose effect has not been seen, because a real save re-scores every existing lead at once.. |
| get_keyword_icp_config | Get a keyword search's ICP filter rules and match mode — by source id, not a username. |
| set_keyword_icp_config | Set a keyword search's ICP filter rules, by source id. Pass dryRun: true to PREVIEW instead: the rules are validated and evaluated against this source’s already-captured leads and NOTHING is written — no rules saved, no lead re-scored, no webhook fired — the reply carrying dryRun: true, counts (matching/notMatching) and a sample of up to ten matching leads. Run it before any rule change whose effect has not been seen, because a real save re-scores every existing lead at once.. |
| create_profile_posts_job | Fetch a person's recent posts. Optional `limit` (1-15) sizes a page. |
| create_company_posts_job | Fetch a company page's recent posts. |
| create_post_reactions_job | Fetch the reactors of a post you track, or of one your keyword searches or posts-only watches found — read only and free. |
| create_post_comments_job | Fetch comments on any tracked post — personal or company page — or on one your keyword searches or posts-only watches found (read only and free). |
| create_company_post_comments_job | Fetch comments on any tracked post, or one your keyword searches or posts-only watches found — equivalent to create_post_comments_job; use either. |
| get_job_status | Poll a job by id until it completes; rowOffset pages the result's rows, 10 at a time. |
| get_profile_urn | Resolve a public LinkedIn handle to the member id track_keyword's fromPerson/mentionsPerson take — free: no enrichment, no tracking, creditsCharged 0. |
| get_profile_posts | Read a profile's posts AND THEIR TEXT without tracking it — the untracked counterpart to the tracked posts job, which 404s for anyone you have not tracked. ⚠ ONE POST IS ONE CREDIT: it used to be free and is not. Say how many with `posts` (1-60, no default) and authorise with `confirmSpend`; without it the call is 409 `spend_confirmation_required` carrying `estimatedCredits` and `remainingBalance` in `details`, and nothing is fetched or charged. You are charged for the posts ACTUALLY RETURNED — `creditsCharged` says what — so a profile with fewer costs fewer and an empty one costs nothing. A balance below the request is a 402 naming both numbers, refused whole. ASK THE PERSON HOW MANY POSTS, AND WHETHER THEY WANT A ONE-OFF READ OR A DAILY WATCH (a posts-only tracked source), before fetching anything. It still persists nothing: no source, no sync, no lead, and NO ENGAGERS, which is the expensive half a tracked sweep does next; captured is false. `limit` and `paginationToken` are gone — the caller states a number and the 15-per-page walk happens internally. A tighter rate limit of 10 calls a minute is now the secondary guard: a 429 means called-too-fast, not out-of-credits. |
| get_company_posts | Read a COMPANY PAGE's posts AND THEIR TEXT without tracking it — the company half of get_profile_posts, and the untracked counterpart to the tracked company posts job, which 404s for a page you have not tracked. ⚠ ONE POST IS ONE CREDIT: it used to be free and is not. Say how many with `posts` (1-60, no default) and authorise with `confirmSpend`; without it the call is 409 `spend_confirmation_required` carrying `estimatedCredits` and `remainingBalance` in `details`, and nothing is fetched or charged. You are charged for the posts ACTUALLY RETURNED — `creditsCharged` says what. `username` is the COMPANY SLUG, the handle from a company page URL (a full company URL is accepted and unwrapped); a linkedin.com/in/… person URL is REFUSED with a 400 naming get_profile_posts rather than searched for as a company, so an agent is never quietly told a real person does not exist — and a refusal charges nothing. ASK THE PERSON HOW MANY POSTS, AND WHETHER THEY WANT A ONE-OFF READ OR A DAILY WATCH (a posts-only tracked source), before fetching anything. It still persists nothing: no source, no sync, no lead, and NO ENGAGERS, which is the expensive half a tracked sweep does next; captured is false. `limit` and `paginationToken` are gone. The rate limit is now the secondary guard — 10 calls a minute, the SAME budget get_profile_posts draws on, so calling both at once spends it twice as fast; a 429 means called-too-fast, not out-of-credits. |
| get_sync_status | Get a tracked source’s capture/sync progress. Top-level stoppedBy 'credit_cap' means the source’s own creditCapPerSync was reached and the run stopped there — an ordinary ending, not a failure; `capture` is the capture half’s state/isFinal/completedAt plus `capture.stoppedBy` ('credits' = the per-sync limit stopped it and there was more, 'exhausted' = it collected everything it found) and `capture.creditsSpent` (the leads that run wrote), which is what tells “collected 100 because that was everything” from “collected 100 because 100 was the cap” — both null until the first run finishes, and null for a keyword search, whose ending is lastRun.stoppedBy. Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. |
| get_keyword_sync_status | Get a keyword search's sweep progress — by source id, not a username. |
| list_leads | List & filter your captured, enriched leads (no post re-sweep). Each lead carries postPostedAt (when the post it engaged with was published) and, on a Comment lead, commentPostedAt (when that comment was written; null when no time was recorded). Also including companyName (also company), companyUrl (website URL), companyDomain (website hostname), companyLinkedinUrl (LinkedIn company page), companyDescription, companyIndustry, companyLocation (headquarters), companyEmployeeCount, companyStaffRange and companyEnrichedAt. companyUrl is the company's own website, from the website field of its company record, and never a LinkedIn URL; companyLinkedinUrl is its LinkedIn company page. Company fields come from the company record, which Cornersight resolves once per company and caches for every lead at that company. They cost no enriching credits. companyStaffRange is the LinkedIn size bucket and companyEmployeeCount is the reported total, so the two can disagree. companyEnrichedAt is null until the company has been resolved; after that, a null company field means the company record has no value for it. Unknown values are null. engagementType is Like, Comment or Author — the person who wrote a post a keyword search kept, when it captures post authors. |
| list_raw_leads | List RAW leads — the leads of sources in raw mode (`enrichLeads: false`), captured and charged exactly as before (1 credit per new person per source, repeats free) but never enriched: LinkedIn URL, URN, name when known, action (Like, Comment or Author), comment text, the post's URL, URN and date, and when it was captured. Raw leads never appear in list_leads, list_engagers, the dashboard, exports, webhooks or integrations — this is how they are delivered. Same source, action, since/until, includeInactive and limit/offset filters as GET /api/v1/leads/raw; reading charges nothing. |
| list_sources | List the people, company pages, tracked posts, and keyword searches this team tracks as lead sources. Reports each source's creditCapPerSync (a person's, company page's or post's per-SYNC cap) and lastRun.stoppedBy, and for a keyword search its config.creditCap (its own per-RUN cap, a different field), schedule (runOnce, endAt, maxRuns) and expression. On a keyword run, lastRun.stoppedBy 'credits' means that cap was reached and there was more to capture — not that the sweep was exhausted; 'post_limit' means a posts_only search bought its postsPerSync posts for the day. A keyword source also reads back who it captures: mode and captureReplies, config.captureEngagers and config.capturePostAuthors. Filter with type: person|company|post|keyword; page with rowOffset. Per sync, every sync — one credit is one lead collected, with no estimate, no confirmation gate and no team ceiling; a keyword search's `creditCap` is the daily bound on a recurring sweep and has all three, and the team's `dailyCeiling` counts keyword spend only. |
| list_engagers | Top engagers — leads aggregated per person with an engagement count, including companyName (also company), companyUrl (website URL), companyDomain (website hostname), companyLinkedinUrl (LinkedIn company page), companyDescription, companyIndustry, companyLocation (headquarters), companyEmployeeCount, companyStaffRange and companyEnrichedAt. companyUrl is the company's own website, from the website field of its company record, and never a LinkedIn URL; companyLinkedinUrl is its LinkedIn company page. Company fields come from the company record, which Cornersight resolves once per company and caches for every lead at that company. They cost no enriching credits. companyStaffRange is the LinkedIn size bucket and companyEmployeeCount is the reported total, so the two can disagree. companyEnrichedAt is null until the company has been resolved; after that, a null company field means the company record has no value for it. Unknown values are null. |
| get_credits_balance | Get the enriching credit balance (balance, used, limit, remaining, plan, nextReset) plus the day's keyword position (spentToday, dailyCeiling, dailyCeilingMode). spentToday is keyword spend and used is enrichment charges — not a subset of one another; spentToday resets at midnight UTC. |
| get_credits_usage | Dated + by-source breakdown of enriching credits charged; rowOffset pages byDate. |
| get_api_keys | List the team's active API keys (masked) + quota (used/max/remaining); rowOffset pages them. |
| push_leads | Push a tracked profile's or company page's already-captured leads to its webhook — historical backfill, all or ICP-only, and the retry for a failed delivery. Charges no credits. |
| push_keyword_leads | The same push for a keyword search, addressed by its source id rather than a username. |
| push_source_leads | The same push for any source by its source id — the only push for a tracked post. Charges no credits. |
| get_lead_push | Progress of one push: pending / sending / delivered / failed / held over exactly that push's leads. |
| get_source_sync_status | Any source's capture/sync progress by SOURCE ID — including a tracked post, whose lifecycle no other tool can follow. |
| sync_source | Sync a tracked person, company page or post NOW by source id — re-tracking never re-syncs. Charged like any sync; never stacks on one in flight; keyword searches are refused. |
| get_source_webhook_config | Any source's webhook configuration by source id: URL, ICP-only, auto-send, sync events. |
| set_source_webhook_config | Configure any source's webhook by source id. Partial update; explicit false is a value, and an empty URL clears delivery. A tracked post takes the URL, ICP-only and auto-send; syncEvents: true on one is refused (400 not_supported_for_post). |
| get_source_icp_config | Any source's ICP filter rules and match mode, by source id. |
| set_source_icp_config | Set any source's ICP rules or match mode by source id; existing leads are re-scored immediately. Pass dryRun: true to PREVIEW instead: the rules are validated and evaluated against this source’s already-captured leads and NOTHING is written — no rules saved, no lead re-scored, no webhook fired — the reply carrying dryRun: true, counts (matching/notMatching) and a sample of up to ten matching leads. Run it before any rule change whose effect has not been seen, because a real save re-scores every existing lead at once.. |
| get_kept_posts | Which posts a keyword search's AI filter KEPT — the URNs behind lastRun's postsKept count. |
| get_source_posts | The posts a POSTS-ONLY source has saved (a keyword search, person or company page tracked with mode posts_only), newest first across every run, each with its text, permalink, date, author and counts. Read-only and free. Addressed by the source id from list_sources; an engagers source answers 404 not_posts_only. 10 posts per response: page with rowOffset. The same rows as GET /api/v1/sources/{id}/posts and the CLI's source-posts. |
| discover_influencers | Start a one-off Discover Influencers run: the people who post about 1-10 keywords and average at least minEngagement likes + comments, at most maxInfluencers (1-500), optionally only in some countries. 1 credit per person added, once. Call without confirmSpend first, say the quoted credits, then re-send with confirmSpend: true. |
| list_discover_runs | The team's Discover runs, newest first: state, candidates, qualified, found, countries and the country check. |
| get_discover_run | One Discover run and its influencers: name, LinkedIn URL, job title, company, country, highest and average engagement, post count, top post. |
| delete_discover_run | Soft-delete a Discover run: it and its influencers leave the lists; the Author leads it added are kept; no refund. |
| get_agent | Read an Engagement Agent: its profile, daily credit limit and how it is split, status, webhook, trial allowance, people search and every source it set up, plus `agents`, every agent the team holds (agentId picks one). Flattened so every list pages with rowOffset. |
| set_agent_profile | Create or partially update an agent's profile (titles, industries, company sizes, countries, topics, competitors, website, daily limit) and webhook (url, autoSend, filters); create: true adds another agent. Starts nothing and charges nothing. Drafting from a website stays on the dashboard. |
| start_agent | Start or restart the agent: one daily keyword search per topic and a one-off people search whose people are watched automatically when it finishes. It recurs daily until stop_agent, so it is refused 409 spend_confirmation_required, with estimatedDailyMax in details, until the person confirms. |
| stop_agent | Stop the agent: every source it set up is untracked and it goes back to draft. Leads are kept. |
| list_agent_leads | Agent Leads, one row per person, ranked by ICP % (the job title must match) then signal (Extra Strong to Weak), as the dashboard shows them; optional filters as the Filters button; 10 per page with nextOffset. checked is everyone the team paid to check; firstRun says whether the first run is over. |
| push_agent_leads | Send Agent Leads to the agent's webhook now (Push historic leads): those passing its filters, or leadIds. Nothing is sent twice; charges nothing. |
| get_heyreach_status | Read the HeyReach connection: whether it is connected, whether HeyReach rejected the key (auto-push waits until it is reconnected), and every auto-push rule. No tool takes a key. |
| list_heyreach_campaigns | List the HeyReach campaigns leads can join, with their LinkedIn senders and canTakeLeads. |
| send_to_heyreach | Add leadIds (or Discover people) to a HeyReach campaign. Only enriched leads with a LinkedIn profile go, a person is never sent twice to the same campaign, and a paused campaign is never resumed. Refused until the person confirms (confirm: true). |
| get_source_heyreach_config | Read any source's HeyReach auto-push by source id: the campaign its new leads go to, filters and whether it is paused. |
| set_source_heyreach_config | Send a source's new leads to a HeyReach campaign automatically (pushHistoric sends past ones once); campaignId null removes it. An agent's is set_agent_profile's heyreach. |
Like the API, job-creating tools return exactly { "jobId", "statusTool": "get_job_status" } — no job status and never the result. The job-status vocabulary (pending | running | completed | failed) appears only on get_job_status responses, which is also the only place the result is available — even for the “immediate” engagement tools, always read it. The agent handles the polling loop for you.
Every array in an MCP response is cut to its first 10 entries — on every tool, not just the job ones — and resultTruncated is then true. This is an MCP-only limit that keeps a tool result from flooding the agent's context; the CLI and the REST API return the whole array. Every tool can reach the rest; what changes is the argument that gets you there.
list_sources, get_credits_usage and get_api_keys page the same way: pass rowOffset and follow resultWindow.nextRowOffset until it is null. Their filters — type, includeInactive, from/to — change which entries match rather than moving the window, and includeInactive makes the list longer. Where a response carries two arrays, one cursor drives both: an array short enough to fit the cap is returned whole in every window and marked windowed: false, so it is complete already and must not be summed once per page.
The spend has to be confirmed by the person, and the threshold is every create. There is no credit figure below which it is skipped, because what is being authorised is a commitment rather than an amount: track_keyword takes three scope fields — creditCap, captureMode, datePosted, the same three the dashboard gates behind its “Search scope” step — plus confirmSpend: true. The same rule applies on a resume (keywords matching an existing search re-cap that search) and on any update that raises what a day can cost. Without the confirmation the call is refused 409 spend_confirmation_required, and the refusal is the useful part: its body carries creditCap, captureMode, datePosted, estimatedDailyMax, remainingBalance and daysToExhaustAtCap. Show those numbers to the person and wait for an answer before retrying, then re-send the identical call with the confirmation added — never a smaller cap to make the refusal go away. A missing scope field is refused earlier, as 400 scope_required, whose field names which one and whose message states the exact JSON to add.
The tool's required list tells an agent which phase the contract is in. A required field in an MCP schema is enforced by the client, before the call is made — so during the roll-out's warning phase the four read as optional and the obligation lives in the descriptions instead, which keeps an old-style call reaching the server that can warn it: it still returns 201, now with a deprecations array naming what to add and the cut-over date. Relay that rather than discarding it. From the cut-over the same four are required in the schema and refused by the endpoint. An agent can move early on its own by sending contractVersion: "2026-11-01".
The cut-over is scheduled for 2026-11-01, and this is the notice of it. Until 2026-11-01 an old-style create — one that omits the three scope fields and confirmSpend — still returns 201/200 and carries a deprecations entry naming exactly what to add. From 2026-11-01 the same request is refused: a missing scope field is 400 scope_required naming the field, and a scope that is stated but not confirmed is 409 spend_confirmation_required — the same refusal a request pinned to contractVersion: "2026-11-01" already gets today, which is how an agent tests the new behaviour before the date. The exact change is four arguments: creditCap: 100, captureMode: "depth", datePosted: "PAST_WEEK", confirmSpend: true. Those three values are what the dashboard's “Search scope” step pre-fills, offered so they can be copied deliberately — they are not server-side defaults and nothing is chosen for the caller, so send the caps the person actually wants, and send confirmSpend only once they have heard the daily figure. The version string and the cut-over date are the same day, which they did not have to be: the version is what you pin, the date is what happens to you if you do not.
track_keyword creates a standing daily charge, and its reply says how big. Both the create and the resume carry estimatedDailyMax — the most one day of that search can cost in enriching credits, the same number the dashboard shows above its Confirm button — and daysToExhaustAtCap, the whole days the team's remaining balance funds at that rate. Relay the daily figure to the person who asked, before or as the search is created. The first sweep starts within seconds and charges, so an agent that waits until afterwards is reporting a bill rather than a price, and this tool has no confirmation step to fall back on. daysToExhaustAtCap: 0 is the warning and not the all-clear: the balance cannot fund one whole day at that cap, so the sweep just queued is the one that gets cut short. Both keys are omitted, never null and never 0, when there is no honest number — no usable creditCap, or a balance that could not be read — so test whether the key is present rather than reading a value out of it. list_sources reports the same two inside a keyword source's config.
Raw leads are read with their own tool. A source created or updated with enrichLeads: false keeps its leads raw: captured and charged exactly as before, one credit per new person per source, but never enriched. list_raw_leads is the only tool that returns them — list_leads and list_engagers never do — and it pages like list_leads. The switch needs no confirmSpend and applies to leads captured after it; on a raw source, the sync status's enrichment.completed counts the leads captured raw.
list_sources includes keyword availability in lastRun.postsAvailable (approximate provider total, which may double-count posts) and lastRun.caughtUp (whether every term returned only previously swept posts). Both are omitted for unmeasured runs.
For job results, page with rowOffset on get_job_status: resultWindow reports { rowOffset, rowsReturned, rowsInPage, nextRowOffset }, and you pass nextRowOffset back until it comes back null. It re-reads the result the job already stored, so it costs no LinkedIn call and no credits. Only then raise page on the reactions/comments tools — that one advances the provider by a whole page of 50. Stop on nextRowOffset: null, not on resultTruncated, which describes only the response in your hand.
Discover Influencers
Discover Influencers is a one-off search for the people who post about a topic and get engagement on it. Each person found is added as an Author lead and appears in Influencer Leads in the dashboard. Four tools cover it: discover_influencers starts a run, list_discover_runs lists them, get_discover_run reads one with its influencers, and delete_discover_run removes one. They call POST/GET /api/v1/discover and GET/DELETE /api/v1/discover/{id}; the full rules are under Discover Influencers.
- Topic: 1-10 keywords, and a post matching any of them counts (no AND/NOT). Each keyword is a separate LinkedIn search.
- Window: always the past month, sorted by relevance, so posts come from across the month; roughly 250-700 posts are read per keyword.
- Engagement: likes (all reactions) + comments per post; reposts and views are not counted.
minEngagementapplies to a person's average across their matching posts (usually one post).highestEngagementis their best post, which Influencer Leads ranks by. - People only: company pages are skipped. At most
maxInfluencers(1-500) are added, highest averages first;qualifiedabovefoundmeans more qualified than the limit allowed. - Countries: optional, up to 20. Only people whose LinkedIn profile is in one of them are added; short forms like UK, USA and UAE work, and a person with no country on their profile is left out. Profiles are looked up before anyone is added, at most min(500, max(100, 5 × maxInfluencers)) per run, at no cost.
- Credits: 1 per influencer added, once. Reading posts and country lookups are free.
- Lifecycle:
running, thendoneorfailed, usually within a few minutes. Job title, company and country are null until enrichment fills them shortly after capture. Deleting keeps the Author leads already added.
Never send confirmSpend on the first call. Without it the tool answers with a spend_confirmation_required error whose details.estimatedCredits is the most the run can cost. The agent says that figure, waits for a yes, then repeats the call with confirmSpend: true, the same rule as track_keyword.
// 1. discover_influencers (no confirmSpend) → error 409
{ "keywords": ["claude code", "ai agents"], "minEngagement": 50, "maxInfluencers": 25, "countries": ["UK"] }
→ { "statusCode": 409, "error": "This run can use up to 25 credits: ...",
"details": { "error": "...", "code": "spend_confirmation_required", "estimatedCredits": 25 } }
// 2. the person agrees; the same call with "confirmSpend": true → the run
→ { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "state": "running", "countries": ["United Kingdom"], "found": 0, ... }
// 3. list_discover_runs {} → { "runs": [{ "id": "7c9e6679-...", "state": "done", "qualified": 31, "found": 25, ... }], "total": 1 }
// 4. get_discover_run { "id": "7c9e6679-..." }
→ { "run": { ... }, "influencers": [{ "name": "Jane Doe", "linkedinUrl": "https://www.linkedin.com/in/jane-doe",
"jobTitle": "Founder", "company": "Acme", "country": "United Kingdom", "highestEngagement": 412,
"avgEngagement": 268.5, "postCount": 2, "topPostUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7381234567890123456/" }] }
// 5. delete_discover_run { "id": "7c9e6679-..." } → { "ok": true, "id": "7c9e6679-...", "deleted": true }Find people in the UK who post about Claude Code and average at least 50 likes and comments. Up to 25 of them. Tell me the cost first.MCP vs. the CLI and the REST API
The three surfaces reach the same endpoints and return the same fields. They differ in how many rows you get back, and in one case in what is available at all. The CLI and REST columns are identical throughout — the CLI is a thin client over the same API — so every difference below is something MCP does and they do not.
| Operation | MCP | CLI & REST |
|---|---|---|
| get_job_status | 10 rows per response; rowOffset walks the stored page 10 at a time until nextRowOffset is null. Within a row, a summary also clips strings over 2,000 characters and cuts or empties nested structures — resultDetail.clipped names each one, and detail: true returns that single row complete. | The whole page the job collected (up to 50 rows), every field of every row. |
| list_leads / list_engagers / list_raw_leads | 10 rows per response — the tools ASK the endpoint for 10 rather than showing 10 of a larger page, so total, limit, offset and hasMore describe the rows you were handed. Page by passing nextOffset back as offset until it is null. resultTruncated no longer fires for the row array here, so a true means nested content was clipped. | Up to limit rows (1-100, default 50). |
| list_sources | 10 sources per response; rowOffset walks the rest until nextRowOffset is null. type and includeInactive narrow WHICH sources match — they do not page, and includeInactive makes the list longer. | Every match in one response; type and includeInactive filter. |
| get_credits_usage | 10 entries per response; rowOffset walks byDate (up to 31 in a billing period). bySource is short enough to fit, so it comes back whole in every window — do not sum it per page. totalCharged is the true total either way. | Every dated and per-source entry in the window. |
| get_api_keys | 10 keys per response; rowOffset walks the rest. The used/max/remaining quota beside them stays exact. | Every active key. |
| get_keyword_webhook_config / set_keyword_webhook_config | Both operations are here, addressed by source id. Every id is now reachable — list_sources pages with rowOffset — but a search past the tenth costs an extra call to reach before you can name it. | The same two by id (keyword-get-webhook / keyword-set-webhook), and sources lists every search, so the id is always in reach. |
| Create / revoke an API key | Not available — deliberately absent, so an agent cannot mint or destroy a credential. | keys-create and keys-revoke; POST and DELETE /api/v1/keys. |
The three tools with no paging control are the ones to watch: list_sources, get_credits_usage and get_api_keys have filters but no argument that moves a window, so a team with more than ten sources, or spend on more than ten days, cannot see the rest of them over MCP. resultTruncated tells you it happened. Use the CLI or the REST API when you need the whole list — and note that the totals alongside those arrays (totalCharged, the key quota) are computed before truncation and stay correct, so never re-derive a total by summing the rows you were shown.
Connect a client
Add this server URL to your client. Nothing else is required — the client opens a Cornersight sign-in the first time it connects.
https://mcp.cornersight.io/mcpSign in (Claude, ChatGPT and other connector UIs)
- Open your client's connector settings (Claude → Settings → Connectors, ChatGPT → Connectors).
- Click Add custom connector, name it Cornersight and paste the URL above. Leave the OAuth client ID and secret fields empty.
- Click Connect. Sign in to Cornersight, choose the team the connection should act on, and click Allow.
Only owners and members who can make changes can authorise a connection. You can switch team later by connecting again and choosing a different one.
API key in a header (Claude Code, Cursor, scripts)
Clients that let you set request headers can skip the sign-in and use an API key instead. Generate one in Cornersight → Settings → API key (it starts with cs_) and send it with the same URL. Either spelling below works — use whichever field your client exposes.
https://mcp.cornersight.io/mcp
X-API-Key: cs_your_key_here
# ...or, equivalently:
Authorization: Bearer cs_your_key_hereKeep the key in a header rather than in the URL. A URL travels through places a header does not — proxy and server access logs, browser history, and Referer headers — and each of those is a copy of a live credential you cannot retract.
Clients that take a URL and cannot sign in
If your client supports neither sign-in nor headers, put the key in the path instead — it authenticates identically. Treat the whole URL as a secret.
https://mcp.cornersight.io/mcp/YOUR_API_KEYA key identifies your team, so a key connection acts on whichever team owns it.
Example prompts
Track the LinkedIn profile "demo-profile" and show me the enrichment result.
Get the reactions on post urn:li:activity:1234567890 and list the companies.The agent calls the matching tool, waits for the job, and returns the data — you never touch the REST endpoints yourself.
Security: your API key is a password — it acts on the team that owns it and can create jobs that consume credits, so don't paste it anywhere public. Prefer the header form; if you use the URL form, remember the whole URL is the secret, so it must never be shared in a screenshot, bug report, or ticket. Revoke a key anytime in Cornersight → Settings → API key, which instantly kills the connection — and rotate it if a URL containing it has been shared.