Endpoints: 28,729MCP servers: 18,413Payout addresses: 2,071Paid calls: 1,558Letters: 14Defects: 1,331counted 3 min ago
teppi

Server definition

Hash
sha256:587a481583eaab0ba5ddf4aba0eac74c06d8fa0a65abff91bbc7477b74d29c5a
What it is
What a remote MCP server returned when asked what it offers: 16 tools

The blob, as servednamed by its sha256

{ "instructions": "Onsa finds real, scored B2B sales leads (actual people with LinkedIn profiles) for your workspace, and keeps every search as a \"campaign\" you can come back to.\n\nRUNNING A SEARCH: `find_leads` starts a live search and returns a `jobId` IMMEDIATELY — the search runs in the background. First leads arrive within 5 minutes in half of searches and within 9 minutes in 9 of 10. More can arrive until the final report, typically about 10 minutes after the start (9 of 10 within 15). Every `fetch_leads` answer carries `progress.note`, which says whether the NEXT call will wait for leads (up to 35 seconds) or answer at once, and when the user should ask again. Call again right away only when the note says the next call waits; otherwise stop polling in this turn. A find_leads job that reads \"pending\" has not failed: unless its `progress.note` says it has most likely finished, it is still searching, and never tell the user no leads were found while it is pending. When `leads` holds a partial list, show it now. If you end your turn first, tell the user when to ask again (the time `progress.note` gives) and offer to check then — the search keeps running in Onsa, a later `fetch_leads` with the same `jobId` picks the results up, and `campaignUrl` shows them live. Hosts that render MCP Apps also show a live card that follows the search on its own. `status` means different things for the two job kinds; the `fetch_leads` description gives the exact rules. For a find_leads job, \"completed\" means the agent posted a reply carrying leads — usually its final report, not always — so `total` can still change: if it rose since your previous call, one more check is worthwhile. A find_leads job that reads \"stalled\" has most likely ended without leads (the agent asked a question, found none, or failed): STOP polling, read `agentMessage` for the reason, tell the user, and either re-run `find_leads` with the answer folded into the query or send them to campaignUrl (a lead that still lands shows up on a later call). A `continue_campaign` job never calls for a new `find_leads` — that would fork a separate campaign: when it has an `agentMessage`, relay it (if that reply confirms a settings-only change, the request is done, even while the job reads \"pending\"; the reply can also be a late report from an earlier search); `newLeads` counts the leads added to the campaign since it started, which can include a late batch from an earlier search; if it reads \"stalled\", no agent reply was confirmed — relay `agentMessage` if it holds one, otherwise tell the user and point them to campaignUrl, or send the request again with `continue_campaign`. Treat a `status` value not listed here as pending. `limit` is a target the search agent aims at, not a cap — it often returns more.\n\nJUDGING A COHORT: every lead carries `score` (1–5) and `scoreExplanation` — the agent's written reasoning for why this person matches. Use those, not just names, when you assess whether a search found the right people.\n\nWRITING OUTREACH THAT IS NOT MAIL-MERGE: `list_pending_outreach` shows drafts awaiting a human. Before improving one, call `get_lead_memo` — Onsa has usually already researched that person, and the memo is far richer than scoreExplanation. Rewrite around ONE specific, checkable fact from it via `rewrite_outreach`, show the user the result, and only then `send_outreach` with their explicit go-ahead. Never invent a fact about a person; if there is no memo, say so and keep the message honest.\n\nGROWING OR STEERING A COHORT: use `continue_campaign`, not `find_leads`. \"More like these\", \"try the Gulf instead\", \"drop the wrong-shaped ones\" all belong in the campaign that already exists — `find_leads` would fork a separate campaign with its own ICP and split the funnel.\n\nREADING WHAT PROSPECTS ACTUALLY SAID: `get_campaign_stats` counts replies and labels them; `list_replies` returns THE REPLY TEXT. Never diagnose a cohort from counts alone — read the replies before you conclude anything about why an ICP is or is not landing. The stored `sentiment` label is written once per lead and unscored replies are missing from the funnel counts entirely, so `list_replies` is both richer and more complete than the stats.\n\nRANKING COHORTS: rank on positivePct, never on replyPct. A rejection is a reply, so a cohort that provokes people scores high on replyPct while producing nothing, and a high replyPct beside a high negativePct means the copy is annoying its audience rather than engaging it. Because every rate counts only the replies Onsa has scored, quote them as \"of the replies Onsa scored\" and use `list_replies` when the question is how many people actually answered. When counts and the Overview page disagree, say why: these count leads, the widget counts actions.\n\nDECIDING WHAT TO DO NEXT: for a general \"What next?\", call `list_next_steps` without a campaignId; when the conversation identifies a campaign, include its ID. Present the nextAction first, with supporting counts; use campaign names, not IDs or setup jargon. For a new workspace, reuse an existing usable brief and an already-authorized search request: do not ask the user to repeat or reapprove it. Otherwise ask one useful question about who they want to reach, not a questionnaire. Never invent a search brief. Read-only guidance never starts a search itself. Prospects can be inspected before connecting outreach. When the user asks how a campaign is doing, what is happening with it, or what they should do about it — anything shaped like \"analyse my campaigns and recommend something\" — call `list_next_steps` for that campaign. It returns the ranked to-do list: people who replied, invites accepted but never messaged, drafts waiting for approval, leads found but never contacted, missing setup. `get_campaign_stats` gives you the funnel and `list_replies` the words, but neither tells you what to DO. An unworked cohort is almost always worth more than another search.\n\nCOMPARING COHORTS: `list_campaigns` shows every past search, `get_campaign` returns one campaign's ICP and outreach template, `get_campaign_leads` returns its leads (use this rather than fetch_leads for a campaign you did not start in this session), and `get_campaign_stats` returns its outreach funnel (invites sent and accepted, messages sent, positive/negative/other responses). Together these let you compare which ICP actually produced replies and propose a better one.\n\nSHARING SELECTED PROSPECTS: call `preview_shortlist` first and show the user the complete snapshot it returns. Explain that creating a link makes that snapshot viewable by anyone with the link for 30 days unless it is revoked, and ask for explicit confirmation before `create_shortlist`. `create_shortlist` requires the previewKey `preview_shortlist` returned, and creates nothing when the selection or the snapshot no longer matches that key (a different selection or order, note or includeRationale, a prospect changed or removed, the campaign renamed); if that happens, preview again and confirm the current snapshot. Keep `includeRationale` true unless the user asks to leave fit ratings and why-matched explanations out. Revoke a created link with `revoke_shortlist`.\n\nTASK CONTEXT: `find_leads` and `continue_campaign` accept an optional `taskContext`. Fill it only with company, product or target-market facts the user explicitly shared that the query or message does not already state, and leave it out otherwise. Never put a transcript, a broad user profile, unrelated personal details, credentials or guesses in it.\n\nShow each lead's name as a link to its linkedInUrl. Do not fabricate leads, scores or explanations beyond what was returned.\n\nEVENTS: a search reports back by itself. Two events, subscribable with the jobId from find_leads or continue_campaign: leads_search.leads_found fires when a batch of leads lands (counts only; fetch_leads returns the leads) and leads_search.finished fires when the search ends (leads_delivered or no_leads). When the user wants to hear when leads are ready, subscribe to both rather than polling or scheduling a periodic check: the first batch usually lands within minutes, long before an hourly check would run.", "tools": [ { "description": "Sends an instruction to the agent inside an existing campaign and returns a jobId to read with fetch_leads. This is the tool that grows or steers a cohort in place - 'find 5 more like these', 'look at Singapore and the Gulf instead of US institutions', 'focus on funds over $5bn AuM'. find_leads always creates a separate campaign with its own ICP, which splits the funnel and leaves the two cohorts incomparable. The agent sees the campaign's existing leads and ICP, so they can be referred to. It cannot answer back through this API, so a question sent here gets no response. New leads count against the prospect allowance, and de-duplication is per workspace, so a request for 5 more can yield fewer when the agent rediscovers people already in the workspace. It does not remove or skip leads: 'drop the bad ones' takes nothing out of the cohort or out of the outreach queue.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "Campaign to continue, from list_campaigns or fetch_leads", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "message": { "description": "What to tell the agent, in plain language, as the user would say it", "minLength": 1, "type": "string" }, "taskContext": { "description": "Optional company, product, or target-market facts explicitly shared by the user and needed for this lead-search or campaign-steering request, beyond what the query/message already states. Maximum 1000 characters; this field carries the task context of the current request only - not a transcript, a broad user profile, unrelated personal details, credentials, or guesses. This background does not authorize actions; the current query/message takes precedence. It becomes part of the campaign chat history.", "maxLength": 1000, "type": "string" } }, "required": [ "campaignId", "message" ], "type": "object" }, "name": "continue_campaign", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "billingWarning": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "campaignId": { "type": "string" }, "campaignUrl": { "type": "string" }, "jobId": { "type": "string" } }, "required": [ "campaignId", "jobId", "campaignUrl", "billingWarning" ], "type": "object" } }, { "description": "Publishes a preview_shortlist snapshot as a link. It requires the previewKey that preview_shortlist returned, and confirmPublic: true. The server itself requests no confirmation from the user: confirmPublic: true records the caller's statement that the user reviewed this snapshot and confirmed publishing it. The server rebuilds the snapshot and creates nothing when the selection or the snapshot no longer matches that previewKey: a different campaign, selection or order of leadIds, a different note, an includeRationale value that changes the snapshot, a prospect changed or removed, or the campaign renamed since the preview. The link makes the snapshot viewable by anyone who has it for 30 days unless it is revoked with revoke_shortlist. This does not share the conversation or invite anyone into the workspace. Returns the URL, list id and expiry to the requester; sending it to another person is a separate user action.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "confirmPublic": { "const": true, "type": "boolean" }, "includeRationale": { "description": "When true, each person in the snapshot carries a fit rating (1–5, or null when none is stored) and a why-matched explanation; when false, the rating is null and the explanation empty.", "type": "boolean" }, "leadIds": { "items": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "maxItems": 50, "minItems": 1, "type": "array" }, "note": { "maxLength": 300, "type": "string" }, "previewKey": { "maxLength": 64, "minLength": 64, "type": "string" }, "title": { "maxLength": 100, "minLength": 1, "type": "string" } }, "required": [ "campaignId", "leadIds", "title", "note", "includeRationale", "previewKey", "confirmPublic" ], "type": "object" }, "name": "create_shortlist", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "expiresAt": { "type": "string" }, "id": { "type": "string" }, "snapshot": { "additionalProperties": {}, "properties": { "note": { "type": "string" }, "people": { "items": { "additionalProperties": {}, "properties": { "companyName": { "type": "string" }, "name": { "type": "string" }, "position": { "type": "string" }, "profileUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "rationale": { "type": "string" }, "score": { "anyOf": [ { "maximum": 5, "minimum": 1, "type": "number" }, { "type": "null" } ] } }, "required": [ "name", "position", "companyName", "profileUrl", "rationale" ], "type": "object" }, "type": "array" }, "title": { "type": "string" } }, "required": [ "title", "note", "people" ], "type": "object" }, "url": { "type": "string" } }, "required": [ "id", "url", "expiresAt", "snapshot" ], "type": "object" } }, { "description": "Returns the status and any results of a find_leads or continue_campaign job, by jobId. Status values are \"pending\", \"completed\" and \"stalled\", and `leads` can hold a partial list while \"pending\". For a find_leads job: \"pending\" while it has no leads, the agent has not answered and it is under about 15 minutes old, and also while it has leads, until a reply carrying leads completes it; \"completed\" once the agent posts a reply carrying leads while this is the oldest open job on its campaign, usually its final report (`total` can still change afterwards); \"stalled\" as soon as the agent answers without any leads (a question, or none found), or when no lead has arrived after about 15 minutes - derived on each read, so a lead that lands later turns it back to \"pending\". For a continue_campaign job: \"pending\" while `newLeads` is 0 and it is under about 15 minutes old, even after the agent has replied (if that reply confirms a settings-only change, the request is done), and while `newLeads` is above 0, until a reply carrying leads completes it; \"completed\" once a reply carrying leads arrives on the campaign while this is the oldest open job there (possibly a late reply from an earlier search), or after about 15 minutes with `newLeads` 0 when an agent reply was confirmed; \"stalled\" after about 15 minutes with `newLeads` 0 when no agent reply was confirmed (agentMessage can still hold one). A reply carrying leads completes only the oldest open job on a campaign, so a newer job stays \"pending\" meanwhile, even with `newLeads` above 0. A pending search keeps running when the conversation ends, and a later call with the same jobId returns what it found. First leads arrive within 5 minutes in half of searches and within 9 minutes in 9 of 10. More can arrive until the final report, typically about 10 minutes after the start (9 of 10 within 15). Also returns `total` (for a find_leads job, the leads the campaign holds now, skipped and deleted ones excluded; for a continuation, equal to `newLeads`), `newLeads` (continuations only: leads added to the campaign since the continuation started - a count by time, which can include a late batch from an earlier search), `returned` (how many this response carries), `campaignId` (accepted by get_campaign, get_campaign_leads and get_campaign_stats) and `campaignUrl`, a deep link to the prospects tab for this search in Onsa. `agentMessage` is the latest agent text in this campaign's chat. For a continuation it is filtered to what was said after the continuation started, though it can be a late message from an earlier search; for a find_leads job it is not filtered, so after a later continue_campaign on the same campaign it can be that request’s reply. A null `agentMessage` does not mean the agent is silent: artifact-only messages carry no text, and an agent that errored writes nothing there at all. The agent cannot be replied to through this API. While a search is running, one call can wait up to 35 seconds for its first leads or its next batch before answering, so a call can take that long; other calls answer at once. `progress` carries startedAt, elapsedSeconds and a note on where the search stands, whether the next call will wait, and when results are likely. Each lead carries name, companyName, linkedInUrl, position, headline, location, industry, companyUrl, email (often null), and score (1-5) with scoreExplanation, the reasoning for why this person matches the ICP.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "jobId": { "description": "The jobId returned by find_leads", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "limit": { "description": "How many leads to return (default 100)", "maximum": 200, "minimum": 1, "type": "integer" } }, "required": [ "jobId" ], "type": "object" }, "name": "fetch_leads", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "agentMessage": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "campaignId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "campaignUrl": { "type": "string" }, "jobId": { "type": "string" }, "leads": { "items": { "additionalProperties": {}, "properties": { "companyName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "companyUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "headline": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "id": { "type": "string" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "linkedInUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "position": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }, "scoreExplanation": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "name", "companyName", "linkedInUrl", "email", "position", "headline", "location", "industry", "companyUrl", "score", "scoreExplanation" ], "type": "object" }, "type": "array" }, "newLeads": { "anyOf": [ { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, { "type": "null" } ] }, "progress": { "additionalProperties": {}, "properties": { "elapsedSeconds": { "anyOf": [ { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, { "type": "null" } ] }, "note": { "type": "string" }, "startedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "startedAt", "elapsedSeconds", "note" ], "type": "object" }, "returned": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "status": { "description": "One of \"pending\", \"completed\", \"stalled\"; more values may be added later. `leads` can hold a partial list while \"pending\". For a find_leads job: \"pending\" while it has no leads, the agent has not answered and it is under about 15 minutes old, and also while it has leads, until a reply carrying leads completes it; \"completed\" once the agent posts a reply carrying leads while this is the oldest open job on its campaign, usually its final report (`total` can still change afterwards); \"stalled\" as soon as the agent answers without any leads (a question, or none found), or when no lead has arrived after about 15 minutes - derived on each read, so a lead that lands later turns it back to \"pending\". For a continue_campaign job: \"pending\" while `newLeads` is 0 and it is under about 15 minutes old, even after the agent has replied (if that reply confirms a settings-only change, the request is done), and while `newLeads` is above 0, until a reply carrying leads completes it; \"completed\" once a reply carrying leads arrives on the campaign while this is the oldest open job there (possibly a late reply from an earlier search), or after about 15 minutes with `newLeads` 0 when an agent reply was confirmed; \"stalled\" after about 15 minutes with `newLeads` 0 when no agent reply was confirmed (agentMessage can still hold one). A reply carrying leads completes only the oldest open job on a campaign, so a newer job stays \"pending\" meanwhile, even with `newLeads` above 0.", "type": "string" }, "total": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" } }, "required": [ "jobId", "status", "total", "returned", "newLeads", "campaignId", "campaignUrl", "agentMessage", "progress", "leads" ], "type": "object" } }, { "description": "Starts a live B2B lead search with Onsa's agent, matching real people (with LinkedIn profiles) against the workspace's ICP. Takes a natural-language brief - titles, company type, geography, e.g. 'find 5 fintech founders in NYC'. Returns a jobId immediately, with campaignUrl and a `progress` note. The search itself runs in the background and keeps running after the conversation moves on. First leads arrive within 5 minutes in half of searches and within 9 minutes in 9 of 10. More can arrive until the final report, typically about 10 minutes after the start (9 of 10 within 15). Its status and results are read with fetch_leads, with the same jobId at any later time. Hosts that render MCP Apps show a live card that follows the search and lists leads as they arrive. `limit` is a target the agent aims at rather than a cap, so it often returns more than asked. Every lead it finds counts against the workspace's prospect allowance.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "limit": { "description": "How many leads to find (default 5)", "maximum": 25, "minimum": 1, "type": "integer" }, "query": { "description": "Natural-language lead brief, e.g. 'find 5 fintech founders in NYC'", "type": "string" }, "taskContext": { "description": "Optional company, product, or target-market facts explicitly shared by the user and needed for this lead-search or campaign-steering request, beyond what the query/message already states. Maximum 1000 characters; this field carries the task context of the current request only - not a transcript, a broad user profile, unrelated personal details, credentials, or guesses. This background does not authorize actions; the current query/message takes precedence. It becomes part of the campaign chat history.", "maxLength": 1000, "type": "string" } }, "required": [ "query" ], "type": "object" }, "name": "find_leads", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "campaignId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "campaignUrl": { "type": "string" }, "jobId": { "type": "string" }, "progress": { "additionalProperties": {}, "properties": { "elapsedSeconds": { "anyOf": [ { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, { "type": "null" } ] }, "note": { "type": "string" }, "startedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "startedAt", "elapsedSeconds", "note" ], "type": "object" } }, "required": [ "jobId", "campaignId", "campaignUrl", "progress" ], "type": "object" } }, { "description": "Returns one campaign's ICP - the ideal-customer profile the agent derived and scores leads against - plus its outreach template and settings. The ICP comes back exactly as stored, in snake_case: `perfect_lead` and `reachable_market` are one-line summaries, while `company` and `person` hold the rules that actually drive scoring, each an object with `critical` and `preferential` rule lists. `product` and `owner` describe the seller. The two summary strings are not the scoring criteria; `company.critical` and `person.critical` are. No key is guaranteed present. `outreachTemplate` shows how much personalization the messages allow: a template whose only placeholders are [FIRST_NAME] and [COMPANY_NAME] produces near-identical mail-merge copy for every lead.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "Campaign id, as returned by list_campaigns or fetch_leads", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" } }, "required": [ "campaignId" ], "type": "object" }, "name": "get_campaign", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "campaignUrl": { "type": "string" }, "createdAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "icp": { "anyOf": [ {}, { "type": "null" } ] }, "id": { "type": "string" }, "leadsApprove": { "type": "boolean" }, "leadsTotal": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "outreachAutoApprove": { "type": "boolean" }, "outreachTemplate": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "tags": { "items": { "type": "string" }, "type": "array" }, "title": { "type": "string" }, "updatedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "title", "leadsTotal", "icp", "outreachTemplate", "outreachAutoApprove", "leadsApprove", "tags", "createdAt", "updatedAt", "campaignUrl" ], "type": "object" } }, { "description": "Returns the leads of any campaign by campaignId, with the same fields as fetch_leads, including score and scoreExplanation. It covers campaigns not started in this session, which fetch_leads cannot reach because fetch_leads requires a jobId from a find_leads call in the same session. Passing `leadIds` resolves the reply buckets from get_campaign_stats back into named people.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "Campaign id, as returned by list_campaigns or fetch_leads", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "leadIds": { "description": "Optional; when set, only these leads are returned, e.g. positiveLeadIds from get_campaign_stats", "items": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "type": "array" }, "limit": { "description": "How many leads to return (default 50)", "maximum": 200, "minimum": 1, "type": "integer" } }, "required": [ "campaignId" ], "type": "object" }, "name": "get_campaign_leads", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "campaignId": { "type": "string" }, "campaignUrl": { "type": "string" }, "leads": { "items": { "additionalProperties": {}, "properties": { "companyName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "companyUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "headline": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "id": { "type": "string" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "linkedInUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "position": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }, "scoreExplanation": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "name", "companyName", "linkedInUrl", "email", "position", "headline", "location", "industry", "companyUrl", "score", "scoreExplanation" ], "type": "object" }, "type": "array" }, "returned": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "total": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" } }, "required": [ "campaignId", "total", "returned", "campaignUrl", "leads" ], "type": "object" } }, { "description": "Returns the outreach funnel for one campaign: invites sent, invites accepted, messages sent, and replies split into positive / negative / other by sentiment. LinkedIn and email are merged, as on Onsa's Overview page. These count leads rather than actions - a lead invited twice counts once - so where a lead was re-invited they read slightly lower than the Overview widget, which counts actions. `invitesSent` can exceed `leadsTotal` without anyone having been invited twice: the counters are already de-duplicated by lead, and the gap means a lead who was contacted has since been skipped or deleted, which action rows survive and leadsTotal does not count. Onsa stores no post likes or emoji reactions at all, so 'LinkedIn reactions' in this data means the replies people sent; list_replies returns their text, and this tool only counts them.\n\n`rates` carries acceptancePct, replyPct, positivePct and negativePct, each already computed over its own correct denominator; leadsTotal is not one of those denominators, since it counts every prospect in the cohort including those never contacted. A rate is null when its denominator is 0, meaning nothing was sent so no rate exists - which is different from the counts above being genuinely 0. All four rates count only replies Onsa has scored, so an unscored or still-in-window reply appears in none of them, and list_replies can legitimately show more replies than the rates imply. Two further properties of the data: the *Scheduled counts are everything queued regardless of date, and sentiment is evaluated once per lead ever rather than once per reply. A campaign whose invites were never sent reads as all zeros, which is 'not tried yet' rather than 'failed'.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "Campaign id, as returned by list_campaigns or fetch_leads", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" } }, "required": [ "campaignId" ], "type": "object" }, "name": "get_campaign_stats", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "campaignId": { "type": "string" }, "campaignUrl": { "type": "string" }, "invitesAccepted": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "invitesScheduled": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "invitesSent": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "leadsTotal": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "messagesScheduled": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "messagesSent": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "rates": { "additionalProperties": {}, "properties": { "acceptancePct": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }, "negativePct": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }, "positivePct": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }, "replyPct": { "anyOf": [ { "type": "number" }, { "type": "null" } ] } }, "required": [ "acceptancePct", "replyPct", "positivePct", "negativePct" ], "type": "object" }, "responses": { "additionalProperties": {}, "properties": { "negative": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "negativeLeadIds": { "items": { "type": "string" }, "type": "array" }, "other": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "otherLeadIds": { "items": { "type": "string" }, "type": "array" }, "positive": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "positiveLeadIds": { "items": { "type": "string" }, "type": "array" } }, "required": [ "positive", "negative", "other", "positiveLeadIds", "negativeLeadIds", "otherLeadIds" ], "type": "object" } }, "required": [ "campaignId", "leadsTotal", "invitesSent", "invitesScheduled", "invitesAccepted", "messagesSent", "messagesScheduled", "responses", "rates", "campaignUrl" ], "type": "object" } }, { "description": "Returns the research memo Onsa's agent wrote about one lead: role history, company size and stage, what they have said publicly, and the angle on them. It is usually far richer than scoreExplanation, and it is the source material for outreach built on a specific, checkable fact rather than a generic opener. Most leads have no memo - Onsa writes one only for prospects it has researched - so `memo: null` is the common case and not an error, and scoreExplanation is the remaining source in that case.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "leadId": { "description": "Lead to read, from any tool that returns leads", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" } }, "required": [ "leadId" ], "type": "object" }, "name": "get_lead_memo", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "companyName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "leadId": { "type": "string" }, "memo": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "memoWrittenAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "scoreExplanation": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "leadId", "name", "companyName", "scoreExplanation", "memo", "memoWrittenAt" ], "type": "object" } }, { "description": "Lists the campaigns (past lead searches) in this workspace that the user takes part in, newest first. Returns the newest `limit` of them, default 50; `returned` against `total` shows whether older campaigns were omitted. Each entry has id, title, leadsTotal, hasIcp, tags, createdAt and updatedAt. An id is accepted by get_campaign for its ICP, get_campaign_leads for its people, get_campaign_stats for its outreach funnel, list_replies for what prospects wrote back, and list_next_steps for what the campaign still needs a human to do. Searches started over MCP often share a generic title, so the ICP and the dates distinguish cohorts more reliably than the title alone. A campaign row exists from the moment a search starts, so the newest entry is frequently still empty, with leadsTotal 0.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "limit": { "description": "How many campaigns to return, newest first (default 50)", "maximum": 200, "minimum": 1, "type": "integer" } }, "type": "object" }, "name": "list_campaigns", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "campaigns": { "items": { "additionalProperties": {}, "properties": { "campaignUrl": { "type": "string" }, "createdAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "hasIcp": { "type": "boolean" }, "id": { "type": "string" }, "leadsTotal": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "tags": { "items": { "type": "string" }, "type": "array" }, "title": { "type": "string" }, "updatedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "title", "leadsTotal", "hasIcp", "tags", "createdAt", "updatedAt", "campaignUrl" ], "type": "object" }, "type": "array" }, "returned": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "total": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" } }, "required": [ "total", "returned", "campaigns" ], "type": "object" } }, { "description": "Returns read-only guidance for what to do next. Without campaignId, it returns the first-search question, active search progress, or campaign names and statuses to choose from. A single accessible campaign is resolved automatically. With campaignId, it returns a ranked to-do list: people who replied, people who accepted an invite but were never messaged, drafts waiting for approval, leads found but never contacted, and setup that is missing. It answers 'what should I do about this campaign today', where get_campaign_stats answers 'how is it doing'. For replies specifically, list_replies is the better source: its `awaitingOurReply` compares timestamps, while the `replied` bucket here covers only leads whose sentiment was scored and does not know whether we have since answered. `leadIds` passed to get_campaign_leads resolves the buckets into named people. Limits of the data: Onsa does not record whether we have already replied, or whether someone was contacted outside Onsa; 'replied' includes only replies whose sentiment was scored, so it is a floor; a withdrawn or unreachable invite leaves no trace, so some 'never contacted' leads may already have been tried. Sentiment is judged once per lead, not per message. Counts are complete; leadIds are capped at 200.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "Optional campaign to inspect; omitted for workspace guidance", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" } }, "type": "object" }, "name": "list_next_steps", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "campaignId": { "type": "string" }, "campaignTitle": { "type": "string" }, "campaignUrl": { "type": "string" }, "campaigns": { "items": { "additionalProperties": {}, "properties": { "campaignUrl": { "type": "string" }, "id": { "type": "string" }, "leadsTotal": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "status": { "enum": [ "search_running", "prospects_available", "no_prospects" ], "type": "string" }, "title": { "type": "string" } }, "required": [ "id", "title", "leadsTotal", "status", "campaignUrl" ], "type": "object" }, "type": "array" }, "items": { "items": { "additionalProperties": {}, "properties": { "count": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "detail": { "type": "string" }, "kind": { "type": "string" }, "leadIds": { "items": { "type": "string" }, "type": "array" } }, "required": [ "kind", "count", "leadIds", "detail" ], "type": "object" }, "type": "array" }, "leadsTotal": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "nextAction": { "additionalProperties": {}, "properties": { "arguments": { "additionalProperties": {}, "propertyNames": { "type": "string" }, "type": "object" }, "count": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "kind": { "type": "string" }, "question": { "type": "string" }, "reuseAuthorizedBrief": { "type": "boolean" }, "tool": { "type": "string" }, "url": { "type": "string" } }, "required": [ "kind" ], "type": "object" }, "note": { "type": "string" }, "returned": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "scope": { "enum": [ "workspace", "campaign" ], "type": "string" }, "searchAvailability": { "additionalProperties": {}, "properties": { "allowance": { "anyOf": [ { "additionalProperties": {}, "properties": { "limit": { "type": "number" }, "remaining": { "type": "number" }, "used": { "type": "number" } }, "required": [ "used", "limit", "remaining" ], "type": "object" }, { "type": "null" } ] }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "status": { "enum": [ "available", "blocked", "unknown" ], "type": "string" } }, "required": [ "status", "reason", "allowance" ], "type": "object" }, "total": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "workspaceState": { "type": "string" }, "workspaceUrl": { "type": "string" } }, "required": [ "items" ], "type": "object" } }, { "description": "Lists outreach messages the agent has drafted that are waiting for a human to approve - the 'a message for X is ready' queue. Each entry carries the draft text, the lead it is for, and why that lead scored as it did. Without campaignId, the list covers every campaign in this workspace that the user takes part in. This tool sends nothing: rewrite_outreach replaces a draft's text, and send_outreach queues one for delivery.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "One campaign to limit the list to; when omitted, the list covers every campaign the user takes part in", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "limit": { "description": "How many drafts to return (default 50)", "maximum": 200, "minimum": 1, "type": "integer" } }, "type": "object" }, "name": "list_pending_outreach", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "pending": { "items": { "additionalProperties": {}, "properties": { "campaignId": { "type": "string" }, "companyName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "draftSubject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "draftText": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "leadId": { "type": "string" }, "linkedInUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }, "scoreExplanation": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "leadId", "campaignId", "name", "companyName", "linkedInUrl", "score", "scoreExplanation", "draftText", "draftSubject" ], "type": "object" }, "type": "array" }, "returned": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "total": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" } }, "required": [ "total", "returned", "pending" ], "type": "object" } }, { "description": "Returns the text of what prospects replied, for every lead in the campaign that answered, paired with the outbound message it answers. get_campaign_stats counts replies and labels them; this returns the words. `sentiment` is Onsa's own label, written once per lead on their first reply - later replies never change it, and a reply Onsa has not scored yet comes back as `sentiment: null`, which means unscored rather than neutral. Those unscored replies are absent from get_campaign_stats entirely, so this tool can return more replies than the funnel counts.\n\nThree derived fields come with the reply. `awaitingOurReply`: the prospect spoke last and no sent message followed. It is structural only - a flat 'no thanks' satisfies it too - and Onsa sees only what Onsa sent, so a reply made by hand inside LinkedIn is invisible to it; what the data supports is 'no reply recorded here'. `daysSinceLastReply` is elapsed whole days rather than time-unanswered: it is populated even where we did answer, so it describes time-unanswered only when `awaitingOurReply` is also true. `looksLikeBroadcast`: the prospect's most recent message reached another profile in this campaign word for word, which indicates a mass DM rather than an answer. All three are floors rather than verdicts - a blast only one lead received is indistinguishable from a real reply, and two people who send the same long template are both flagged. Top-level `awaitingOurReplyCount` spans the whole campaign rather than this page, and leaves out broadcasts and replies scored negative; it can exceed `returned` when `limit` is small.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "description": "Campaign to read replies from", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "limit": { "description": "Maximum replies to return, newest first (default 50)", "maximum": 200, "minimum": 1, "type": "integer" } }, "required": [ "campaignId" ], "type": "object" }, "name": "list_replies", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "awaitingOurReplyCount": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "campaignId": { "type": "string" }, "campaignTitle": { "type": "string" }, "campaignUrl": { "type": "string" }, "conversations": { "items": { "additionalProperties": {}, "properties": { "awaitingOurReply": { "type": "boolean" }, "companyName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "daysSinceLastReply": { "anyOf": [ { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, { "type": "null" } ] }, "lastReplyAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "leadId": { "type": "string" }, "linkedInUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "looksLikeBroadcast": { "type": "boolean" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "position": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "sentiment": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "turns": { "items": { "additionalProperties": {}, "properties": { "at": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "role": { "enum": [ "us", "them" ], "type": "string" }, "text": { "type": "string" } }, "required": [ "role", "text", "at" ], "type": "object" }, "type": "array" } }, "required": [ "leadId", "name", "companyName", "position", "linkedInUrl", "sentiment", "lastReplyAt", "daysSinceLastReply", "awaitingOurReply", "looksLikeBroadcast", "turns" ], "type": "object" }, "type": "array" }, "returned": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" }, "total": { "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer" } }, "required": [ "campaignId", "campaignTitle", "campaignUrl", "total", "returned", "awaitingOurReplyCount", "conversations" ], "type": "object" } }, { "description": "Prepares a preview of 1–50 selected prospects from a campaign the user takes part in, and publishes nothing. The snapshot holds a list title, which is the campaign's name, the note, and for each person only a name, role, company, LinkedIn profile and, when includeRationale is true, a fit rating and why-matched explanation. The preview returns the complete snapshot a link would expose, with the previewKey that create_shortlist requires; a created link stays viewable for 30 days by anyone who has it, and can be revoked with revoke_shortlist.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "campaignId": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "includeRationale": { "description": "When true, each person in the snapshot carries a fit rating (1–5, or null when none is stored) and a why-matched explanation; when false, the rating is null and the explanation empty.", "type": "boolean" }, "leadIds": { "items": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "maxItems": 50, "minItems": 1, "type": "array" }, "note": { "maxLength": 300, "type": "string" }, "title": { "maxLength": 100, "minLength": 1, "type": "string" } }, "required": [ "campaignId", "leadIds", "title", "note", "includeRationale" ], "type": "object" }, "name": "preview_shortlist", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "previewKey": { "type": "string" }, "snapshot": { "additionalProperties": {}, "properties": { "note": { "type": "string" }, "people": { "items": { "additionalProperties": {}, "properties": { "companyName": { "type": "string" }, "name": { "type": "string" }, "position": { "type": "string" }, "profileUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "rationale": { "type": "string" }, "score": { "anyOf": [ { "maximum": 5, "minimum": 1, "type": "number" }, { "type": "null" } ] } }, "required": [ "name", "position", "companyName", "profileUrl", "rationale" ], "type": "object" }, "type": "array" }, "title": { "type": "string" } }, "required": [ "title", "note", "people" ], "type": "object" } }, "required": [ "snapshot", "previewKey" ], "type": "object" } }, { "description": "Revokes a list link by the id create_shortlist returned; only the user who created the link can revoke it. Future views stop immediately; previously copied or downloaded information cannot be recalled.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "id": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" } }, "required": [ "id" ], "type": "object" }, "name": "revoke_shortlist", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "revoked": { "type": "boolean" } }, "required": [ "revoked" ], "type": "object" } }, { "description": "Replaces the text of an outreach draft that is waiting for approval. The current draft and the lead's scoreExplanation come from list_pending_outreach; get_lead_memo carries the richer research on that person. The rewritten draft stays in the approval queue, and this tool sends nothing.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "leadId": { "description": "Lead whose draft to rewrite, from list_pending_outreach", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Email subject; only email delivery reads it, and send_outreach, a LinkedIn send, does not. When omitted, the stored subject stays unchanged, so a body-only edit keeps it; a string replaces it, and null clears it." }, "text": { "description": "The full replacement message. This same string is called `draftText` when list_pending_outreach returns it, and `confirmText` when you pass it to send_outreach — three names, one set of bytes.", "minLength": 1, "type": "string" } }, "required": [ "leadId", "text" ], "type": "object" }, "name": "rewrite_outreach", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "leadId": { "type": "string" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "text": { "type": "string" }, "updated": { "type": "boolean" } }, "required": [ "leadId", "updated", "text", "subject" ], "type": "object" } }, { "description": "Queues one already-approved outreach draft for delivery to a real person on LinkedIn. It requires `confirmText`, the draft body character-for-character as stored, and `confirmName`, the recipient's name: drafts are often near-identical between people, so matching the body alone does not identify which one was meant. A mismatch is refused without returning the stored text, which list_pending_outreach supplies. The server additionally requires a confirmation from the person at the keyboard, rendered by the MCP client and quoting the draft as stored; that approval is single-use and bound to one recipient and one draft. A client that cannot render such a confirmation receives a refusal carrying a link to approve inside the Onsa app, and nothing is queued. On success the message is queued rather than delivered: Onsa sends it on its own schedule, subject to daily pacing limits.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "confirmName": { "description": "The recipient's name as shown to the user, to prove you meant this person", "minLength": 1, "type": "string" }, "confirmText": { "description": "The exact draft text you showed the user and they approved, verbatim", "minLength": 1, "type": "string" }, "leadId": { "description": "Lead to message, from list_pending_outreach", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string" } }, "required": [ "leadId", "confirmText", "confirmName" ], "type": "object" }, "name": "send_outreach", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "leadId": { "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "queued": { "type": "boolean" } }, "required": [ "leadId", "queued", "name" ], "type": "object" } } ] }
Verify it yourselfcurl -s https://api.teppi.xyz/v1/evidence/sha256:587a481583eaab0ba5ddf4aba0eac74c06d8fa0a65abff91bbc7477b74d29c5a | sha256sum