@agifyai/leadify-mcp 8.7.1 → 8.7.3

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
@@ -233,6 +233,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
233
233
  | `update_lead_group` | Modifier un groupe, y compris son `entity_kind`, avec réconciliation du schéma côté Leadify. |
234
234
  | `add_leads` | Ajouter un ou plusieurs leads à un groupe. |
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
+ | `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. |
236
237
  | `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination, dont le critère composé `event_filter`. |
237
238
  | `get_lead` | Récupérer les détails complets d'un lead par son ID. |
238
239
  | `update_lead` | Mettre à jour un ou plusieurs champs d'un lead existant. |
@@ -277,6 +278,9 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
277
278
  | `leadify_read_activity_feed` | Lire l'audit borné d'un lead ; le feed ne constitue jamais une preuve de réponse. |
278
279
  | `get_commercial_truth` | Réconcilier CRM, email, LinkedIn, Unipile et activité avec un statut, une fraîcheur et une action recommandée ; aucune écriture ni envoi. |
279
280
  | `compile_context_pack` | Compiler en lecture seule un Context Pack canonique depuis une cible explicite (`organization`, `lead_group`, `campaign`, `account`, `person`) ou un couple lead + campagne pour `WRITE_OUTREACH`. Retourne publications courantes, sources, freshness, contradictions et provenance, sans choisir de cible implicite. |
281
+ | `get_claim_contradiction` | Lire une contradiction canonique tenant-scopée avec ses deux claims immuables, leurs preuves, sa version et son éventuelle résolution auditée. |
282
+ | `resolve_claim_contradiction` | Arbitrer explicitement une contradiction relue par sélection d’un claim ou coexistence, avec justification, version, état attendu et clé d’idempotence ; aucun effet commercial. |
283
+ | `resolve_claim_contradictions_batch` | Soumettre jusqu’à 100 arbitrages explicites et relire chaque résultat ordonné, sans choix automatique ni résolution implicite. |
280
284
  | `get_context_workspace` | Lire le seul Context Workspace encore exposé et versionné : le `company_brain` d'une organisation explicitement sélectionnée. Retourne le brouillon et la dernière version publiée ; l'historique GTM reste stocké mais n'est plus exposé. |
281
285
  | `list_verticals` / `get_vertical` | Lister ou lire les Verticales JSON simples d’une organisation explicitement sélectionnée, avec leurs Personas et Offres liées. |
282
286
  | `create_vertical` | Créer une Verticale tenant-scoped en `DRAFT`, sans activation ni sélection implicite. Son schéma JSON fermé porte les règles sectorielles et `commercialExperience` ; les métadonnées de preuve/source/provenance et les champs propres à l’Offre sont refusés. |
@@ -320,3 +324,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
320
324
  | `append_fine_tuning` | Appendre du contenu à une section du Fine Tuning (nonNegotiableRules, pitfalls, structure, examples). Toujours en mode append — garantie contractuelle. |
321
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. |
322
326
  | `pipeline_next_lead` | Sélectionner le prochain lead à traiter (score descendant, sans message). Exclusion des IDs déjà vus, limit 1-5. |
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`.
package/dist/server.js CHANGED
@@ -20,6 +20,7 @@ import { registerCommercialTruthTools } from "./tools/commercial_truth.js";
20
20
  import { registerEventTools } from "./tools/events.js";
21
21
  import { registerContextEntityTools } from "./tools/context_entities.js";
22
22
  import { registerPersonEmploymentTools } from "./tools/person_employment.js";
23
+ import { registerClaimContradictionTools } from "./tools/claim_contradictions.js";
23
24
  import { MCP_SERVER_NAME, MCP_VERSION } from "./version.js";
24
25
  export function createServer() {
25
26
  const server = new McpServer({
@@ -33,6 +34,7 @@ export function createServer() {
33
34
  registerLeadTools(server);
34
35
  registerRelationshipTools(server);
35
36
  registerCanonicalBackfillTools(server);
37
+ registerClaimContradictionTools(server);
36
38
  registerPersonEmploymentTools(server);
37
39
  registerEventTools(server);
38
40
  registerCommercialTruthTools(server);
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type LeadifyClient } from "../client.js";
3
+ type ClaimContradictionClient = Pick<LeadifyClient, "get" | "post">;
4
+ export declare function registerClaimContradictionTools(server: McpServer, client?: ClaimContradictionClient): void;
5
+ export {};
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ import { getClient } from "../client.js";
3
+ import { handleToolError, toolResult } from "../types.js";
4
+ const id = z.string().trim().min(1).max(200);
5
+ const decision = z.discriminatedUnion("type", [
6
+ z.object({ type: z.literal("SELECT_CLAIM"), retained_claim_id: id }).strict(),
7
+ z.object({ type: z.literal("COEXIST") }).strict(),
8
+ ]);
9
+ const resolutionFields = {
10
+ decision,
11
+ justification: z.string().trim().min(8).max(2_000),
12
+ expected_version: z.number().int().min(1),
13
+ expected_status: z.literal("OPEN"),
14
+ idempotency_key: z.string().trim().min(8).max(220),
15
+ };
16
+ const resolution = z.object(resolutionFields).strict();
17
+ const resolutionItem = resolution.extend({ contradiction_id: id }).strict();
18
+ function apiResolution(input) {
19
+ return {
20
+ decision: input.decision.type === "SELECT_CLAIM"
21
+ ? { type: input.decision.type, retainedClaimId: input.decision.retained_claim_id }
22
+ : { type: input.decision.type },
23
+ justification: input.justification,
24
+ expectedVersion: input.expected_version,
25
+ expectedStatus: input.expected_status,
26
+ idempotencyKey: input.idempotency_key,
27
+ };
28
+ }
29
+ export function registerClaimContradictionTools(server, client = getClient()) {
30
+ server.registerTool("get_claim_contradiction", {
31
+ title: "Read a canonical claim contradiction",
32
+ description: "Read one tenant-scoped ClaimContradiction with both immutable claims, their evidence, provenance, current version and audited resolution. This never resolves the contradiction or changes commercial state.",
33
+ inputSchema: {
34
+ organization_id: id.describe("Organization explicitly selected through discover_leadify_context."),
35
+ contradiction_id: id.describe("ClaimContradiction ID obtained from a fresh Context Pack or prior read."),
36
+ },
37
+ annotations: { readOnlyHint: true },
38
+ }, async ({ organization_id, contradiction_id }) => {
39
+ try {
40
+ return toolResult(await client.get(`/api/claim-contradictions/${encodeURIComponent(contradiction_id)}`, new URLSearchParams({ organizationId: organization_id })));
41
+ }
42
+ catch (error) {
43
+ return handleToolError(error);
44
+ }
45
+ });
46
+ server.registerTool("resolve_claim_contradiction", {
47
+ title: "Resolve a canonical claim contradiction",
48
+ description: "Persist one explicit, audited decision after reading the contradiction. SELECT_CLAIM must name one member claim; COEXIST keeps both. The server rejects stale versions, changed state, cross-tenant access and divergent idempotent replays. This never qualifies leads, creates messages, changes campaigns or sends outreach.",
49
+ inputSchema: {
50
+ organization_id: id.describe("Explicit organization; an admin write key is required."),
51
+ contradiction_id: id.describe("ClaimContradiction ID read immediately before this decision."),
52
+ ...resolutionFields,
53
+ },
54
+ annotations: { destructiveHint: false, idempotentHint: true },
55
+ }, async ({ organization_id, contradiction_id, ...input }) => {
56
+ try {
57
+ return toolResult(await client.post(`/api/claim-contradictions/${encodeURIComponent(contradiction_id)}/resolve`, { organizationId: organization_id, ...apiResolution(input) }));
58
+ }
59
+ catch (error) {
60
+ return handleToolError(error);
61
+ }
62
+ });
63
+ server.registerTool("resolve_claim_contradictions_batch", {
64
+ title: "Resolve explicit claim contradictions in a batch",
65
+ description: "Submit up to 100 independent, explicit ClaimContradiction decisions. The API reports each item in order as resolved, replayed or error; there is no automatic choice, latest-wins rule or commercial side effect.",
66
+ inputSchema: {
67
+ organization_id: id.describe("Explicit organization shared by every decision; an admin write key is required."),
68
+ decisions: z.array(resolutionItem).min(1).max(100),
69
+ },
70
+ annotations: { destructiveHint: false, idempotentHint: true },
71
+ }, async ({ organization_id, decisions }) => {
72
+ try {
73
+ return toolResult(await client.post("/api/claim-contradictions/resolve-batch", {
74
+ organizationId: organization_id,
75
+ decisions: decisions.map(({ contradiction_id, ...input }) => ({
76
+ contradictionId: contradiction_id,
77
+ ...apiResolution(input),
78
+ })),
79
+ }));
80
+ }
81
+ catch (error) {
82
+ return handleToolError(error);
83
+ }
84
+ });
85
+ }
@@ -1,7 +1,49 @@
1
1
  import { z } from "zod";
2
2
  import { getClient } from "../client.js";
3
3
  import { handleToolError, toolResult } from "../types.js";
4
+ const evidenceDate = z.string().datetime();
5
+ const evidenceId = z.string().trim().min(1);
4
6
  export function registerPersonEmploymentTools(server, client = getClient()) {
7
+ server.tool("create_person_employment_evidence", "Create one tenant-scoped, idempotent employment proof for an exact Person and Company Lead. Use the returned evidence.id unchanged in upsert_person_leads_with_employment; do not invent or reuse Company or event evidence IDs.", {
8
+ organization_id: z.string().trim().min(1),
9
+ company_lead_id: z.string().trim().min(1),
10
+ person: z.object({
11
+ full_name: z.string().trim().min(1).max(500),
12
+ email: z.string().trim().email().max(320).optional(),
13
+ linkedin_url: z.string().trim().url().max(2_000).optional(),
14
+ }).strict(),
15
+ provider: z.string().trim().min(1).max(120),
16
+ source_url: z.string().trim().url().max(2_000),
17
+ excerpt: z.string().trim().min(1).max(20_000),
18
+ observed_at: evidenceDate,
19
+ verified_at: evidenceDate,
20
+ idempotency_key: z.string().trim().min(1).max(220),
21
+ }, async ({ organization_id, company_lead_id, person, provider, source_url, excerpt, observed_at, verified_at, idempotency_key }) => {
22
+ try {
23
+ return toolResult(await client.post("/api/person-employment/evidence", {
24
+ organizationId: organization_id,
25
+ companyLeadId: company_lead_id,
26
+ person: { fullName: person.full_name, email: person.email, linkedinUrl: person.linkedin_url },
27
+ provider,
28
+ sourceUrl: source_url,
29
+ excerpt,
30
+ observedAt: observed_at,
31
+ verifiedAt: verified_at,
32
+ idempotencyKey: idempotency_key,
33
+ }));
34
+ }
35
+ catch (error) {
36
+ return handleToolError(error);
37
+ }
38
+ });
39
+ server.tool("get_person_employment_evidence", "Read back one employment proof only within the explicitly selected tenant. Use this to obtain or verify the evidence.id before the atomic Person employment upsert.", { organization_id: z.string().trim().min(1), evidence_id: evidenceId }, async ({ organization_id, evidence_id }) => {
40
+ try {
41
+ return toolResult(await client.get(`/api/person-employment/evidence/${encodeURIComponent(evidence_id)}`, new URLSearchParams({ organizationId: organization_id })));
42
+ }
43
+ catch (error) {
44
+ return handleToolError(error);
45
+ }
46
+ });
5
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.", {
6
48
  people: z.array(z.object({
7
49
  organization_id: z.string().trim().min(1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "8.7.1",
3
+ "version": "8.7.3",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",