@agifyai/leadify-mcp 8.7.4 → 8.7.5

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
@@ -231,7 +231,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
231
231
  | `get_lead_group` | Consulter les métadonnées compactes d'un groupe accessible par son ID. |
232
232
  | `create_lead_group` | Créer un groupe, choisir son type canonique optionnel et rattacher atomiquement `persona_id` avec l’`offer_id` ACTIVE du même tenant. |
233
233
  | `update_lead_group` | Modifier un groupe avec réconciliation du schéma ; lorsque le serveur exige un contexte complet, transmettre ensemble `persona_id` et `offer_id` sans inférer l’offre. |
234
- | `add_leads` | Ajouter un ou plusieurs leads à un groupe. |
234
+ | `add_leads` | Import plat générique vers un groupe : ne crée aucune relation d’emploi, aucun rattachement parent ni identité canonique. Ne jamais l’utiliser pour rattacher une Personne à une Company (voir `upsert_person_leads_with_employment`). |
235
235
  | `upsert_person_leads_with_employment` | Créer ou réconcilier atomiquement des Person Leads, PERSON, EMPLOYED_BY, projections et historique, sans effet commercial. |
236
236
  | `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. |
237
237
  | `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination, dont le critère composé `event_filter`. |
@@ -325,6 +325,51 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
325
325
  | `set_fine_tuning_output_config` | Remplacer uniquement la configuration métier de sorties d’un Fine Tuning (`linkedinConnection`, `linkedinMessage`, `email`, activations et quantités). Préserve Markdown, langues et fallback ; ne génère, ne planifie, n’active ni n’envoie aucun outreach. |
326
326
  | `pipeline_next_lead` | Sélectionner le prochain lead à traiter (score descendant, sans message). Exclusion des IDs déjà vus, limit 1-5. |
327
327
 
328
- ### `source-icp-prospects` : preuve d’emploi
329
-
330
- Pour chaque candidat accepté, créer `create_person_employment_evidence` avec le tenant, la Company Lead, l’identité Person, la provenance et une clé idempotente. Relire si nécessaire avec `get_person_employment_evidence`, puis transmettre exclusivement l’`evidence.id` retourné dans `evidence_ids` de `upsert_person_leads_with_employment`.
328
+ ### `source-icp-prospects` : sourcing Personne rattachée à une Company
329
+
330
+ `add_leads` est un import générique de leads plats et ne garantit aucune
331
+ relation d’emploi : une Personne sourcée pour une entreprise doit toujours
332
+ passer par `upsert_person_leads_with_employment`, seul chemin d’écriture
333
+ Person admis pour le sourcing. Ce tool crée ou réutilise la Person Lead et la
334
+ Person, crée ou réutilise une relation active `EMPLOYED_BY` vers la cible
335
+ légale de la Company (Legal Entity ou Establishment compatible), maintient les
336
+ projections `parent_lead` / `child_leads` consommées par l’interface, écrit
337
+ uniquement les champs admis par le schéma du groupe Personne, ajoute
338
+ l’historique sans écraser l’existant, applique l’idempotence par entrée et
339
+ relit les objets avant de répondre. Chaque entrée est transactionnelle
340
+ (échec = rollback complet de l’entrée) ; un batch peut mêler succès et échecs
341
+ par entrée mais ne rend jamais un succès ambigu.
342
+
343
+ Chaîne obligatoire, dans l’ordre :
344
+
345
+ 1. Vérifier l’identité canonique de la Company Lead. Si elle n’expose ni
346
+ Account ni Legal Entity / Establishment compatible, la préparer d’abord
347
+ avec `preview_canonical_identity_backfill` puis
348
+ `apply_canonical_identity_backfill` en rejouant exactement le digest
349
+ prévisualisé. Toute contradiction d’identité bloque proprement : jamais de
350
+ rapprochement au nom approximatif.
351
+ 2. Pour chaque candidat accepté, créer `create_person_employment_evidence`
352
+ avec le tenant, la Company Lead exacte, l’identité Person, la provenance
353
+ et une clé idempotente. Relire si nécessaire avec
354
+ `get_person_employment_evidence`, puis transmettre exclusivement
355
+ l’`evidence.id` retourné dans `evidence_ids` de
356
+ `upsert_person_leads_with_employment`.
357
+ 3. Appeler `upsert_person_leads_with_employment` avec les IDs exacts
358
+ (`organization_id`, `person_group_id`, `company_lead_id` identiques au
359
+ même tenant), les seuls champs admis par le schéma Personne,
360
+ `card_history_append`, et une `idempotency_key` unique par entrée.
361
+ Pour réparer une Person Lead orpheline exacte sans doublon, passer son
362
+ `person_lead_id` : la réparation suit la même transaction et les mêmes
363
+ contrôles, et préserve LinkedIn, sources, `card_history`,
364
+ `qualification_status`, `hide_from_campaign` et les champs sans lien avec
365
+ la relation.
366
+ 4. Vérifier dans les deux sens avant de considérer la Personne rattachée :
367
+ `get_lead` sur la Personne (parent résolvable) et
368
+ `list_canonical_relationships` filtré sur la Person puis sur la cible
369
+ légale (lien `EMPLOYED_BY` actif). Une relation ou projection manquante
370
+ est un échec explicite, jamais un succès partiel.
371
+
372
+ Aucune mutation de sourcing ne qualifie le lead, n’enrichit les contacts
373
+ (email/téléphone), ne génère de message, n’inscrit en campagne et n’active
374
+ aucun canal externe : le readback porte `noSend: true` et
375
+ `externalActivation: 0`.
@@ -116,7 +116,11 @@ export function registerLeadTools(server, client = getClient()) {
116
116
  });
117
117
  }
118
118
  // ── add_leads ──────────────────────────────────────────────────────────
119
- server.tool("add_leads", "Add one or more leads to a specific lead group in Leadify. Provide the group ID " +
119
+ server.tool("add_leads", "Flat generic lead import only: add one or more leads to a specific lead group in Leadify. " +
120
+ "It stores plain field values and guarantees NO employment relationship, NO parent link and NO canonical identity. " +
121
+ "It must never be used to attach a Person to a Company: Person sourcing attached to a Company must use " +
122
+ "upsert_person_leads_with_employment (after create_person_employment_evidence, and preview_canonical_identity_backfill plus " +
123
+ "apply_canonical_identity_backfill when the Company lacks canonical identity). Provide the group ID " +
120
124
  "and an array of lead objects with any combination of fields (email, firstName, " +
121
125
  "lastName, company, jobTitle, phone, custom fields, card_* fields, percent_* " +
122
126
  "fields, etc.). Canonical relation fields are read-only here: use " +
@@ -130,7 +134,8 @@ export function registerLeadTools(server, client = getClient()) {
130
134
  .array(leadInputSchema)
131
135
  .describe("Array of lead objects. Each object is a key-value map of field names to values. " +
132
136
  "Common fields: email, firstName, lastName, company, jobTitle, phone, linkedin, " +
133
- "website, seniority, specialty, age. location must be {city, region?, postalCode?, countryCode, street?}; geo is computed by Leadify. Also supports card_* fields, " +
137
+ "website, seniority, specialty, age. The company field is flat display text only and never creates an employment relationship or parent link. " +
138
+ "location must be {city, region?, postalCode?, countryCode, street?}; geo is computed by Leadify. Also supports card_* fields, " +
134
139
  "percent_* fields and boolean flags (decisionMaking, excluded). Canonical relation fields are rejected."),
135
140
  is_select_fields: z
136
141
  .array(z.string())
@@ -44,7 +44,7 @@ export function registerPersonEmploymentTools(server, client = getClient()) {
44
44
  return handleToolError(error);
45
45
  }
46
46
  });
47
- server.tool("upsert_person_leads_with_employment", "Atomically create or reconcile Person Leads with their canonical PERSON identity, EMPLOYED_BY relationship, parent_lead/child_leads projections, evidence and append-only card_history. company_lead_id must be exact and already project an Account plus Legal Entity or compatible Establishment. Each batch entry is independently transactional and idempotent. This is the only supported Person write path for sourcing; never fall back to add_leads, canonical backfill, or separate relationship writes. It never qualifies, enriches contact details, sends outreach, creates campaigns, or activates anything.", {
47
+ server.tool("upsert_person_leads_with_employment", "Atomically create or reconcile Person Leads with their canonical PERSON identity, an active EMPLOYED_BY relationship to the Company legal target, parent_lead/child_leads projections, evidence and append-only card_history. company_lead_id must be exact and already project an Account plus Legal Entity or compatible Establishment: when it does not, prepare it first with preview_canonical_identity_backfill then apply_canonical_identity_backfill using the returned digest, and never fuzzy-match identity. Pass person_lead_id to repair one exact orphan Person Lead without creating a duplicate; otherwise a new Person Lead is created. fields must only contain keys admitted by the Person group schema and are forwarded verbatim so the backend rejects unknown keys; history is append-only via card_history_append and existing card_history is never overwritten. Each batch entry is independently transactional (a failure rolls that entry back entirely) and idempotent by idempotency_key; a changed payload under the same key is rejected, never merged. The success readback carries personLeadId, personId, companyLeadId, legalTarget, relationshipId, relationship, projections, cardHistory, noSend and externalActivation: verify the EMPLOYED_BY link in both directions (get_lead plus list_canonical_relationships) before treating the Person as attached. This is the only supported Person write path for sourcing; never fall back to add_leads, direct canonical backfill writes, or separate relationship writes. It never creates emails or phones, never qualifies the lead, never generates messages, never enrolls in a campaign and never activates any external channel.", {
48
48
  people: z.array(z.object({
49
49
  organization_id: z.string().trim().min(1),
50
50
  person_group_id: z.string().trim().min(1),
@@ -52,7 +52,7 @@ export function registerPersonEmploymentTools(server, client = getClient()) {
52
52
  person_lead_id: z.string().trim().min(1).optional(),
53
53
  fields: z.record(z.unknown()),
54
54
  card_history_append: z.string().trim().min(1).max(100_000),
55
- evidence_ids: z.array(z.string().trim().min(1)).min(1).max(100),
55
+ evidence_ids: z.array(z.string().trim().min(1)).min(1).max(100).optional(),
56
56
  idempotency_key: z.string().trim().min(1).max(220),
57
57
  }).strict()).min(1).max(100),
58
58
  }, async ({ people }) => {
@@ -65,7 +65,7 @@ export function registerPersonEmploymentTools(server, client = getClient()) {
65
65
  personLeadId: person.person_lead_id,
66
66
  fields: person.fields,
67
67
  cardHistoryAppend: person.card_history_append,
68
- evidenceIds: person.evidence_ids,
68
+ ...(person.evidence_ids !== undefined ? { evidenceIds: person.evidence_ids } : {}),
69
69
  idempotencyKey: person.idempotency_key,
70
70
  })),
71
71
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "8.7.4",
3
+ "version": "8.7.5",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",