@agifyai/leadify-mcp 8.7.2 → 8.7.4
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
|
@@ -229,8 +229,8 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
229
229
|
| `test_api_key` | Vérifier que la clé API configurée est valide (health check). |
|
|
230
230
|
| `list_lead_groups` | Lister les groupes accessibles d'une organisation explicitement sélectionnée, en vue compacte. |
|
|
231
231
|
| `get_lead_group` | Consulter les métadonnées compactes d'un groupe accessible par son ID. |
|
|
232
|
-
| `create_lead_group` | Créer un groupe
|
|
233
|
-
| `update_lead_group` | Modifier un groupe,
|
|
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
|
+
| `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
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
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. |
|
|
@@ -278,6 +278,9 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
278
278
|
| `leadify_read_activity_feed` | Lire l'audit borné d'un lead ; le feed ne constitue jamais une preuve de réponse. |
|
|
279
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. |
|
|
280
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. |
|
|
281
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é. |
|
|
282
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. |
|
|
283
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. |
|
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
|
+
}
|
|
@@ -48,6 +48,11 @@ export function registerLeadGroupTools(server, writeClient) {
|
|
|
48
48
|
.optional()
|
|
49
49
|
.describe("ICP persona ID to assign. Must belong to the same org or be global. " +
|
|
50
50
|
"Omit to create the group without a persona."),
|
|
51
|
+
offer_id: z
|
|
52
|
+
.string()
|
|
53
|
+
.nullable()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Active offer ID to assign with persona_id. Must belong to the same organization."),
|
|
51
56
|
}, async (params) => {
|
|
52
57
|
try {
|
|
53
58
|
const body = {
|
|
@@ -66,6 +71,8 @@ export function registerLeadGroupTools(server, writeClient) {
|
|
|
66
71
|
body.disabledTools = params.disabled_tools;
|
|
67
72
|
if (params.persona_id !== undefined)
|
|
68
73
|
body.personaId = params.persona_id;
|
|
74
|
+
if (params.offer_id !== undefined)
|
|
75
|
+
body.offerId = params.offer_id;
|
|
69
76
|
const data = await (writeClient ?? getClient()).post("/api/lead-group", body);
|
|
70
77
|
return toolResult(data);
|
|
71
78
|
}
|
|
@@ -91,6 +98,11 @@ export function registerLeadGroupTools(server, writeClient) {
|
|
|
91
98
|
.nullable()
|
|
92
99
|
.optional()
|
|
93
100
|
.describe("New ICP persona ID. Pass null to unassign. Must belong to the same org or be global."),
|
|
101
|
+
offer_id: z
|
|
102
|
+
.string()
|
|
103
|
+
.nullable()
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Active offer ID to assign with persona_id. Pass null only when removing the binding."),
|
|
94
106
|
persona_types: z
|
|
95
107
|
.array(z.string())
|
|
96
108
|
.optional()
|
|
@@ -116,6 +128,8 @@ export function registerLeadGroupTools(server, writeClient) {
|
|
|
116
128
|
body.entityKind = params.entity_kind;
|
|
117
129
|
if (params.persona_id !== undefined)
|
|
118
130
|
body.personaId = params.persona_id;
|
|
131
|
+
if (params.offer_id !== undefined)
|
|
132
|
+
body.offerId = params.offer_id;
|
|
119
133
|
if (params.persona_types !== undefined)
|
|
120
134
|
body.personaTypes = params.persona_types;
|
|
121
135
|
if (params.company_profile !== undefined)
|