@agifyai/leadify-mcp 9.0.0 → 9.1.1

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
@@ -312,9 +312,9 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
312
312
  | `apply_canonical_identity_backfill` | Appliquer exactement un backfill d'identité relu par digest, sans outreach ni activation. |
313
313
  | `update_company_brain_sections` | Modifier uniquement les sections globales encore actives (`identity`, `positioning`, `allowedVocabulary`, `prohibitedVocabulary`, `legalConstraints`). Les anciennes sections Offre/Verticale sont filtrées et refusées. |
314
314
  | `publish_context_workspace` | Publier une révision prête du Company Brain après confirmation explicite (`confirm_publish: true`). Admin de l'organisation requis ; les raisons de non-readiness sont renvoyées par le serveur. |
315
- | `list_persona_contracts` / `get_persona_contract` | Lister ou lire les contrats Persona canoniques v2 d’une organisation explicitement sélectionnée. `intelligence` ne contient que `painPoints`, `icpStrategy` et `cardAnalysisSections` ; `activeTools` n’appartient pas au contrat. |
316
- | `create_persona_contract` | Créer un Persona tenant-scoped et lié à une Verticale depuis un contrat canonique v2 complet et strict. `tool_pack_id` est requis à la création et reste hors contrat. Toute clé inconnue est refusée. |
317
- | `replace_persona_contract` / `patch_persona_contract` | Remplacer ou modifier un contrat canonique v2 avec verrou optimiste. `name` / `description` sont acceptés sur replace. `tool_pack_id` est optionnel : s’il est omis, le pack persisté est réutilisé. Une Persona ACTIVE mutée repasse en DRAFT. |
315
+ | `list_persona_contracts` / `get_persona_contract` | Lister ou lire les contrats Persona canoniques v2 d’une organisation explicitement sélectionnée. Le contrat suit celui de Leadify : `intelligence` ne contient que `painPoints`, `outreach` que `outreachTemplates`, et les blocs retirés par PRD-1816 (`agentDomain`, `signalTierRules`, `enableSignals`, `icpStrategy`, `cardAnalysisSections`, `outreachSignalRouting`, `toneInstructions`, `outreachFields`) sont refusés à l’écriture et retirés à la lecture des anciens contrats ; `activeTools` n’appartient pas au contrat. |
316
+ | `create_persona_contract` | Créer un Persona tenant-scoped et lié à une Verticale depuis un contrat canonique v2 complet et strict. Aucun pack d’outils n’est demandé : `tool_pack_id`, s’il est encore envoyé, est ignoré et n’est jamais transmis à Leadify. Toute clé inconnue du contrat est refusée. |
317
+ | `replace_persona_contract` / `patch_persona_contract` | Remplacer ou modifier un contrat canonique v2 avec verrou optimiste. `name` / `description` sont acceptés sur replace. `tool_pack_id` est ignoré, comme à la création. Une Persona ACTIVE mutée repasse en DRAFT. |
318
318
  | `change_persona_status` | Passer un Persona entre `DRAFT`, `ACTIVE` et `ARCHIVED` avec le même verrou `updated_at`, readiness et protection des références. Après un replace/patch, rappeler ce tool vers `ACTIVE` pour que les groupes résolvent à nouveau la Persona. |
319
319
  | `list_data_sources` | Lister toutes les sources de données configurées (par pays puis nom). |
320
320
  | `create_data_source` | Créer une nouvelle source de données (admin uniquement). |
@@ -1,9 +1,12 @@
1
1
  import { z } from "zod";
2
2
  import { getClient } from "../client.js";
3
3
  import { toolResult, handleToolError } from "../types.js";
4
- const CAMPAIGN_STATUSES = ["DRAFT", "ACTIVE", "PAUSED", "COMPLETED"];
4
+ // PILOT (Leadify PRD-2353) : entre le backlog et le lancement, la campagne rédige des
5
+ // messages d'essai sans rien envoyer. Il s'entre et se quitte par les routes du pilote.
6
+ const CAMPAIGN_STATUSES = ["DRAFT", "PILOT", "ACTIVE", "PAUSED", "COMPLETED"];
5
7
  const CAMPAIGN_EFFECTIVE_STATES = [
6
8
  "DRAFT",
9
+ "PILOT",
7
10
  "SCHEDULED",
8
11
  "ACTIVE",
9
12
  "PAUSED_MANUAL",
@@ -6,15 +6,28 @@ import { LeadifyApiError, handleToolError, toolResult } from "../types.js";
6
6
  * versioned `personaContract`. These tools deliberately do not expose any
7
7
  * historical Persona field or field-specific patch operation.
8
8
  *
9
- * The published MCP contract must match the server `personaContractSchema`.
10
- * `intelligence.activeTools` is not part of that document; the tool pack lives
11
- * on `toolPackId`, outside the contract.
9
+ * The published MCP contract must match the server `personaContractSchema`
10
+ * (Leadify `apps/server/src/lib/persona-contract.ts`). `intelligence.activeTools`
11
+ * is not part of that document, and the blocks removed by PRD-1816 are refused
12
+ * on write and stripped from older stored contracts on read.
13
+ *
14
+ * PRD-2295: since PRD-2238 Leadify no longer requires or reads a tool pack.
15
+ * `tool_pack_id` stays accepted on writes so older callers keep working, but it
16
+ * is ignored and never sent to the API.
12
17
  */
13
18
  const tierSchema = z.object({
14
19
  rule: z.string(),
15
20
  expectedEvidence: z.string(),
16
21
  examples: z.array(z.string()).max(20),
17
22
  }).strict();
23
+ // PRD-1816: blocks Leadify removed from the contract.
24
+ const REMOVED_CONTRACT_KEYS = {
25
+ targeting: ["agentDomain"],
26
+ qualification: ["signalTierRules", "enableSignals"],
27
+ intelligence: ["icpStrategy", "cardAnalysisSections"],
28
+ outreach: ["outreachSignalRouting", "toneInstructions", "outreachFields"],
29
+ };
30
+ const REMOVED_TARGET_PROFILE_KEYS = ["buyerTypes", "coreDefinition", "productFamilies", "notTheDefinition"];
18
31
  const commonContractSchema = z.object({
19
32
  schemaVersion: z.literal(2),
20
33
  kind: z.enum(["PERSON", "COMPANY"]),
@@ -22,9 +35,21 @@ const commonContractSchema = z.object({
22
35
  targetProfile: z.unknown().nullable(),
23
36
  disqualificationCriteria: z.unknown().nullable(),
24
37
  schemaPacks: z.array(z.string()).max(2),
25
- agentDomain: z.string().nullable(),
26
38
  outputLanguage: z.string().nullable(),
27
- }).strict(),
39
+ }).strict().superRefine((targeting, ctx) => {
40
+ const profile = targeting.targetProfile;
41
+ if (!profile || typeof profile !== "object" || Array.isArray(profile))
42
+ return;
43
+ for (const key of REMOVED_TARGET_PROFILE_KEYS) {
44
+ if (profile[key] !== undefined) {
45
+ ctx.addIssue({
46
+ code: z.ZodIssueCode.custom,
47
+ path: ["targetProfile", key],
48
+ message: `targeting.targetProfile.${key} was removed (PRD-1816)`,
49
+ });
50
+ }
51
+ }
52
+ }),
28
53
  qualification: z.object({
29
54
  tiers: z.object({
30
55
  hot: tierSchema,
@@ -38,19 +63,12 @@ const commonContractSchema = z.object({
38
63
  level: z.enum(["GOLDEN", "CRITICAL", "STRONG", "INFO"]),
39
64
  justification: z.string().min(1),
40
65
  }).strict()).max(100),
41
- signalTierRules: z.unknown().nullable(),
42
- enableSignals: z.boolean(),
43
66
  }).strict(),
44
67
  intelligence: z.object({
45
68
  painPoints: z.unknown().nullable(),
46
- icpStrategy: z.unknown().nullable(),
47
- cardAnalysisSections: z.unknown().nullable(),
48
69
  }).strict(),
49
70
  outreach: z.object({
50
- outreachSignalRouting: z.unknown().nullable(),
51
71
  outreachTemplates: z.unknown().nullable(),
52
- toneInstructions: z.string().nullable(),
53
- outreachFields: z.unknown().nullable(),
54
72
  }).strict(),
55
73
  }).strict();
56
74
  const personExtensionSchema = z.object({
@@ -81,13 +99,38 @@ function withoutOutOfContractKeys(value) {
81
99
  }
82
100
  return contract;
83
101
  }
102
+ /** Older stored contracts may still carry the PRD-1816 blocks: read them without those blocks. */
103
+ function withoutRemovedContractKeys(value) {
104
+ const contract = withoutOutOfContractKeys(value);
105
+ if (!contract || typeof contract !== "object" || Array.isArray(contract))
106
+ return contract;
107
+ const record = contract;
108
+ for (const [section, keys] of Object.entries(REMOVED_CONTRACT_KEYS)) {
109
+ const block = record[section];
110
+ if (!block || typeof block !== "object" || Array.isArray(block))
111
+ continue;
112
+ for (const key of keys)
113
+ delete block[key];
114
+ }
115
+ const targeting = record.targeting;
116
+ const profile = targeting?.targetProfile;
117
+ if (profile && typeof profile === "object" && !Array.isArray(profile)) {
118
+ for (const key of REMOVED_TARGET_PROFILE_KEYS)
119
+ delete profile[key];
120
+ }
121
+ return record;
122
+ }
84
123
  function parsePersistedContract(value) {
85
- return contractSchema.parse(withoutOutOfContractKeys(value));
124
+ return contractSchema.parse(withoutRemovedContractKeys(value));
86
125
  }
87
126
  function parseWrittenContract(value) {
88
- return parsePersistedContract(value);
127
+ return contractSchema.parse(withoutOutOfContractKeys(value));
89
128
  }
90
129
  const contractInputSchema = z.preprocess(withoutOutOfContractKeys, contractSchema).describe("Complete strict Persona contract, schemaVersion 2. intelligence.activeTools is not a contract field and is stripped if sent.");
130
+ const ignoredToolPackId = z
131
+ .string()
132
+ .optional()
133
+ .describe("Deprecated and ignored: Leadify no longer uses tool packs. Never sent to the API; omit it.");
91
134
  const tenantScope = {
92
135
  organization_id: z
93
136
  .string()
@@ -102,12 +145,6 @@ const optimisticScope = {
102
145
  .datetime({ offset: true })
103
146
  .describe("Exact updatedAt obtained from a preceding read."),
104
147
  };
105
- function requireToolPackId(persona, override) {
106
- const toolPackId = override?.trim() || persona.toolPackId?.trim();
107
- if (!toolPackId)
108
- throw new Error("PERSONA_TOOL_PACK_REQUIRED");
109
- return toolPackId;
110
- }
111
148
  function canonicalPersona(persona) {
112
149
  if (!persona.personaContract || typeof persona.personaContract !== "object") {
113
150
  throw new Error("PERSONA_CONTRACT_REQUIRED: run the audited Persona migration before using this MCP.");
@@ -118,7 +155,6 @@ function canonicalPersona(persona) {
118
155
  name: persona.name,
119
156
  description: persona.description ?? null,
120
157
  verticalId: persona.verticalId,
121
- toolPackId: persona.toolPackId ?? null,
122
158
  updatedAt: persona.updatedAt,
123
159
  revision: persona.revision ?? 0,
124
160
  status: persona.status,
@@ -171,7 +207,6 @@ async function replaceContract(input) {
171
207
  organizationId: input.organizationId,
172
208
  verticalId: persona.verticalId,
173
209
  expectedUpdatedAt: input.expectedUpdatedAt,
174
- toolPackId: requireToolPackId(persona, input.toolPackId),
175
210
  ...(input.description !== undefined ? { description: input.description } : {}),
176
211
  personaContract: parseWrittenContract(input.contract),
177
212
  });
@@ -198,24 +233,19 @@ export function registerPersonaTools(server, injectedClient) {
198
233
  return handleToolError(error);
199
234
  }
200
235
  });
201
- server.tool("create_persona_contract", "Create a Persona from one complete canonical contract v2. intelligence.activeTools is not part of the contract. tool_pack_id is required on create and lives outside the contract.", {
236
+ server.tool("create_persona_contract", "Create a Persona from one complete canonical contract v2. intelligence.activeTools is not part of the contract. No tool pack is needed.", {
202
237
  ...tenantScope,
203
238
  vertical_id: z.string().trim().min(1),
204
239
  name: z.string().trim().min(1),
205
240
  description: z.string().optional(),
206
- tool_pack_id: z
207
- .string()
208
- .trim()
209
- .min(1)
210
- .describe("Tenant-scoped tool pack. Not part of personaContract."),
241
+ tool_pack_id: ignoredToolPackId,
211
242
  contract: contractInputSchema,
212
- }, async ({ organization_id, vertical_id, name, description, tool_pack_id, contract }) => {
243
+ }, async ({ organization_id, vertical_id, name, description, contract }) => {
213
244
  try {
214
245
  return toolResult(await client.post("/api/persona", {
215
246
  organizationId: organization_id,
216
247
  verticalId: vertical_id,
217
248
  name,
218
- toolPackId: tool_pack_id,
219
249
  ...(description !== undefined ? { description } : {}),
220
250
  personaContract: parseWrittenContract(contract),
221
251
  }));
@@ -224,19 +254,14 @@ export function registerPersonaTools(server, injectedClient) {
224
254
  return handleToolError(error);
225
255
  }
226
256
  });
227
- server.tool("replace_persona_contract", "Replace the complete canonical contract using optimistic concurrency. Omitting tool_pack_id reuses the persisted pack. Mutating an ACTIVE Persona returns it to DRAFT; call change_persona_status to reactivate.", {
257
+ server.tool("replace_persona_contract", "Replace the complete canonical contract using optimistic concurrency. Mutating an ACTIVE Persona returns it to DRAFT; call change_persona_status to reactivate.", {
228
258
  ...optimisticScope,
229
259
  id: z.string().min(1),
230
260
  name: z.string().trim().min(1).optional(),
231
261
  description: z.string().optional(),
232
- tool_pack_id: z
233
- .string()
234
- .trim()
235
- .min(1)
236
- .optional()
237
- .describe("Optional pack change. When omitted, the persisted toolPackId is reused."),
262
+ tool_pack_id: ignoredToolPackId,
238
263
  contract: contractInputSchema,
239
- }, async ({ organization_id, id, expected_updated_at, name, description, tool_pack_id, contract }) => {
264
+ }, async ({ organization_id, id, expected_updated_at, name, description, contract }) => {
240
265
  try {
241
266
  return toolResult(await replaceContract({
242
267
  organizationId: organization_id,
@@ -244,7 +269,6 @@ export function registerPersonaTools(server, injectedClient) {
244
269
  expectedUpdatedAt: expected_updated_at,
245
270
  name,
246
271
  description,
247
- toolPackId: tool_pack_id,
248
272
  contract: parseWrittenContract(contract),
249
273
  client,
250
274
  }));
@@ -258,13 +282,8 @@ export function registerPersonaTools(server, injectedClient) {
258
282
  id: z.string().min(1),
259
283
  path: z.string().trim().min(1),
260
284
  value: z.unknown(),
261
- tool_pack_id: z
262
- .string()
263
- .trim()
264
- .min(1)
265
- .optional()
266
- .describe("Optional pack change. When omitted, the persisted toolPackId is reused."),
267
- }, async ({ organization_id, id, expected_updated_at, path, value, tool_pack_id }) => {
285
+ tool_pack_id: ignoredToolPackId,
286
+ }, async ({ organization_id, id, expected_updated_at, path, value }) => {
268
287
  try {
269
288
  const persona = await fetchPersona(id, organization_id, client);
270
289
  const contract = canonicalPersona(persona).contract;
@@ -272,7 +291,6 @@ export function registerPersonaTools(server, injectedClient) {
272
291
  organizationId: organization_id,
273
292
  id,
274
293
  expectedUpdatedAt: expected_updated_at,
275
- toolPackId: tool_pack_id,
276
294
  contract: contractSchema.parse(setContractPath(contract, path, value)),
277
295
  client,
278
296
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "9.0.0",
3
+ "version": "9.1.1",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",