@agifyai/leadify-mcp 8.7.0 → 8.7.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
@@ -300,10 +300,10 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
300
300
  | `apply_canonical_identity_backfill` | Appliquer exactement un backfill d'identité relu par digest, sans outreach ni activation. |
301
301
  | `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. |
302
302
  | `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. |
303
- | `list_persona_contracts` / `get_persona_contract` | Lister ou lire les contrats Persona canoniques d’une organisation explicitement sélectionnée. |
304
- | `create_persona_contract` | Créer un Persona tenant-scoped et lié à une Verticale depuis un contrat canonique v2 complet et strict. Toute clé inconnue est refusée. |
305
- | `replace_persona_contract` / `patch_persona_contract` | Remplacer ou modifier un contrat canonique v2 avec verrou optimiste. Le document est revalidé intégralement avant l’écriture et aucune colonne Persona historique n’est exposée. |
306
- | `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. |
303
+ | `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. |
304
+ | `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. |
305
+ | `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. |
306
+ | `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. |
307
307
  | `list_data_sources` | Lister toutes les sources de données configurées (par pays puis nom). |
308
308
  | `create_data_source` | Créer une nouvelle source de données (admin uniquement). |
309
309
  | `update_data_source` | Modifier une source de données existante (admin uniquement). |
@@ -5,6 +5,10 @@ import { LeadifyApiError, handleToolError, toolResult } from "../types.js";
5
5
  * PRD-1423 hard cutover: Persona business content is represented only by the
6
6
  * versioned `personaContract`. These tools deliberately do not expose any
7
7
  * historical Persona field or field-specific patch operation.
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.
8
12
  */
9
13
  const tierSchema = z.object({
10
14
  rule: z.string(),
@@ -38,7 +42,6 @@ const commonContractSchema = z.object({
38
42
  enableSignals: z.boolean(),
39
43
  }).strict(),
40
44
  intelligence: z.object({
41
- activeTools: z.array(z.string()),
42
45
  painPoints: z.unknown().nullable(),
43
46
  icpStrategy: z.unknown().nullable(),
44
47
  cardAnalysisSections: z.unknown().nullable(),
@@ -67,7 +70,24 @@ const personExtensionSchema = z.object({
67
70
  const contractSchema = z.discriminatedUnion("kind", [
68
71
  commonContractSchema.extend({ kind: z.literal("COMPANY"), person: z.null() }).strict(),
69
72
  commonContractSchema.extend({ kind: z.literal("PERSON"), person: personExtensionSchema }).strict(),
70
- ]).describe("Complete strict Persona contract, schemaVersion 2.");
73
+ ]);
74
+ function withoutOutOfContractKeys(value) {
75
+ if (!value || typeof value !== "object" || Array.isArray(value))
76
+ return value;
77
+ const contract = structuredClone(value);
78
+ const intelligence = contract.intelligence;
79
+ if (intelligence && typeof intelligence === "object" && !Array.isArray(intelligence)) {
80
+ delete intelligence.activeTools;
81
+ }
82
+ return contract;
83
+ }
84
+ function parsePersistedContract(value) {
85
+ return contractSchema.parse(withoutOutOfContractKeys(value));
86
+ }
87
+ function parseWrittenContract(value) {
88
+ return parsePersistedContract(value);
89
+ }
90
+ 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.");
71
91
  const tenantScope = {
72
92
  organization_id: z
73
93
  .string()
@@ -82,15 +102,23 @@ const optimisticScope = {
82
102
  .datetime({ offset: true })
83
103
  .describe("Exact updatedAt obtained from a preceding read."),
84
104
  };
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
+ }
85
111
  function canonicalPersona(persona) {
86
112
  if (!persona.personaContract || typeof persona.personaContract !== "object") {
87
113
  throw new Error("PERSONA_CONTRACT_REQUIRED: run the audited Persona migration before using this MCP.");
88
114
  }
89
- const contract = contractSchema.parse(persona.personaContract);
115
+ const contract = parsePersistedContract(persona.personaContract);
90
116
  return {
91
117
  id: persona.id,
92
118
  name: persona.name,
119
+ description: persona.description ?? null,
93
120
  verticalId: persona.verticalId,
121
+ toolPackId: persona.toolPackId ?? null,
94
122
  updatedAt: persona.updatedAt,
95
123
  revision: persona.revision ?? 0,
96
124
  status: persona.status,
@@ -139,11 +167,13 @@ async function replaceContract(input) {
139
167
  }
140
168
  return input.client.post("/api/persona", {
141
169
  id: persona.id,
142
- name: persona.name,
170
+ name: input.name ?? persona.name,
143
171
  organizationId: input.organizationId,
144
172
  verticalId: persona.verticalId,
145
173
  expectedUpdatedAt: input.expectedUpdatedAt,
146
- personaContract: input.contract,
174
+ toolPackId: requireToolPackId(persona, input.toolPackId),
175
+ ...(input.description !== undefined ? { description: input.description } : {}),
176
+ personaContract: parseWrittenContract(input.contract),
147
177
  });
148
178
  }
149
179
  export function registerPersonaTools(server, injectedClient) {
@@ -168,33 +198,54 @@ export function registerPersonaTools(server, injectedClient) {
168
198
  return handleToolError(error);
169
199
  }
170
200
  });
171
- server.tool("create_persona_contract", "Create a Persona from one complete canonical contract. Historical Persona fields are not accepted.", {
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.", {
172
202
  ...tenantScope,
173
203
  vertical_id: z.string().trim().min(1),
174
204
  name: z.string().trim().min(1),
175
205
  description: z.string().optional(),
176
- contract: contractSchema,
177
- }, async ({ organization_id, vertical_id, name, description, contract }) => {
206
+ tool_pack_id: z
207
+ .string()
208
+ .trim()
209
+ .min(1)
210
+ .describe("Tenant-scoped tool pack. Not part of personaContract."),
211
+ contract: contractInputSchema,
212
+ }, async ({ organization_id, vertical_id, name, description, tool_pack_id, contract }) => {
178
213
  try {
179
214
  return toolResult(await client.post("/api/persona", {
180
215
  organizationId: organization_id,
181
216
  verticalId: vertical_id,
182
217
  name,
218
+ toolPackId: tool_pack_id,
183
219
  ...(description !== undefined ? { description } : {}),
184
- personaContract: contract,
220
+ personaContract: parseWrittenContract(contract),
185
221
  }));
186
222
  }
187
223
  catch (error) {
188
224
  return handleToolError(error);
189
225
  }
190
226
  });
191
- server.tool("replace_persona_contract", "Replace the complete canonical contract using optimistic concurrency.", { ...optimisticScope, id: z.string().min(1), contract: contractSchema }, async ({ organization_id, id, expected_updated_at, contract }) => {
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.", {
228
+ ...optimisticScope,
229
+ id: z.string().min(1),
230
+ name: z.string().trim().min(1).optional(),
231
+ 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."),
238
+ contract: contractInputSchema,
239
+ }, async ({ organization_id, id, expected_updated_at, name, description, tool_pack_id, contract }) => {
192
240
  try {
193
241
  return toolResult(await replaceContract({
194
242
  organizationId: organization_id,
195
243
  id,
196
244
  expectedUpdatedAt: expected_updated_at,
197
- contract,
245
+ name,
246
+ description,
247
+ toolPackId: tool_pack_id,
248
+ contract: parseWrittenContract(contract),
198
249
  client,
199
250
  }));
200
251
  }
@@ -202,12 +253,18 @@ export function registerPersonaTools(server, injectedClient) {
202
253
  return handleToolError(error);
203
254
  }
204
255
  });
205
- server.tool("patch_persona_contract", "Change one canonical contract path using optimistic concurrency. Removing a field is forbidden because the contract is strict and complete.", {
256
+ server.tool("patch_persona_contract", "Change one canonical contract path using optimistic concurrency. Removing a field is forbidden because the contract is strict and complete. intelligence.activeTools is not a contract path. Mutating an ACTIVE Persona returns it to DRAFT; call change_persona_status to reactivate.", {
206
257
  ...optimisticScope,
207
258
  id: z.string().min(1),
208
259
  path: z.string().trim().min(1),
209
260
  value: z.unknown(),
210
- }, async ({ organization_id, id, expected_updated_at, path, value }) => {
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 }) => {
211
268
  try {
212
269
  const persona = await fetchPersona(id, organization_id, client);
213
270
  const contract = canonicalPersona(persona).contract;
@@ -215,6 +272,7 @@ export function registerPersonaTools(server, injectedClient) {
215
272
  organizationId: organization_id,
216
273
  id,
217
274
  expectedUpdatedAt: expected_updated_at,
275
+ toolPackId: tool_pack_id,
218
276
  contract: contractSchema.parse(setContractPath(contract, path, value)),
219
277
  client,
220
278
  }));
@@ -223,7 +281,7 @@ export function registerPersonaTools(server, injectedClient) {
223
281
  return handleToolError(error);
224
282
  }
225
283
  });
226
- server.tool("change_persona_status", "Change only Persona lifecycle status; it never changes business contract content.", { ...optimisticScope, id: z.string().min(1), status: z.enum(["DRAFT", "ACTIVE", "ARCHIVED"]) }, async ({ organization_id, id, expected_updated_at, status }) => {
284
+ server.tool("change_persona_status", "Change only Persona lifecycle status; it never changes business contract content. After replace/patch, an ACTIVE Persona is typically returned to DRAFT and must be set back to ACTIVE for groups to resolve it again.", { ...optimisticScope, id: z.string().min(1), status: z.enum(["DRAFT", "ACTIVE", "ARCHIVED"]) }, async ({ organization_id, id, expected_updated_at, status }) => {
227
285
  try {
228
286
  return toolResult(await client.post(`/api/persona/${encodeURIComponent(id)}/status?organizationId=${encodeURIComponent(organization_id)}`, { expectedUpdatedAt: expected_updated_at, status }));
229
287
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "8.7.0",
3
+ "version": "8.7.1",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",