@vruum/mcp 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tools.json +680 -49
package/tools.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$comment": "GENERATED by backend/scripts/dump_member_tools.py — do not edit. The member-visible tool surface of the hosted Vruum MCP (https://api.vruum.ai/mcp), served statically by @vruum/mcp for credential-free introspection.",
3
3
  "server": "ai.vruum/mcp",
4
- "tool_count": 29,
4
+ "tool_count": 32,
5
5
  "tools": [
6
6
  {
7
7
  "annotations": {
@@ -9,7 +9,7 @@
9
9
  "openWorldHint": false,
10
10
  "readOnlyHint": true
11
11
  },
12
- "description": "Fetch\n\nFetch one entity (or a small batch) by type + id — the consolidated read tool.\n\nTypes and id semantics:\n- campaign: a campaign by UUID (array of UUIDs → per-id map). List\n campaigns with `search` type='campaigns'; create/update/diagnose/member\n changes with `manage_campaign`.\n- plan: a person outreach plan. SCALAR id = the plan UUID; ARRAY id =\n PERSON UUIDs (routes to the native batch person-plans read, returning a\n person_id → plan|null map). Note the scalar/array dimension difference.\n- conversation: full message history for a person (id = person_id).\n- person_research: cached person research (id = person UUID; array → per-id map).\n- company_research: cached company research (id = website/domain string;\n array → native batch lookup).\n- account_state: per-account state row (id = company_id).\n- insights (subtype required): quality | pattern_tags | prompts |\n benchmarks | improve | reply_diagnosis (id = person_id) |\n mcp_patterns | mcp_pattern_detail (id = pattern_id).\n- scoreboard (subtype required): impact | bowtie (id = company_id;\n filters: {window_days?}).\n- settings (subtype required): profile | automation | marketing |\n channel_status | booking_link (no id).\n- billing: billing status for your company (no id).\n- publish_readiness: publish pre-check for a LinkedIn draft (id = post_id).\n- research_playbook: ICP + research workflow (id = optional campaign UUID).\n- job: background job status — poll after async tools like\n people create / import_prospects / auto_fill_company_profile (id =\n scalar job UUID; a one-element array is normalized).\n- csv_import: CSV import progress (id = the job id returned by\n import_prospects csv_start).\n- deal: a raw deal row by UUID (for the enriched view use get_deal_360).\n- deal_pipelines: the tenant's configured pipelines and stages (no id;\n filters: {include_archived?}). Returns pipeline/stage IDs, ordering,\n stage kinds, probability defaults, archived state, and the default\n pipeline ID.\n- deal_alerts: at-risk deal alerts (no id).\n- post_analytics: LinkedIn post performance (id = post UUID; omit for\n all posts).\n- seller_signals: the seller signal bundle for content drafting (id =\n author user id, optional; filters: draft_brief/include_* flags).\n- stats (subtype required): outreach (filters: {start_date?, end_date?})\n | plan | person | prose (filters: {days?, default 30, use 7 for a\n weekly cut} — prose-quality trends from the outcome-linked review\n corpus: per-surface reply outcomes + reply rate and per-gate-code\n outcome lift (primary), plus keep/edit/kill/override mix, override\n codes, edit-rate trend vs the prior window, rules_version\n distribution) — aggregate stats snapshots.\n- marketing (subtype required): overview | activity (filters: {days?,\n limit?}).\n- ads (subtype required): attribution (filters: {window_days?,\n outreach_campaign_id?, by}; by=creative|audience is REQUIRED and\n selects a per-creative or per-audience engagement funnel) |\n | creative (id = ad creative UUID; returns the row + a derived\n `terminal` verdict — failed with a probe-code error prefix means the\n asset itself is bad, re-export and re-store; otherwise `next_call`\n hints the poll/retry) | campaign (id = ad campaign UUID; returns the\n row + spend_today_cents + last_sync_at) | targeting_entities (filters:\n {facet (titles|seniorities|industries|locations|staff_count_ranges),\n q (min 2 chars), integration_id?}; resolves display names into the\n LinkedIn entity URNs the boost audience.facets form requires —\n locations targeting is mandatory on every facet boost).\n- skill: a published skill body (id = skill UUID).\n- relationship_attempt: one durable relationship action (id = the\n `attempt_...` reference returned by manage_relationship_action).\n\nBatch: id arrays are accepted for campaign, plan, person_research and\ncompany_research (max 100 ids). Loop batches return ``{id: payload}`` with\nper-id 4xx failures mapped to ``{id: {\"error\": detail}}``; any 5xx aborts\nthe whole call. Errors carry the same status as the underlying endpoint\nwith the detail prefixed ``\"<type> '<id>': ...\"``.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
12
+ "description": "Fetch\n\nFetch one entity (or a small batch) by type + id — the consolidated read tool.\n\nTypes and id semantics:\n- claim: one hypothesis-ledger claim by UUID — statement, scope,\n derived confidence, invalidating conditions, linked evidence with\n provenance and liveness, and the append-only verdict history (VRU-840).\n Find ids with search type='claims'.\n- campaign: a campaign by UUID (array of UUIDs → per-id map). List\n campaigns with `search` type='campaigns'; create/update/diagnose/member\n changes with `manage_campaign`.\n- plan: a person outreach plan. SCALAR id = the plan UUID; ARRAY id =\n PERSON UUIDs (routes to the native batch person-plans read, returning a\n person_id → plan|null map). Note the scalar/array dimension difference.\n- conversation: full message history for a person (id = person_id).\n- person_research: cached person research (id = person UUID; array → per-id map).\n- company_research: cached company research (id = website/domain string;\n array → native batch lookup).\n- account_state: per-account state row (id = company_id).\n- insights (subtype required): quality | pattern_tags | prompts |\n benchmarks | improve | reply_diagnosis (id = person_id) |\n mcp_patterns | mcp_pattern_detail (id = pattern_id).\n- scoreboard (subtype required): impact | bowtie (id = company_id;\n filters: {window_days?}).\n- settings (subtype required): profile | automation | marketing |\n channel_status | booking_link | campaign_delegations | ai_compute\n (no id). ai_compute is READ-ONLY status: which provider/model\n interactive and background runs currently resolve to, the fallback\n behavior, coarse per-source health, and whether the caller's own\n subscription is connected. Connecting, rotating, testing and revoking\n compute credentials is deliberately not reachable from any tool.\n- billing: billing status for your company (no id).\n- publish_readiness: publish pre-check for a LinkedIn draft (id = post_id).\n- research_playbook: ICP + research workflow (id = optional campaign UUID).\n- job: background job status — poll after async tools like\n people create / import_prospects / auto_fill_company_profile (id =\n scalar job UUID; a one-element array is normalized).\n- csv_import: CSV import progress (id = the job id returned by\n import_prospects csv_start).\n- company_resolution_queue: people missing an employer link (depth\n counts only genuinely-unlinked people; silent pin re-verification is\n excluded), oldest age, deferral reason, and projected completion\n horizon (no id).\n- deal: a raw deal row by UUID (for the enriched view use get_deal_360).\n- deal_pipelines: the tenant's configured pipelines and stages (no id;\n filters: {include_archived?}). Returns pipeline/stage IDs, ordering,\n stage kinds, probability defaults, archived state, and the default\n pipeline ID.\n- deal_alerts: at-risk deal alerts (no id).\n- post_analytics: LinkedIn post performance (id = post UUID; omit for\n all posts).\n- seller_signals: the seller signal bundle for content drafting (id =\n author user id, optional; filters: draft_brief/include_* flags).\n- stats (subtype required): outreach (filters: {start_date?, end_date?})\n | plan | person | prose (filters: {days?, default 30, use 7 for a\n weekly cut} — prose-quality trends from the outcome-linked review\n corpus: per-surface reply outcomes + reply rate and per-gate-code\n outcome lift (primary), plus keep/edit/kill/override mix, override\n codes, edit-rate trend vs the prior window, rules_version\n distribution) — aggregate stats snapshots.\n- marketing (subtype required): overview | activity (filters: {days?,\n limit?}).\n- ads (subtype required): attribution (filters: {window_days?,\n outreach_campaign_id?, by}; by=creative|audience is REQUIRED and\n selects a per-creative or per-audience engagement funnel) |\n | creative (id = ad creative UUID; returns the row + a derived\n `terminal` verdict — failed with a probe-code error prefix means the\n asset itself is bad, re-export and re-store; otherwise `next_call`\n hints the poll/retry) | campaign (id = ad campaign UUID; returns the\n row + spend_today_cents + last_sync_at) | targeting_entities (filters:\n {facet (titles|seniorities|industries|locations|staff_count_ranges),\n q (min 2 chars), integration_id?}; resolves display names into the\n LinkedIn entity URNs the boost audience.facets form requires —\n locations targeting is mandatory on every facet boost).\n- skill: a published skill body (id = skill UUID).\n- relationship_attempt: one durable relationship action (id = the\n `attempt_...` reference returned by manage_relationship_action).\n\nBatch: id arrays are accepted for campaign, plan, person_research and\ncompany_research (max 100 ids). Loop batches return ``{id: payload}`` with\nper-id 4xx failures mapped to ``{id: {\"error\": detail}}``; any 5xx aborts\nthe whole call. Errors carry the same status as the underlying endpoint\nwith the detail prefixed ``\"<type> '<id>': ...\"``.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
13
13
  "inputSchema": {
14
14
  "properties": {
15
15
  "filters": {
@@ -27,7 +27,7 @@
27
27
  "type": "object"
28
28
  },
29
29
  "for_company": {
30
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
30
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
31
31
  "title": "for_company",
32
32
  "type": "string"
33
33
  },
@@ -58,12 +58,12 @@
58
58
  "type": "null"
59
59
  }
60
60
  ],
61
- "description": "Variant selector — required for insights (quality | pattern_tags | prompts | benchmarks | improve | reply_diagnosis | mcp_patterns | mcp_pattern_detail), scoreboard (impact | bowtie) and settings (profile | automation | marketing | channel_status | booking_link). Other types take no subtype.",
61
+ "description": "Variant selector — required for insights (quality | pattern_tags | prompts | benchmarks | improve | reply_diagnosis | mcp_patterns | mcp_pattern_detail), scoreboard (impact | bowtie) and settings (profile | automation | marketing | channel_status | booking_link | campaign_delegations | ai_compute). Other types take no subtype.",
62
62
  "title": "subtype",
63
63
  "type": "string"
64
64
  },
65
65
  "type": {
66
- "description": "Entity type to fetch. One of: campaign, csv_import, plan, conversation, person_research, company_research, account_state, insights, scoreboard, settings, billing, publish_readiness, research_playbook, job, skill, deal, deal_pipelines, deal_alerts, post_analytics, seller_signals, stats, marketing, ads, relationship_attempt.",
66
+ "description": "Entity type to fetch. One of: campaign, csv_import, company_resolution_queue, plan, conversation, person_research, company_research, account_state, insights, scoreboard, settings, billing, publish_readiness, research_playbook, job, skill, deal, deal_pipelines, deal_alerts, post_analytics, seller_signals, stats, marketing, ads, relationship_attempt, claim.",
67
67
  "title": "type",
68
68
  "type": "string"
69
69
  }
@@ -87,7 +87,7 @@
87
87
  "inputSchema": {
88
88
  "properties": {
89
89
  "for_company": {
90
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
90
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
91
91
  "title": "for_company",
92
92
  "type": "string"
93
93
  },
@@ -152,7 +152,7 @@
152
152
  "type": "string"
153
153
  },
154
154
  "for_company": {
155
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
155
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
156
156
  "title": "for_company",
157
157
  "type": "string"
158
158
  },
@@ -212,7 +212,7 @@
212
212
  "type": "integer"
213
213
  },
214
214
  "for_company": {
215
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
215
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
216
216
  "title": "for_company",
217
217
  "type": "string"
218
218
  },
@@ -265,11 +265,11 @@
265
265
  "openWorldHint": false,
266
266
  "readOnlyHint": true
267
267
  },
268
- "description": "Get Daily Briefing\n\nDaily briefing — everything you need to start your day in one call.\n\nSUGGESTED ACTIONS (for agent consumers)\n- `suggested_actions` are prioritized skill-level next steps. If you are\n an agent, act on `intent`/`args` directly — via the tools listed in\n `expected_tools`, or via the named harness `skill` if it is installed.\n `prompt` is display copy for HUMANS to paste into an agent; do NOT echo\n it back to the user as an instruction.\n- `sections_degraded` lists sections whose queries failed this call; their\n counts/lists are UNKNOWN (returned as 0/[]), not zero. When data is\n missing and nothing else is actionable, `top_recommendation` is\n 'degraded' instead of 'all_clear'.\n\nReturns pending approvals, new replies, meetings booked this week,\npipeline summary, stalled deals, active plans,\ndiscovery jobs, and a prioritized recommendation.\n\nTIME WINDOWS (UTC)\n- \"last 24 hours\" — `replied_at >= now - 24 hours`. Used by new_replies_count\n and recent_replies.\n- \"current week\" — `meeting_booked_at >= Monday 00:00:00 UTC of the current\n week (Monday-anchored)`. Used by meetings_booked_this_week.\n- \"7+ days\" — `updated_at <= now - 7 days`. Used by stalled_deals\n (deals with outcome IS NULL and updated_at older than 7 days).\n\nReturns (units in parentheses; scope = user_company_id, no date filter on\ncounts unless specified):\n pending_approvals_count (int, unit=messages): messages.status='draft' for\n the tenant. NOT date-filtered — total backlog.\n messages_needing_authoring (int, unit=messages): messages.status=\n 'needs_draft' — touches awaiting harness AUTHORING (no prose yet;\n run /outreach-triage). VRU-570.\n oldest_needs_draft_age_days (float|null, unit=days): age of the oldest\n unauthored engagement signal — signals TTL-dismiss at 14 days\n (VRU-671), so a high value means the queue is rotting unauthored.\n engagements_needing_authoring (int, unit=engagements):\n linkedin_engagement_queue.status='needs_draft' — comments awaiting\n authoring (run /engagement-triage). VRU-570.\n top_pending_approvals (list, unit=messages, max 3): a preview of the most\n recent pending drafts; each item has message_id, person_name, channel,\n category, subject, content_preview (200-char truncation).\n new_replies_count (int, unit=messages): messages.has_reply=true with\n replied_at within the last 24 hours.\n recent_replies (list, unit=messages, max 3): preview of those replies.\n meetings_booked_this_week (int, unit=unique_people): distinct\n company_people with meeting_booked_at since Monday 00:00 UTC. People,\n not meetings — same person twice in one week still counts as 1.\n pipeline_summary (list of PipelineStage, unit=deals): aggregated per-stage\n for open deals (outcome IS NULL). Each: stage, count (unit=deals),\n total_value (sum of estimated_value).\n stalled_deals (list, unit=deals, max 5): open deals (outcome IS NULL) whose\n updated_at is older than 7 days. days_stalled is calendar days since\n updated_at.\n deal_alerts (list of DealAlert, unit=deals): per-deal alerts surfaced from\n DealService.get_deal_alerts (severity / detail / days).\n deal_alerts_count (int, unit=deals): len(deal_alerts).\n active_outreach_plans (int, unit=plans): outreach_plans.status='active'.\n Not date-filtered.\n channel_holds (list, unit=sender accounts): current LinkedIn quota or\n reconnect blocks, including retry_at and affected approved sends.\n Agents must report these before claiming outreach is all-clear.\n discovery_jobs_in_progress (int, unit=jobs):\n background_jobs.operation_type='person_discovery' with status IN\n ('pending', 'processing').\n tasks_due_today_count (int, unit=tasks): open/in_progress tasks with\n due_at <= now (due today or overdue) — same set as\n get_tasks(due_today=true). Committed follow-ups, incl. action\n items captured from meetings.\n top_tasks_due (list of TaskDue, max 5): preview of those tasks; each\n has task_id, title, due_at, priority, person_id, deal_id.\n top_recommendation (str): one of 'channel_reconnect',\n 'pending_approvals', 'new_replies', 'deal_alerts', 'tasks_due',\n 'stalled_deals', 'channel_quota', 'degraded', 'all_clear'.\n 'degraded' = section data is missing and nothing else\n is actionable (see sections_degraded above). Priority:\n reconnect > approvals > replies > CRITICAL deal alerts > tasks_due >\n non-critical deal alerts > stalled > quota pacing > all-clear.\n recommendation_detail (str): human prose explanation of the recommendation.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"pending_approvals_count\": 1,\n \"messages_needing_authoring\": 1,\n \"engagements_needing_authoring\": 1,\n \"engagers_awaiting_decision\": 1,\n \"top_pending_approvals\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"new_replies_count\": 1,\n \"recent_replies\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"meetings_booked_this_week\": 1,\n \"pipeline_summary\": [\n {\n \"stage\": \"Stage\",\n \"count\": 1,\n \"total_value\": 1.0\n }\n ],\n \"stalled_deals\": [\n {\n \"deal_id\": \"Deal Id\",\n \"days_stalled\": 1\n }\n ],\n \"deal_alerts\": [\n {\n \"deal_id\": \"Deal Id\",\n \"deal_name\": \"Deal Name\",\n \"alert_type\": \"Alert Type\",\n \"severity\": \"Severity\",\n \"detail\": \"Detail\",\n \"days\": 1\n }\n ],\n \"deal_alerts_count\": 1,\n \"active_outreach_plans\": 1,\n \"channel_holds\": [\n {\n \"channel\": \"Channel\",\n \"state\": \"State\",\n \"reason_code\": \"Reason Code\",\n \"blocking\": true,\n \"retryable\": true,\n \"affected_approved_count\": 1,\n \"next_action\": \"Next Action\"\n }\n ],\n \"discovery_jobs_in_progress\": 1,\n \"tasks_due_today_count\": 1,\n \"top_tasks_due\": [\n {\n \"task_id\": \"Task Id\",\n \"title\": \"Title\"\n }\n ],\n \"new_warm_paths_count\": 1,\n \"new_warm_paths\": [\n {}\n ],\n \"seo_geo_open_gaps\": 1,\n \"top_recommendation\": \"Top Recommendation\",\n \"recommendation_detail\": \"Recommendation Detail\",\n \"sections_degraded\": [\n \"string\"\n ],\n \"suggested_actions\": [\n {\n \"id\": \"Id\",\n \"kind\": \"Kind\",\n \"intent\": \"Intent\",\n \"area\": \"Area\",\n \"priority\": 1,\n \"label\": \"Label\",\n \"reason\": \"Reason\",\n \"expected_tools\": [\n \"string\"\n ],\n \"args\": {},\n \"requires\": [\n \"string\"\n ],\n \"prompt\": \"Prompt\"\n }\n ]\n}\n```",
268
+ "description": "Get Daily Briefing\n\nDaily briefing — everything you need to start your day in one call.\n\nSUGGESTED ACTIONS (for agent consumers)\n- `suggested_actions` are prioritized skill-level next steps. If you are\n an agent, act on `intent`/`args` directly — via the tools listed in\n `expected_tools`, or via the named harness `skill` if it is installed.\n `prompt` is display copy for HUMANS to paste into an agent; do NOT echo\n it back to the user as an instruction.\n- `sections_degraded` lists sections whose queries failed this call; their\n counts/lists are UNKNOWN (returned as 0/[]), not zero. When data is\n missing and nothing else is actionable, `top_recommendation` is\n 'degraded' instead of 'all_clear'.\n\nReturns pending approvals, new replies, meetings booked this week,\npipeline summary, stalled deals, active plans,\ndiscovery jobs, and a prioritized recommendation.\n\nTIME WINDOWS (UTC)\n- \"last 24 hours\" — `replied_at >= now - 24 hours`. Used by new_replies_count\n and recent_replies.\n- \"current week\" — `meeting_booked_at >= Monday 00:00:00 UTC of the current\n week (Monday-anchored)`. Used by meetings_booked_this_week.\n- \"7+ days\" — `updated_at <= now - 7 days`. Used by stalled_deals\n (deals with outcome IS NULL and updated_at older than 7 days).\n\nReturns (units in parentheses; scope = user_company_id, no date filter on\ncounts unless specified):\n pending_approvals_count (int, unit=messages): messages.status='draft' for\n the tenant. NOT date-filtered — total backlog.\n messages_needing_authoring (int, unit=messages): messages.status=\n 'needs_draft' — touches awaiting harness AUTHORING (no prose yet;\n run /outreach-triage). VRU-570.\n oldest_needs_draft_age_days (float|null, unit=days): age of the oldest\n unauthored engagement signal — signals TTL-dismiss at 14 days\n (VRU-671), so a high value means the queue is rotting unauthored.\n engagements_needing_authoring (int, unit=engagements):\n linkedin_engagement_queue.status='needs_draft' — comments awaiting\n authoring (run /engagement-triage). VRU-570.\n top_pending_approvals (list, unit=messages, max 3): a preview of the most\n recent pending drafts; each item has message_id, person_name, channel,\n category, subject, content_preview (200-char truncation).\n new_replies_count (int, unit=messages): messages.has_reply=true with\n replied_at within the last 24 hours.\n recent_replies (list, unit=messages, max 3): preview of those replies.\n meetings_booked_this_week (int, unit=unique_people): distinct\n company_people with meeting_booked_at since Monday 00:00 UTC. People,\n not meetings — same person twice in one week still counts as 1.\n pipeline_summary (list of PipelineStage, unit=deals): aggregated per-stage\n for open deals (outcome IS NULL). Each: stage, count (unit=deals),\n total_value (sum of estimated_value).\n stalled_deals (list, unit=deals, max 5): open deals (outcome IS NULL) whose\n updated_at is older than 7 days. days_stalled is calendar days since\n updated_at.\n deal_alerts (list of DealAlert, unit=deals): per-deal alerts surfaced from\n DealService.get_deal_alerts (severity / detail / days).\n deal_alerts_count (int, unit=deals): len(deal_alerts).\n active_outreach_plans (int, unit=plans): outreach_plans.status='active'.\n Not date-filtered.\n channel_holds (list, unit=sender accounts): current LinkedIn quota or\n reconnect blocks, including retry_at and affected approved sends.\n Agents must report these before claiming outreach is all-clear.\n discovery_jobs_in_progress (int, unit=jobs):\n background_jobs.operation_type='person_discovery' with status IN\n ('pending', 'processing').\n tasks_due_today_count (int, unit=tasks): open/in_progress tasks with\n due_at <= now (due today or overdue) — same set as\n get_tasks(due_today=true). Committed follow-ups, incl. action\n items captured from meetings.\n top_tasks_due (list of TaskDue, max 5): preview of those tasks; each\n has task_id, title, due_at, priority, person_id, deal_id.\n motion_health (MotionHealth): outbound conversion over the trailing 60\n days, tenant-wide, as UNIQUE PEOPLE contacted vs replied. READ THIS\n BEFORE RECOMMENDING MORE VOLUME. state='broken' (>=100 contacted,\n <1% reply rate) means the queue is not the bottleneck and approving\n more drafts is negative-EV work; the fix is worth more than the\n throughput. state='unmeasured' (<30 contacted, or an incomplete\n scan) means there is not enough evidence to judge — treat it as\n silence, never as failure. Carries worst_campaign_* : the campaign\n that spent the most contacts for zero replies.\n campaign_health (list of CampaignHealth): the same numbers per campaign,\n highest-volume first. reply_rate is null (not 0.0) when nothing was\n contacted — unmeasured is not zero.\n content_cadence (ContentCadence): days_since_last_post and 30-day post\n volume. A dark feed removes the warm surface outreach converts\n against.\n expansion_candidates_count (int, unit=deals): won deals 60+ days old\n whose account has no open follow-on (/expansion-fill cohort).\n winback_candidates_count (int, unit=deals): deals lost between 90 days\n and 18 months ago. WINDOW ONLY — `deals.loss_reason` is free text,\n so the terminal-loss filter is the skill's job, not this count's.\n synthesis_applied (bool): true when suggested_actions came from the\n cached daily LLM synthesis; false means the deterministic floor is\n serving (synthesis absent, stale, or contract-rejected).\n synthesis_headline (str|null): one-line statement of the actual\n bottleneck from that synthesis; null when the floor is serving.\n top_recommendation (str): the winning suggested action, as a legacy\n alias where one exists ('channel_reconnect', 'pending_approvals',\n 'new_replies', 'deal_alerts', 'tasks_due') and otherwise the\n action's own intent (e.g. 'fix_campaign', 'work_warm_paths'). It is\n derived FROM suggested_actions[0], so the two can never disagree.\n 'degraded' = section data is missing and nothing else is actionable\n (see sections_degraded above); 'all_clear' = nothing actionable.\n recommendation_detail (str): human prose explanation — the winning\n action's `reason`.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"pending_approvals_count\": 1,\n \"messages_needing_authoring\": 1,\n \"engagements_needing_authoring\": 1,\n \"engagers_awaiting_decision\": 1,\n \"top_pending_approvals\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"new_replies_count\": 1,\n \"recent_replies\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"meetings_booked_this_week\": 1,\n \"pipeline_summary\": [\n {\n \"stage\": \"Stage\",\n \"count\": 1,\n \"total_value\": 1.0\n }\n ],\n \"stalled_deals\": [\n {\n \"deal_id\": \"Deal Id\",\n \"days_stalled\": 1\n }\n ],\n \"deal_alerts\": [\n {\n \"deal_id\": \"Deal Id\",\n \"deal_name\": \"Deal Name\",\n \"alert_type\": \"Alert Type\",\n \"severity\": \"Severity\",\n \"detail\": \"Detail\",\n \"days\": 1\n }\n ],\n \"deal_alerts_count\": 1,\n \"active_outreach_plans\": 1,\n \"channel_holds\": [\n {\n \"channel\": \"Channel\",\n \"state\": \"State\",\n \"reason_code\": \"Reason Code\",\n \"blocking\": true,\n \"retryable\": true,\n \"affected_approved_count\": 1,\n \"next_action\": \"Next Action\"\n }\n ],\n \"discovery_jobs_in_progress\": 1,\n \"tasks_due_today_count\": 1,\n \"top_tasks_due\": [\n {\n \"task_id\": \"Task Id\",\n \"title\": \"Title\"\n }\n ],\n \"new_warm_paths_count\": 1,\n \"new_warm_paths\": [\n {}\n ],\n \"seo_geo_open_gaps\": 1,\n \"motion_health\": {\n \"state\": \"State\",\n \"window_days\": 1,\n \"contacted\": 1,\n \"replied\": 1,\n \"worst_campaign_contacted\": 1\n },\n \"campaign_health\": [\n {\n \"contacted\": 1,\n \"replied\": 1\n }\n ],\n \"content_cadence\": {\n \"posts_last_30d\": 1\n },\n \"expansion_candidates_count\": 1,\n \"winback_candidates_count\": 1,\n \"synthesis_applied\": true,\n \"top_recommendation\": \"Top Recommendation\",\n \"recommendation_detail\": \"Recommendation Detail\",\n \"sections_degraded\": [\n \"string\"\n ],\n \"suggested_actions\": [\n {\n \"id\": \"Id\",\n \"kind\": \"Kind\",\n \"intent\": \"Intent\",\n \"area\": \"Area\",\n \"priority\": 1,\n \"label\": \"Label\",\n \"reason\": \"Reason\",\n \"expected_tools\": [\n \"string\"\n ],\n \"args\": {},\n \"requires\": [\n \"string\"\n ],\n \"prompt\": \"Prompt\",\n \"ev_score\": 1,\n \"impact\": 1,\n \"confidence\": 1,\n \"perishability\": 1,\n \"effort_minutes\": 1,\n \"evidence\": [\n \"string\"\n ]\n }\n ]\n}\n```",
269
269
  "inputSchema": {
270
270
  "properties": {
271
271
  "for_company": {
272
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
272
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
273
273
  "title": "for_company",
274
274
  "type": "string"
275
275
  }
@@ -294,7 +294,7 @@
294
294
  "type": "string"
295
295
  },
296
296
  "for_company": {
297
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
297
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
298
298
  "title": "for_company",
299
299
  "type": "string"
300
300
  },
@@ -331,7 +331,7 @@
331
331
  "type": "null"
332
332
  }
333
333
  ],
334
- "description": "Filter by campaign name or UUID. Returns only engagements for people in that campaign. IGNORED when source='engagers' (engagers are not campaign-keyed until the operator acts).",
334
+ "description": "Filter by campaign name or UUID. Returns engagements AUTHORED under that campaign's plans (the row's source_campaign_id — not the person's current campaign membership). Unknown campaign = 404. IGNORED when source='engagers' (engagers are not campaign-keyed until the operator acts).",
335
335
  "title": "campaign",
336
336
  "type": "string"
337
337
  },
@@ -355,7 +355,7 @@
355
355
  "type": "string"
356
356
  },
357
357
  "for_company": {
358
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
358
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
359
359
  "title": "for_company",
360
360
  "type": "string"
361
361
  },
@@ -421,6 +421,71 @@
421
421
  "name": "get_engagement_review",
422
422
  "title": "Get Engagement Review"
423
423
  },
424
+ {
425
+ "annotations": {
426
+ "destructiveHint": false,
427
+ "openWorldHint": false,
428
+ "readOnlyHint": true
429
+ },
430
+ "description": "Get Network Identity Review\n\nDeterministic identity-quality queue for the caller's relationship graph.\n\nReports split people, unresolved company affiliations, company aliases, and\nmachine/shared mailboxes represented as humans. Read-only: every issue\nincludes evidence and a typed suggested action for\n``manage_network_identity_resolution``.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"issues\": [\n {\n \"ref\": \"Ref\",\n \"kind\": \"Kind\",\n \"severity\": \"Severity\",\n \"summary\": \"Summary\",\n \"entity_refs\": [\n \"string\"\n ],\n \"identity_refs\": [\n \"string\"\n ],\n \"evidence\": {},\n \"suggested_action\": {}\n }\n ],\n \"total\": 1,\n \"limit\": 1,\n \"offset\": 1,\n \"has_more\": true,\n \"capped\": true\n}\n```",
431
+ "inputSchema": {
432
+ "properties": {
433
+ "for_company": {
434
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
435
+ "title": "for_company",
436
+ "type": "string"
437
+ },
438
+ "kind": {
439
+ "anyOf": [
440
+ {
441
+ "enum": [
442
+ "split_person",
443
+ "missing_company_affiliation",
444
+ "shared_mailbox_as_person",
445
+ "company_alias"
446
+ ],
447
+ "type": "string"
448
+ },
449
+ {
450
+ "type": "null"
451
+ }
452
+ ],
453
+ "title": "kind",
454
+ "type": "string"
455
+ },
456
+ "limit": {
457
+ "default": 50,
458
+ "maximum": 100,
459
+ "minimum": 1,
460
+ "title": "limit",
461
+ "type": "integer"
462
+ },
463
+ "offset": {
464
+ "default": 0,
465
+ "minimum": 0,
466
+ "title": "offset",
467
+ "type": "integer"
468
+ },
469
+ "person_ref": {
470
+ "anyOf": [
471
+ {
472
+ "type": "string"
473
+ },
474
+ {
475
+ "type": "null"
476
+ }
477
+ ],
478
+ "description": "Optional network_person_<uuid> reference for one prospect.",
479
+ "title": "person_ref",
480
+ "type": "string"
481
+ }
482
+ },
483
+ "title": "get_network_identity_reviewArguments",
484
+ "type": "object"
485
+ },
486
+ "name": "get_network_identity_review",
487
+ "title": "Get Network Identity Review"
488
+ },
424
489
  {
425
490
  "annotations": {
426
491
  "destructiveHint": false,
@@ -431,7 +496,7 @@
431
496
  "inputSchema": {
432
497
  "properties": {
433
498
  "for_company": {
434
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
499
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
435
500
  "title": "for_company",
436
501
  "type": "string"
437
502
  },
@@ -456,7 +521,22 @@
456
521
  "openWorldHint": false,
457
522
  "readOnlyHint": true
458
523
  },
459
- "description": "Get Outreach Review\n\nDefault tool for outreach queue triage — pending messages with full context for review.\n\nStart here when reviewing or managing the outreach queue. Returns pending\nmessages — both 'needs_draft' (unauthored, awaiting harness authoring: write\nvia manage_messages edit) and 'draft' (authored, awaiting approve/reject),\ndisambiguated by each item's `status` — enriched with: person context,\ncampaign instructions (tone rules, selling strategy, touch sequence), match\nanalysis (summary, alignment points, recommended approach), company research\nsummary, recent LinkedIn posts, conversation thread, and outreach plan state.\nOne call gives you everything needed to author, approve, or reject.\n\n`channel_holds` reports LinkedIn sender blocks such as daily quota,\nprovider throttling, or reconnect-required state. If\n`channel_holds_degraded` is true, an empty hold list is unknown rather than\nproof that the channel is healthy.\n\nUse content_length='full' for detailed review (capped at 5 items to prevent\nconnection drops from oversized payloads — use offset to paginate),\nor 'preview' (default) for quick scanning with truncated content.\n\nFilter by campaign name or ID to review a specific queue (e.g. campaign='DFW CFOs').\n\nUse message_ids to fetch specific messages by ID (comma-separated UUIDs, max 20).\nBypasses sorting/pagination — returns full context for exactly those messages.\nUseful for subagent dispatch: pull the lightweight queue first, split into batches,\nthen each subagent calls with its assigned message_ids.\n\nOperators: pass for_company parameter to review a specific client company.\n\nReturns (units in parentheses; scope = messages.status IN ('draft','needs_draft')\nfor this tenant unless message_ids is set):\n items (list of ReviewItem, unit=messages): one entry per pending message\n (not per person — a person with two pending messages shows up twice).\n Each item:\n - message_id, status ('needs_draft' = author it, 'draft' = approve it),\n channel, subject, content, category (initial / followup /\n reply_response), priority_score, created_at\n - expires_at (needs_draft only): when the nightly sweep auto-rejects\n this row if still unauthored (needs_draft_since + 14 days —\n regenerate grants a fresh window). Author nearest-deadline-first.\n An expired touch shows up later as status='rejected'\n with blank content and ai_decision_context.expired_signal — that is\n GC, NOT an operator rejection; revive via manage_messages\n action=regenerate, then author and edit as normal.\n - person_id, person_name, person_title, person_company, match_score,\n stage (the company_people stage)\n - campaign_name, campaign_tone_instructions, campaign_selling_strategy,\n campaign_touch_sequence (per-touch instructions)\n - match_summary, alignment_points, recommended_approach\n - company_summary (recent company research),\n recent_linkedin_posts (last 3 posts; unit=posts)\n - conversation_thread (list of outbound+inbound rows, unit=messages,\n ordered by created_at; truncated to 200 chars when content_length='preview')\n - outreach_plan_id (the linked plan's id — pass straight to\n manage_outreach pause/resume/stop; no get_person_360 needed),\n plan_status, plan_strategy, touches_completed, max_touches,\n plan_next_action_at (when the plan's next touch fires — an\n approved-but-deferred send happens at this time),\n connection_status (the plan's LinkedIn connection state) (from\n the linked outreach_plan; unit=plans)\n - PRIORITIZE WARM ITEMS: a linkedin_message item with\n connection_status='connected' is a follow-up to someone who\n ACCEPTED the connection request — author/approve these before\n any cold lane; they are the highest-EV rows in the queue.\n total_pending (int, unit=messages): EXACT count of actionable pending messages\n matching the campaign / message_ids filter for the tenant,\n BEFORE pagination. Filters: status IN ('draft','needs_draft'),\n excludes externally scheduled/reservation-protected rows,\n user_company_id, and the campaign filter when set. Does NOT\n date-filter — the queue is \"everything currently pending.\"\n offset (int): echoes the request offset.\n limit (int, unit=messages): echoes the page size. When content_length='full'\n this is capped at 5 (regardless of the requested limit).\n approved_pending_send_count (int, unit=messages): approved rows that\n have NOT sent yet (they no longer show as items). Non-zero after a\n bulk approve means those sends are SCHEDULED (per-person cooldown /\n designed sequence timing) — they are not lost, not sent, and must\n not be re-drafted or manually re-sent.\n externally_scheduled_count (int, unit=messages): Gmail/provider-scheduled\n rows protected by an external-send reservation. They are excluded\n from actionable items and must not be approved or re-drafted.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"items\": [\n {\n \"message_id\": \"Message Id\",\n \"conversation_thread\": [\n {}\n ],\n \"attachments\": [\n {}\n ]\n }\n ],\n \"total_pending\": 1,\n \"offset\": 1,\n \"limit\": 1,\n \"approved_pending_send_count\": 1,\n \"externally_scheduled_count\": 1,\n \"channel_holds\": [\n {\n \"channel\": \"Channel\",\n \"state\": \"State\",\n \"reason_code\": \"Reason Code\",\n \"blocking\": true,\n \"retryable\": true,\n \"affected_approved_count\": 1,\n \"next_action\": \"Next Action\"\n }\n ],\n \"channel_holds_degraded\": true\n}\n```",
524
+ "description": "Get Operator Companies\n\nList the companies the caller can act in, with summary counts.\n\nReturns every tenant where the caller has membership when they belong to\nat least two tenants. Drives the company switcher. This is intentionally\nNOT gated on operator (Vruum-staff) or owner role. The route keeps its\nhistorical ``/operator`` path for backwards-compat; switch-eligibility is a\nmembership fact, not a staff role. The frontend derives ``canSwitch`` from\nthe list length and ``isOperator`` from the per-company ``role`` field, so a\nnon-operator multi-tenant user gets the switcher but not operator-only UI.\n\nSingle-tenant callers get an empty list (200) rather than 404 because every\nauthenticated client polls this route on mount.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"companies\": [\n {\n \"id\": \"Id\",\n \"role\": \"Role\"\n }\n ],\n \"counts_available\": true\n}\n```",
525
+ "inputSchema": {
526
+ "properties": {},
527
+ "title": "get_operator_companiesArguments",
528
+ "type": "object"
529
+ },
530
+ "name": "get_operator_companies",
531
+ "title": "Get Operator Companies"
532
+ },
533
+ {
534
+ "annotations": {
535
+ "destructiveHint": false,
536
+ "openWorldHint": false,
537
+ "readOnlyHint": true
538
+ },
539
+ "description": "Get Outreach Review\n\nDefault tool for outreach queue triage — pending messages with full context for review.\n\nStart here when reviewing or managing the outreach queue. Returns pending\nmessages — both 'needs_draft' (unauthored, awaiting harness authoring: write\nvia manage_messages edit) and 'draft' (authored, awaiting approve/reject),\ndisambiguated by each item's `status` — enriched with: person context,\ncampaign instructions (tone rules, selling strategy, touch sequence), match\nanalysis (summary, alignment points, recommended approach), company research\nsummary, recent LinkedIn posts, conversation thread, and outreach plan state.\nOne call gives you everything needed to author, approve, or reject.\n\n`channel_holds` reports LinkedIn sender blocks such as daily quota,\nprovider throttling, or reconnect-required state. If\n`channel_holds_degraded` is true, an empty hold list is unknown rather than\nproof that the channel is healthy.\n\nUse content_length='full' for detailed review (capped at 5 items to prevent\nconnection drops from oversized payloads — use offset to paginate),\nor 'preview' (default) for quick scanning with truncated content.\n\nFilter by campaign name or ID to review a specific queue (e.g. campaign='DFW CFOs').\n\nUse message_ids to fetch specific messages by ID (comma-separated UUIDs, max 20).\nBypasses sorting/pagination — returns full context for exactly those messages.\nUseful for subagent dispatch: pull the lightweight queue first, split into batches,\nthen each subagent calls with its assigned message_ids.\n\nOperators: pass for_company parameter to review a specific client company.\n\nReturns (units in parentheses; scope = messages.status IN ('draft','needs_draft')\nfor this tenant unless message_ids is set):\n items (list of ReviewItem, unit=messages): one entry per pending message\n (not per person — a person with two pending messages shows up twice).\n Each item:\n - message_id, status ('needs_draft' = author it, 'draft' = approve it),\n channel, subject, content, category (initial / followup /\n reply_response), priority_score, created_at\n - expires_at (needs_draft only): when the nightly sweep auto-rejects\n this row if still unauthored (needs_draft_since + 14 days —\n regenerate grants a fresh window). Author nearest-deadline-first.\n An expired touch shows up later as status='rejected'\n with blank content and ai_decision_context.expired_signal — that is\n GC, NOT an operator rejection; revive via manage_messages\n action=regenerate, then author and edit as normal.\n - person_id, person_name, person_title, person_company, match_score,\n stage (the company_people stage)\n - campaign_name, campaign_tone_instructions, campaign_selling_strategy,\n campaign_touch_sequence (per-touch instructions)\n - match_summary, alignment_points, recommended_approach\n - company_summary (recent company research),\n recent_linkedin_posts (last 3 posts; unit=posts)\n - manual_notes (full content only): up to 10 newest-first person\n timeline notes. Each note is capped at 4,000 characters, each\n person at 8,000, and all repeated item payloads at 30,000 total.\n A cut note ends with `...[truncated]`. When campaign instructions\n name a saved per-person draft, use it instead of the 200-character\n activity summary.\n - conversation_thread (list of outbound+inbound rows, unit=messages,\n ordered by created_at; truncated to 200 chars when content_length='preview')\n - outreach_plan_id (the linked plan's id — pass straight to\n manage_outreach pause/resume/stop; no get_person_360 needed),\n plan_status, plan_strategy, touches_completed, max_touches,\n plan_next_action_at (when the plan's next touch fires — an\n approved-but-deferred send happens at this time),\n connection_status (the plan's LinkedIn connection state) (from\n the linked outreach_plan; unit=plans)\n - PRIORITIZE WARM ITEMS: a linkedin_message item with\n connection_status='connected' is a follow-up to someone who\n ACCEPTED the connection request — author/approve these before\n any cold lane; they are the highest-EV rows in the queue.\n total_pending (int, unit=messages): EXACT count of actionable pending messages\n matching the campaign / message_ids filter for the tenant,\n BEFORE pagination. Filters: status IN ('draft','needs_draft'),\n excludes externally scheduled/reservation-protected rows,\n user_company_id, and the campaign filter when set. Does NOT\n date-filter — the queue is \"everything currently pending.\"\n offset (int): echoes the request offset.\n limit (int, unit=messages): echoes the page size. When content_length='full'\n this is capped at 5 (regardless of the requested limit).\n approved_pending_send_count (int, unit=messages): approved rows that\n have NOT sent yet (they no longer show as items). Non-zero after a\n bulk approve means those sends are SCHEDULED (per-person cooldown /\n designed sequence timing) — they are not lost, not sent, and must\n not be re-drafted or manually re-sent.\n externally_scheduled_count (int, unit=messages): Gmail/provider-scheduled\n rows protected by an external-send reservation. They are excluded\n from actionable items and must not be approved or re-drafted.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"items\": [\n {\n \"message_id\": \"Message Id\",\n \"manual_notes\": [\n \"string\"\n ],\n \"conversation_thread\": [\n {}\n ],\n \"attachments\": [\n {}\n ]\n }\n ],\n \"total_pending\": 1,\n \"offset\": 1,\n \"limit\": 1,\n \"approved_pending_send_count\": 1,\n \"externally_scheduled_count\": 1,\n \"channel_holds\": [\n {\n \"channel\": \"Channel\",\n \"state\": \"State\",\n \"reason_code\": \"Reason Code\",\n \"blocking\": true,\n \"retryable\": true,\n \"affected_approved_count\": 1,\n \"next_action\": \"Next Action\"\n }\n ],\n \"channel_holds_degraded\": true\n}\n```",
460
540
  "inputSchema": {
461
541
  "properties": {
462
542
  "campaign": {
@@ -479,7 +559,7 @@
479
559
  "type": "string"
480
560
  },
481
561
  "for_company": {
482
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
562
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
483
563
  "title": "for_company",
484
564
  "type": "string"
485
565
  },
@@ -554,7 +634,7 @@
554
634
  "type": "string"
555
635
  },
556
636
  "for_company": {
557
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
637
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
558
638
  "title": "for_company",
559
639
  "type": "string"
560
640
  },
@@ -607,7 +687,7 @@
607
687
  "inputSchema": {
608
688
  "properties": {
609
689
  "for_company": {
610
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
690
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
611
691
  "title": "for_company",
612
692
  "type": "string"
613
693
  },
@@ -674,7 +754,7 @@
674
754
  "type": "string"
675
755
  },
676
756
  "for_company": {
677
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
757
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
678
758
  "title": "for_company",
679
759
  "type": "string"
680
760
  },
@@ -728,7 +808,7 @@
728
808
  "openWorldHint": true,
729
809
  "readOnlyHint": false
730
810
  },
731
- "description": "Import Prospects\n\nSource and import prospects — the consolidated import tool.\n\nSales Navigator actions (`payload` = the search/import fields):\n- sales_nav_search: search LinkedIn people ({keywords, mode?,\n title?, company_headcount_min/max?, list_id?, saved_search_id?,\n limit?, cursor?}). `mode` must be `basic` or `sales_nav`; use\n `basic` for exact-name/profile resolution fallback when Sales Nav is\n unavailable.\n- sales_nav_preview: preview a Sales Nav search against your pipeline\n ({search?, count?, campaign?}).\n- sales_nav_import: import profiles ({search?, count?, profiles_json?,\n auto_enroll?, campaign?}). `profiles_json` accepts sales_nav_search\n output directly: each object needs linkedin_url plus either\n first_name/last_name or a single `name` field (mode=basic results);\n `headline` is used as a title fallback.\n- sales_nav_dismiss: dismiss profiles from future imports\n ({linkedin_urls} — comma-separated).\n- sales_nav_searches: manage saved searches ({action: list|create|\n update|delete, name?, keywords?, filters_json?, frequency?,\n search_id?}).\n\nCSV actions:\n- csv_preview: parse + preview a CSV payload ({csv_content, …}) before\n importing.\n- csv_start: start an import job ({file_content, column_mapping,\n campaign_id?, skip_research?}). Returns a job id — poll it with\n fetch type='csv_import'.\n\nPayloads are validated by the underlying route's own schema, so a bad\nfield returns that route's precise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
811
+ "description": "Import Prospects\n\nSource and import prospects — the consolidated import tool.\n\nLinkedIn people search (`payload` = the search fields):\n- linkedin_people_search: search PEOPLE through regular LinkedIn\n ({keywords, limit?}). This action does not require Sales Navigator and\n always uses basic mode. It returns at most 25 results and charges the\n capped requested limit against the shared LinkedIn daily activity budget\n used by reads and sends. The charge occurs before the provider call and\n is not refunded for zero results or an upstream failure. It does not accept\n company, title, headcount, list, saved-search, cursor, mode, or arbitrary\n seat selection. Vruum selects the current user's active connected account\n first and can fall back to another active tenant LinkedIn account.\n\n LinkedIn text search is best for resolving people at a known company or\n resolving an exact person. It is not a company-discovery source. Examples:\n {\"keywords\": \"Acme VP Sales\"} and\n {\"keywords\": \"Jordan Lee Acme\"}.\n- sales_nav_search: compatibility alias for existing clients. Its `mode`\n may be `basic` or `sales_nav`; omit it for legacy basic behavior. New\n regular LinkedIn searches should migrate to `linkedin_people_search`.\n `mode='sales_nav'` accepts {keywords, title?,\n company_headcount_min/max?, list_id?, saved_search_id?, limit?, cursor?}\n and requires a Sales Navigator subscription.\n\nSales Navigator import actions:\n- sales_nav_preview: preview a Sales Nav search against your pipeline\n ({search?, count?, campaign?}).\n- sales_nav_import: import people or accounts. For account imports, use\n {search, target: \"accounts\"}; `search` is a saved account search name or\n ID, and do not pass campaign or auto_enroll. A unique match returns a\n job_id to poll with fetch type='job'; no_match, ambiguous, and error\n responses do not return a job. People imports accept {search?, count?,\n profiles_json?, auto_enroll?, campaign?}.\n `profiles_json` accepts sales_nav_search output directly: each object\n needs linkedin_url plus either first_name/last_name or a single `name`\n field (mode=basic results); `headline` is used as a title fallback.\n- sales_nav_dismiss: dismiss profiles from future imports\n ({linkedin_urls} — comma-separated).\n- sales_nav_searches: list seats with {action: \"accounts\"}; each config_id\n can be passed as search_account_id. Actions list and create support saved\n people or account searches. Use search_type=\"company\" to create an\n account search; create requires name and filters_json, where filters_json\n is a JSON-encoded array string. Register attaches replay filters to an\n existing account search and requires search_id, name, and filters_json.\n Actions update and delete support people searches only. Other fields are {name?, keywords?, search_type?:\n \"lead\"|\"company\", search_account_id?, frequency?, search_id?}.\n\nCSV actions:\n- csv_preview: parse + preview a CSV payload ({file_content, …}) before\n importing.\n- csv_start: start an import job ({file_content, column_mapping,\n campaign_id?, skip_research?}). Returns a job id — poll it with\n fetch type='csv_import'.\n\nPayloads are validated by the underlying route's own schema, so a bad\nfield returns that route's precise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
732
812
  "inputSchema": {
733
813
  "properties": {
734
814
  "action": {
@@ -737,7 +817,7 @@
737
817
  "type": "string"
738
818
  },
739
819
  "for_company": {
740
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
820
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
741
821
  "title": "for_company",
742
822
  "type": "string"
743
823
  },
@@ -802,7 +882,7 @@
802
882
  "inputSchema": {
803
883
  "properties": {
804
884
  "for_company": {
805
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
885
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
806
886
  "title": "for_company",
807
887
  "type": "string"
808
888
  }
@@ -819,7 +899,7 @@
819
899
  "openWorldHint": false,
820
900
  "readOnlyHint": false
821
901
  },
822
- "description": "Manage Account\n\nMaintain account state and impact history — the consolidated account writer.\n\nActions:\n- state: update an account's lifecycle state (id = the account's\n company_id; `payload` = {account_stage?, health_score?, arr_current?,\n renewal_at?, notes?, …}).\n- record_impact: log a value-delivered impact event on an account\n (id = company_id; `payload` = {practice, event_type, person_id?,\n value_delivered_numeric?, summary?, …}).\n- delete_impact: permanently remove an impact event (id = the impact\n EVENT/activity id, not the account). DESTRUCTIVE.\n\nBulk: id arrays loop with per-item status (partial failure reported per\nitem, never silent; max 100). Payloads are validated by the underlying\nroute's schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
902
+ "description": "Manage Account\n\nMaintain account state and impact history — the consolidated account writer.\n\nActions:\n- state: update an account's lifecycle state (id = the account's\n company_id; `payload` = {account_stage?, health_score?, arr_current?,\n renewal_at?, notes?, …}).\n- record_impact: log a value-delivered impact event on an account\n (id = company_id; `payload` = {practice, event_type, person_id?,\n value_delivered_numeric?, summary?, …}).\n- delete_impact: permanently remove an impact event (id = the impact\n EVENT/activity id, not the account). DESTRUCTIVE.\n- adopt_directory: add a shared canonical company to this tenant's CRM\n (id = directory company UUID; no payload). The global company remains\n shared and cannot be edited unless the tenant separately owns it.\n\nBulk: id arrays loop with per-item status (partial failure reported per\nitem, never silent; max 100). Payloads are validated by the underlying\nroute's schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
823
903
  "inputSchema": {
824
904
  "properties": {
825
905
  "action": {
@@ -828,7 +908,7 @@
828
908
  "type": "string"
829
909
  },
830
910
  "for_company": {
831
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
911
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
832
912
  "title": "for_company",
833
913
  "type": "string"
834
914
  },
@@ -889,7 +969,7 @@
889
969
  "openWorldHint": false,
890
970
  "readOnlyHint": false
891
971
  },
892
- "description": "Manage Campaign\n\nCreate and manage campaigns — the consolidated campaign writer.\n\nUse `search` with `type='campaigns'` to list campaigns. Use `fetch` with\n`type='campaign'` and `id=<campaign_uuid>` to read one campaign. Use this\nwriter for every campaign mutation and diagnosis.\n\nOutreach actions (default kind; id = campaign UUID):\n- create: create a campaign (no id; `payload` = the campaign fields);\n bulk via `items`.\n- clone: duplicate a campaign (`payload` = {name} for the copy).\n- update: update campaign fields — tone, cadence, targeting (`payload`).\n- delete: permanently delete a campaign. DESTRUCTIVE.\n- members: assign/unassign people (`payload` = {action: assign|unassign,\n person_ids: [...], confirm?}).\n- diagnose: run the campaign health diagnosis (read-back; no payload).\n- pause: campaign-level kill switch (no payload). Sets the campaign paused\n AND stops every active plan under it in one call — no per-plan pause\n loop. Returns {plans_paused}. `id` may be an array to pause several\n campaigns.\n- resume: clear the pause and restart ONLY the plans this campaign paused\n (reschedules them for immediate execution). Plans paused for a reply,\n OOO, or rejected draft — or paused individually — are left untouched.\n No payload; returns {plans_resumed}.\n\nAd actions (kind='ad'; id = ad-campaign UUID): approve, reject, pause,\nresume, edit_budget — `payload` carries the action's fields (e.g.\n{daily_budget_cents} for edit_budget, {rejection_reason} for reject).\nboost (no id — there's no campaign yet): run a paid LinkedIn campaign.\n`payload` carries {content_post_id (sponsor an EXISTING published post)\nXOR creative_id (a stored VIDEO creative → Direct Sponsored Content,\nMETRICS-ONLY — no engager bridge), vehicle?, objective?,\nbudget:{daily_budget_cents/total_budget_cents}, audience (see below),\nduration_days?, approval_mode?('draft'|'auto'), integration_id?}.\n\nBoost audience — exactly one of:\n- {facets}: the PRIMARY form. LinkedIn-native firmographic targeting: a\n map of facet key (titles, seniorities, industries, locations,\n staff_count_ranges) → list of LinkedIn entity URNs. 'locations' is\n REQUIRED (LinkedIn rejects campaigns without location targeting).\n Values must be entity URNs, never display names — resolve names FIRST\n via `fetch type='ads' subtype='targeting_entities'\n filters={facet, q}` (that resolve-then-boost flow is the happy path).\n- {matched_audience_id}: a PRE-EXISTING uploaded matched audience only.\n- {criteria}: partner-gated — rejected 400 with code\n matched_audience_partner_gated (LinkedIn's audience-member API needs\n partner access); the error's `fix` shows the facets form to use instead.\n\nVehicle/objective/format legality (enforced server-side BEFORE any spend;\nillegal combos 400 with the legal alternatives enumerated):\n| vehicle | objectives | source | destination CTA |\n|------------------|-------------------------------------|-------------------|------------------------|\n| thought_leader | BRAND_AWARENESS, ENGAGEMENT only | member post only | NOT allowed |\n| page_sponsored | all (BRAND_AWARENESS, ENGAGEMENT, | org post or video | required for |\n| | WEBSITE_VISIT, WEBSITE_CONVERSION, | creative_id (DSC) | WEBSITE_VISIT / |\n| | LEAD_GENERATION, VIDEO_VIEW) | | WEBSITE_CONVERSION |\nDestination = the post's external_link (set at draft/edit via\nmanage_content) or the video creative's cta_destination_url. Vehicle is\ninferred from the post's publish identity when omitted (member ⇒\nthought_leader, organization ⇒ page_sponsored). Objectives use LinkedIn's\nenum verbatim — do not lowercase.\n\nstore_creative (no id — there's no campaign yet): store a harness creative\nas a DRAFT (zero spend; approval is separate). Stored assets serve ads AND\norganic post attachments (attach via manage_content draft/edit\npayload.attachment_creative_id — VRU-726). BASE64 `payload` (small\nPNG/JPEG/PDF; format decided by server-side sniff): {asset_base64 (raw\nbase64, no data: URL — renamed from image_base64), generation_prompt\n(required for images only), filename?, campaign_id? (owned draft warming\ncampaign to attach), generation_provenance? ({model, tool, generated_at,\nnotes} allow-list, string values)}. PRESIGNED `payload` (the primary path\nfor real files — mp4 ≤200MB 3s–30min, or PDF ≤25MB): {filename,\nsize_bytes, content_type ('video/mp4' | 'application/pdf')} → returns a\npresigned upload_url; `curl --fail-with-body -T <file> '<upload_url>'`,\nthen call store_creative again with {creative_id} to validate/finalize\n(idempotent; a validation failure returns the failed row with ok=false and\nthe restart step; unfinalized rows are swept after 24h). VIDEO also\naccepts {media_url (public https — fetched + validated in the background)}\nand optional {thumbnail_base64}. `filename` becomes the stored file_name —\nfor documents it is the rendered LinkedIn carousel TITLE. media_url video\nstores are ASYNC — poll `fetch type='ads' subtype='creative'\nid=<creative_id>` until upload_status leaves 'uploading' ('failed' +\nerror_message starting with a probe code means re-export/re-store;\notherwise retriable).\n\nIdentity setup (kind='ad', no id — VRU-659):\n- set_page: `payload` = {organization_urn? (urn:li:organization:<digits>),\n integration_id?}. Omit organization_urn to DISCOVER the candidate Pages\n from the connected LinkedIn accounts, then call again with one. Required\n before page_sponsored campaigns and video ads.\n- authorize_author: `payload` = {author_user_id, auto_approve?,\n integration_id?} — records the operator ATTESTATION that this team\n member consented to Thought Leader Ad sponsorship (required before a\n thought_leader boost; LinkedIn still enforces its own permission).\n Response lists the currently authorized authors.\n- revoke_author: `payload` = {author_user_id, integration_id?}.\n\nBulk: `id` arrays loop with per-item status (partial failure is reported\nper item, never silent; max 100). Payloads are validated by the\nunderlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
972
+ "description": "Manage Campaign\n\nCreate and manage campaigns — the consolidated campaign writer.\n\nUse `search` with `type='campaigns'` to list campaigns. Use `fetch` with\n`type='campaign'` and `id=<campaign_uuid>` to read one campaign. Use this\nwriter for every campaign mutation and diagnosis.\n\nOutreach actions (default kind; id = campaign UUID):\n- create: create a campaign (no id; `payload` = the campaign fields);\n bulk via `items`.\n- clone: duplicate a campaign (`payload` = {name} for the copy).\n- update: update campaign fields — tone, cadence, targeting (`payload`).\n- delete: permanently delete a campaign. DESTRUCTIVE.\n- members: assign/unassign people (`payload` = {action: assign|unassign,\n person_ids: [...], confirm?}).\n- diagnose: run the campaign health diagnosis (read-back; no payload).\n- apply_template: render a touch's template over the campaign's\n UNAUTHORED (needs_draft) rows for that touch — deterministic\n substitution of the operator's own template prose ({{first_name}},\n {{last_name}}, {{company}}, {{title}}), never an LLM. `payload` =\n {step, cursor?}. BATCHED: processes up to 500 rows per call and\n returns {rendered, skipped (first 50), skipped_total,\n skipped_counts, total_unauthored, has_more, next_cursor}. While\n has_more is true you MUST call again with payload {step, cursor:\n <next_cursor>} — repeating without the cursor restarts from the\n beginning and skipped rows block the tail. Unrenderable rows are\n skipped with the reason and stay unauthored (missing variable\n value, channel outside the step's chain, email/InMail template\n without a subject). Set the template first via update: a\n touch_sequence step gains {\"template\": {\"subject\"?, \"body\"}}.\n- touch_asset: attach or remove one reusable asset for one touch channel.\n `payload` = {step, channel, content_base64, filename?} for an upload;\n {step, channel, connection_id, source_record_id} for a synced Google\n Drive document; or {step, channel, remove: true}. Assets are supported\n on email, linkedin_message, and linkedin_inmail only. Drive documents\n must be explicitly classified public and are snapshotted when configured,\n so later sends use the reviewed version. The campaign must be paused and\n have no pending messages before its assets can change.\n- pause: campaign-level kill switch (no payload). Sets the campaign paused\n AND stops every active plan under it in one call — no per-plan pause\n loop. Returns {plans_paused}. `id` may be an array to pause several\n campaigns.\n- resume: clear the pause and restart ONLY the plans this campaign paused\n (reschedules them for immediate execution). Plans paused for a reply,\n OOO, or rejected draft — or paused individually — are left untouched.\n No payload; returns {plans_resumed}.\n\nAd actions (kind='ad'; id = ad-campaign UUID): approve, reject, pause,\nresume, edit_budget — `payload` carries the action's fields (e.g.\n{daily_budget_cents} for edit_budget, {rejection_reason} for reject).\nboost (no id — there's no campaign yet): run a paid LinkedIn campaign.\n`payload` carries {content_post_id (sponsor an EXISTING published post)\nXOR creative_id (a stored VIDEO creative → Direct Sponsored Content,\nMETRICS-ONLY — no engager bridge), vehicle?, objective?,\nbudget:{daily_budget_cents/total_budget_cents}, audience (see below),\nduration_days?, approval_mode?('draft'|'auto'), integration_id?}.\n\nBoost audience — exactly one of:\n- {facets}: the PRIMARY form. LinkedIn-native firmographic targeting: a\n map of facet key (titles, seniorities, industries, locations,\n staff_count_ranges) → list of LinkedIn entity URNs. 'locations' is\n REQUIRED (LinkedIn rejects campaigns without location targeting).\n Values must be entity URNs, never display names — resolve names FIRST\n via `fetch type='ads' subtype='targeting_entities'\n filters={facet, q}` (that resolve-then-boost flow is the happy path).\n- {matched_audience_id}: a PRE-EXISTING uploaded matched audience only.\n- {criteria}: partner-gated — rejected 400 with code\n matched_audience_partner_gated (LinkedIn's audience-member API needs\n partner access); the error's `fix` shows the facets form to use instead.\n\nVehicle/objective/format legality (enforced server-side BEFORE any spend;\nillegal combos 400 with the legal alternatives enumerated):\n| vehicle | objectives | source | destination CTA |\n|------------------|-------------------------------------|-------------------|------------------------|\n| thought_leader | BRAND_AWARENESS, ENGAGEMENT only | member post only | NOT allowed |\n| page_sponsored | all (BRAND_AWARENESS, ENGAGEMENT, | org post or video | required for |\n| | WEBSITE_VISIT, WEBSITE_CONVERSION, | creative_id (DSC) | WEBSITE_VISIT / |\n| | LEAD_GENERATION, VIDEO_VIEW) | | WEBSITE_CONVERSION |\nDestination = the post's external_link (set at draft/edit via\nmanage_content) or the video creative's cta_destination_url. Vehicle is\ninferred from the post's publish identity when omitted (member ⇒\nthought_leader, organization ⇒ page_sponsored). Objectives use LinkedIn's\nenum verbatim — do not lowercase.\n\nstore_creative (no id — there's no campaign yet): store a harness creative\nas a DRAFT (zero spend; approval is separate). Stored assets serve ads AND\norganic post attachments (attach via manage_content draft/edit\npayload.attachment_creative_id — VRU-726). BASE64 `payload` (small\nPNG/JPEG/PDF; format decided by server-side sniff): {asset_base64 (raw\nbase64, no data: URL — renamed from image_base64), generation_prompt\n(required for images only), filename?, campaign_id? (owned draft warming\ncampaign to attach), generation_provenance? ({model, tool, generated_at,\nnotes} allow-list, string values)}. PRESIGNED `payload` (the primary path\nfor real files — mp4 ≤200MB 3s–30min, or PDF ≤25MB): {filename,\nsize_bytes, content_type ('video/mp4' | 'application/pdf')} → returns a\npresigned upload_url; `curl --fail-with-body -T <file> '<upload_url>'`,\nthen call store_creative again with {creative_id} to validate/finalize\n(idempotent; a validation failure returns the failed row with ok=false and\nthe restart step; unfinalized rows are swept after 24h). VIDEO also\naccepts {media_url (public https — fetched + validated in the background)}\nand optional {thumbnail_base64}. `filename` becomes the stored file_name —\nfor documents it is the rendered LinkedIn carousel TITLE. media_url video\nstores are ASYNC — poll `fetch type='ads' subtype='creative'\nid=<creative_id>` until upload_status leaves 'uploading' ('failed' +\nerror_message starting with a probe code means re-export/re-store;\notherwise retriable).\n\nIdentity setup (kind='ad', no id — VRU-659):\n- set_page: `payload` = {organization_urn? (urn:li:organization:<digits>),\n integration_id?}. Omit organization_urn to DISCOVER the candidate Pages\n from the connected LinkedIn accounts, then call again with one. Required\n before page_sponsored campaigns and video ads.\n- authorize_author: `payload` = {author_user_id, auto_approve?,\n integration_id?} — records the operator ATTESTATION that this team\n member consented to Thought Leader Ad sponsorship (required before a\n thought_leader boost; LinkedIn still enforces its own permission).\n Response lists the currently authorized authors.\n- revoke_author: `payload` = {author_user_id, integration_id?}.\n\nBulk: `id` arrays loop with per-item status (partial failure is reported\nper item, never silent; max 100). Payloads are validated by the\nunderlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
893
973
  "inputSchema": {
894
974
  "properties": {
895
975
  "action": {
@@ -898,7 +978,7 @@
898
978
  "type": "string"
899
979
  },
900
980
  "for_company": {
901
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
981
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
902
982
  "title": "for_company",
903
983
  "type": "string"
904
984
  },
@@ -982,7 +1062,7 @@
982
1062
  "type": "string"
983
1063
  },
984
1064
  "for_company": {
985
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1065
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
986
1066
  "title": "for_company",
987
1067
  "type": "string"
988
1068
  },
@@ -1052,7 +1132,7 @@
1052
1132
  "type": "string"
1053
1133
  },
1054
1134
  "for_company": {
1055
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1135
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1056
1136
  "title": "for_company",
1057
1137
  "type": "string"
1058
1138
  },
@@ -1122,7 +1202,7 @@
1122
1202
  "type": "string"
1123
1203
  },
1124
1204
  "for_company": {
1125
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1205
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1126
1206
  "title": "for_company",
1127
1207
  "type": "string"
1128
1208
  },
@@ -1192,7 +1272,7 @@
1192
1272
  "type": "string"
1193
1273
  },
1194
1274
  "for_company": {
1195
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1275
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1196
1276
  "title": "for_company",
1197
1277
  "type": "string"
1198
1278
  },
@@ -1262,7 +1342,7 @@
1262
1342
  "type": "string"
1263
1343
  },
1264
1344
  "for_company": {
1265
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1345
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1266
1346
  "title": "for_company",
1267
1347
  "type": "string"
1268
1348
  },
@@ -1323,7 +1403,188 @@
1323
1403
  "openWorldHint": false,
1324
1404
  "readOnlyHint": false
1325
1405
  },
1326
- "description": "Manage Outreach\n\nStart and steer outreach plans — the consolidated outreach writer.\n\nActions:\n- start: enroll a person in outreach. id = PERSON UUID (scalar starts\n one plan; an id ARRAY routes to the native bulk-start endpoint).\n `payload` optional: {max_touches?, allowed_channels?} — applied to\n every started plan. Enrollment always schedules the first action\n immediately; a `start_immediately` key is ignored (forced true).\n- plan_override: record a user override on a plan (id = plan UUID;\n `payload` = the override fields).\n- channels: update a plan's allowed channels (id = plan UUID; `payload`\n = {allowed_channels, …}).\n- trigger_touch: queue the plan's next touch now (id = plan UUID; no\n payload).\n- pause / resume: pause or resume a plan (id = plan UUID; `payload`\n is not needed).\n- update: update an active or paused plan without changing its execution\n state (id = plan UUID; `payload` = {max_touches?, allowed_channels?}).\n Use an id ARRAY to repair a campaign cohort consistently.\n\nBulk: `start` arrays hit the native bulk endpoint; pause/resume/\nplan_override/channels/trigger_touch arrays loop with per-item status —\npartial failure is reported per item, never silent (max 100). Payloads\nare validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1406
+ "description": "Manage Network Identity Resolution\n\nPreview or apply one durable identity-resolution decision.\n\n``commit=false`` is mandatory preview mode and performs no write. Re-submit\nthe reviewed action with ``commit=true`` to merge prospect rows, merge one\nowned source identity, add/remove a company affiliation, or classify an\nemail mailbox. Decisions are tenant-scoped, attributed to the caller, and\nsurvive future syncs. Required active-projection rebuilds run asynchronously.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"status\": \"Status\",\n \"change\": {},\n \"next_actions\": [\n \"string\"\n ]\n}\n```",
1407
+ "inputSchema": {
1408
+ "properties": {
1409
+ "action": {
1410
+ "anyOf": [
1411
+ {
1412
+ "const": "merge_identity",
1413
+ "title": "Action",
1414
+ "type": "string"
1415
+ },
1416
+ {
1417
+ "const": "merge_people",
1418
+ "title": "Action",
1419
+ "type": "string"
1420
+ },
1421
+ {
1422
+ "const": "review_people",
1423
+ "title": "Action",
1424
+ "type": "string"
1425
+ },
1426
+ {
1427
+ "const": "set_affiliation",
1428
+ "title": "Action",
1429
+ "type": "string"
1430
+ },
1431
+ {
1432
+ "const": "classify_mailbox",
1433
+ "title": "Action",
1434
+ "type": "string"
1435
+ }
1436
+ ],
1437
+ "title": "action",
1438
+ "type": "string"
1439
+ },
1440
+ "affiliation_kind": {
1441
+ "default": "employment",
1442
+ "enum": [
1443
+ "employment",
1444
+ "advisory",
1445
+ "membership",
1446
+ "other"
1447
+ ],
1448
+ "title": "affiliation_kind",
1449
+ "type": "string"
1450
+ },
1451
+ "basis": {
1452
+ "default": "manual",
1453
+ "enum": [
1454
+ "manual",
1455
+ "email_domain",
1456
+ "source_claim",
1457
+ "company_name"
1458
+ ],
1459
+ "title": "basis",
1460
+ "type": "string"
1461
+ },
1462
+ "classification": {
1463
+ "enum": [
1464
+ "person",
1465
+ "shared_mailbox",
1466
+ "automated"
1467
+ ],
1468
+ "title": "classification",
1469
+ "type": "string"
1470
+ },
1471
+ "commit": {
1472
+ "default": false,
1473
+ "title": "commit",
1474
+ "type": "boolean"
1475
+ },
1476
+ "company_ref": {
1477
+ "maxLength": 100,
1478
+ "minLength": 1,
1479
+ "title": "company_ref",
1480
+ "type": "string"
1481
+ },
1482
+ "decision": {
1483
+ "anyOf": [
1484
+ {
1485
+ "const": "not_duplicate",
1486
+ "title": "Decision",
1487
+ "type": "string"
1488
+ },
1489
+ {
1490
+ "default": "add",
1491
+ "enum": [
1492
+ "add",
1493
+ "remove"
1494
+ ],
1495
+ "title": "Decision",
1496
+ "type": "string"
1497
+ }
1498
+ ],
1499
+ "title": "decision",
1500
+ "type": "string"
1501
+ },
1502
+ "for_company": {
1503
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1504
+ "title": "for_company",
1505
+ "type": "string"
1506
+ },
1507
+ "reason": {
1508
+ "maxLength": 500,
1509
+ "minLength": 3,
1510
+ "title": "reason",
1511
+ "type": "string"
1512
+ },
1513
+ "source_identity_ref": {
1514
+ "maxLength": 100,
1515
+ "minLength": 1,
1516
+ "title": "source_identity_ref",
1517
+ "type": "string"
1518
+ },
1519
+ "source_person_ref": {
1520
+ "maxLength": 100,
1521
+ "minLength": 1,
1522
+ "title": "source_person_ref",
1523
+ "type": "string"
1524
+ },
1525
+ "surviving_person_ref": {
1526
+ "maxLength": 100,
1527
+ "minLength": 1,
1528
+ "title": "surviving_person_ref",
1529
+ "type": "string"
1530
+ },
1531
+ "target_entity_ref": {
1532
+ "anyOf": [
1533
+ {
1534
+ "maxLength": 100,
1535
+ "type": "string"
1536
+ },
1537
+ {
1538
+ "type": "null"
1539
+ }
1540
+ ],
1541
+ "title": "target_entity_ref",
1542
+ "type": "string"
1543
+ },
1544
+ "target_person_ref": {
1545
+ "anyOf": [
1546
+ {
1547
+ "maxLength": 100,
1548
+ "type": "string"
1549
+ },
1550
+ {
1551
+ "type": "null"
1552
+ }
1553
+ ],
1554
+ "title": "target_person_ref",
1555
+ "type": "string"
1556
+ },
1557
+ "title": {
1558
+ "anyOf": [
1559
+ {
1560
+ "maxLength": 200,
1561
+ "type": "string"
1562
+ },
1563
+ {
1564
+ "type": "null"
1565
+ }
1566
+ ],
1567
+ "title": "title",
1568
+ "type": "string"
1569
+ }
1570
+ },
1571
+ "required": [
1572
+ "action",
1573
+ "reason"
1574
+ ],
1575
+ "title": "manage_network_identity_resolutionArguments",
1576
+ "type": "object"
1577
+ },
1578
+ "name": "manage_network_identity_resolution",
1579
+ "title": "Manage Network Identity Resolution"
1580
+ },
1581
+ {
1582
+ "annotations": {
1583
+ "destructiveHint": false,
1584
+ "openWorldHint": false,
1585
+ "readOnlyHint": false
1586
+ },
1587
+ "description": "Manage Outreach\n\nStart and steer outreach plans — the consolidated outreach writer.\n\nActions:\n- start: enroll a person in outreach. id = PERSON UUID (scalar starts\n one plan; an id ARRAY routes to the native bulk-start endpoint).\n `payload` optional: {max_touches?, allowed_channels?} — applied to\n every started plan. Enrollment always schedules the first action\n immediately; a `start_immediately` key is ignored (forced true).\n For researched/discovered prospects, call manage_person action=save_discovered\n first with a strong current-company binding and\n start only when its response has `company_bound=true` and the quality\n gates passed. Never use start to bypass an unresolved or name-only\n company save.\n- plan_override: record a user override on a plan (id = plan UUID;\n `payload` = the override fields).\n- channels: update a plan's allowed channels (id = plan UUID; `payload`\n = {allowed_channels, …}).\n- trigger_touch: queue the plan's next touch now (id = plan UUID; no\n payload). Refused with a structured 409 when the plan's pending\n approved email cannot send now: `email_daily_cap_reached` (the\n conversation's owning mailbox — or, for a first email, the sender\n pool — is at its daily cap; the error carries the capacity numbers,\n when capacity resets, and when the plan retries automatically) or\n `thread_mailbox_unavailable` (the owning mailbox is disconnected,\n resting, or excluded — reconnect it; another mailbox is never\n substituted into an existing conversation). A success here means the\n touch is genuinely queued, not that it may silently re-defer.\n- pause / resume: pause or resume a plan (id = plan UUID; `payload`\n is not needed).\n- update: update an active or paused plan without changing its execution\n state (id = plan UUID; `payload` = {max_touches?, allowed_channels?}).\n Use an id ARRAY to repair a campaign cohort consistently.\n\nBulk: `start` arrays hit the native bulk endpoint; pause/resume/\nplan_override/channels/trigger_touch arrays loop with per-item status —\npartial failure is reported per item, never silent (max 100). Payloads\nare validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1327
1588
  "inputSchema": {
1328
1589
  "properties": {
1329
1590
  "action": {
@@ -1332,7 +1593,7 @@
1332
1593
  "type": "string"
1333
1594
  },
1334
1595
  "for_company": {
1335
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1596
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1336
1597
  "title": "for_company",
1337
1598
  "type": "string"
1338
1599
  },
@@ -1393,7 +1654,7 @@
1393
1654
  "openWorldHint": false,
1394
1655
  "readOnlyHint": false
1395
1656
  },
1396
- "description": "Manage Person\n\nCreate and manage people in your pipeline — the consolidated person writer.\n\nActions (id = person UUID unless noted):\n- create: add a person manually (no id; `payload` = the manual-person\n fields, e.g. {name, company, title, email, linkedin_url, skip_research});\n bulk via `items` (one payload per person).\n- update_contact: override contact fields (`payload` = contact overrides).\n- set_position: set the primary position (`payload` = position fields).\n- note: add a timeline note (`payload` = {body}).\n- log_interaction: log a manual touch that happened outside an automated\n sequence — a call (with disposition + what was said), or an\n email/LinkedIn/other touch made elsewhere. `payload` =\n {interaction_kind: call|email|linkedin|meeting|other, direction?:\n outbound|inbound, summary?, disposition? (calls only), occurred_at?\n (ISO, backdatable), deal_id?}. Lands on the person's timeline attributed\n to you (performed_by_user_id).\n- archive: soft-delete one or many people — stops outreach plans and\n rejects pending drafts (id or id array; no payload). DESTRUCTIVE.\n- unarchive: restore archived people (id or id array; no payload).\n- meeting_booked: mark a meeting booked with this person (`payload` =\n {idempotency_key: UUID}; reuse it when retrying the same booking).\n- save_discovered: THE creator — save a researched prospect into the\n pipeline, atomically (no id; bulk via `items`). Two shapes:\n NEW prospect: `payload` = {person: {first_name, last_name,\n linkedin_url|email, company_id (from save_company) or\n company_name+anchor, ...research fields}, assessment: {match_score\n 0-100, match_summary, alignment_points?, concerns?, why_now?,\n recommended_approach?, overall_confidence?, scored_by?}, campaign_id or\n assessment_campaign_id}. Person + research + pipeline membership land in\n ONE transaction — a rejected or failed save persists nothing.\n EXISTING person: `payload` = {person_id, campaign_id?,\n assessment_campaign_id?, assessment?} — the assessment is applied\n update-in-place (THE path to score an existing stub). Pass exactly one\n of `person` / `person_id`. Use assessment_campaign_id to preserve\n campaign-scoring provenance without assigning the person. Your\n assessment is AUTHORITATIVE: the backend records it with provenance and\n never re-scores it. Backend floor stays mechanical: dedupe first\n (anchors resolving to an existing person continue as a duplicate\n update); new rows require a LinkedIn URL or email and stay within the\n per-tenant discovery-volume cap; the `person` shape requires\n `assessment` + a campaign ref (did no fit analysis? use action=create\n instead). Duplicates do not consume or re-check the new-row cap.\n- set_persona: set a contact's buying-role persona — economic_buyer /\n decision_maker / influencer / unknown (`payload` = {persona,\n reasoning?}). The harness classifies buying role at touch time and\n writes it here; there is no backend classifier.\n\nBulk: `id` arrays apply the same action to each person (archive/unarchive\nroute to the native bulk endpoints; others loop with per-item status —\npartial failure is reported per item, never silent). `items` is for\ncreate-like actions, max 100 per call. Payloads are validated by the\nunderlying route's own schema, so a bad field returns that route's\nprecise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1657
+ "description": "Manage Person\n\nCreate and manage people in your pipeline — the consolidated person writer.\n\nActions (id = person UUID unless noted):\n- create: add a person manually (no id; `payload` = the manual-person\n fields, e.g. {name, company, title, email, linkedin_url, skip_research});\n bulk via `items` (one payload per person).\n- update_contact: override contact fields (`payload` = contact overrides).\n- set_position: set the primary position (`payload` = position fields).\n- note: add a timeline note (`payload` = {body}).\n- log_interaction: log a manual touch that happened outside an automated\n sequence — a call (with disposition + what was said), or an\n email/LinkedIn/other touch made elsewhere. `payload` =\n {interaction_kind: call|email|linkedin|meeting|other, direction?:\n outbound|inbound, summary?, disposition? (calls only), occurred_at?\n (ISO, backdatable), deal_id?}. Lands on the person's timeline attributed\n to you (performed_by_user_id).\n- archive: soft-delete one or many people — stops outreach plans and\n rejects pending drafts (id or id array; no payload). DESTRUCTIVE.\n- unarchive: restore archived people (id or id array; no payload).\n- meeting_booked: mark a meeting booked with this person (`payload` =\n {idempotency_key: UUID}; reuse it when retrying the same booking).\n- save_discovered: THE creator — save a researched prospect into the\n pipeline, atomically (no id; bulk via `items`). Two shapes:\n NEW prospect: `payload` = {person: {first_name, last_name,\n linkedin_url|email, company_id (from research action=save_company) or\n company_name plus company_domain/company_website/company_linkedin_url,\n ...research fields}, assessment: {match_score\n 0-100, match_summary, alignment_points?, concerns?, why_now?,\n recommended_approach?, overall_confidence?, scored_by?}, campaign_id or\n assessment_campaign_id}. Person + research + pipeline membership land in\n ONE transaction — a rejected or failed save persists nothing.\n EXISTING person: `payload` = {person_id, company_id, campaign_id?,\n assessment_campaign_id?, assessment?}; if company_id is unavailable,\n pass company_name plus at least one top-level company_domain,\n company_website, or company_linkedin_url. Never send a bare person_id:\n the binding atomically adds/promotes the current position and pins the\n pipeline membership while the assessment is applied update-in-place\n (THE path to score an existing stub). Pass exactly one of `person` /\n `person_id`. Use assessment_campaign_id to preserve\n campaign-scoring provenance without assigning the person. Your\n assessment is AUTHORITATIVE: the backend records it with provenance and\n never re-scores it. Backend floor stays mechanical: dedupe first\n (anchors resolving to an existing person continue as a duplicate\n update); new rows require a LinkedIn URL or email and stay within the\n per-tenant discovery-volume cap; the `person` shape requires\n `assessment` + a campaign ref (did no fit analysis? use action=create\n instead). Duplicates do not consume or re-check the new-row cap. The\n response includes `company_id` and `company_bound`; require\n `company_bound=true` before treating the save as company-complete or\n calling manage_outreach action=start. If false, do not enroll—repair the\n company binding and retry save_discovered.\n- set_persona: set a contact's buying-role persona — economic_buyer /\n decision_maker / influencer / unknown (`payload` = {persona,\n reasoning?}). The harness classifies buying role at touch time and\n writes it here; there is no backend classifier.\n- adopt_directory: add an existing shared canonical person to this tenant's\n CRM (id = directory person UUID; no payload). This copies no tenant-private\n notes, activity, campaign, score, or outreach state. It is free for the\n tenant.\n\nBulk: `id` arrays apply the same action to each person (archive/unarchive\nroute to the native bulk endpoints; others loop with per-item status —\npartial failure is reported per item, never silent). `items` is for\ncreate-like actions, max 100 per call. Payloads are validated by the\nunderlying route's own schema, so a bad field returns that route's\nprecise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1397
1658
  "inputSchema": {
1398
1659
  "properties": {
1399
1660
  "action": {
@@ -1402,7 +1663,7 @@
1402
1663
  "type": "string"
1403
1664
  },
1404
1665
  "for_company": {
1405
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1666
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1406
1667
  "title": "for_company",
1407
1668
  "type": "string"
1408
1669
  },
@@ -1466,12 +1727,176 @@
1466
1727
  "description": "Manage Relationship Action\n\nDraft, approve, and record the outcome of ONE warm-introduction attempt.\n\nA \"relationship action\" is a single attempt to reach a target person\n*through a mutual connection* rather than by contacting them directly:\nasking a colleague for an intro, checking whether a connector actually\nknows them, or requesting an entry into their company. `find_warm_path`\ndiscovers those routes and never writes; this tool is the only way to act\non one.\n\n**Use this instead of the direct-outreach tools when the path runs through\na person.** `manage_outreach` and `manage_messages` send to the target\nthemselves; `manage_engagements` handles reactions and comments on\ncontent. This tool messages the *connector*.\n\n**Nothing here auto-sends.** Every attempt walks a state machine you drive\none call at a time: `draft` creates it from a `result_ref` returned by\n`find_warm_path`, then transitions approve the exact message text, confirm\nthat you sent it yourself outside Vruum, classify how the connector\nreplied, record the final outcome, or cancel. The response's\n`approval_required` and `next_actions` fields tell you which transitions\nare legal right now — read them rather than guessing.\n\nTwo consistency rules the server enforces:\n\n- **`draft` is idempotent on `client_request_id`.** Reuse the same value\n when retrying a draft; a new value creates a second attempt.\n- **Every transition needs `expected_version`.** A stale version means\n someone else moved the attempt and yours is rejected rather than\n silently overwriting — re-read the attempt and retry.\n\nA `409 route_stale` on draft means the underlying relationship evidence\nchanged since it was reviewed; re-run `find_warm_path` and draft from a\nfresh `result_ref`.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"status\": \"Status\",\n \"attempt\": {\n \"ref\": \"Ref\",\n \"relationship_owner_user_id\": \"Relationship Owner User Id\",\n \"action_type\": \"Action Type\",\n \"target_ref\": \"Target Ref\",\n \"target_snapshot\": {},\n \"result_ref\": \"Result Ref\",\n \"result_type\": \"Result Type\",\n \"channel\": \"Channel\",\n \"client_request_id\": \"Client Request Id\",\n \"route_fingerprint\": \"Route Fingerprint\",\n \"message_text\": \"Message Text\",\n \"state\": \"State\",\n \"version\": 1,\n \"created_at\": \"Created At\",\n \"updated_at\": \"Updated At\"\n },\n \"approval_required\": true,\n \"next_actions\": [\n \"string\"\n ],\n \"web_url\": \"Web Url\"\n}\n```",
1467
1728
  "inputSchema": {
1468
1729
  "properties": {
1730
+ "action": {
1731
+ "anyOf": [
1732
+ {
1733
+ "const": "draft",
1734
+ "title": "Action",
1735
+ "type": "string"
1736
+ },
1737
+ {
1738
+ "const": "edit_draft",
1739
+ "title": "Action",
1740
+ "type": "string"
1741
+ },
1742
+ {
1743
+ "const": "approve",
1744
+ "title": "Action",
1745
+ "type": "string"
1746
+ },
1747
+ {
1748
+ "const": "confirm_external",
1749
+ "title": "Action",
1750
+ "type": "string"
1751
+ },
1752
+ {
1753
+ "const": "classify_response",
1754
+ "title": "Action",
1755
+ "type": "string"
1756
+ },
1757
+ {
1758
+ "const": "record_outcome",
1759
+ "title": "Action",
1760
+ "type": "string"
1761
+ },
1762
+ {
1763
+ "const": "cancel",
1764
+ "title": "Action",
1765
+ "type": "string"
1766
+ }
1767
+ ],
1768
+ "title": "action",
1769
+ "type": "string"
1770
+ },
1771
+ "attempt_ref": {
1772
+ "maxLength": 500,
1773
+ "minLength": 1,
1774
+ "title": "attempt_ref",
1775
+ "type": "string"
1776
+ },
1777
+ "channel": {
1778
+ "default": "external",
1779
+ "enum": [
1780
+ "email",
1781
+ "linkedin",
1782
+ "external"
1783
+ ],
1784
+ "title": "channel",
1785
+ "type": "string"
1786
+ },
1787
+ "client_request_id": {
1788
+ "maxLength": 36,
1789
+ "minLength": 36,
1790
+ "title": "client_request_id",
1791
+ "type": "string"
1792
+ },
1793
+ "expected_version": {
1794
+ "minimum": 1.0,
1795
+ "title": "expected_version",
1796
+ "type": "integer"
1797
+ },
1469
1798
  "for_company": {
1470
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1799
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1471
1800
  "title": "for_company",
1472
1801
  "type": "string"
1802
+ },
1803
+ "message_text": {
1804
+ "anyOf": [
1805
+ {
1806
+ "maxLength": 5000,
1807
+ "type": "string"
1808
+ },
1809
+ {
1810
+ "type": "null"
1811
+ },
1812
+ {
1813
+ "maxLength": 5000,
1814
+ "minLength": 1,
1815
+ "title": "Message Text",
1816
+ "type": "string"
1817
+ }
1818
+ ],
1819
+ "title": "message_text",
1820
+ "type": "string"
1821
+ },
1822
+ "outcome": {
1823
+ "additionalProperties": false,
1824
+ "properties": {
1825
+ "note": {
1826
+ "anyOf": [
1827
+ {
1828
+ "maxLength": 500,
1829
+ "type": "string"
1830
+ },
1831
+ {
1832
+ "type": "null"
1833
+ }
1834
+ ],
1835
+ "title": "Note"
1836
+ },
1837
+ "status": {
1838
+ "enum": [
1839
+ "conversation_booked",
1840
+ "routed_to_right_person",
1841
+ "introduction_made",
1842
+ "not_interested",
1843
+ "other"
1844
+ ],
1845
+ "title": "Status",
1846
+ "type": "string"
1847
+ }
1848
+ },
1849
+ "required": [
1850
+ "status"
1851
+ ],
1852
+ "title": "outcome",
1853
+ "type": "object"
1854
+ },
1855
+ "purpose": {
1856
+ "anyOf": [
1857
+ {
1858
+ "maxLength": 500,
1859
+ "type": "string"
1860
+ },
1861
+ {
1862
+ "type": "null"
1863
+ }
1864
+ ],
1865
+ "title": "purpose",
1866
+ "type": "string"
1867
+ },
1868
+ "response": {
1869
+ "enum": [
1870
+ "can_intro",
1871
+ "knows_not_comfortable",
1872
+ "knows_not_well",
1873
+ "does_not_know",
1874
+ "needs_context",
1875
+ "no_response"
1876
+ ],
1877
+ "title": "response",
1878
+ "type": "string"
1879
+ },
1880
+ "result_ref": {
1881
+ "maxLength": 500,
1882
+ "minLength": 1,
1883
+ "title": "result_ref",
1884
+ "type": "string"
1885
+ },
1886
+ "sent": {
1887
+ "title": "sent",
1888
+ "type": "boolean"
1889
+ },
1890
+ "target": {
1891
+ "maxLength": 500,
1892
+ "minLength": 1,
1893
+ "title": "target",
1894
+ "type": "string"
1473
1895
  }
1474
1896
  },
1897
+ "required": [
1898
+ "action"
1899
+ ],
1475
1900
  "title": "manage_relationship_actionArguments",
1476
1901
  "type": "object"
1477
1902
  },
@@ -1493,7 +1918,7 @@
1493
1918
  "type": "string"
1494
1919
  },
1495
1920
  "for_company": {
1496
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1921
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1497
1922
  "title": "for_company",
1498
1923
  "type": "string"
1499
1924
  },
@@ -1554,7 +1979,7 @@
1554
1979
  "openWorldHint": false,
1555
1980
  "readOnlyHint": false
1556
1981
  },
1557
- "description": "Manage Settings\n\nUpdate company configuration — the consolidated settings writer.\n\nActions (no ids — settings are singletons per company):\n- profile: update the company profile / ICP (`payload` = the profile\n fields; this steers research match scoring).\n- auto_fill: AI-fill the company profile from the website (`payload`\n optional: {website_url?}; returns a job to poll via\n fetch type='job').\n- automation: update outreach automation settings (`payload` = the\n automation fields).\n- marketing: update LinkedIn marketing settings (`payload` = the\n marketing fields).\n- cta_link: manage call-to-action links (`payload` = {action:\n create|update|delete, cta_link_id?, name?, url?, cta_type?,\n is_active?}).\n- hubspot_sync_settings: set the custom HubSpot contact properties to\n mirror into company_people.custom_fields (`payload` =\n {extra_contact_properties: [...]}); CHANGING the list auto-enqueues a\n full backfill so existing contacts pick up the new attributes.\n- hubspot_backfill: kick a full HubSpot re-pull for this company (owners\n → companies → pipelines → contacts → deals → lists → engagements). No\n payload. Heavy (hours at 250k+ contacts). The recovery path when\n contacts were imported before their custom properties were configured.\n Operators target a client with `for_company`.\n\nPayloads are validated by the underlying route's own schema, so a bad\nfield returns that route's precise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1982
+ "description": "Manage Settings\n\nUpdate company configuration — the consolidated settings writer.\n\nActions (no ids — settings are singletons per company):\n- profile: update the company profile / ICP (`payload` = the profile\n fields; this steers research match scoring). If supplied, `industry`\n must be one CanonicalIndustry value: Agriculture, Construction,\n Consumer Goods, Consumer Services, Education, Energy & Utilities,\n Financial Services, Government, Healthcare, Hospitality & Travel,\n Legal Services, Manufacturing, Marketing & Advertising,\n Media & Entertainment, Nonprofit, Professional Services, Real Estate,\n Retail & E-commerce, Software & Technology, Staffing & Recruiting,\n Telecommunications, Transportation & Logistics, Wholesale & Distribution,\n or Other. Provider labels and Unspecified are rejected.\n- auto_fill: AI-fill the company profile from the website (`payload`\n optional: {website_url?}; returns a job to poll via\n fetch type='job').\n- automation: update outreach automation settings (`payload` = the\n automation fields).\n- marketing: update LinkedIn marketing settings (`payload` = the\n marketing fields).\n- cta_link: manage call-to-action links (`payload` = {action:\n create|update|delete, cta_link_id?, name?, url?, cta_type?,\n is_active?}).\n- hubspot_sync_settings: set the custom HubSpot contact properties to\n mirror into company_people.custom_fields (`payload` =\n {extra_contact_properties: [...]}); CHANGING the list auto-enqueues a\n full backfill so existing contacts pick up the new attributes.\n- hubspot_backfill: kick a full HubSpot re-pull for this company (owners\n → companies → pipelines → contacts → deals → lists → engagements). No\n payload. Heavy (hours at 250k+ contacts). The recovery path when\n contacts were imported before their custom properties were configured.\n Operators target a client with `for_company`.\n- campaign_access_grant: let a member manage one sender's campaigns\n (`payload` = {sender_user_id, delegate_user_id}). The sender, owner, or\n operator can grant access.\n- campaign_access_revoke: revoke that access (`id` = delegation UUID).\n The sender, owner, or operator can revoke access.\n\nPayloads are validated by the underlying route's own schema, so a bad\nfield returns that route's precise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1558
1983
  "inputSchema": {
1559
1984
  "properties": {
1560
1985
  "action": {
@@ -1563,7 +1988,7 @@
1563
1988
  "type": "string"
1564
1989
  },
1565
1990
  "for_company": {
1566
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1991
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1567
1992
  "title": "for_company",
1568
1993
  "type": "string"
1569
1994
  },
@@ -1624,7 +2049,7 @@
1624
2049
  "openWorldHint": true,
1625
2050
  "readOnlyHint": false
1626
2051
  },
1627
- "description": "Research\n\nRun prospect/company research and save the results — the consolidated research tool.\n\nLookup actions (external providers — results are returned, not stored,\nunless noted):\n- find_linkedin: locate a person's LinkedIn URL (id = person UUID;\n `payload` optional {name_override?, company_override?, auto_save?} —\n auto_save stores a high-confidence match).\n- enrich_company: scrape a company website (`payload` = {domain,\n page_type?}).\n- find_email: find a work email (`payload` = {first_name, last_name,\n domain}).\n- linkedin_fetch: pull a LinkedIn profile (`payload` = {linkedin_url,\n include_company?, include_posts?, posts_limit?}).\n- careers_parse: parse a careers page for hiring signals (`payload` =\n {domain}).\n\nSave actions (write research rows):\n- save_person: UPDATE research on an EXISTING person — pass the person UUID\n as `payload.person_id` (preferred; refreshes researched_at and the\n research fields in place), or matching email/linkedin anchors. Cannot\n create: new prospects go through manage_person action=save_discovered\n with `payload.person` + `payload.assessment`. Never pass the person UUID\n as `id` — this action takes no id.\n- save_company: atomically patch sourced public company research. Pass\n `payload={idempotency_key, name, person_id?, website?, linkedin_url?,\n company_summary?, company_stage?, current_priorities?, funding_data?,\n growth_metrics?, sources_by_field}`. Omitted fields are preserved;\n explicit null clears. When researching a specific person's employer,\n ALWAYS pass their UUID as `payload.person_id` — the response's\n `person_link` confirms the write landed on that person's linked company\n (repointing an anchorless duplicate link when needed); a `mismatch`\n status means the person's touches will NOT see this research, and a\n `conflict` status is a transient race — replay the identical payload\n (person_id is exempt from the idempotency hash, so adding it to a\n replay is the supported repair).\n `sources_by_field` must contain exactly every\n supplied non-null research field, with entries\n `{url, title?, observed_at}`. Example:\n `{\"idempotency_key\":\"pipeline/example.com/save-v1\",\"name\":\"Example\",\n \"website\":\"https://example.com\",\"company_summary\":\"Example sells …\",\n \"sources_by_field\":{\"company_summary\":[{\"url\":\"https://example.com/about\",\n \"title\":\"About\",\"observed_at\":\"2026-07-30T20:00:00Z\"}]}}`.\n\nBatch research: every action accepts the facade's backward-compatible\nrequest cap of 100 with per-item results (partial failure is reported per\nitem, never silent). Operators should execute the smaller policy waves\ndocumented by pipeline-fill (10 companies or 5 people at a time) so\nprogress and retries stay bounded. find_linkedin also takes person-id\narrays. Payloads are validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
2052
+ "description": "Research\n\nRun prospect/company research and save the results — the consolidated research tool.\n\nLookup actions (results are returned, not stored unless noted):\n- find_linkedin: locate a person's LinkedIn URL (id = person UUID;\n `payload` optional {name_override?, company_override?, auto_save?} —\n auto_save stores a high-confidence match).\n- enrich_account: acquire a bounded first-party evidence bundle for a\n tenant-visible account (id = canonical company UUID). The response also\n carries canonical company fields, cached shared research, and freshness\n metadata for the harness to combine with its own web and MCP sources.\n Persist sourced conclusions with save_company. The legacy enrich_company\n action remains the domain/page website-fetch primitive.\n- find_email: find a work email (`payload` = {first_name, last_name,\n domain}).\n- linkedin_fetch: pull a LinkedIn profile (`payload` = {linkedin_url,\n include_company?, include_posts?, posts_limit?}).\n- enrich_person: enrich each person according to missing contact and profile\n fields (id = person UUID, or an array of up to 100;\n `payload` optional {force_refresh?}). Returns a job_id; poll it with\n fetch type=job. Outcomes report a stable reason code, and jobs with any\n errored outcome terminate as completed_with_failures. Successful outcomes\n report readiness=email_ready, incomplete, or manual_review plus the\n canonical employer binding.\n Vruum applies separate monthly profile and Hunter-credit soft limits.\n Harness web search and other MCP tools remain complementary for deeper\n research. The legacy enrich_profile name is an alias.\n- enrich: canonical scoped enrichment. It inspects each person and runs\n only the needed profile or verified-email work. Pass `payload` with exactly one of\n `{person_ids: [...]}`, `{campaign_id: ...}`, or `{list_id: ...}`.\n Returns a job_id; poll it with fetch type=job. The enrich_emails action\n remains as a compatibility alias for the same scoped planner.\n- careers_parse: parse a careers page for hiring signals (`payload` =\n {domain}).\n\nSave actions (write research rows):\n- save_person: UPDATE research on an EXISTING person — pass the person UUID\n as `payload.person_id` (preferred; refreshes researched_at and the\n research fields in place), or matching email/linkedin anchors. Cannot\n create: new prospects go through manage_person action=save_discovered\n with `payload.person` + `payload.assessment`. Never pass the person UUID\n as `id` — this action takes no id.\n- save_company: atomically patch sourced public company research. Pass\n `payload={idempotency_key, name, industry, person_id?, website?, linkedin_url?,\n company_summary?, company_stage?, current_priorities?, funding_data?,\n growth_metrics?, sources_by_field}`.\n `industry` is required and must be one CanonicalIndustry value:\n Agriculture, Construction, Consumer Goods, Consumer Services, Education,\n Energy & Utilities, Financial Services, Government, Healthcare,\n Hospitality & Travel, Legal Services, Manufacturing, Marketing & Advertising,\n Media & Entertainment, Nonprofit, Professional Services, Real Estate,\n Retail & E-commerce, Software & Technology, Staffing & Recruiting,\n Telecommunications, Transportation & Logistics, Wholesale & Distribution,\n or Other. Provider labels and Unspecified are rejected.\n Omitted research fields are preserved; explicit null clears. When\n researching a specific person's employer,\n ALWAYS pass their UUID as `payload.person_id` — the response's\n `person_link` confirms the write landed on that person's linked company\n (repointing an anchorless duplicate link when needed); a `mismatch`\n status means the person's touches will NOT see this research, and a\n `conflict` status is a transient race — replay the identical payload\n (person_id is exempt from the idempotency hash, so adding it to a\n replay is the supported repair).\n For a researched-prospect save, save_company is also the company identity resolution step.\n Reuse a trustworthy Vruum company_id when already known; otherwise call\n save_company using name plus website and/or linkedin_url. Call it even\n with no new research fields, and capture the returned company_id for\n manage_person action=save_discovered. A company name alone is not a save identity.\n `sources_by_field` must contain exactly every\n supplied non-null research field, with entries\n `{url, title?, observed_at}`. Example:\n `{\"idempotency_key\":\"pipeline/example.com/save-v1\",\"name\":\"Example\",\n \"industry\":\"Software & Technology\",\"website\":\"https://example.com\",\n \"company_summary\":\"Example sells …\",\n \"sources_by_field\":{\"company_summary\":[{\"url\":\"https://example.com/about\",\n \"title\":\"About\",\"observed_at\":\"2026-07-30T20:00:00Z\"}]}}`.\n\nHypothesis-ledger actions (VRU-840 — beliefs with scoreboards, never prose):\n- save_claim: record one falsifiable belief. `payload` = {statement,\n scope ({person_id|company_id|deal_id|campaign_id|segment|tenant}),\n origin (diagnostic|system|operator|pin|experiment),\n invalidating_conditions?: [..], evidence?: [{kind, payload, source,\n method?, staleness_class (durable|slow|fast|volatile),\n trust? (first_party|untrusted_capture|third_party), stance?\n (supports|contradicts), weight?, person_id?|company_id?|\n deal_id?|campaign_ref?|tenant_scoped? (accounts are the tenant-company\n pair — link company_id)}]}. Confidence is derived from the\n linked evidence and cannot be asserted; identical evidence payloads\n dedupe. Returns the claim with its full scoreboard. Use `items` to save\n a batch (e.g. a positioning diagnostic's claim set).\n- save_evidence: mint one observation with provenance, optionally linking\n it to an existing claim (`payload.claim_id` + stance/weight).\n- record_claim_verdict: append a verdict (id = claim UUID; `payload` =\n {status, basis, confidence?, note?, experiment_id?}). basis='causal'\n requires experiment_id — the laboratory honesty rule.\nRead claims with search type='claims' and fetch type='claim'.\n\nBatch research returns per-item results (partial failure is reported per\nitem, never silent). The facade's backward-compatible request cap is 100;\nenrich_account accepts up to 10 accounts per request. Operators should execute the smaller policy waves\ndocumented by pipeline-fill (10 companies or 5 people at a time) so\nprogress and retries stay bounded. find_linkedin also takes person-id\narrays. Payloads are validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1628
2053
  "inputSchema": {
1629
2054
  "properties": {
1630
2055
  "action": {
@@ -1633,7 +2058,7 @@
1633
2058
  "type": "string"
1634
2059
  },
1635
2060
  "for_company": {
1636
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
2061
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
1637
2062
  "title": "for_company",
1638
2063
  "type": "string"
1639
2064
  },
@@ -1694,7 +2119,7 @@
1694
2119
  "openWorldHint": true,
1695
2120
  "readOnlyHint": true
1696
2121
  },
1697
- "description": "Search\n\nPolymorphic read-only search across your Vruum workspace.\n\nOne tool for every list/queue/search read. Pass `type` to pick the entity:\npeople (your pipeline — keyword `query`, batch dedup array, or list-mode\n`filters`), companies (EXTERNAL Hunter.io domain search for\ndecision-makers — requires filters.domain; 503 if Hunter.io is not\nconfigured), deals, campaigns, messages (outreach queue), engagements\n(LinkedIn engagement queue), content (LinkedIn content calendar), kb\n(knowledge-base search), skills (published skills), cta_links, warm_paths\n(open targets that have a warm intro path — see find_warm_path for one\ntarget), relationship_attempts (the durable action timeline shared with\nthe Relationships workspace).\n\nAll types except `companies` read internal, tenant-scoped data only.\nPagination (`limit`/`offset`) is supported for people (list mode), deals,\nmessages, engagements, warm_paths, and relationship_attempts; other types reject it. Results are returned\nunder `{type, results}` with each entity type's native payload shape.\n\nCampaign routing: use `type='campaigns'` here to list campaigns (newest\nfirst, compact by default — the prose-heavy AI-strategy fields come back\nnull; see filters.fields). To read one campaign by UUID with its full\nconfig, use `fetch` with `type='campaign'`. To create, update, clone,\ndelete, diagnose, or manage campaign members, use `manage_campaign`.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
2122
+ "description": "Search\n\nPolymorphic read-only search across your Vruum workspace.\n\nOne tool for every list/queue/search read. Pass `type` to pick the entity:\npeople (your pipeline — keyword `query`, batch dedup array, or list-mode\n`filters`), companies (company-domain contact search for decision-makers —\nrequires filters.domain), directory_people (the shared people directory —\nrequires filters.query), and directory_companies (the shared company directory —\nfilters.query?, filters.industry?, or both; industry is the canonical enum,\nincluding Unspecified for missing source data). Directory results contain\ncontact/company/freshness fields only, never another tenant's CRM state.\nOther types are deals, campaigns, messages (outreach queue), engagements\n(LinkedIn engagement queue), content (LinkedIn content calendar), claims\n(the hypothesis ledger: falsifiable beliefs with evidence-backed\nconfidence — filter by status or scope; render one with fetch\ntype='claim'), kb\n(knowledge-base search), skills (published skills), cta_links, warm_paths\n(open targets that have a warm intro path — see find_warm_path for one\ntarget), relationship_attempts (the durable action timeline shared with\nthe Relationships workspace).\n\nAll types except `companies` read internal, tenant-scoped data only.\nPagination (`limit`/`offset`) is supported for people (list mode), deals,\nmessages, engagements, warm_paths, and relationship_attempts; other types reject it. Results are returned\nunder `{type, results}` with each entity type's native payload shape.\n\nCampaign routing: use `type='campaigns'` here to list campaigns (newest\nfirst, compact by default — the prose-heavy AI-strategy fields come back\nnull; see filters.fields). To read one campaign by UUID with its full\nconfig, use `fetch` with `type='campaign'`. To create, update, clone,\ndelete, diagnose, or manage campaign members, use `manage_campaign`.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1698
2123
  "inputSchema": {
1699
2124
  "properties": {
1700
2125
  "filters": {
@@ -1873,7 +2298,7 @@
1873
2298
  },
1874
2299
  {
1875
2300
  "additionalProperties": false,
1876
- "description": "Filters for ``type='companies'`` — EXTERNAL Hunter.io domain search via ``find_people_at_company``.",
2301
+ "description": "Filters for ``type='companies'`` contact search via ``find_people_at_company``.",
1877
2302
  "properties": {
1878
2303
  "domain": {
1879
2304
  "description": "Company domain to search (e.g. 'acme.com'). Required.",
@@ -1976,7 +2401,7 @@
1976
2401
  "type": "null"
1977
2402
  }
1978
2403
  ],
1979
- "description": "Campaign UUID, or '__none__' for people with no campaign assigned (resolved via the queue's campaign_id resolver).",
2404
+ "description": "Campaign UUID, or '__none__' for rows authored outside any campaign. Filters the message's AUTHORING campaign (source_campaign_id) — not the person's current campaign membership.",
1980
2405
  "title": "Campaign Id"
1981
2406
  },
1982
2407
  "category": {
@@ -2028,6 +2453,23 @@
2028
2453
  "description": "Only messages that have (true) / have not (false) received a reply.",
2029
2454
  "title": "Has Reply"
2030
2455
  },
2456
+ "message_type": {
2457
+ "anyOf": [
2458
+ {
2459
+ "enum": [
2460
+ "outreach",
2461
+ "inbound",
2462
+ "all"
2463
+ ],
2464
+ "type": "string"
2465
+ },
2466
+ {
2467
+ "type": "null"
2468
+ }
2469
+ ],
2470
+ "description": "Row family (VRU-832). Omit or 'outreach' for outbound touches (default, unchanged). 'inbound' lists received replies — reply text in content, receive time in created_at, status 'received' (no actionable-status default applies); combine with category='person_reply' to exclude unsolicited inbound. 'all' returns both families.",
2471
+ "title": "Message Type"
2472
+ },
2031
2473
  "person_id": {
2032
2474
  "anyOf": [
2033
2475
  {
@@ -2266,6 +2708,18 @@
2266
2708
  "title": "Include Content",
2267
2709
  "type": "boolean"
2268
2710
  },
2711
+ "modified_after": {
2712
+ "anyOf": [
2713
+ {
2714
+ "type": "string"
2715
+ },
2716
+ {
2717
+ "type": "null"
2718
+ }
2719
+ ],
2720
+ "description": "ISO-8601 timestamp (no offset = UTC); requires doc_type='connector'. Returns only synced documents modified strictly after this instant — with no query, ordered newest-first. Use this (NOT a keyword query, which is a relevance-ranked top-N sample) for exhaustive 'what synced since <date>' discovery, e.g. finding new meeting transcripts past a watermark.",
2721
+ "title": "Modified After"
2722
+ },
2269
2723
  "query": {
2270
2724
  "anyOf": [
2271
2725
  {
@@ -2329,16 +2783,190 @@
2329
2783
  "title": "FacadeRelationshipAttemptsFilters",
2330
2784
  "type": "object"
2331
2785
  },
2786
+ {
2787
+ "additionalProperties": false,
2788
+ "description": "Shared people-directory lookup, with no tenant-private fields.",
2789
+ "properties": {
2790
+ "query": {
2791
+ "description": "Name, title, company, or email search text.",
2792
+ "maxLength": 160,
2793
+ "minLength": 3,
2794
+ "title": "Query",
2795
+ "type": "string"
2796
+ }
2797
+ },
2798
+ "required": [
2799
+ "query"
2800
+ ],
2801
+ "title": "FacadeDirectoryPeopleFilters",
2802
+ "type": "object"
2803
+ },
2804
+ {
2805
+ "additionalProperties": false,
2806
+ "description": "Shared company-directory lookup by text, canonical industry, or both.",
2807
+ "properties": {
2808
+ "industry": {
2809
+ "anyOf": [
2810
+ {
2811
+ "description": "Canonical research values plus the fallback bucket used by search.",
2812
+ "enum": [
2813
+ "Unspecified",
2814
+ "Agriculture",
2815
+ "Construction",
2816
+ "Consumer Goods",
2817
+ "Consumer Services",
2818
+ "Education",
2819
+ "Energy & Utilities",
2820
+ "Financial Services",
2821
+ "Government",
2822
+ "Healthcare",
2823
+ "Hospitality & Travel",
2824
+ "Legal Services",
2825
+ "Manufacturing",
2826
+ "Marketing & Advertising",
2827
+ "Media & Entertainment",
2828
+ "Nonprofit",
2829
+ "Professional Services",
2830
+ "Real Estate",
2831
+ "Retail & E-commerce",
2832
+ "Software & Technology",
2833
+ "Staffing & Recruiting",
2834
+ "Telecommunications",
2835
+ "Transportation & Logistics",
2836
+ "Wholesale & Distribution",
2837
+ "Other"
2838
+ ],
2839
+ "title": "SearchableIndustry",
2840
+ "type": "string"
2841
+ },
2842
+ {
2843
+ "type": "null"
2844
+ }
2845
+ ],
2846
+ "description": "Canonical company-industry filter. Unspecified selects companies without enough source data. Provider labels such as SaaS are rejected."
2847
+ },
2848
+ "query": {
2849
+ "anyOf": [
2850
+ {
2851
+ "maxLength": 160,
2852
+ "minLength": 3,
2853
+ "type": "string"
2854
+ },
2855
+ {
2856
+ "type": "null"
2857
+ }
2858
+ ],
2859
+ "description": "Optional company name, domain, or website search text.",
2860
+ "title": "Query"
2861
+ }
2862
+ },
2863
+ "title": "FacadeDirectoryCompaniesFilters",
2864
+ "type": "object"
2865
+ },
2866
+ {
2867
+ "additionalProperties": false,
2868
+ "description": "Filters for ``type='claims'`` (routes to ``list_claims`` — the VRU-840\nhypothesis ledger).",
2869
+ "properties": {
2870
+ "campaign_id": {
2871
+ "anyOf": [
2872
+ {
2873
+ "type": "string"
2874
+ },
2875
+ {
2876
+ "type": "null"
2877
+ }
2878
+ ],
2879
+ "description": "Claims scoped to this campaign UUID.",
2880
+ "title": "Campaign Id"
2881
+ },
2882
+ "company_id": {
2883
+ "anyOf": [
2884
+ {
2885
+ "type": "string"
2886
+ },
2887
+ {
2888
+ "type": "null"
2889
+ }
2890
+ ],
2891
+ "description": "Claims scoped to this company UUID.",
2892
+ "title": "Company Id"
2893
+ },
2894
+ "deal_id": {
2895
+ "anyOf": [
2896
+ {
2897
+ "type": "string"
2898
+ },
2899
+ {
2900
+ "type": "null"
2901
+ }
2902
+ ],
2903
+ "description": "Claims scoped to this deal UUID.",
2904
+ "title": "Deal Id"
2905
+ },
2906
+ "limit": {
2907
+ "default": 50,
2908
+ "maximum": 100.0,
2909
+ "minimum": 1.0,
2910
+ "title": "Limit",
2911
+ "type": "integer"
2912
+ },
2913
+ "person_id": {
2914
+ "anyOf": [
2915
+ {
2916
+ "type": "string"
2917
+ },
2918
+ {
2919
+ "type": "null"
2920
+ }
2921
+ ],
2922
+ "description": "Claims scoped to this person UUID.",
2923
+ "title": "Person Id"
2924
+ },
2925
+ "segment": {
2926
+ "anyOf": [
2927
+ {
2928
+ "type": "string"
2929
+ },
2930
+ {
2931
+ "type": "null"
2932
+ }
2933
+ ],
2934
+ "description": "Claims scoped to this named segment.",
2935
+ "title": "Segment"
2936
+ },
2937
+ "status": {
2938
+ "anyOf": [
2939
+ {
2940
+ "enum": [
2941
+ "open",
2942
+ "supported",
2943
+ "refuted",
2944
+ "stale",
2945
+ "retired"
2946
+ ],
2947
+ "type": "string"
2948
+ },
2949
+ {
2950
+ "type": "null"
2951
+ }
2952
+ ],
2953
+ "description": "Claim status filter.",
2954
+ "title": "Status"
2955
+ }
2956
+ },
2957
+ "title": "FacadeClaimsFilters",
2958
+ "type": "object"
2959
+ },
2332
2960
  {
2333
2961
  "type": "null"
2334
2962
  }
2335
2963
  ],
2336
- "description": "Type-specific filters — the object shape is determined by `type` (e.g. {domain} for companies, {stage, outcome, query} for deals). Unknown keys are rejected with 422.",
2964
+ "description": "Type-specific filters — the object shape is determined by `type` (e.g. {domain} for companies, {stage, outcome, query} for deals, or {query?, industry?} for directory_companies). Unknown keys are rejected with 422.",
2337
2965
  "title": "filters",
2338
2966
  "type": "object"
2339
2967
  },
2340
2968
  "for_company": {
2341
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
2969
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
2342
2970
  "title": "for_company",
2343
2971
  "type": "string"
2344
2972
  },
@@ -2352,7 +2980,7 @@
2352
2980
  "type": "null"
2353
2981
  }
2354
2982
  ],
2355
- "description": "Page size. Supported for people, deals, messages, warm_paths, and relationship_attempts (max 100), plus engagements (max 200); people keyword-query mode caps at 40. Rejected (422) for other types.",
2983
+ "description": "Page size. Supported for people, deals, messages, warm_paths, and relationship_attempts (max 100), engagements (max 200), and directory searches (max 40); people keyword-query mode caps at 40. Rejected (422) for other types.",
2356
2984
  "title": "limit",
2357
2985
  "type": "integer"
2358
2986
  },
@@ -2443,7 +3071,10 @@
2443
3071
  "skills",
2444
3072
  "cta_links",
2445
3073
  "warm_paths",
2446
- "relationship_attempts"
3074
+ "relationship_attempts",
3075
+ "directory_people",
3076
+ "directory_companies",
3077
+ "claims"
2447
3078
  ],
2448
3079
  "title": "type",
2449
3080
  "type": "string"
@@ -2473,7 +3104,7 @@
2473
3104
  "type": "string"
2474
3105
  },
2475
3106
  "for_company": {
2476
- "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_overview with view='companies' to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
3107
+ "description": "Optional company ID or name (UUID, or case-insensitive EXACT name match — partial / substring matches are NOT supported; ambiguous names raise 400) to execute this tool as. Use get_operator_companies, or get_operator_overview with view='companies', to list available companies. Only needed if you manage multiple companies. Access is enforced server-side: the tool resolves the value to a user_company_id and verifies the caller's membership in `company_users` — a 403 'Company not found or access denied' means EITHER the value did not match a known company OR the caller is not a member of it. The MCP operator role itself is NOT auto-granted — operators get this access by being members of each client company via `company_users` (the same row that grants any normal user access).",
2477
3108
  "title": "for_company",
2478
3109
  "type": "string"
2479
3110
  },