이 MCP로 할 수 있는 일
Runs structured local-business lead campaigns, translates ICP descriptions into queries, verifies lead emails, supports review and suppression workflows, and exports selected leads.
도구
입력 스키마
{'type': 'object', 'title': 'add_suppressionArguments', 'required': ['scope', 'value', 'reason'], 'properties': {'scope': {'type': 'string', 'title': 'Scope'}, 'value': {'type': 'string', 'title': 'Value'}, 'reason': {'type': 'string', 'title': 'Reason'}}}
출력 스키마
{'$defs': {'OkResult': {'type': 'object', 'title': 'OkResult', 'required': ['ok'], 'properties': {'ok': {'type': 'boolean', 'title': 'Ok'}}}, 'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}}, 'anyOf': [{'$ref': '#/$defs/OkResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'OkOutput'}
입력 스키마
{'type': 'object', 'title': 'fetch_resultsArguments', 'required': ['job_id'], 'properties': {'limit': {'type': 'integer', 'title': 'Limit', 'default': 100}, 'job_id': {'type': 'string', 'title': 'Job Id'}, 'offset': {'type': 'integer', 'title': 'Offset', 'default': 0}, 'kept_only': {'type': 'boolean', 'title': 'Kept Only', 'default': False}, 'match_status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Match Status', 'default': None}}}
출력 스키마
{'$defs': {'LeadRow': {'type': 'object', 'title': 'LeadRow', 'required': ['id', 'match_status', 'review_status'], 'properties': {'id': {'type': 'integer', 'title': 'Id'}, 'email': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Email', 'default': None}, 'domain': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Domain', 'default': None}, 'handle': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Handle', 'default': None, 'description': "the creator's public @handle"}, 'platform': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Platform', 'default': None, '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"}, 'channel_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Channel Id', 'default': None, '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"}, 'tech_state': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Tech State', 'default': None, 'description': 'platform-membership verdict from the site crawl: TECH_DETECTED / TECH_NONE_DETECTED; empty when the site could not be crawled'}, 'launched_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Launched At', 'default': None, '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)'}, 'niche_state': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Niche State', 'default': None, '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"}, 'person_last': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Person Last', 'default': None, 'description': "named person's last name (person-target campaigns)"}, 'person_role': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Person Role', 'default': None, 'description': "the person's role at the company (e.g. 'founder')"}, 'verified_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Verified At', 'default': None, 'description': 'ISO timestamp of the verification'}, 'verified_by': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Verified By', 'default': None, '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."}, 'evidence_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Evidence Url', 'default': None, 'description': 'URL of the launch listing where we saw the person/company'}, 'match_status': {'type': 'string', 'title': 'Match Status', 'description': "validated (verified email, billable) | needs_review (no verified email or unresolved predicate; may be 'needs_review:<fields>')"}, 'person_first': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Person First', 'default': None, 'description': "named person's first name (person-target campaigns)"}, 'tech_receipt': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Tech Receipt', 'default': None, 'description': 'quotable proof line for the detected platform (dated evidence from the crawl) — forward it as-is'}, 'niche_receipt': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Niche Receipt', 'default': None, '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"}, 'review_status': {'enum': ['kept', 'discarded', 'unreviewed'], 'type': 'string', 'title': 'Review Status'}, 'last_upload_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Last Upload At', 'default': None, '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"}, 'person_receipt': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Person Receipt', 'default': None, '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'}, 'audience_receipt': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Audience Receipt', 'default': None, '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"}, 'discovery_source': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Discovery Source', 'default': None, 'description': 'which feed surfaced the lead (producthunt / yc / tinylaunch); empty for Maps and web-search leads'}, 'rejection_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Rejection Reason', 'default': None, '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"}, 'subscriber_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Subscriber Count', 'default': None, '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"}, 'verifier_verdict': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Verifier Verdict', 'default': None, 'description': "verifier's raw verdict (e.g. 'valid')"}}, 'additionalProperties': True}, 'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'CampaignSummary': {'type': 'object', 'title': 'CampaignSummary', 'properties': {'sourced': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Sourced', 'default': None}, 'needs_review': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Needs Review', 'default': None}, 'fake_pipeline': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'title': 'Fake Pipeline', 'default': None, '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.'}, 'gate_survivors': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Gate Survivors', 'default': None, 'description': 'internal diagnostic'}, 'schema_version': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Schema Version', 'default': None, 'description': 'internal diagnostic'}, 'tier_a_matched': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Tier A Matched', 'default': None, 'description': 'internal diagnostic'}, 'flagged_reasons': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'integer'}}, {'type': 'null'}], 'title': 'Flagged Reasons', 'default': None}, 'removed_reasons': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'integer'}}, {'type': 'null'}], 'title': 'Removed Reasons', 'default': None}, 'total_discovered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Total Discovered', 'default': None}}, 'additionalProperties': True}, 'FetchResultsResult': {'type': 'object', 'title': 'FetchResultsResult', 'required': ['total', 'offset', 'limit', 'credits_spent', 'rows'], 'properties': {'rows': {'type': 'array', 'items': {'$ref': '#/$defs/LeadRow'}, 'title': 'Rows'}, 'limit': {'type': 'integer', 'title': 'Limit'}, 'total': {'type': 'integer', 'title': 'Total'}, 'offset': {'type': 'integer', 'title': 'Offset'}, 'summary': {'anyOf': [{'$ref': '#/$defs/CampaignSummary'}, {'type': 'null'}], 'default': None}, 'credits_spent': {'type': 'integer', 'title': 'Credits Spent'}}}}, 'anyOf': [{'$ref': '#/$defs/FetchResultsResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'FetchResultsOutput'}
입력 스키마
{'type': 'object', 'title': 'get_campaign_statusArguments', 'required': ['job_id'], 'properties': {'job_id': {'type': 'string', 'title': 'Job Id'}, 'wait_seconds': {'type': 'integer', 'title': 'Wait Seconds', 'default': 0}}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'CampaignSummary': {'type': 'object', 'title': 'CampaignSummary', 'properties': {'sourced': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Sourced', 'default': None}, 'needs_review': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Needs Review', 'default': None}, 'fake_pipeline': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'title': 'Fake Pipeline', 'default': None, '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.'}, 'gate_survivors': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Gate Survivors', 'default': None, 'description': 'internal diagnostic'}, 'schema_version': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Schema Version', 'default': None, 'description': 'internal diagnostic'}, 'tier_a_matched': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Tier A Matched', 'default': None, 'description': 'internal diagnostic'}, 'flagged_reasons': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'integer'}}, {'type': 'null'}], 'title': 'Flagged Reasons', 'default': None}, 'removed_reasons': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'integer'}}, {'type': 'null'}], 'title': 'Removed Reasons', 'default': None}, 'total_discovered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Total Discovered', 'default': None}}, 'additionalProperties': True}, 'CampaignStatusResult': {'type': 'object', 'title': 'CampaignStatusResult', 'required': ['status', 'credits_spent'], 'properties': {'status': {'enum': ['queued', 'running', 'done', 'done_partial', 'failed'], 'type': 'string', 'title': 'Status', 'description': 'done_partial = finished but stopped early — status_reason says why; delivered results are still valid. failed = no results; status_reason says why.'}, 'summary': {'anyOf': [{'$ref': '#/$defs/CampaignSummary'}, {'type': 'null'}], 'default': None}, 'credits_spent': {'type': 'integer', 'title': '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)'}, 'status_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Status Reason', 'default': None, '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.'}}, 'additionalProperties': True}}, 'anyOf': [{'$ref': '#/$defs/CampaignStatusResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'CampaignStatusOutput'}
입력 스키마
{'type': 'object', 'title': 'get_creditsArguments', 'properties': {'limit': {'type': 'integer', 'title': 'Limit', 'default': 50}, 'offset': {'type': 'integer', 'title': 'Offset', 'default': 0}}}
출력 스키마
{'$defs': {'CreditsResult': {'type': 'object', 'title': 'CreditsResult', 'required': ['balance', 'packs', 'ledger'], 'properties': {'packs': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'title': 'Packs'}, 'ledger': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'title': 'Ledger'}, 'balance': {'type': 'integer', 'title': 'Balance'}}, 'additionalProperties': True}, 'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}}, 'anyOf': [{'$ref': '#/$defs/CreditsResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'CreditsOutput'}
입력 스키마
{'type': 'object', 'title': 'get_icp_packArguments', 'properties': {}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'IcpPackResult': {'type': 'object', 'title': 'IcpPackResult', 'required': ['packs'], 'properties': {'packs': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'title': 'Packs', 'description': 'saved vertical/geo templates; empty list is normal for a tenant with none — not an error'}}}}, 'anyOf': [{'$ref': '#/$defs/IcpPackResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'IcpPackOutput'}
입력 스키마
{'type': 'object', 'title': 'get_usageArguments', 'properties': {}}
출력 스키마
{'$defs': {'UsageResult': {'type': 'object', 'title': 'UsageResult', 'required': ['vendor_calls_today', 'per_day_max', 'remaining'], 'properties': {'remaining': {'type': 'integer', 'title': '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'}, 'per_day_max': {'type': 'integer', 'title': 'Per Day Max', 'description': "the tenant's daily vendor quota, in units"}, 'vendor_calls_today': {'type': 'integer', 'title': 'Vendor Calls Today', 'description': 'external vendor calls (discovery, crawl, verification, enrichment) made for this tenant since midnight'}}}, 'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}}, 'anyOf': [{'$ref': '#/$defs/UsageResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'UsageOutput'}
입력 스키마
{'type': 'object', 'title': 'list_campaignsArguments', 'properties': {'q': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Q', 'default': None}, 'limit': {'type': 'integer', 'title': 'Limit', 'default': 50}, 'order': {'type': 'string', 'title': 'Order', 'default': 'desc'}, 'offset': {'type': 'integer', 'title': 'Offset', 'default': 0}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Status', 'default': None}, 'created_after': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Created After', 'default': None}, 'created_before': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Created Before', 'default': None}}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'CampaignSummary': {'type': 'object', 'title': 'CampaignSummary', 'properties': {'sourced': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Sourced', 'default': None}, 'needs_review': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Needs Review', 'default': None}, 'fake_pipeline': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'title': 'Fake Pipeline', 'default': None, '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.'}, 'gate_survivors': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Gate Survivors', 'default': None, 'description': 'internal diagnostic'}, 'schema_version': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Schema Version', 'default': None, 'description': 'internal diagnostic'}, 'tier_a_matched': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Tier A Matched', 'default': None, 'description': 'internal diagnostic'}, 'flagged_reasons': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'integer'}}, {'type': 'null'}], 'title': 'Flagged Reasons', 'default': None}, 'removed_reasons': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'integer'}}, {'type': 'null'}], 'title': 'Removed Reasons', 'default': None}, 'total_discovered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Total Discovered', 'default': None}}, 'additionalProperties': True}, 'CampaignListItem': {'type': 'object', 'title': 'CampaignListItem', 'required': ['job_id', 'status'], 'properties': {'geo': {'type': 'string', 'title': 'Geo', 'default': ''}, 'query': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'title': 'Query', 'default': None, 'description': 'the structured query the campaign ran (including any `discovery` block) — reuse it to resubmit or refine'}, 'job_id': {'type': 'string', 'title': 'Job Id'}, 'status': {'enum': ['queued', 'running', 'done', 'done_partial', 'failed'], 'type': 'string', 'title': 'Status'}, 'summary': {'anyOf': [{'$ref': '#/$defs/CampaignSummary'}, {'type': 'null'}], 'default': None}, 'vertical': {'type': 'string', 'title': 'Vertical', 'default': '', 'description': 'empty for a locationless campaign (launch_feeds / web_search discovery)'}, 'created_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Created At', 'default': None}, 'agent_label': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Agent Label', 'default': None, 'description': 'label of the API key that submitted it; null for dashboard-submitted campaigns'}, 'finished_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Finished At', 'default': None}, 'credits_spent': {'type': 'integer', 'title': '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)'}, 'status_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Status Reason', 'default': None, 'description': 'why the campaign ended done_partial or failed; null otherwise'}}, 'additionalProperties': True}, 'ListCampaignsResult': {'type': 'object', 'title': 'ListCampaignsResult', 'required': ['total', 'offset', 'limit', 'campaigns'], 'properties': {'limit': {'type': 'integer', 'title': 'Limit'}, 'total': {'type': 'integer', 'title': 'Total', 'description': 'campaigns matching the filters (not just this page)'}, 'offset': {'type': 'integer', 'title': 'Offset'}, 'campaigns': {'type': 'array', 'items': {'$ref': '#/$defs/CampaignListItem'}, 'title': 'Campaigns'}}}}, 'anyOf': [{'$ref': '#/$defs/ListCampaignsResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'ListCampaignsOutput'}
입력 스키마
{'type': 'object', 'title': 'review_leadArguments', 'required': ['job_id', 'result_id', 'status'], 'properties': {'job_id': {'type': 'string', 'title': 'Job Id'}, 'status': {'type': 'string', 'title': 'Status'}, 'result_id': {'type': 'integer', 'title': 'Result Id'}}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'ReviewLeadResult': {'type': 'object', 'title': 'ReviewLeadResult', 'required': ['ok', 'job_id', 'result_id', 'status'], 'properties': {'ok': {'type': 'boolean', 'title': 'Ok'}, 'job_id': {'type': 'string', 'title': 'Job Id'}, 'status': {'enum': ['kept', 'discarded', 'unreviewed'], 'type': 'string', 'title': 'Status'}, 'result_id': {'type': 'integer', 'title': 'Result Id'}}}}, 'anyOf': [{'$ref': '#/$defs/ReviewLeadResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'ReviewLeadOutput'}
입력 스키마
{'type': 'object', 'title': 'send_feedbackArguments', 'required': ['message'], 'properties': {'kind': {'enum': ['tool', 'product'], 'type': 'string', 'title': 'Kind', 'default': 'tool'}, 'tool': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Tool', 'default': None}, 'message': {'type': 'string', 'title': 'Message'}, 'category': {'enum': ['bug', 'confusing', 'missing_feature', 'lead_quality', 'pricing', 'praise', 'other'], 'type': 'string', 'title': 'Category', 'default': 'other'}}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'FeedbackResult': {'type': 'object', 'title': 'FeedbackResult', 'required': ['ok'], 'properties': {'ok': {'type': 'boolean', 'title': 'Ok'}}}}, 'anyOf': [{'$ref': '#/$defs/FeedbackResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'FeedbackOutput'}
입력 스키마
{'type': 'object', '$defs': {'QueryInput': {'type': 'object', 'title': 'QueryInput', 'properties': {'rank': {'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'], 'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Rank', 'default': None, 'description': 'optional field to sort/prioritize results by (same vocabulary as predicate fields)'}, 'discovery': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'title': 'Discovery', 'default': None, '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.'}, 'predicates': {'type': 'array', 'items': {'$ref': '#/$defs/QueryPredicateInput'}, 'title': 'Predicates', 'description': 'all predicates must hold for a lead to count as a confirmed match'}}, '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.", 'additionalProperties': True}, 'QueryPredicateInput': {'type': 'object', 'title': 'QueryPredicateInput', 'required': ['field', 'op'], 'properties': {'op': {'enum': ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'contains', 'not_contains', 'in', 'exists'], 'type': 'string', 'title': 'Op', 'description': 'comparison operator'}, 'field': {'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'], 'type': 'string', 'title': '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.", 'x-field-catalog': {'has_h1': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, '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)'}, 'rating': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'float', 'example': 4.2, 'description': 'average star rating, 0-5'}, 'category': {'ops': ['eq'], 'type': 'str', 'example': 'plumber', 'description': 'the business vertical/type — goes in the top-level `category`, NOT a predicate'}, 'licensed': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, 'description': 'site states licensed/insured'}, 'platform': {'ops': ['eq', 'neq'], 'type': 'str', 'example': 'wix', 'description': 'site builder/platform (wix, squarespace, wordpress, godaddy, weebly, shopify, custom)'}, 'ads_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'values': ['ADS_PRESENT', 'NO_ADS_FOUND'], 'example': 'NO_ADS_FOUND', 'description': 'paid-ads verdict ADS_PRESENT/NO_ADS_FOUND, null when unresolved'}, 'has_hours': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, 'description': 'business lists opening hours'}, 'has_title': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, 'description': 'the homepage has a non-empty <title>'}, 'serp_rank': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq'], 'type': 'int', 'example': 10, '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."}, 'has_robots': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, '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)'}, 'is_claimed': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'the Google listing is claimed/managed by the owner'}, 'page_count': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 1, 'description': 'how many of home/contact/about/team pages resolved'}, 'tech_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'values': ['TECH_DETECTED', 'TECH_NONE_DETECTED'], 'example': 'TECH_DETECTED', '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'}, 'has_sitemap': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, '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)'}, 'has_website': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'business has any website at all'}, 'areas_served': {'ops': ['contains', 'not_contains'], 'type': 'list[str]', 'example': 'Phoenix', 'description': 'service-area city names parsed from the site'}, 'has_viewport': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'the site is mobile-friendly (has a viewport meta tag)'}, 'is_franchise': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, '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'}, 'review_count': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 30, 'description': 'number of reviews'}, 'total_photos': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 10, 'description': 'number of listing photos'}, 'booking_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'values': ['BOOKING_PRESENT', 'BOOKING_ABSENT'], 'example': 'BOOKING_ABSENT', 'description': 'online-booking verdict BOOKING_PRESENT/BOOKING_ABSENT, null when unresolved'}, 'detected_tech': {'ops': ['contains', 'not_contains'], 'type': 'list[str]', 'example': 'shopify', 'description': 'tech/widgets detected on the site (e.g. shopify, calendly, toast, doordash)'}, 'has_real_site': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'has a real website (not social-only / not empty)'}, 'in_local_pack': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'appears in the Google Maps local 3-pack for its category+city'}, 'runs_meta_ads': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, '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'}, 'website_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'values': ['WEBSITE_NONE', 'WEBSITE_SOCIAL_ONLY', 'WEBSITE_PRESENT'], 'example': 'WEBSITE_NONE', 'description': 'website-presence verdict — WEBSITE_NONE / WEBSITE_SOCIAL_ONLY / WEBSITE_PRESENT, null when unresolved'}, 'emergency_24_7': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, 'description': 'site advertises 24/7 emergency service'}, 'has_schema_org': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'the site has schema.org structured data'}, 'is_social_only': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, 'description': 'the only web presence is a social page (Facebook/Instagram/etc.)'}, 'multi_location': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, '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'}, 'outdated_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'values': ['OUTDATED', 'CURRENT'], 'example': 'OUTDATED', 'description': 'outdated-website verdict OUTDATED/CURRENT, null when unresolved'}, 'first_review_at': {'ops': ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'exists'], 'type': 'str', 'example': '2024-01-15', '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"}, 'runs_google_ads': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'the business runs Google Search ads (advertiser-level, any campaign)'}, 'has_contact_form': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'the site has a contact/inquiry form'}, 'seo_basics_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'values': ['SEO_BASICS_MISSING', 'SEO_BASICS_PRESENT'], 'example': 'SEO_BASICS_MISSING', '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"}, 'titles_identical': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, '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)'}, 'years_in_business': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 1998, '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"}, 'review_trend_state': {'ops': ['eq', 'neq', 'exists'], 'type': 'str', 'example': 'NEGATIVE_WAVE', '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"}, 'website_social_only': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': True, 'description': "the business's only web presence is a social/directory page like Facebook, no real website"}, 'has_meta_description': {'ops': ['eq', 'exists'], 'type': 'bool', 'example': False, 'description': 'the homepage has a meta description (basic SEO)'}, 'low_star_reviews_30d': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 3, '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"}, 'low_star_reviews_60d': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 3, '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"}, 'low_star_reviews_90d': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 3, '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"}, 'locations_in_campaign': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'int', 'example': 3, 'description': 'how many businesses in this search share this name or domain at different addresses (>1 = multi-location/chain in this run)'}, 'digital_maturity_score': {'ops': ['lt', 'lte', 'gt', 'gte', 'eq', 'neq'], 'type': 'float', 'example': 50, 'description': '0-100 composite web-maturity score (low = bigger digital gap)'}}}, 'value': {'title': 'Value', 'default': None, 'description': "comparison value; omit for op='exists'"}}, 'additionalProperties': True}}, 'title': 'start_campaignArguments', 'required': ['query'], 'properties': {'geo': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Geo', 'default': None}, 'query': {'$ref': '#/$defs/QueryInput'}, 'category': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Category', 'default': None}, 'location': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Location', 'default': None}, 'vertical': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Vertical', 'default': None}, 'idempotency_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Idempotency Key', 'default': None}}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'StartCampaignResult': {'type': 'object', 'title': 'StartCampaignResult', 'required': ['job_id'], 'properties': {'job_id': {'type': 'string', 'title': 'Job Id'}, 'idempotent_replay': {'type': 'boolean', 'title': 'Idempotent Replay', 'default': False}}}}, 'anyOf': [{'$ref': '#/$defs/StartCampaignResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'StartCampaignOutput'}
입력 스키마
{'type': 'object', 'title': 'translate_icpArguments', 'required': ['text'], 'properties': {'text': {'type': 'string', 'title': 'Text'}}}
출력 스키마
{'$defs': {'ErrorEnvelope': {'type': 'object', 'title': 'ErrorEnvelope', 'required': ['code', 'message'], 'properties': {'code': {'type': 'string', 'title': 'Code'}, 'message': {'type': 'string', 'title': 'Message'}}, '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.', 'additionalProperties': True}, 'TranslateResult': {'type': 'object', 'title': 'TranslateResult', 'required': ['query', 'vertical', 'geo', 'category', 'location', 'preview', 'unsupported'], 'properties': {'geo': {'type': 'string', 'title': 'Geo', 'description': 'Pass to start_campaign as geo'}, 'query': {'type': 'object', 'title': 'Query', 'additionalProperties': True}, 'preview': {'type': 'string', 'title': 'Preview'}, 'category': {'type': 'string', 'title': 'Category'}, 'location': {'type': 'string', 'title': 'Location'}, 'vertical': {'type': 'string', 'title': 'Vertical', 'description': 'Pass to start_campaign as vertical'}, 'discovery': {'type': 'object', 'title': 'Discovery', '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.", 'additionalProperties': True}, 'unsupported': {'type': 'array', 'items': {'type': 'string'}, 'title': 'Unsupported'}, 'suggested_rewrite': {'type': 'string', 'title': '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.'}}, 'additionalProperties': True}}, 'anyOf': [{'$ref': '#/$defs/TranslateResult'}, {'$ref': '#/$defs/ErrorEnvelope'}], 'title': 'TranslateOutput'}
최근 도구 변경
유사한 MCP 서버
neuron
Operates AI chatbots and messaging workflows across WhatsApp and social channels, including contacts, campaigns, conversations, p…
Meta Council
Provides governed workflows for consulting, outreach, sales deals, invoices, marketing campaigns, content approvals, accounting a…
Rahul D Sarker: Marketing & RevOps Tools
Provides marketing, revenue operations, sales, advertising, SaaS, and conversion calculators, along with article search and consu…
Emailchaser
Manages cold-email campaigns, leads, sender mailboxes, sequences, outreach automation, deliverability, prospect sourcing, webhook…
LinkedIn MCP Server (Salesbot)
Supports LinkedIn lead discovery, contact enrichment, CRM pipelines, campaign sequencing, message approval and human-reviewed out…
send16-mcp
Provides email marketing and transactional messaging tools for contacts, audiences, campaigns, templates, automations, analytics,…
GrowthKit Revenue Intelligence
Supports B2B revenue operations with lead campaigns, ICP and company enrichment, CRM records and activities, outreach drafting, t…
Nevent MCP
Enables management and analysis of live-event marketing campaigns, audiences, email and messaging templates, paid ads, tracking l…