@agifyai/leadify-mcp 9.8.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 +27 -3
- package/dist/server.js +2 -0
- package/dist/tools/agent_outreach.js +4 -3
- package/dist/tools/agent_outreach_schemas.d.ts +18 -0
- package/dist/tools/agent_outreach_schemas.js +3 -0
- package/dist/tools/contact_tracking_contract.d.ts +18 -0
- package/dist/tools/contact_tracking_contract.js +59 -0
- package/dist/tools/lead_move.d.ts +44 -44
- package/dist/tools/outreach_exclusion.js +11 -5
- package/dist/tools/prospection_tracking.d.ts +6 -0
- package/dist/tools/prospection_tracking.js +95 -0
- package/dist/tools/views.d.ts +24 -2
- package/dist/tools/views.js +26 -4
- package/package.json +1 -1
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
|
|
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
|
|
@@ -581,7 +603,9 @@ Gmail confirmé, sans message Gmail en attente ou inconnu. Les invitations sont
|
|
|
581
603
|
comptées séparément. `replies_to_handle` sélectionne les réponses à traiter dans
|
|
582
604
|
leur propre fil. `filters.signal_filter` accepte `detected_since`,
|
|
583
605
|
`detected_before`, `levels` (`STRONG`, `GOLDEN`, `CRITICAL`) et `scope`
|
|
584
|
-
(`PERSON`, `COMPANY`, `BOTH`).
|
|
606
|
+
(`PERSON`, `COMPANY`, `BOTH`). `filters.qualified: true` garde les personnes
|
|
607
|
+
dont la qualification est terminée ; `filters.qualification_levels` (`hot`,
|
|
608
|
+
`warm`, `cold`, de une à trois valeurs) filtre la température (PRD-2650). L'absence d'historique connu n'est pas la preuve
|
|
585
609
|
qu'une personne n'a jamais été contactée.
|
|
586
610
|
|
|
587
611
|
La sélection expire après dix minutes. Passer son `nextCursor` dans `cursor` avec
|
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
|
};
|
|
@@ -91,10 +91,11 @@ export function registerAgentOutreachTools(server, client = getClient()) {
|
|
|
91
91
|
}
|
|
92
92
|
register("list_outreach_programs", "List compact accessible autonomous outreach programs and the server contract version. Pagination: pass nextCursor unchanged as after_id.", z.object({ organization_id: identifier, after_id: identifier.optional(), limit: z.number().int().min(1).max(50).default(25) }).strict(), async (v) => requireOutreachVersion(await client.get("/api/agent-outreach/programs", params(v.organization_id, { afterId: v.after_id, limit: v.limit })), "1.0.0"));
|
|
93
93
|
register("get_outreach_program_context", "Read one program's rules, authorization revision and permitted sender identities. Cannot authorize, resume, raise caps, grant rights or change senders.", z.object(scope).strict(), async (v) => requireOutreachVersion(await client.get(programPath(v.program_id), params(v.organization_id)), "1.0.0"));
|
|
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. 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"));
|
|
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."
|
|
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}`;
|
|
@@ -99,6 +99,8 @@ export declare const filters: z.ZodObject<{
|
|
|
99
99
|
unknown: z.ZodOptional<z.ZodBoolean>;
|
|
100
100
|
reply: z.ZodOptional<z.ZodEnum<["TO_HANDLE", "WAITING", "NONE"]>>;
|
|
101
101
|
excluded: z.ZodOptional<z.ZodBoolean>;
|
|
102
|
+
qualified: z.ZodOptional<z.ZodBoolean>;
|
|
103
|
+
qualification_levels: z.ZodOptional<z.ZodArray<z.ZodEnum<["hot", "warm", "cold"]>, "many">>;
|
|
102
104
|
}, "strict", z.ZodTypeAny, {
|
|
103
105
|
group_id?: string | undefined;
|
|
104
106
|
unknown?: boolean | undefined;
|
|
@@ -125,6 +127,8 @@ export declare const filters: z.ZodObject<{
|
|
|
125
127
|
pending?: boolean | undefined;
|
|
126
128
|
reply?: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
127
129
|
excluded?: boolean | undefined;
|
|
130
|
+
qualified?: boolean | undefined;
|
|
131
|
+
qualification_levels?: ("hot" | "warm" | "cold")[] | undefined;
|
|
128
132
|
}, {
|
|
129
133
|
group_id?: string | undefined;
|
|
130
134
|
unknown?: boolean | undefined;
|
|
@@ -151,6 +155,8 @@ export declare const filters: z.ZodObject<{
|
|
|
151
155
|
pending?: boolean | undefined;
|
|
152
156
|
reply?: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
153
157
|
excluded?: boolean | undefined;
|
|
158
|
+
qualified?: boolean | undefined;
|
|
159
|
+
qualification_levels?: ("hot" | "warm" | "cold")[] | undefined;
|
|
154
160
|
}>;
|
|
155
161
|
export declare const contextModules: z.ZodObject<{
|
|
156
162
|
prospect: z.ZodString;
|
|
@@ -970,6 +976,8 @@ export declare const querySchema: z.ZodObject<{
|
|
|
970
976
|
unknown: z.ZodOptional<z.ZodBoolean>;
|
|
971
977
|
reply: z.ZodOptional<z.ZodEnum<["TO_HANDLE", "WAITING", "NONE"]>>;
|
|
972
978
|
excluded: z.ZodOptional<z.ZodBoolean>;
|
|
979
|
+
qualified: z.ZodOptional<z.ZodBoolean>;
|
|
980
|
+
qualification_levels: z.ZodOptional<z.ZodArray<z.ZodEnum<["hot", "warm", "cold"]>, "many">>;
|
|
973
981
|
}, "strict", z.ZodTypeAny, {
|
|
974
982
|
group_id?: string | undefined;
|
|
975
983
|
unknown?: boolean | undefined;
|
|
@@ -996,6 +1004,8 @@ export declare const querySchema: z.ZodObject<{
|
|
|
996
1004
|
pending?: boolean | undefined;
|
|
997
1005
|
reply?: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
998
1006
|
excluded?: boolean | undefined;
|
|
1007
|
+
qualified?: boolean | undefined;
|
|
1008
|
+
qualification_levels?: ("hot" | "warm" | "cold")[] | undefined;
|
|
999
1009
|
}, {
|
|
1000
1010
|
group_id?: string | undefined;
|
|
1001
1011
|
unknown?: boolean | undefined;
|
|
@@ -1022,6 +1032,8 @@ export declare const querySchema: z.ZodObject<{
|
|
|
1022
1032
|
pending?: boolean | undefined;
|
|
1023
1033
|
reply?: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
1024
1034
|
excluded?: boolean | undefined;
|
|
1035
|
+
qualified?: boolean | undefined;
|
|
1036
|
+
qualification_levels?: ("hot" | "warm" | "cold")[] | undefined;
|
|
1025
1037
|
}>>;
|
|
1026
1038
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
1027
1039
|
cursor: z.ZodOptional<z.ZodString>;
|
|
@@ -1054,6 +1066,8 @@ export declare const querySchema: z.ZodObject<{
|
|
|
1054
1066
|
pending?: boolean | undefined;
|
|
1055
1067
|
reply?: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
1056
1068
|
excluded?: boolean | undefined;
|
|
1069
|
+
qualified?: boolean | undefined;
|
|
1070
|
+
qualification_levels?: ("hot" | "warm" | "cold")[] | undefined;
|
|
1057
1071
|
};
|
|
1058
1072
|
limit: number;
|
|
1059
1073
|
organization_id: string;
|
|
@@ -1090,6 +1104,8 @@ export declare const querySchema: z.ZodObject<{
|
|
|
1090
1104
|
pending?: boolean | undefined;
|
|
1091
1105
|
reply?: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
1092
1106
|
excluded?: boolean | undefined;
|
|
1107
|
+
qualified?: boolean | undefined;
|
|
1108
|
+
qualification_levels?: ("hot" | "warm" | "cold")[] | undefined;
|
|
1093
1109
|
} | undefined;
|
|
1094
1110
|
limit?: number | undefined;
|
|
1095
1111
|
cursor?: string | undefined;
|
|
@@ -1761,6 +1777,8 @@ export declare function queryBody(v: z.infer<typeof querySchema>): {
|
|
|
1761
1777
|
unknown: boolean | undefined;
|
|
1762
1778
|
reply: "TO_HANDLE" | "WAITING" | "NONE" | undefined;
|
|
1763
1779
|
excluded: boolean | undefined;
|
|
1780
|
+
qualified: boolean | undefined;
|
|
1781
|
+
qualificationLevels: ("hot" | "warm" | "cold")[] | undefined;
|
|
1764
1782
|
};
|
|
1765
1783
|
};
|
|
1766
1784
|
export declare function decisionBody(d: z.infer<typeof decision>): {
|
|
@@ -21,6 +21,8 @@ export const filters = z.object({
|
|
|
21
21
|
last_confirmed_outbound_before: z.object({ channel: z.enum(["any", "GMAIL", "LINKEDIN"]), before: date }).strict().optional(),
|
|
22
22
|
counts: z.array(count).max(6).optional(), never_on: z.array(target).max(3).optional(),
|
|
23
23
|
pending: z.boolean().optional(), unknown: z.boolean().optional(), reply: z.enum(["TO_HANDLE", "WAITING", "NONE"]).optional(), excluded: z.boolean().optional(),
|
|
24
|
+
// PRD-2650 (server: PRD-2649): qualification finished, and its temperature.
|
|
25
|
+
qualified: z.boolean().optional(), qualification_levels: z.array(z.enum(["hot", "warm", "cold"])).min(1).max(3).optional(),
|
|
24
26
|
}).strict();
|
|
25
27
|
// PRD-2632 : one fingerprint per module, copied from get_outreach_context_summary.decisionContext.modules.
|
|
26
28
|
export const contextModules = z.object({ prospect: version, company: version, workspace: version, company_brain: version, vertical: version, offers: version,
|
|
@@ -89,6 +91,7 @@ export function queryBody(v) {
|
|
|
89
91
|
groupId: f.group_id, signalFilter: f.signal_filter && { detectedSince: f.signal_filter.detected_since, detectedBefore: f.signal_filter.detected_before, levels: f.signal_filter.levels, scope: f.signal_filter.scope },
|
|
90
92
|
lastConfirmedOutboundBefore: f.last_confirmed_outbound_before, counts: f.counts, neverOn: f.never_on,
|
|
91
93
|
pending: f.pending, unknown: f.unknown, reply: f.reply, excluded: f.excluded,
|
|
94
|
+
qualified: f.qualified, qualificationLevels: f.qualification_levels,
|
|
92
95
|
} };
|
|
93
96
|
}
|
|
94
97
|
export function decisionBody(d) {
|
|
@@ -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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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).
|
|
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
|
-
|
|
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.
|
|
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)
|
|
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
|
+
}
|
package/dist/tools/views.d.ts
CHANGED
|
@@ -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" | "
|
|
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" | "
|
|
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;
|
package/dist/tools/views.js
CHANGED
|
@@ -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(
|
|
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."
|
|
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."
|
|
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);
|