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

Server definition

Hash
sha256:87cbd089406d891b6508b411ed06ea14d2d9c7ba2893ca970a32ad130b905df8
What it is
What a remote MCP server returned when asked what it offers: 11 tools

The blob, as servednamed by its sha256

{ "instructions": null, "tools": [ { "description": "Add an email or domain to the suppression list (scope is 'email' or 'domain')\n so future campaigns exclude it.", "inputSchema": { "properties": { "reason": { "title": "Reason", "type": "string" }, "scope": { "title": "Scope", "type": "string" }, "value": { "title": "Value", "type": "string" } }, "required": [ "scope", "value", "reason" ], "title": "add_suppressionArguments", "type": "object" }, "name": "add_suppression", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "OkResult": { "properties": { "ok": { "title": "Ok", "type": "boolean" } }, "required": [ "ok" ], "title": "OkResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/OkResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "OkOutput" } }, { "description": "Fetch a page of leads produced by a campaign (paginate with offset/limit).\n kept_only=True returns only rows the user marked kept in review (for export).\n match_status narrows to one population: 'validated' (verified email, billed) or\n 'needs_review' (surfaced free; rejection_reason says why) — omit it for both.\n Returns {summary, total, offset, limit, rows}; total counts the filtered\n population. Rows carry verification receipts (verified_by/verified_at/\n verifier_verdict) when a live verification exists — treat them as the\n authoritative proof; null means no receipt, not a failure. Two more quotable\n proof lines appear where earned: person_receipt (who the person is and where we\n saw them launch, on person-target leads) and tech_receipt (dated evidence of the\n detected platform) — forward them as-is.", "inputSchema": { "properties": { "job_id": { "title": "Job Id", "type": "string" }, "kept_only": { "default": false, "title": "Kept Only", "type": "boolean" }, "limit": { "default": 100, "title": "Limit", "type": "integer" }, "match_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Match Status" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "job_id" ], "title": "fetch_resultsArguments", "type": "object" }, "name": "fetch_results", "outputSchema": { "$defs": { "CampaignSummary": { "additionalProperties": true, "properties": { "fake_pipeline": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "true ONLY in demo/sandbox mode; absent in production. If present and true, the leads are synthetic fixtures — flag to the human, do not treat as real.", "title": "Fake Pipeline" }, "flagged_reasons": { "anyOf": [ { "additionalProperties": { "type": "integer" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Flagged Reasons" }, "gate_survivors": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Gate Survivors" }, "needs_review": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Needs Review" }, "removed_reasons": { "anyOf": [ { "additionalProperties": { "type": "integer" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Removed Reasons" }, "schema_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Schema Version" }, "sourced": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sourced" }, "tier_a_matched": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Tier A Matched" }, "total_discovered": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Discovered" } }, "title": "CampaignSummary", "type": "object" }, "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "FetchResultsResult": { "properties": { "credits_spent": { "title": "Credits Spent", "type": "integer" }, "limit": { "title": "Limit", "type": "integer" }, "offset": { "title": "Offset", "type": "integer" }, "rows": { "items": { "$ref": "#/$defs/LeadRow" }, "title": "Rows", "type": "array" }, "summary": { "anyOf": [ { "$ref": "#/$defs/CampaignSummary" }, { "type": "null" } ], "default": null }, "total": { "title": "Total", "type": "integer" } }, "required": [ "total", "offset", "limit", "credits_spent", "rows" ], "title": "FetchResultsResult", "type": "object" }, "LeadRow": { "additionalProperties": true, "properties": { "audience_receipt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "quotable line saying how much the audience figure is worth when it is NOT a confirmed fact: on TikTok, reported by our data source on a given date, because TikTok publishes no official figure to check it against; on either surface, that the platform disclosed no count at all when subscriber_count is null. Forward it as-is alongside subscriber_count. EMPTY means, and only ever means, that the number is confirmed against the platform's own API (YouTube), so there is nothing to disclaim", "title": "Audience Receipt" }, "channel_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "the creator's channel id (YouTube) or numeric profile id (TikTok) — the stable identity the lead was billed against. Never the @handle, which can change", "title": "Channel Id" }, "discovery_source": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "which feed surfaced the lead (producthunt / yc / tinylaunch); empty for Maps and web-search leads", "title": "Discovery Source" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "evidence_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "URL of the launch listing where we saw the person/company", "title": "Evidence Url" }, "handle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "the creator's public @handle", "title": "Handle" }, "id": { "title": "Id", "type": "integer" }, "last_upload_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "ISO date behind the 'still active' claim. On YouTube it is the channel's most recent upload. On TikTok it is the newest post THIS SEARCH matched, which can only under-state how active the creator is — never over-state it. Empty when the platform did not answer", "title": "Last Upload At" }, "launched_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "ISO date the company launched on its feed; empty when the feed lists it without a date (e.g. YC carries a batch instead)", "title": "Launched At" }, "match_status": { "description": "validated (verified email, billable) | needs_review (no verified email or unresolved predicate; may be 'needs_review:<fields>')", "title": "Match Status", "type": "string" }, "niche_receipt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "quotable proof line for the niche verdict (where the niche appears in the channel's own words, and when we checked) — forward it as-is; empty on an UNKNOWN, because there is no claim to prove", "title": "Niche Receipt" }, "niche_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "whether the channel's own text confirmed the niche the campaign asked for: MATCH / UNKNOWN. An UNKNOWN row is free and says so via rejection_reason couldnt_confirm:niche_state", "title": "Niche State" }, "person_first": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "named person's first name (person-target campaigns)", "title": "Person First" }, "person_last": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "named person's last name (person-target campaigns)", "title": "Person Last" }, "person_receipt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "quotable proof line for a person lead: who they are, where we saw them launch, and who verified the email — forward it as-is", "title": "Person Receipt" }, "person_role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "the person's role at the company (e.g. 'founder')", "title": "Person Role" }, "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "on a creator lead, the surface the creator lives on ('youtube' or 'tiktok'); on an ordinary business lead, the site builder behind its website (wix / squarespace / shopify / ...). Empty when neither applies", "title": "Platform" }, "rejection_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "why a row is needs_review, as a stable code: no_verified_email (no mailbox we could verify), couldnt_confirm or couldnt_confirm:<field> (a filter came back unknown; <field> names the *_state signal, e.g. couldnt_confirm:tech_state), no_person_identified (person campaign: we could not name a human), email_not_the_person (person campaign: deliverable address, but it belongs to someone else), email_not_at_company (company campaign: the address lives at a different company's domain), creator_gone (creators campaign: the channel or profile is no longer reachable), subs_out_of_range (creators campaign: the creator's audience is outside the requested subscriber band), subs_unknown (creators campaign: an audience band was requested and the platform does not disclose this creator's count — a YouTube channel that hides it, or a TikTok record with no follower number — so the audience is unconfirmed rather than measured; subscriber_count is null), creator_inactive (creators campaign: nothing posted inside the activity window), couldnt_confirm:niche_state (creators campaign: the creator's own words — channel title/description on YouTube, bio or matched post on TikTok — did not confirm the niche), out_of_credits (balance hit 0 mid-run — top up and re-run to deliver), key_budget_exhausted (the submitting API key's own budget ran out, though the account may still have credit). null when delivered", "title": "Rejection Reason" }, "review_status": { "enum": [ "kept", "discarded", "unreviewed" ], "title": "Review Status", "type": "string" }, "subscriber_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "the creator's audience — and where it comes from differs by platform, which matters if you forward it. On YOUTUBE it is the platform's own official API figure (never a scraped number). On TIKTOK no free official figure exists, so this is what our data source reported and nothing independent confirmed it; audience_receipt carries that attribution in the customer's own words. Either way it is the figure the subscriber-band claim was checked against. NULL means the platform did not disclose a count at all (a YouTube channel that hides it, a TikTok record with no follower number) — never read that as zero, and see rejection_reason subs_unknown", "title": "Subscriber Count" }, "tech_receipt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "quotable proof line for the detected platform (dated evidence from the crawl) — forward it as-is", "title": "Tech Receipt" }, "tech_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "platform-membership verdict from the site crawl: TECH_DETECTED / TECH_NONE_DETECTED; empty when the site could not be crawled", "title": "Tech State" }, "verified_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "ISO timestamp of the verification", "title": "Verified At" }, "verified_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "who verified the email (e.g. 'zerobounce'); null = no live verification receipt. With verified_at/verifier_verdict this is the proof-of-verification you can log or forward — a lead with verified_by set was checked against the live mailbox provider, not a cached guess.", "title": "Verified By" }, "verifier_verdict": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "verifier's raw verdict (e.g. 'valid')", "title": "Verifier Verdict" } }, "required": [ "id", "match_status", "review_status" ], "title": "LeadRow", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/FetchResultsResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "FetchResultsOutput" } }, { "description": "Check the status of a previously submitted campaign (queued/running/done/\n done_partial/failed) by job_id. Prefer wait_seconds=45 over sleeping between\n polls: the call holds until the status or pipeline stage changes (or the timer\n expires) and returns the normal snapshot either way — loop on it until `status`\n is terminal. Out-of-range wait_seconds clamps to [0, 45], never errors.", "inputSchema": { "properties": { "job_id": { "title": "Job Id", "type": "string" }, "wait_seconds": { "default": 0, "title": "Wait Seconds", "type": "integer" } }, "required": [ "job_id" ], "title": "get_campaign_statusArguments", "type": "object" }, "name": "get_campaign_status", "outputSchema": { "$defs": { "CampaignStatusResult": { "additionalProperties": true, "properties": { "credits_spent": { "description": "credits billed for this job so far (1 per validated lead; 2 per validated lead for person-target or Meta-ads campaigns)", "title": "Credits Spent", "type": "integer" }, "status": { "description": "done_partial = finished but stopped early — status_reason says why; delivered results are still valid. failed = no results; status_reason says why.", "enum": [ "queued", "running", "done", "done_partial", "failed" ], "title": "Status", "type": "string" }, "status_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "human-readable reason the job ended done_partial or failed (e.g. credits ran out, a processing limit was hit, time limit). null while queued/running and on a clean done.", "title": "Status Reason" }, "summary": { "anyOf": [ { "$ref": "#/$defs/CampaignSummary" }, { "type": "null" } ], "default": null } }, "required": [ "status", "credits_spent" ], "title": "CampaignStatusResult", "type": "object" }, "CampaignSummary": { "additionalProperties": true, "properties": { "fake_pipeline": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "true ONLY in demo/sandbox mode; absent in production. If present and true, the leads are synthetic fixtures — flag to the human, do not treat as real.", "title": "Fake Pipeline" }, "flagged_reasons": { "anyOf": [ { "additionalProperties": { "type": "integer" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Flagged Reasons" }, "gate_survivors": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Gate Survivors" }, "needs_review": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Needs Review" }, "removed_reasons": { "anyOf": [ { "additionalProperties": { "type": "integer" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Removed Reasons" }, "schema_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Schema Version" }, "sourced": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sourced" }, "tier_a_matched": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Tier A Matched" }, "total_discovered": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Discovered" } }, "title": "CampaignSummary", "type": "object" }, "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/CampaignStatusResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "CampaignStatusOutput" } }, { "description": "Check this tenant's prepaid credit balance (a validated lead costs 1 credit —\n 2 for person-target or Meta-ads campaigns), the available credit packs, and the\n ledger (paginate ledger entries with offset/limit, newest first). Campaigns\n cannot start with a zero balance — buy a pack via the dashboard when balance\n runs low.\n When you authenticate with an API key this also returns YOUR key's remaining\n budgets — check it before large campaigns.", "inputSchema": { "properties": { "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "get_creditsArguments", "type": "object" }, "name": "get_credits", "outputSchema": { "$defs": { "CreditsResult": { "additionalProperties": true, "properties": { "balance": { "title": "Balance", "type": "integer" }, "ledger": { "items": { "additionalProperties": true, "type": "object" }, "title": "Ledger", "type": "array" }, "packs": { "items": { "additionalProperties": true, "type": "object" }, "title": "Packs", "type": "array" } }, "required": [ "balance", "packs", "ledger" ], "title": "CreditsResult", "type": "object" }, "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/CreditsResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "CreditsOutput" } }, { "description": "List the ICP packs (saved vertical/geo templates) available to this tenant.", "inputSchema": { "properties": {}, "title": "get_icp_packArguments", "type": "object" }, "name": "get_icp_pack", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "IcpPackResult": { "properties": { "packs": { "description": "saved vertical/geo templates; empty list is normal for a tenant with none — not an error", "items": { "additionalProperties": true, "type": "object" }, "title": "Packs", "type": "array" } }, "required": [ "packs" ], "title": "IcpPackResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/IcpPackResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "IcpPackOutput" } }, { "description": "Today's external vendor usage against the tenant's daily quota\n (vendor_calls_today / per_day_max / remaining). This is the OTHER ceiling besides\n credits: when remaining hits 0, campaign work pauses until midnight even with a\n healthy credit balance — check it before launching a large or urgent campaign.\n `remaining` is quota UNITS, which usually equals calls; a creator discovery costs\n one unit per delivered record, so it can consume far more than one call's worth.\n Mirrors REST GET /v1/usage.", "inputSchema": { "properties": {}, "title": "get_usageArguments", "type": "object" }, "name": "get_usage", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "UsageResult": { "properties": { "per_day_max": { "description": "the tenant's daily vendor quota, in units", "title": "Per Day Max", "type": "integer" }, "remaining": { "description": "quota units left today; at 0 new campaign work pauses until midnight (quota, not credits — buying credits does not raise it). Usually equal to per_day_max minus vendor_calls_today, since most vendor calls cost one unit; a creator discovery costs one unit per delivered record, so it can consume far more than the one call it looks like", "title": "Remaining", "type": "integer" }, "vendor_calls_today": { "description": "external vendor calls (discovery, crawl, verification, enrichment) made for this tenant since midnight", "title": "Vendor Calls Today", "type": "integer" } }, "required": [ "vendor_calls_today", "per_day_max", "remaining" ], "title": "UsageResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/UsageResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "UsageOutput" } }, { "description": "List this tenant's campaigns, newest first — the recovery path when a job_id\n was lost: every row carries job_id, status, the query it ran, and credits spent,\n so you can resume polling (get_campaign_status) or fetch results without\n resubmitting (and double-charging). Filters: q (substring over vertical/geo),\n status (queued | running | done | done_partial | failed), created_after/\n created_before (ISO-8601 window over creation time), order ('desc' default,\n 'asc' oldest first). Paginate with offset/limit (limit 1-200); `total` counts\n everything matching the filters, not just this page.", "inputSchema": { "properties": { "created_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created After" }, "created_before": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Before" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" }, "order": { "default": "desc", "title": "Order", "type": "string" }, "q": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Q" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "title": "list_campaignsArguments", "type": "object" }, "name": "list_campaigns", "outputSchema": { "$defs": { "CampaignListItem": { "additionalProperties": true, "properties": { "agent_label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "label of the API key that submitted it; null for dashboard-submitted campaigns", "title": "Agent Label" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "credits_spent": { "default": 0, "description": "credits billed for this campaign (from the ledger; 1 per validated lead, 2 for person-target or Meta-ads campaigns)", "title": "Credits Spent", "type": "integer" }, "finished_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Finished At" }, "geo": { "default": "", "title": "Geo", "type": "string" }, "job_id": { "title": "Job Id", "type": "string" }, "query": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "the structured query the campaign ran (including any `discovery` block) — reuse it to resubmit or refine", "title": "Query" }, "status": { "enum": [ "queued", "running", "done", "done_partial", "failed" ], "title": "Status", "type": "string" }, "status_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "why the campaign ended done_partial or failed; null otherwise", "title": "Status Reason" }, "summary": { "anyOf": [ { "$ref": "#/$defs/CampaignSummary" }, { "type": "null" } ], "default": null }, "vertical": { "default": "", "description": "empty for a locationless campaign (launch_feeds / web_search discovery)", "title": "Vertical", "type": "string" } }, "required": [ "job_id", "status" ], "title": "CampaignListItem", "type": "object" }, "CampaignSummary": { "additionalProperties": true, "properties": { "fake_pipeline": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "true ONLY in demo/sandbox mode; absent in production. If present and true, the leads are synthetic fixtures — flag to the human, do not treat as real.", "title": "Fake Pipeline" }, "flagged_reasons": { "anyOf": [ { "additionalProperties": { "type": "integer" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Flagged Reasons" }, "gate_survivors": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Gate Survivors" }, "needs_review": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Needs Review" }, "removed_reasons": { "anyOf": [ { "additionalProperties": { "type": "integer" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Removed Reasons" }, "schema_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Schema Version" }, "sourced": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sourced" }, "tier_a_matched": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "internal diagnostic", "title": "Tier A Matched" }, "total_discovered": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Discovered" } }, "title": "CampaignSummary", "type": "object" }, "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "ListCampaignsResult": { "properties": { "campaigns": { "items": { "$ref": "#/$defs/CampaignListItem" }, "title": "Campaigns", "type": "array" }, "limit": { "title": "Limit", "type": "integer" }, "offset": { "title": "Offset", "type": "integer" }, "total": { "description": "campaigns matching the filters (not just this page)", "title": "Total", "type": "integer" } }, "required": [ "total", "offset", "limit", "campaigns" ], "title": "ListCampaignsResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/ListCampaignsResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "ListCampaignsOutput" } }, { "description": "Mark a lead kept or discarded after review (status: kept | discarded |\n unreviewed). Then fetch_results(kept_only=true) returns only kept leads — the\n export set.", "inputSchema": { "properties": { "job_id": { "title": "Job Id", "type": "string" }, "result_id": { "title": "Result Id", "type": "integer" }, "status": { "title": "Status", "type": "string" } }, "required": [ "job_id", "result_id", "status" ], "title": "review_leadArguments", "type": "object" }, "name": "review_lead", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "ReviewLeadResult": { "properties": { "job_id": { "title": "Job Id", "type": "string" }, "ok": { "title": "Ok", "type": "boolean" }, "result_id": { "title": "Result Id", "type": "integer" }, "status": { "enum": [ "kept", "discarded", "unreviewed" ], "title": "Status", "type": "string" } }, "required": [ "ok", "job_id", "result_id", "status" ], "title": "ReviewLeadResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/ReviewLeadResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "ReviewLeadOutput" } }, { "description": "Send feedback to the Nose for Leads team. Set kind='product' for feedback\n about the leads, the service, coverage, or pricing — relay what the business\n owner tells you (categories: lead_quality, pricing, missing_feature, praise,\n other). Set kind='tool' (default) for feedback about these MCP tools\n themselves — a confusing description, a bug, a missing capability. Use freely;\n it's our main signal for improving the product and the agent experience.", "inputSchema": { "properties": { "category": { "default": "other", "enum": [ "bug", "confusing", "missing_feature", "lead_quality", "pricing", "praise", "other" ], "title": "Category", "type": "string" }, "kind": { "default": "tool", "enum": [ "tool", "product" ], "title": "Kind", "type": "string" }, "message": { "title": "Message", "type": "string" }, "tool": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tool" } }, "required": [ "message" ], "title": "send_feedbackArguments", "type": "object" }, "name": "send_feedback", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "FeedbackResult": { "properties": { "ok": { "title": "Ok", "type": "boolean" } }, "required": [ "ok" ], "title": "FeedbackResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/FeedbackResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "FeedbackOutput" } }, { "description": "Submit a lead-generation campaign. Pass translate_icp's `vertical`, `geo`, and\n `query` fields directly (category/location are ALIASES for vertical/geo, accepted\n so translate output round-trips; prefer vertical/geo when constructing calls).\n `query`'s full shape and field vocabulary are in this tool's schema — build it\n with translate_icp from free text (recommended), or construct it directly. An\n unknown predicate field (or rank), and any unknown `discovery` key, is rejected\n with a 422 naming it; an unknown top-level `query` key is silently ignored, so\n keep to predicates/rank/discovery — a typo there will not error, it will just do\n nothing.\n Costs 1 credit per validated lead — every campaign type, including founder/person\n campaigns and campaigns that filter on Meta ads. A campaign cannot overspend: if\n discovered leads exceed your balance it delivers what your credits cover and the\n campaign ends `done_partial`; check get_credits first if your balance is low.\n vertical/geo are REQUIRED except for a locationless campaign — `query` carrying a\n discovery block with mode 'launch_feeds' or 'web_search' — which has no geography\n at all.\n If the tenant has no card on file this returns a 403 `card_required` error with a\n `setup_url` — surface that URL to the user so they can verify a card (never\n charged unless they buy a pack).\n Pass a stable idempotency_key when you might retry — a retry returns the ORIGINAL\n campaign (idempotent_replay=true), never a duplicate charge.\n Returns {job_id, idempotent_replay}; poll get_campaign_status with job_id.", "inputSchema": { "$defs": { "QueryInput": { "additionalProperties": true, "description": "The structured filter start_campaign runs. Build it with translate_icp from free\ntext (recommended), or construct it directly from this schema's vocabulary.", "properties": { "discovery": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "OPTIONAL non-Maps discovery directive. Omit it (the default) to discover local businesses on Google Maps, which is what vertical/geo describe. Two other modes exist, both with NO geography (vertical/geo are then not required). mode \"launch_feeds\": companies that RECENTLY LAUNCHED — and, optionally, the named founders behind them — from Product Hunt, Y Combinator and TinyLaunch; keys target_type (\"person\" for named founders or \"company\"; both bill 1 credit per validated lead), role (default \"founder\"), launch_window_days (1-365, default 90), category_keywords (e.g. [\"b2b\", \"saas\"]; empty keeps everything). mode \"web_search\": long-tail ONLINE businesses Google Maps never had, found by organic search — keys search_queries (1-5 queries, each a separate paid search; say what the business IS, e.g. [\"marketing agencies for dentists\"]), search_location (search locale, default \"United States\"), search_depth (10-100, default 60). It finds COMPANIES ONLY (billed 1 credit) and cannot carry the city-keyed signals runs_google_ads / serp_rank / runs_meta_ads / in_local_pack / ads_state. Filter platform membership with the detected_tech and tech_state fields instead. Firmographics (funding stage, headcount, revenue, technology spend) are NOT available in any mode and must never be written into a search query. Prefer translate_icp, which emits this block for you; a malformed or unknown-mode block is rejected at submit with a 422 naming the problem, never silently run as a Maps campaign. A mode may also be temporarily unavailable when its costs are not covered: submitting it returns a 422 that says so, and nothing is charged.", "title": "Discovery" }, "predicates": { "description": "all predicates must hold for a lead to count as a confirmed match", "items": { "$ref": "#/$defs/QueryPredicateInput" }, "title": "Predicates", "type": "array" }, "rank": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "optional field to sort/prioritize results by (same vocabulary as predicate fields)", "enum": [ "ads_state", "areas_served", "booking_state", "category", "detected_tech", "digital_maturity_score", "emergency_24_7", "first_review_at", "has_contact_form", "has_h1", "has_hours", "has_meta_description", "has_real_site", "has_robots", "has_schema_org", "has_sitemap", "has_title", "has_viewport", "has_website", "in_local_pack", "is_claimed", "is_franchise", "is_social_only", "licensed", "locations_in_campaign", "low_star_reviews_30d", "low_star_reviews_60d", "low_star_reviews_90d", "multi_location", "outdated_state", "page_count", "platform", "rating", "review_count", "review_trend_state", "runs_google_ads", "runs_meta_ads", "seo_basics_state", "serp_rank", "tech_state", "titles_identical", "total_photos", "website_social_only", "website_state", "years_in_business" ], "title": "Rank" } }, "title": "QueryInput", "type": "object" }, "QueryPredicateInput": { "additionalProperties": true, "properties": { "field": { "description": "the business attribute to filter on. The enum is the supported vocabulary; x-field-catalog documents each field's meaning, type, allowed ops, an example value, and — for the *_state verdict fields — the exact values to compare against. An unknown predicate field is rejected at submit with a 422 naming it.", "enum": [ "ads_state", "areas_served", "booking_state", "category", "detected_tech", "digital_maturity_score", "emergency_24_7", "first_review_at", "has_contact_form", "has_h1", "has_hours", "has_meta_description", "has_real_site", "has_robots", "has_schema_org", "has_sitemap", "has_title", "has_viewport", "has_website", "in_local_pack", "is_claimed", "is_franchise", "is_social_only", "licensed", "locations_in_campaign", "low_star_reviews_30d", "low_star_reviews_60d", "low_star_reviews_90d", "multi_location", "outdated_state", "page_count", "platform", "rating", "review_count", "review_trend_state", "runs_google_ads", "runs_meta_ads", "seo_basics_state", "serp_rank", "tech_state", "titles_identical", "total_photos", "website_social_only", "website_state", "years_in_business" ], "title": "Field", "type": "string", "x-field-catalog": { "ads_state": { "description": "paid-ads verdict ADS_PRESENT/NO_ADS_FOUND, null when unresolved", "example": "NO_ADS_FOUND", "ops": [ "eq", "neq", "exists" ], "type": "str", "values": [ "ADS_PRESENT", "NO_ADS_FOUND" ] }, "areas_served": { "description": "service-area city names parsed from the site", "example": "Phoenix", "ops": [ "contains", "not_contains" ], "type": "list[str]" }, "booking_state": { "description": "online-booking verdict BOOKING_PRESENT/BOOKING_ABSENT, null when unresolved", "example": "BOOKING_ABSENT", "ops": [ "eq", "neq", "exists" ], "type": "str", "values": [ "BOOKING_PRESENT", "BOOKING_ABSENT" ] }, "category": { "description": "the business vertical/type — goes in the top-level `category`, NOT a predicate", "example": "plumber", "ops": [ "eq" ], "type": "str" }, "detected_tech": { "description": "tech/widgets detected on the site (e.g. shopify, calendly, toast, doordash)", "example": "shopify", "ops": [ "contains", "not_contains" ], "type": "list[str]" }, "digital_maturity_score": { "description": "0-100 composite web-maturity score (low = bigger digital gap)", "example": 50, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "float" }, "emergency_24_7": { "description": "site advertises 24/7 emergency service", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "first_review_at": { "description": "the date of the business's EARLIEST Google review (ISO, e.g. 2024-01-15) -- only set when the reviews we fetched are provably its ENTIRE review history. This is an AGE FLOOR ('has existed at least since this date'), never an opening date: a long-established business that only recently got its first review looks new by this measure. Blank/null when we only saw part of its history and cannot know the true first review -- a real, unbilled outcome, not an error. Use this to answer how-long-has-this-business-existed style asks by comparing against a cutoff date; never present it as a founding or opening date", "example": "2024-01-15", "ops": [ "eq", "neq", "lt", "lte", "gt", "gte", "exists" ], "type": "str" }, "has_contact_form": { "description": "the site has a contact/inquiry form", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_h1": { "description": "the homepage has a non-empty <h1> heading, read from raw served HTML (may read absent on client-rendered sites; prefer the seo_basics segment for render-safe SEO health)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_hours": { "description": "business lists opening hours", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "has_meta_description": { "description": "the homepage has a meta description (basic SEO)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_real_site": { "description": "has a real website (not social-only / not empty)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_robots": { "description": "the site has a /robots.txt, read from raw served HTML (may read absent on client-rendered sites; prefer the seo_basics segment for render-safe SEO health)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_schema_org": { "description": "the site has schema.org structured data", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_sitemap": { "description": "the site has a /sitemap.xml, read from raw served HTML (may read absent on client-rendered sites; prefer the seo_basics segment for render-safe SEO health)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_title": { "description": "the homepage has a non-empty <title>", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "has_viewport": { "description": "the site is mobile-friendly (has a viewport meta tag)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "has_website": { "description": "business has any website at all", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "in_local_pack": { "description": "appears in the Google Maps local 3-pack for its category+city", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "is_claimed": { "description": "the Google listing is claimed/managed by the owner", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "is_franchise": { "description": "the business name matches a known national franchise brand (dated list); False means no match on this list, NOT verified independent — never claim independence from this field alone", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "is_social_only": { "description": "the only web presence is a social page (Facebook/Instagram/etc.)", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "licensed": { "description": "site states licensed/insured", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "locations_in_campaign": { "description": "how many businesses in this search share this name or domain at different addresses (>1 = multi-location/chain in this run)", "example": 3, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "low_star_reviews_30d": { "description": "how many of this business's Google reviews are rated 2 stars or less, counting back 30 days from the date we FETCHED its reviews (not today's date). null means we could not prove we saw the whole 30-day window -- a real, unbilled outcome, not an error, and NOT the same as a proven zero (zero means we saw the whole window and it was clean). For a plain yes/no 'does this business have a reputation problem' ask, use review_trend_state instead of a raw count", "example": 3, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "low_star_reviews_60d": { "description": "how many of this business's Google reviews are rated 2 stars or less, counting back 60 days from the date we FETCHED its reviews (not today's date). null means we could not prove we saw the whole 60-day window -- a real, unbilled outcome, not an error, and NOT the same as a proven zero (zero means we saw the whole window and it was clean). For a plain yes/no 'does this business have a reputation problem' ask, use review_trend_state instead of a raw count", "example": 3, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "low_star_reviews_90d": { "description": "how many of this business's Google reviews are rated 2 stars or less, counting back 90 days from the date we FETCHED its reviews (not today's date). null means we could not prove we saw the whole 90-day window -- a real, unbilled outcome, not an error, and NOT the same as a proven zero (zero means we saw the whole window and it was clean). For a plain yes/no 'does this business have a reputation problem' ask, use review_trend_state instead of a raw count", "example": 3, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "multi_location": { "description": "the same business record was found in multiple data sources and merged into one canonical record during dedupe (a dedupe multiplicity) — NOT chain/multi-site detection; use locations_in_campaign for multi-location/chain filtering", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "outdated_state": { "description": "outdated-website verdict OUTDATED/CURRENT, null when unresolved", "example": "OUTDATED", "ops": [ "eq", "neq", "exists" ], "type": "str", "values": [ "OUTDATED", "CURRENT" ] }, "page_count": { "description": "how many of home/contact/about/team pages resolved", "example": 1, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "platform": { "description": "site builder/platform (wix, squarespace, wordpress, godaddy, weebly, shopify, custom)", "example": "wix", "ops": [ "eq", "neq" ], "type": "str" }, "rating": { "description": "average star rating, 0-5", "example": 4.2, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "float" }, "review_count": { "description": "number of reviews", "example": 30, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "review_trend_state": { "description": "recent-reviews verdict — NEGATIVE_WAVE when the business received 3 or more reviews rated 2 stars or less in the last 90 days, STABLE when it did not, null when we could not see the whole 90-day window (a real, unbilled outcome — not an error, and not the same as STABLE). Use this for 'recent bad reviews', 'wave of 1-star reviews', 'reputation problems lately'. It is about RECENT ratings only — for overall rating or review volume use rating / review_count instead", "example": "NEGATIVE_WAVE", "ops": [ "eq", "neq", "exists" ], "type": "str" }, "runs_google_ads": { "description": "the business runs Google Search ads (advertiser-level, any campaign)", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "runs_meta_ads": { "description": "the business runs active Meta/Facebook ads (Ad Library). EXPERIMENTAL: the resolver is unverified and OFF by default (needs --enable-meta); without it this yields needs_review, never a confident answer", "example": false, "ops": [ "eq", "exists" ], "type": "bool" }, "seo_basics_state": { "description": "render-safe SEO health verdict: SEO_BASICS_MISSING (2+ of no title/no meta description/no H1/identical titles/no sitemap+robots) or SEO_BASICS_PRESENT; null when unresolved or the site is client-rendered (title/meta/H1 injected at runtime, so raw HTML can't be trusted) — prefer this over the raw has_title/has_h1/etc. fields for SEO-health filtering", "example": "SEO_BASICS_MISSING", "ops": [ "eq", "neq", "exists" ], "type": "str", "values": [ "SEO_BASICS_MISSING", "SEO_BASICS_PRESENT" ] }, "serp_rank": { "description": "Google organic position for its category+city, 1=top. Prefer page thresholds: <=10 'page 1', <=50 'top 50', >10 'not on page 1', eq 1 'ranked #1'. Never emit a value above 100 or compare against the not-ranked sentinel. Approximate (keyword-proxy): a business may rank for a narrower term than category+city, so 'not ranking' can under-count.", "example": 10, "ops": [ "lt", "lte", "gt", "gte", "eq" ], "type": "int" }, "tech_state": { "description": "platform-membership verdict from the site crawl — TECH_DETECTED / TECH_NONE_DETECTED, null when the site could not be crawled or is client-rendered (a runtime-injected widget is invisible in served HTML). Pair it with detected_tech to name the platform", "example": "TECH_DETECTED", "ops": [ "eq", "neq", "exists" ], "type": "str", "values": [ "TECH_DETECTED", "TECH_NONE_DETECTED" ] }, "titles_identical": { "description": "the <title> tag is identical across every crawled page (thin/templated-site smell), read from raw served HTML (may read absent on client-rendered sites; prefer the seo_basics segment for render-safe SEO health)", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "total_photos": { "description": "number of listing photos", "example": 10, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" }, "website_social_only": { "description": "the business's only web presence is a social/directory page like Facebook, no real website", "example": true, "ops": [ "eq", "exists" ], "type": "bool" }, "website_state": { "description": "website-presence verdict — WEBSITE_NONE / WEBSITE_SOCIAL_ONLY / WEBSITE_PRESENT, null when unresolved", "example": "WEBSITE_NONE", "ops": [ "eq", "neq", "exists" ], "type": "str", "values": [ "WEBSITE_NONE", "WEBSITE_SOCIAL_ONLY", "WEBSITE_PRESENT" ] }, "years_in_business": { "description": "the FOUNDING YEAR (a 4-digit year like 1998), parsed from 'since/established YYYY' — NOT a duration. To find businesses younger than N years, use years_in_business > (current_year - N); older than N, use < that value", "example": 1998, "ops": [ "lt", "lte", "gt", "gte", "eq", "neq" ], "type": "int" } } }, "op": { "description": "comparison operator", "enum": [ "eq", "neq", "lt", "lte", "gt", "gte", "contains", "not_contains", "in", "exists" ], "title": "Op", "type": "string" }, "value": { "default": null, "description": "comparison value; omit for op='exists'", "title": "Value" } }, "required": [ "field", "op" ], "title": "QueryPredicateInput", "type": "object" } }, "properties": { "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "geo": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Geo" }, "idempotency_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idempotency Key" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "query": { "$ref": "#/$defs/QueryInput" }, "vertical": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Vertical" } }, "required": [ "query" ], "title": "start_campaignArguments", "type": "object" }, "name": "start_campaign", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "StartCampaignResult": { "properties": { "idempotent_replay": { "default": false, "title": "Idempotent Replay", "type": "boolean" }, "job_id": { "title": "Job Id", "type": "string" } }, "required": [ "job_id" ], "title": "StartCampaignResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/StartCampaignResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "StartCampaignOutput" } }, { "description": "Translate free-text ICP description (e.g. \"plumbers in phoenix with no\n website\") into a structured query. Call this BEFORE start_campaign — start_campaign\n only accepts a structured `query` dict, never free text. Returns {query, vertical,\n geo, category, location, preview, unsupported, discovery} — pass vertical/geo/query\n straight to start_campaign.\n `discovery` is non-empty when the ask routes OUTSIDE Google Maps, in one of two\n shapes. Mode 'launch_feeds': people/companies that RECENTLY LAUNCHED (e.g.\n \"founders of B2B SaaS that launched in the last 3 months\") — it names the launch\n feeds to search and whether the leads are people or companies. Mode 'web_search':\n long-tail ONLINE businesses Google Maps never had (e.g. \"marketing agencies for\n dentists\"), found via paid organic search — companies only, never people. Either\n way the block is already embedded inside the returned `query`, so passing `query`\n through unchanged is all that's needed; neither mode has a geography, so\n vertical/geo come back empty and start_campaign does not require them.\n Anything we cannot actually source lands in `unsupported` instead — read that list\n aloud to the user rather than pretending the campaign will cover it.", "inputSchema": { "properties": { "text": { "title": "Text", "type": "string" } }, "required": [ "text" ], "title": "translate_icpArguments", "type": "object" }, "name": "translate_icp", "outputSchema": { "$defs": { "ErrorEnvelope": { "additionalProperties": true, "description": "Typed error: `code` is a stable machine key (auth_failed, not_found, invalid_input,\npayment_required, internal_error, ...); `message` is human-readable.\n\n`extra=\"allow\"` (card-check gate, spec 2026-07-10 WS-A A1): `_envelope` in\nmcp_server.py adds an optional `setup_url` key ONLY for CardRequired — every other\nerror dict still carries just {code, message}, so without this config pydantic's\ndefault `extra=\"ignore\"` would silently strip setup_url on model_validate, before it\never reached model_dump. A declared `setup_url: str | None = None` field instead was\nruled out: FastMCP's convert_result calls `model_dump(mode=\"json\")` with no\nexclude_none, so a declared-but-unset field would surface as `\"setup_url\": null` on\nEVERY other error and break the existing exact-envelope-shape tests.", "properties": { "code": { "title": "Code", "type": "string" }, "message": { "title": "Message", "type": "string" } }, "required": [ "code", "message" ], "title": "ErrorEnvelope", "type": "object" }, "TranslateResult": { "additionalProperties": true, "properties": { "category": { "title": "Category", "type": "string" }, "discovery": { "additionalProperties": true, "description": "the non-Maps discovery directive ({} for an ordinary Maps ask): mode 'launch_feeds', 'web_search' or 'creators' plus its settings. Informational only — it is already embedded in `query`, so pass `query` through to start_campaign unchanged; never re-attach this block yourself.", "title": "Discovery", "type": "object" }, "geo": { "description": "Pass to start_campaign as geo", "title": "Geo", "type": "string" }, "location": { "title": "Location", "type": "string" }, "preview": { "title": "Preview", "type": "string" }, "query": { "additionalProperties": true, "title": "Query", "type": "object" }, "suggested_rewrite": { "default": "", "description": "An honest counter-offer when a refused item (e.g. an off-limits platform like LinkedIn) has a servable alternative (e.g. Instagram). Empty when there is nothing to offer. This is a suggestion only — we never run it for you; the customer must ask for it explicitly.", "title": "Suggested Rewrite", "type": "string" }, "unsupported": { "items": { "type": "string" }, "title": "Unsupported", "type": "array" }, "vertical": { "description": "Pass to start_campaign as vertical", "title": "Vertical", "type": "string" } }, "required": [ "query", "vertical", "geo", "category", "location", "preview", "unsupported" ], "title": "TranslateResult", "type": "object" } }, "anyOf": [ { "$ref": "#/$defs/TranslateResult" }, { "$ref": "#/$defs/ErrorEnvelope" } ], "title": "TranslateOutput" } } ] }
Verify it yourselfcurl -s https://api.teppi.xyz/v1/evidence/sha256:87cbd089406d891b6508b411ed06ea14d2d9c7ba2893ca970a32ad130b905df8 | sha256sum