@agifyai/leadify-mcp 9.9.0 → 9.10.0

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.
package/README.md CHANGED
@@ -268,8 +268,14 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
268
268
  | `upsert_person_leads_with_employment` | Créer ou réconcilier atomiquement des Person Leads, PERSON, EMPLOYED_BY, projections et historique, sans effet commercial. |
269
269
  | `create_person_employment_evidence` / `get_person_employment_evidence` | Créer puis relire une preuve d’emploi individuelle tenant-scopée ; transmettre l’`evidence.id` retourné tel quel à l’upsert Person. |
270
270
  | `exclude_account` | Exclure une entreprise (établissement) et toutes ses personnes liées : cause protégée `ACCOUNT_EXCLUDED`, motif et URL source obligatoires, `level: "establishment"`. |
271
- | `exclude_lead` / `reintegrate_lead` | Exclure ou réintégrer une personne avec les mêmes commandes que l'interface ; raison obligatoire, aucun motif protégé levé. |
272
- | `get_outreach_state` | Lire causes actives, version et date de 1 à 100 fiches. |
271
+ | `exclude_lead` / `reintegrate_lead` | Exclure ou réintégrer une personne avec les mêmes commandes que l'interface ; raison obligatoire, aucun motif protégé levé. `exclude_lead` accepte `until` (AAAA-MM-JJ) pour une exclusion temporaire. |
272
+ | `get_outreach_state` | Lire causes actives, version, date et fin d'exclusion temporaire (`excludedUntil`, `causes[].until`) de 1 à 100 fiches. |
273
+ | `get_contact_state` | Avant d'écrire à quelqu'un : état de contact de 1 à 100 personnes, tel que la pastille et « Dernier contact » de Leadify (`PersonContactSummary`). |
274
+ | `list_follow_ups` | Fil « À relancer » (`DUE`) ou « Réponses à traiter » (`REPLY_TO_HANDLE`) de la page Suivi, même ordre, paginé par curseur. |
275
+ | `get_prospection_activity` | Compteurs et série jour par jour de la page Suivi pour 7, 30, 90 jours ou depuis toujours. |
276
+ | `get_capture_coverage` | Boîtes mail et comptes LinkedIn réellement captés, avec leur état. |
277
+ | `get_follow_up_cadence` | Rythme de relance de l'organisation (lecture seule). |
278
+ | `get_prospect_approach` | Avant d'écrire à une personne : son Approche (quotidien, problématique, ce qu'elle veut, ce qu'on lui apporte). |
273
279
  | `preview_move_leads` | Essai à blanc du déplacement de fiches vers un autre groupe de la même organisation : sélection par identifiants, vue ou filtre (500 fiches au plus par appel, lots ensuite), refus par fiche avec motif, doublons dans le groupe cible, champs de schéma et relations à ajouter, blocages et empreinte du plan. |
274
280
  | `move_leads` | Appliquer un déplacement prévisualisé ; exige l'empreinte de la prévisualisation et une clé d'idempotence. La fiche garde son identifiant et tout son historique ; jamais de fusion. |
275
281
  | `undo_move_leads` / `get_lead_move` | Annuler ou relire une opération de déplacement par son identifiant. |
@@ -438,6 +444,22 @@ de délivrabilité ni l'exclusion d'une entreprise. Chaque écriture accepte
438
444
  `get_outreach_state` relit 1 à 100 fiches. Routes serveur : `/api/outreach/*`
439
445
  (PRD-2337), auteur tracé dans Leadify : propriétaire de la clé, source Agent.
440
446
 
447
+ ### Suivi de la prospection (PRD-2691)
448
+
449
+ Les agents lisent ce que voit l'humain dans Leadify (jalon XXII, PRD-2657). Avant
450
+ d'écrire à quelqu'un : `get_contact_state`, puis `get_prospect_approach`. Les
451
+ échanges captés à la main (emails et messages LinkedIn, avec texte, auteur et
452
+ origine) arrivent dans `organizationContact.exchanges` de
453
+ `get_outreach_contact_history` et de la première page de
454
+ `get_outreach_context_history`. Les vues (`preview_lead_view`,
455
+ `create_lead_view`) acceptent les 12 champs `__contact_*` (dont `__contact_due`,
456
+ le filtre « À relancer »), `within_last_days`, `older_than_days` et
457
+ `includeEmpty`. Les listes fermées des entrées sont recopiées octet pour octet du
458
+ contrat partagé `contact-tracking.v1` dans `src/tools/contact_tracking_contract.ts` ;
459
+ les réponses du serveur sont rendues telles quelles, en JSON compact. Routes :
460
+ `POST /api/contact-state`, `GET /api/prospection-tracking/{follow-up-feed,activity,coverage}`,
461
+ `GET /api/campaign-wizard/follow-up-cadence`, `GET /api/prospect-approach/{leadId}`.
462
+
441
463
  ### Déplacer des prospects vers un autre groupe (PRD-2478)
442
464
 
443
465
  Pour réorganiser une base, **déplacer** les fiches plutôt que les copier
package/dist/server.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { registerAgentOutreachTools } from "./tools/agent_outreach.js";
2
2
  import { registerQualificationRunTools } from "./tools/qualification_runs.js";
3
3
  import { registerOutreachExclusionTools } from "./tools/outreach_exclusion.js";
4
+ import { registerProspectionTrackingTools } from "./tools/prospection_tracking.js";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { registerAuthTools } from "./tools/auth.js";
6
7
  import { registerLeadTools } from "./tools/leads.js";
@@ -61,5 +62,6 @@ export function createServer() {
61
62
  registerPipelineTools(server);
62
63
  registerQualificationRunTools(server);
63
64
  registerOutreachExclusionTools(server);
65
+ registerProspectionTrackingTools(server);
64
66
  return server;
65
67
  }
@@ -74,7 +74,7 @@ const CONTEXT_MODULE_DESCRIPTION = {
74
74
  playbook: "The published sales PLAYBOOK (GTM) of the organisation.",
75
75
  qualification: "What is KNOWN about the person: qualification document, freshness, failures and any refresh requested. Read the company module for the company cards.",
76
76
  signals: "WHY NOW: recent signals about the person and her company, newest first. Paged.",
77
- history: "What was ALREADY SAID: messages exchanged with their text, known threads, the program's own messages, coverage. Newest first. Paged.",
77
+ history: "What was ALREADY SAID: messages exchanged with their text, known threads, the program's own messages, coverage. Newest first. Paged. The first page (no cursor) also carries organizationContact, outside the module version: alreadyInExchange, firstContactAllowed and exchanges[] captured across the whole organization, including emails and LinkedIn messages written by hand (at, channel, direction, origin MANUAL/CAMPAIGN/AGENT, author, subject, text).",
78
78
  crm: "CRM situation, exclusion and contact state: commercial status, reception, human takeover, thread-read readiness, last confirmed outbound and inbound. Check it before any first contact.",
79
79
  authorization: "What is PERMITTED: program state, caps, senders and the key's rights, with the authorization revision to copy into the decision.",
80
80
  };
@@ -94,7 +94,8 @@ export function registerAgentOutreachTools(server, client = getClient()) {
94
94
  register("query_outreach_prospects", "Select canonical people server-side before pagination. Recipes: followup_due = seven days without pending/unknown/reply; channel_gap = LinkedIn messages >=2 and Gmail=0; replies_to_handle. For 72 hours set filters.last_confirmed_outbound_before.before to an explicit ISO timestamp. Signal filters support detection dates, levels and person/company scope. filters.qualified:true keeps the people whose qualification is finished; filters.qualification_levels (hot, warm, cold; 1 to 3 values) filters the temperature. Keep cursor, filters and limit identical on continuation; selections expire after ten minutes. An incomplete history never proves no previous contact.", querySchema, async (v) => requireOutreachVersion(await client.post(postPath(`${programPath(v.program_id)}/prospects/query`, v.organization_id), queryBody(v)), "1.0.0"));
95
95
  const personSchema = z.object({ ...scope, lead_id: identifier }).strict();
96
96
  register("get_outreach_prospect_summary", "Read a selected person's compact identity, contact counts and selection reasons. This is NOT the writing context: use get_outreach_prospect_context before drafting.", personSchema, v => client.get(`${programPath(v.program_id)}/prospects/${encoded(v.lead_id)}`, params(v.organization_id)));
97
- register("get_outreach_contact_history", "Read confirmed contact evidence for one person across accessible accounts, including pending, unknown and manual activity. Coverage limits remain explicit.", personSchema, v => client.get(`${programPath(v.program_id)}/contacts/${encoded(v.lead_id)}`, params(v.organization_id)));
97
+ register("get_outreach_contact_history", "Read confirmed contact evidence for one person across accessible accounts, including pending, unknown and manual activity. Coverage limits remain explicit." +
98
+ " Also organizationContact (whole organization, every mailbox and LinkedIn account of every member, not only the program senders): alreadyInExchange, firstContactAllowed, lastContactAt, reasonCode and exchanges[], the messages captured by Leadify, including emails and LinkedIn messages written by hand: at, channel, direction, kind, origin (MANUAL, CAMPAIGN, AGENT), author (member, Louise, Inconnu, or the person for a reply), subject and text (readable, quoted history removed, cut at 600 characters). firstContactAllowed=false forbids a first contact.", personSchema, v => client.get(`${programPath(v.program_id)}/contacts/${encoded(v.lead_id)}`, params(v.organization_id)));
98
99
  register("get_outreach_prospect_context", "LEGACY whole writing context, about 90 KB: runtimes that set aside tool results above 50,000 characters never see it. Agents must NOT call it: read get_outreach_context_summary, then the get_outreach_context_<module> tools. Kept for clients that can read very large results. Draft only when readiness.ready=true; WAIT requires resolving the stated reasons. Copy all versions, factsVersion, contextVersion and readAt into the current decision. Never starts qualification. A human takeover can only be resumed by a human in Leadify.", personSchema, async (v) => requireOutreachVersion(await client.get(`${programPath(v.program_id, true)}/people/${encoded(v.lead_id)}/context`, params(v.organization_id)), "agent-writing-context.v1"));
99
100
  // PRD-2632 : the same context in pieces. Summary first, then one tool per module.
100
101
  const contextPath = (v, tail) => `${programPath(v.program_id, true)}/people/${encoded(v.lead_id)}/context/${tail}`;
@@ -0,0 +1,18 @@
1
+ export declare const CONTACT_TRACKING_CONTRACT_VERSION: "contact-tracking.v1";
2
+ export declare const CONTACT_TRACKING_CHANNELS: readonly ["EMAIL", "LINKEDIN"];
3
+ export declare const CONTACT_STATES: readonly ["NEVER", "INVITATION_PENDING", "CONTACTED", "DUE", "REPLY_TO_HANDLE", "IN_CONVERSATION", "SEQUENCE_DONE", "AUTOMATED"];
4
+ export declare const TRACKING_PERIODS: readonly ["7d", "30d", "90d", "all"];
5
+ export declare const FOLLOW_UP_FEED_KINDS: readonly ["DUE", "REPLY_TO_HANDLE"];
6
+ export declare const FOLLOW_UP_FEED_PAGE_SIZE: {
7
+ readonly default: 25;
8
+ readonly max: 100;
9
+ };
10
+ export declare const CONTACT_VIEW_OPERATORS: readonly ["equals", "not_equals", "gte", "lte", "is_true", "is_false", "is_null", "is_not_null", "within_last_days", "older_than_days"];
11
+ export type ContactViewOperator = (typeof CONTACT_VIEW_OPERATORS)[number];
12
+ export declare const CONTACT_FILTER_INCLUDE_EMPTY_OPERATORS: readonly ["gte", "lte", "within_last_days", "older_than_days"];
13
+ /**
14
+ * Clés de `CONTACT_VIEW_FIELDS` du même contrat, dans le même ordre (les
15
+ * définitions complètes, libellés et opérateurs par type, restent côté
16
+ * serveur, qui les vérifie).
17
+ */
18
+ export declare const CONTACT_VIEW_FIELD_KEYS: readonly ["__contact_state", "__contact_last_at", "__contact_last_channel", "__contact_touch_count", "__contact_email_touch_count", "__contact_linkedin_touch_count", "__contact_next_due_at", "__contact_due", "__contact_last_reply_at", "__contact_days_without_reply", "__contact_linkedin_connection", "__contact_last_actor"];
@@ -0,0 +1,59 @@
1
+ // PRD-2691 — copie octet pour octet des listes fermées du contrat partagé de
2
+ // Leadify, `packages/shared-types/src/contact-tracking.ts` (contrat
3
+ // `contact-tracking.v1`, AgifyAI/leadify@d067af92). Le MCP ne peut pas importer
4
+ // `@leadify/shared-types` (paquet privé du monorepo) : seules ces lignes sont
5
+ // recopiées, sans retouche, pour fermer les entrées des outils. Les formes de
6
+ // sortie (PersonContactSummary, FollowUpFeedPage, TrackingActivityResult,
7
+ // CaptureCoverage, FollowUpCadenceSettings, ProspectApproachRead) ne sont pas
8
+ // redéfinies : le MCP rend la réponse du serveur telle quelle. Les règles
9
+ // croisées (opérateur permis par champ, valeur attendue, includeEmpty) restent
10
+ // vérifiées par le serveur, qui rend ses refus tels quels.
11
+ // Pour mettre à jour : recopier les mêmes lignes depuis le contrat, puis lancer
12
+ // `npm test` (test/prospection-tracking.test.mjs vérifie les valeurs).
13
+ export const CONTACT_TRACKING_CONTRACT_VERSION = 'contact-tracking.v1';
14
+ export const CONTACT_TRACKING_CHANNELS = ['EMAIL', 'LINKEDIN'];
15
+ export const CONTACT_STATES = [
16
+ 'NEVER',
17
+ 'INVITATION_PENDING',
18
+ 'CONTACTED',
19
+ 'DUE',
20
+ 'REPLY_TO_HANDLE',
21
+ 'IN_CONVERSATION',
22
+ 'SEQUENCE_DONE',
23
+ 'AUTOMATED',
24
+ ];
25
+ export const TRACKING_PERIODS = ['7d', '30d', '90d', 'all'];
26
+ export const FOLLOW_UP_FEED_KINDS = ['DUE', 'REPLY_TO_HANDLE'];
27
+ export const FOLLOW_UP_FEED_PAGE_SIZE = { default: 25, max: 100 };
28
+ export const CONTACT_VIEW_OPERATORS = [
29
+ 'equals',
30
+ 'not_equals',
31
+ 'gte',
32
+ 'lte',
33
+ 'is_true',
34
+ 'is_false',
35
+ 'is_null',
36
+ 'is_not_null',
37
+ 'within_last_days',
38
+ 'older_than_days',
39
+ ];
40
+ export const CONTACT_FILTER_INCLUDE_EMPTY_OPERATORS = ['gte', 'lte', 'within_last_days', 'older_than_days'];
41
+ /**
42
+ * Clés de `CONTACT_VIEW_FIELDS` du même contrat, dans le même ordre (les
43
+ * définitions complètes, libellés et opérateurs par type, restent côté
44
+ * serveur, qui les vérifie).
45
+ */
46
+ export const CONTACT_VIEW_FIELD_KEYS = [
47
+ '__contact_state',
48
+ '__contact_last_at',
49
+ '__contact_last_channel',
50
+ '__contact_touch_count',
51
+ '__contact_email_touch_count',
52
+ '__contact_linkedin_touch_count',
53
+ '__contact_next_due_at',
54
+ '__contact_due',
55
+ '__contact_last_reply_at',
56
+ '__contact_days_without_reply',
57
+ '__contact_linkedin_connection',
58
+ '__contact_last_actor',
59
+ ];
@@ -30,11 +30,11 @@ declare const selectionSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"kind", [z.Z
30
30
  value: z.ZodOptional<z.ZodUnknown>;
31
31
  }, "strip", z.ZodTypeAny, {
32
32
  field: string;
33
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
33
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
34
34
  value?: unknown;
35
35
  }, {
36
36
  field: string;
37
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
37
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
38
38
  value?: unknown;
39
39
  }>, "many">>;
40
40
  or_filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -43,11 +43,11 @@ declare const selectionSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"kind", [z.Z
43
43
  value: z.ZodOptional<z.ZodUnknown>;
44
44
  }, "strip", z.ZodTypeAny, {
45
45
  field: string;
46
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
46
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
47
47
  value?: unknown;
48
48
  }, {
49
49
  field: string;
50
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
50
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
51
51
  value?: unknown;
52
52
  }>, "many">>;
53
53
  search: z.ZodOptional<z.ZodString>;
@@ -56,13 +56,13 @@ declare const selectionSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"kind", [z.Z
56
56
  group_id: string;
57
57
  filters?: {
58
58
  field: string;
59
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
59
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
60
60
  value?: unknown;
61
61
  }[] | undefined;
62
62
  search?: string | undefined;
63
63
  or_filters?: {
64
64
  field: string;
65
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
65
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
66
66
  value?: unknown;
67
67
  }[] | undefined;
68
68
  }, {
@@ -70,13 +70,13 @@ declare const selectionSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"kind", [z.Z
70
70
  group_id: string;
71
71
  filters?: {
72
72
  field: string;
73
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
73
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
74
74
  value?: unknown;
75
75
  }[] | undefined;
76
76
  search?: string | undefined;
77
77
  or_filters?: {
78
78
  field: string;
79
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
79
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
80
80
  value?: unknown;
81
81
  }[] | undefined;
82
82
  }>]>, {
@@ -90,13 +90,13 @@ declare const selectionSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"kind", [z.Z
90
90
  group_id: string;
91
91
  filters?: {
92
92
  field: string;
93
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
93
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
94
94
  value?: unknown;
95
95
  }[] | undefined;
96
96
  search?: string | undefined;
97
97
  or_filters?: {
98
98
  field: string;
99
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
99
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
100
100
  value?: unknown;
101
101
  }[] | undefined;
102
102
  }, {
@@ -110,13 +110,13 @@ declare const selectionSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"kind", [z.Z
110
110
  group_id: string;
111
111
  filters?: {
112
112
  field: string;
113
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
113
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
114
114
  value?: unknown;
115
115
  }[] | undefined;
116
116
  search?: string | undefined;
117
117
  or_filters?: {
118
118
  field: string;
119
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
119
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
120
120
  value?: unknown;
121
121
  }[] | undefined;
122
122
  }>;
@@ -150,11 +150,11 @@ declare const previewSchema: z.ZodObject<{
150
150
  value: z.ZodOptional<z.ZodUnknown>;
151
151
  }, "strip", z.ZodTypeAny, {
152
152
  field: string;
153
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
153
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
154
154
  value?: unknown;
155
155
  }, {
156
156
  field: string;
157
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
157
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
158
158
  value?: unknown;
159
159
  }>, "many">>;
160
160
  or_filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -163,11 +163,11 @@ declare const previewSchema: z.ZodObject<{
163
163
  value: z.ZodOptional<z.ZodUnknown>;
164
164
  }, "strip", z.ZodTypeAny, {
165
165
  field: string;
166
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
166
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
167
167
  value?: unknown;
168
168
  }, {
169
169
  field: string;
170
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
170
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
171
171
  value?: unknown;
172
172
  }>, "many">>;
173
173
  search: z.ZodOptional<z.ZodString>;
@@ -176,13 +176,13 @@ declare const previewSchema: z.ZodObject<{
176
176
  group_id: string;
177
177
  filters?: {
178
178
  field: string;
179
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
179
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
180
180
  value?: unknown;
181
181
  }[] | undefined;
182
182
  search?: string | undefined;
183
183
  or_filters?: {
184
184
  field: string;
185
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
185
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
186
186
  value?: unknown;
187
187
  }[] | undefined;
188
188
  }, {
@@ -190,13 +190,13 @@ declare const previewSchema: z.ZodObject<{
190
190
  group_id: string;
191
191
  filters?: {
192
192
  field: string;
193
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
193
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
194
194
  value?: unknown;
195
195
  }[] | undefined;
196
196
  search?: string | undefined;
197
197
  or_filters?: {
198
198
  field: string;
199
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
199
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
200
200
  value?: unknown;
201
201
  }[] | undefined;
202
202
  }>]>, {
@@ -210,13 +210,13 @@ declare const previewSchema: z.ZodObject<{
210
210
  group_id: string;
211
211
  filters?: {
212
212
  field: string;
213
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
213
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
214
214
  value?: unknown;
215
215
  }[] | undefined;
216
216
  search?: string | undefined;
217
217
  or_filters?: {
218
218
  field: string;
219
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
219
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
220
220
  value?: unknown;
221
221
  }[] | undefined;
222
222
  }, {
@@ -230,13 +230,13 @@ declare const previewSchema: z.ZodObject<{
230
230
  group_id: string;
231
231
  filters?: {
232
232
  field: string;
233
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
233
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
234
234
  value?: unknown;
235
235
  }[] | undefined;
236
236
  search?: string | undefined;
237
237
  or_filters?: {
238
238
  field: string;
239
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
239
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
240
240
  value?: unknown;
241
241
  }[] | undefined;
242
242
  }>;
@@ -253,13 +253,13 @@ declare const previewSchema: z.ZodObject<{
253
253
  group_id: string;
254
254
  filters?: {
255
255
  field: string;
256
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
256
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
257
257
  value?: unknown;
258
258
  }[] | undefined;
259
259
  search?: string | undefined;
260
260
  or_filters?: {
261
261
  field: string;
262
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
262
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
263
263
  value?: unknown;
264
264
  }[] | undefined;
265
265
  };
@@ -277,13 +277,13 @@ declare const previewSchema: z.ZodObject<{
277
277
  group_id: string;
278
278
  filters?: {
279
279
  field: string;
280
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
280
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
281
281
  value?: unknown;
282
282
  }[] | undefined;
283
283
  search?: string | undefined;
284
284
  or_filters?: {
285
285
  field: string;
286
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
286
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
287
287
  value?: unknown;
288
288
  }[] | undefined;
289
289
  };
@@ -321,11 +321,11 @@ declare const applySchema: z.ZodObject<{
321
321
  value: z.ZodOptional<z.ZodUnknown>;
322
322
  }, "strip", z.ZodTypeAny, {
323
323
  field: string;
324
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
324
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
325
325
  value?: unknown;
326
326
  }, {
327
327
  field: string;
328
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
328
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
329
329
  value?: unknown;
330
330
  }>, "many">>;
331
331
  or_filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -334,11 +334,11 @@ declare const applySchema: z.ZodObject<{
334
334
  value: z.ZodOptional<z.ZodUnknown>;
335
335
  }, "strip", z.ZodTypeAny, {
336
336
  field: string;
337
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
337
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
338
338
  value?: unknown;
339
339
  }, {
340
340
  field: string;
341
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
341
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
342
342
  value?: unknown;
343
343
  }>, "many">>;
344
344
  search: z.ZodOptional<z.ZodString>;
@@ -347,13 +347,13 @@ declare const applySchema: z.ZodObject<{
347
347
  group_id: string;
348
348
  filters?: {
349
349
  field: string;
350
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
350
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
351
351
  value?: unknown;
352
352
  }[] | undefined;
353
353
  search?: string | undefined;
354
354
  or_filters?: {
355
355
  field: string;
356
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
356
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
357
357
  value?: unknown;
358
358
  }[] | undefined;
359
359
  }, {
@@ -361,13 +361,13 @@ declare const applySchema: z.ZodObject<{
361
361
  group_id: string;
362
362
  filters?: {
363
363
  field: string;
364
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
364
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
365
365
  value?: unknown;
366
366
  }[] | undefined;
367
367
  search?: string | undefined;
368
368
  or_filters?: {
369
369
  field: string;
370
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
370
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
371
371
  value?: unknown;
372
372
  }[] | undefined;
373
373
  }>]>, {
@@ -381,13 +381,13 @@ declare const applySchema: z.ZodObject<{
381
381
  group_id: string;
382
382
  filters?: {
383
383
  field: string;
384
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
384
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
385
385
  value?: unknown;
386
386
  }[] | undefined;
387
387
  search?: string | undefined;
388
388
  or_filters?: {
389
389
  field: string;
390
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
390
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
391
391
  value?: unknown;
392
392
  }[] | undefined;
393
393
  }, {
@@ -401,13 +401,13 @@ declare const applySchema: z.ZodObject<{
401
401
  group_id: string;
402
402
  filters?: {
403
403
  field: string;
404
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
404
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
405
405
  value?: unknown;
406
406
  }[] | undefined;
407
407
  search?: string | undefined;
408
408
  or_filters?: {
409
409
  field: string;
410
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
410
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
411
411
  value?: unknown;
412
412
  }[] | undefined;
413
413
  }>;
@@ -425,13 +425,13 @@ declare const applySchema: z.ZodObject<{
425
425
  group_id: string;
426
426
  filters?: {
427
427
  field: string;
428
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
428
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
429
429
  value?: unknown;
430
430
  }[] | undefined;
431
431
  search?: string | undefined;
432
432
  or_filters?: {
433
433
  field: string;
434
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
434
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
435
435
  value?: unknown;
436
436
  }[] | undefined;
437
437
  };
@@ -451,13 +451,13 @@ declare const applySchema: z.ZodObject<{
451
451
  group_id: string;
452
452
  filters?: {
453
453
  field: string;
454
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
454
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
455
455
  value?: unknown;
456
456
  }[] | undefined;
457
457
  search?: string | undefined;
458
458
  or_filters?: {
459
459
  field: string;
460
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
460
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
461
461
  value?: unknown;
462
462
  }[] | undefined;
463
463
  };
@@ -39,15 +39,19 @@ export function registerOutreachExclusionTools(server, client = getClient()) {
39
39
  server.tool("exclude_lead", "Exclude one person from prospecting: no message is drafted, enrolled or sent, and their running campaigns stop. " +
40
40
  "Same command as the Exclude button in Leadify; the history shows the API key owner as author. caused_by is MANUAL " +
41
41
  "(default) or TARGET_NOT_FIT (out of target). For a company use exclude_account instead (this tool refuses company leads " +
42
- "with 409 OUTREACH_COMPANY_USE_ACCOUNT_ROUTE). Returns the re-read state. " + NEVER_IN_TEXT, {
42
+ "with 409 OUTREACH_COMPANY_USE_ACCOUNT_ROUTE). Pass until (YYYY-MM-DD) for a temporary exclusion, e.g. 'recontact after the " +
43
+ "summer'; get_outreach_state then shows excludedUntil and causes[].until. Returns the re-read state. " + NEVER_IN_TEXT, {
43
44
  ...writeShape,
44
45
  lead_id: id.describe("Lead id of the person."),
45
46
  caused_by: z.enum(["MANUAL", "TARGET_NOT_FIT"]).default("MANUAL"),
46
47
  reason: reason.describe("Why the person is excluded; shown in the Leadify history."),
47
- }, async ({ organization_id, lead_id, caused_by, reason, idempotency_key, expected_version }) => {
48
+ until: z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/, "YYYY-MM-DD expected").optional().describe("Optional last day of a TEMPORARY exclusion, YYYY-MM-DD (Europe/Paris): the person stays excluded through that whole day, " +
49
+ "then the exclusion ends by itself and she can come back in 'À relancer'. Omit for a permanent exclusion. The server refuses " +
50
+ "a past date or a cause that cannot be temporary; its refusal is returned as is."),
51
+ }, async ({ organization_id, lead_id, caused_by, reason, until, idempotency_key, expected_version }) => {
48
52
  try {
49
53
  return toolResult(await client.post("/api/outreach/leads/exclude", {
50
- organizationId: organization_id, leadId: lead_id, causedBy: caused_by, reason,
54
+ organizationId: organization_id, leadId: lead_id, causedBy: caused_by, reason, until,
51
55
  idempotencyKey: idempotency_key, expectedVersion: expected_version, origin: "AGENT",
52
56
  }));
53
57
  }
@@ -57,7 +61,8 @@ export function registerOutreachExclusionTools(server, client = getClient()) {
57
61
  });
58
62
  server.tool("reintegrate_lead", "Allow again a person excluded by hand (MANUAL, out of target, or a migrated historical flag). A justification is " +
59
63
  "required. It never lifts an opt-out, a deliverability protection or a company exclusion: if one remains, state.status " +
60
- "stays \"excluded\" and state.causes says why. No campaign is restarted. Returns the re-read state.", {
64
+ "stays \"excluded\" and state.causes says why. It also ends a manual temporary exclusion before its until date. No campaign is " +
65
+ "restarted. Returns the re-read state.", {
61
66
  ...writeShape,
62
67
  lead_id: id.describe("Lead id of the person."),
63
68
  reason: reason.describe("Why the person may be contacted again; shown in the Leadify history."),
@@ -74,7 +79,8 @@ export function registerOutreachExclusionTools(server, client = getClient()) {
74
79
  });
75
80
  server.tool("get_outreach_state", "Read the prospecting state of 1 to 100 leads (people or companies): status allowed or excluded, active causes with " +
76
81
  "their origin and note, version (pass it as expected_version to a write), lastTransitionAt and accountSources " +
77
- "(companies whose exclusion the person inherits). Leads absent from the organization are listed in missingLeadIds. " +
82
+ "(companies whose exclusion the person inherits), excludedUntil and causes[].until for a temporary exclusion (null = " +
83
+ "permanent). Leads absent from the organization are listed in missingLeadIds. " +
78
84
  "Read this before deciding, instead of looking for exclusions in text.", {
79
85
  organization_id: id,
80
86
  lead_ids: z.array(id).min(1).max(100),
@@ -0,0 +1,6 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type LeadifyClient } from "../client.js";
3
+ export declare const PROSPECTION_TRACKING_TOOL_NAMES: readonly ["get_contact_state", "list_follow_ups", "get_prospection_activity", "get_capture_coverage", "get_follow_up_cadence", "get_prospect_approach"];
4
+ type Client = Pick<LeadifyClient, "get" | "post">;
5
+ export declare function registerProspectionTrackingTools(server: McpServer, client?: Client): void;
6
+ export {};
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import { getClient } from "../client.js";
3
+ import { handleToolError } from "../types.js";
4
+ import { CONTACT_STATES, CONTACT_TRACKING_CHANNELS, FOLLOW_UP_FEED_KINDS, FOLLOW_UP_FEED_PAGE_SIZE, TRACKING_PERIODS, } from "./contact_tracking_contract.js";
5
+ // PRD-2691 — suivi de la prospection (jalon XXII, PRD-2657) en lecture : ce que
6
+ // voit l'humain dans Leadify (pastille, « Dernier contact », page Suivi, Approche),
7
+ // lu par les mêmes routes par clé API. Aucune écriture ici.
8
+ export const PROSPECTION_TRACKING_TOOL_NAMES = [
9
+ "get_contact_state",
10
+ "list_follow_ups",
11
+ "get_prospection_activity",
12
+ "get_capture_coverage",
13
+ "get_follow_up_cadence",
14
+ "get_prospect_approach",
15
+ ];
16
+ const id = z.string().trim().min(1).max(200);
17
+ const organizationId = id.describe("Organization to read. Must be in the API key's scope.");
18
+ const channel = z.enum(CONTACT_TRACKING_CHANNELS);
19
+ const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
20
+ /** One line of JSON: these reads go into an agent context with a budget. */
21
+ const compact = (data) => ({ content: [{ type: "text", text: JSON.stringify(data) }] });
22
+ function query(values) {
23
+ return new URLSearchParams(Object.entries(values)
24
+ .filter((entry) => entry[1] !== undefined)
25
+ .map(([key, value]) => [key, String(value)]));
26
+ }
27
+ const STATES = CONTACT_STATES.join(", ");
28
+ export function registerProspectionTrackingTools(server, client = getClient()) {
29
+ const read = (name, description, shape, action) => {
30
+ server.tool(name, description, shape, READ_ONLY, (async (input) => {
31
+ try {
32
+ return compact(await action(input));
33
+ }
34
+ catch (error) {
35
+ return handleToolError(error);
36
+ }
37
+ }));
38
+ };
39
+ read("get_contact_state", "BEFORE writing to anyone, read this. Contact state of 1 to 100 people, exactly as the Leadify dot and the " +
40
+ "'Dernier contact' line show it (contract contact-tracking.v1, PersonContactSummary): state (" + STATES + "; DUE is " +
41
+ "resolved at read time), per-channel email and LinkedIn touches, sequence and next step, LinkedIn connection, " +
42
+ "lastContact with its channel, date and author (member, Louise or Inconnu) and source (MANUAL, CAMPAIGN, AGENT), " +
43
+ "lastReplyAt, daysWithoutReply, nextDueAt and nextDueChannel, exclusion (excluded, reason, until) and automatedBy. " +
44
+ "Hand-sent emails and LinkedIn messages captured from connected mailboxes and accounts count like any other touch. " +
45
+ "Do not start a first contact when the state is not NEVER, when exclusion.excluded is true, or when the state is " +
46
+ "AUTOMATED (a campaign or Louise already follows the person up); REPLY_TO_HANDLE means a human must answer. " +
47
+ "A person found in several leads appears once, with all her leadIds. Answer: {summaries, unknownLeadIds}; leads " +
48
+ "outside the key's organizations or not in a people group are listed in unknownLeadIds. Read the captured message " +
49
+ "texts with get_outreach_contact_history (organizationContact.exchanges).", { lead_ids: z.array(id).min(1).max(100).describe("Lead ids of people (1 to 100). A personId is not accepted: pass any of its leads.") }, ({ lead_ids }) => client.post("/api/contact-state", { leadIds: [...new Set(lead_ids)] }));
50
+ read("list_follow_ups", "The Leadify 'À relancer' feed (kind DUE, default) or 'Réponses à traiter' (kind REPLY_TO_HANDLE), all groups of the " +
51
+ "organization, in the same order as the Suivi page: most overdue first. Use it to pick who to follow up today; " +
52
+ "in the DUE feed, people handled by a campaign or by Louise (AUTOMATED) and excluded people never appear. Each item: personId, " +
53
+ "leadId, groupId, person (name, jobTitle), company, groups, due (channel, step FIRST_MESSAGE or FOLLOW_UP with its " +
54
+ "index and total, dueAt, daysLate, acceptedAt, alsoDue) or reply (at, channel, daysWaiting), lastMessage, recentSignal, reason " +
55
+ "(EXCLUSION_ENDED or INVITATION_NOT_ACCEPTED) and href. The page also gives total (same filters) and nextUpcoming " +
56
+ `when empty. Pages of ${FOLLOW_UP_FEED_PAGE_SIZE.default} by default (max ${FOLLOW_UP_FEED_PAGE_SIZE.max}): pass ` +
57
+ "nextCursor unchanged as cursor, with the same filters, until it is null. Read get_contact_state before writing.", {
58
+ organization_id: organizationId,
59
+ kind: z.enum(FOLLOW_UP_FEED_KINDS).optional().describe("DUE ('À relancer', default) or REPLY_TO_HANDLE ('Réponses à traiter')."),
60
+ member_user_id: id.optional().describe("Only people whose last contact was made by this member (user id)."),
61
+ channel: channel.optional().describe("EMAIL or LINKEDIN."),
62
+ group_id: id.optional().describe("Only people present in this lead group."),
63
+ cursor: z.string().min(1).max(500).optional().describe("nextCursor from the previous page, unchanged. Omit for the first page."),
64
+ limit: z.number().int().min(1).max(FOLLOW_UP_FEED_PAGE_SIZE.max).optional(),
65
+ }, ({ organization_id, kind, member_user_id, channel, group_id, cursor, limit }) => client.get("/api/prospection-tracking/follow-up-feed", query({
66
+ organizationId: organization_id, kind, memberUserId: member_user_id, channel, groupId: group_id, cursor, limit,
67
+ })));
68
+ read("get_prospection_activity", "Counters and day-by-day series of the Leadify Suivi page (tab Activité) for a period: messagesSent (emails, " +
69
+ "LinkedIn messages), invitations (sent, accepted, acceptanceRate), peopleContacted (total, new), replies (people, " +
70
+ "messages, rate), meetings, dueToday, previousCounters for the same period just before (null for 'all') and days " +
71
+ "(one entry per day, Europe/Paris: emails, linkedinMessages, linkedinInvitations, replies). Manual, campaign and " +
72
+ "Louise touches all count. Use it to report on prospecting activity, not to choose who to contact.", {
73
+ organization_id: organizationId,
74
+ period: z.enum(TRACKING_PERIODS).optional().describe("7d, 30d (default), 90d or all."),
75
+ member_user_id: id.optional().describe("Only touches made by this member (user id)."),
76
+ group_id: id.optional().describe("Only people present in this lead group."),
77
+ channel: channel.optional().describe("EMAIL or LINKEDIN."),
78
+ }, ({ organization_id, period, member_user_id, group_id, channel }) => client.get("/api/prospection-tracking/activity", query({
79
+ organizationId: organization_id, period, memberUserId: member_user_id, groupId: group_id, channel,
80
+ })));
81
+ read("get_capture_coverage", "Which mailboxes (Gmail, Outlook) and LinkedIn accounts of the organization's members are really captured, with " +
82
+ "their status (mailboxes SYNCED, BACKFILLING, NEEDS_RECONNECT or ERROR; LinkedIn CONNECTED, NEEDS_RECONNECT or ERROR), " +
83
+ "lastSyncAt, coveredThrough, tracked per member, historyStartsOn and backfill progress. Read it before " +
84
+ "concluding that someone was never contacted: a person can only look NEVER on a channel that is captured. An " +
85
+ "organization admin key sees every member; otherwise only the key owner.", { organization_id: organizationId }, ({ organization_id }) => client.get("/api/prospection-tracking/coverage", query({ organizationId: organization_id })));
86
+ read("get_follow_up_cadence", "The organization's follow-up rhythm (Paramètres > Prospection > Rythme de relance), as FollowUpCadenceSettings: " +
87
+ "cadence.email and cadence.linkedin with followUps and delaysDays (calendar days before each follow-up; default 4 " +
88
+ "follow-ups, [4, 8, 16, 30]), isDefault, invitationStaleDays (21), canEdit and updatedAt. It is what computes nextDueAt in get_contact_state and the order of list_follow_ups. " +
89
+ "Read-only: only an organization admin changes it, in Leadify.", { organization_id: organizationId }, ({ organization_id }) => client.get("/api/campaign-wizard/follow-up-cadence", query({ organizationId: organization_id })));
90
+ read("get_prospect_approach", "BEFORE writing to a person, read her Approche (first section of the Prospection tab in Leadify): her daily work " +
91
+ "in 1 to 5 bullets, her problem, what she wants (null if nothing solid) and what we can bring her, each block marked " +
92
+ "inferred or not, with sources. Answer: {contractVersion, leadId, status, current, versionCount}; current holds " +
93
+ "version, generatedAt (when it was prepared), createdAt, author and approach {daily, problem, wants, offer, sources}; status NOT_PREPARED means no Approche yet (current is null): do not invent one. It is prepared " +
94
+ "automatically for people qualified hot only. This tool never prepares or changes it.", { lead_id: id.describe("Lead id of the person.") }, ({ lead_id }) => client.get(`/api/prospect-approach/${encodeURIComponent(lead_id)}`));
95
+ }
@@ -6,11 +6,33 @@ export declare const filterSchema: z.ZodObject<{
6
6
  value: z.ZodOptional<z.ZodUnknown>;
7
7
  }, "strip", z.ZodTypeAny, {
8
8
  field: string;
9
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
9
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
10
10
  value?: unknown;
11
11
  }, {
12
12
  field: string;
13
- operator: "equals" | "contains" | "gte" | "lte" | "not_equals" | "not_contains" | "is_true" | "is_false" | "is_null" | "is_not_null" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
13
+ operator: "equals" | "not_equals" | "gte" | "lte" | "is_true" | "is_false" | "is_null" | "is_not_null" | "contains" | "not_contains" | "activity_days_ago" | "activity_count_equals" | "activity_count_gte" | "activity_count_lte" | "bbox";
14
14
  value?: unknown;
15
15
  }>;
16
+ /**
17
+ * PRD-2691 : saved views also accept the contact fields of the shared contract
18
+ * (PRD-2665), their two day-count operators and `includeEmpty`. The server
19
+ * checks which operator and value each `__contact_*` field allows.
20
+ */
21
+ export declare const CONTACT_VIEW_FIELDS_HELP: string;
22
+ export declare const viewFilterSchema: z.ZodObject<{
23
+ field: z.ZodString;
24
+ operator: z.ZodEnum<[string, ...string[]]>;
25
+ value: z.ZodOptional<z.ZodUnknown>;
26
+ includeEmpty: z.ZodOptional<z.ZodBoolean>;
27
+ }, "strip", z.ZodTypeAny, {
28
+ field: string;
29
+ operator: string;
30
+ value?: unknown;
31
+ includeEmpty?: boolean | undefined;
32
+ }, {
33
+ field: string;
34
+ operator: string;
35
+ value?: unknown;
36
+ includeEmpty?: boolean | undefined;
37
+ }>;
16
38
  export declare function registerLeadViewTools(server: McpServer): void;
@@ -1,17 +1,37 @@
1
1
  import { z } from "zod";
2
2
  import { getClient } from "../client.js";
3
3
  import { handleToolError, toolResult } from "../types.js";
4
+ import { CONTACT_FILTER_INCLUDE_EMPTY_OPERATORS, CONTACT_VIEW_FIELD_KEYS, CONTACT_VIEW_OPERATORS } from "./contact_tracking_contract.js";
4
5
  export const filterSchema = z.object({
5
6
  field: z.string().describe("A standard, virtual, or selected lead-group schema field."),
6
7
  operator: z.enum(["equals", "contains", "gte", "lte", "not_equals", "not_contains", "is_true", "is_false", "is_null", "is_not_null", "activity_days_ago", "activity_count_equals", "activity_count_gte", "activity_count_lte", "bbox"]),
7
8
  value: z.unknown().optional(),
8
9
  });
10
+ const BASE_FILTER_OPERATORS = filterSchema.shape.operator.options;
11
+ /**
12
+ * PRD-2691 : saved views also accept the contact fields of the shared contract
13
+ * (PRD-2665), their two day-count operators and `includeEmpty`. The server
14
+ * checks which operator and value each `__contact_*` field allows.
15
+ */
16
+ export const CONTACT_VIEW_FIELDS_HELP = `Contact fields (same as the Leadify 'Contact' filters): ${CONTACT_VIEW_FIELD_KEYS.join(", ")}. ` +
17
+ "__contact_due is_true = the 'À relancer' quick filter (due today or late, never AUTOMATED nor excluded); sort it by " +
18
+ "__contact_next_due_at asc. Choice fields (__contact_state, __contact_last_channel, __contact_linkedin_connection) take " +
19
+ "equals/not_equals with an array of values; __contact_last_actor takes an array of member user ids; dates take gte/lte " +
20
+ "(YYYY-MM-DD), within_last_days or older_than_days (whole days, 0 = today) and is_null/is_not_null; numbers take " +
21
+ "equals/gte/lte. A lead never contacted counts as __contact_state NEVER.";
22
+ export const viewFilterSchema = z.object({
23
+ field: z.string().describe(`A standard, virtual, selected lead-group schema field, or a contact field. ${CONTACT_VIEW_FIELDS_HELP}`),
24
+ operator: z.enum([...new Set([...BASE_FILTER_OPERATORS, ...CONTACT_VIEW_OPERATORS])]),
25
+ value: z.unknown().optional(),
26
+ includeEmpty: z.boolean().optional().describe(`Contact date fields only, with ${CONTACT_FILTER_INCLUDE_EMPTY_OPERATORS.join(", ")}: also keep leads without a date ` +
27
+ "(for example 'never contacted or last contact before 2026-09-01' on __contact_last_at)."),
28
+ });
9
29
  const viewConfigSchema = {
10
30
  organization_id: z.string().describe("Organization that owns the explicitly selected lead group."),
11
31
  lead_group_id: z.string().describe("Lead group ID. The view is shared with this group's organization."),
12
32
  name: z.string().min(1),
13
- filters: z.array(filterSchema),
14
- columns: z.array(z.string()).optional(),
33
+ filters: z.array(viewFilterSchema),
34
+ columns: z.array(z.string()).optional().describe("Optional columns, including __contact_* keys (never shown by default)."),
15
35
  sort: z.object({ field: z.string(), direction: z.enum(["asc", "desc"]) }).optional(),
16
36
  pagination: z.object({ page: z.number().int().min(1), limit: z.number().int().min(1).max(1000) }).optional(),
17
37
  };
@@ -35,7 +55,8 @@ export function registerLeadViewTools(server) {
35
55
  return handleToolError(error);
36
56
  }
37
57
  });
38
- server.tool("preview_lead_view", "Validate a proposed organization-shared lead view without writing it. Use this before create_lead_view; no data or preference is changed.", viewConfigSchema, async ({ organization_id, lead_group_id, name, filters, columns, sort, pagination }) => {
58
+ server.tool("preview_lead_view", "Validate a proposed organization-shared lead view without writing it. Use this before create_lead_view; no data or preference is changed. " +
59
+ "Filters may use the contact fields __contact_* (for example the 'À relancer' filter __contact_due is_true); refusals of the server are returned as is.", viewConfigSchema, async ({ organization_id, lead_group_id, name, filters, columns, sort, pagination }) => {
39
60
  try {
40
61
  const data = await getClient().post(`/api/lead-group/${encodeURIComponent(lead_group_id)}/views/preview`, { organizationId: organization_id, name, filters, columns, sort, pagination });
41
62
  return toolResult(data);
@@ -44,7 +65,8 @@ export function registerLeadViewTools(server) {
44
65
  return handleToolError(error);
45
66
  }
46
67
  });
47
- server.tool("create_lead_view", "Create an organization-shared saved view after previewing it. Idempotency key is required: retry the same request with the same key; changing the payload with that key conflicts. Update and delete are intentionally unavailable.", { ...viewConfigSchema, idempotency_key: z.string().min(8).max(200) }, async ({ organization_id, lead_group_id, name, filters, columns, sort, pagination, idempotency_key }) => {
68
+ server.tool("create_lead_view", "Create an organization-shared saved view after previewing it. Idempotency key is required: retry the same request with the same key; changing the payload with that key conflicts. Update and delete are intentionally unavailable. " +
69
+ "Filters may use the contact fields __contact_*, with within_last_days, older_than_days and includeEmpty (see preview_lead_view).", { ...viewConfigSchema, idempotency_key: z.string().min(8).max(200) }, async ({ organization_id, lead_group_id, name, filters, columns, sort, pagination, idempotency_key }) => {
48
70
  try {
49
71
  const data = await getClient().post(`/api/lead-group/${encodeURIComponent(lead_group_id)}/views`, { organizationId: organization_id, name, filters, columns, sort, pagination, idempotencyKey: idempotency_key });
50
72
  return toolResult(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "9.9.0",
3
+ "version": "9.10.0",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",