@agifyai/leadify-mcp 8.5.4 → 8.6.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 +27 -24
- package/dist/server.js +2 -0
- package/dist/tools/campaigns.js +58 -24
- package/dist/tools/context_entities.d.ts +668 -0
- package/dist/tools/context_entities.js +528 -0
- package/dist/tools/context_workspace.js +104 -49
- package/dist/tools/personas.js +142 -100
- package/dist/tools/pipeline.d.ts +4 -1
- package/dist/tools/pipeline.js +26 -35
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/tools/dataroom.d.ts +0 -2
- package/dist/tools/dataroom.js +0 -889
package/dist/tools/personas.js
CHANGED
|
@@ -63,6 +63,17 @@ export const CRM_SCHEMA_PACKS = [
|
|
|
63
63
|
"recruitment_hr",
|
|
64
64
|
"community_creator",
|
|
65
65
|
];
|
|
66
|
+
const optimisticPersonaMutationShape = {
|
|
67
|
+
organization_id: z
|
|
68
|
+
.string()
|
|
69
|
+
.trim()
|
|
70
|
+
.min(1)
|
|
71
|
+
.describe("Required organization ID; no tenant is inferred."),
|
|
72
|
+
expected_updated_at: z
|
|
73
|
+
.string()
|
|
74
|
+
.datetime({ offset: true })
|
|
75
|
+
.describe("Exact Persona updatedAt returned by the read that this edit is based on. A stale value is rejected with 409."),
|
|
76
|
+
};
|
|
66
77
|
const SECTOR_BASE_COMPATIBILITY = {
|
|
67
78
|
healthcare: ["base_person", "base_company"],
|
|
68
79
|
medtech: ["base_person", "base_company"],
|
|
@@ -174,10 +185,10 @@ class PersonaPatchError extends Error {
|
|
|
174
185
|
* Strategy: fast-path the direct GET; on 404, fall back to the permissive
|
|
175
186
|
* list endpoint and find by id. Other errors propagate immediately.
|
|
176
187
|
*/
|
|
177
|
-
async function fetchPersona(id, client = getClient()) {
|
|
188
|
+
async function fetchPersona(id, organizationId, client = getClient()) {
|
|
178
189
|
const directPath = `/api/persona/${encodeURIComponent(id)}`;
|
|
179
190
|
try {
|
|
180
|
-
const data = await client.get(directPath);
|
|
191
|
+
const data = await client.get(directPath, new URLSearchParams({ organizationId }));
|
|
181
192
|
if (!data || typeof data !== "object") {
|
|
182
193
|
throw new PersonaPatchError("read", directPath, null, data, "Unexpected response shape (expected an object).");
|
|
183
194
|
}
|
|
@@ -192,12 +203,11 @@ async function fetchPersona(id, client = getClient()) {
|
|
|
192
203
|
}
|
|
193
204
|
// 404 on direct GET — fall through to list-based lookup.
|
|
194
205
|
}
|
|
195
|
-
//
|
|
196
|
-
// (own org + globals for regular users; cross-org for super admins).
|
|
206
|
+
// Tenant-scoped fallback keeps the same explicit organization boundary.
|
|
197
207
|
const listPath = "/api/persona";
|
|
198
208
|
let listed;
|
|
199
209
|
try {
|
|
200
|
-
listed = await client.get(listPath);
|
|
210
|
+
listed = await client.get(listPath, new URLSearchParams({ organizationId }));
|
|
201
211
|
}
|
|
202
212
|
catch (err) {
|
|
203
213
|
if (err instanceof LeadifyApiError) {
|
|
@@ -218,12 +228,26 @@ async function fetchPersona(id, client = getClient()) {
|
|
|
218
228
|
}
|
|
219
229
|
return found;
|
|
220
230
|
}
|
|
221
|
-
async function postPersonaUpdate(
|
|
231
|
+
async function postPersonaUpdate(persona, fieldsToWrite, organizationId, expectedUpdatedAt, client = getClient()) {
|
|
222
232
|
const path = "/api/persona";
|
|
233
|
+
if (typeof persona.verticalId !== "string" || persona.verticalId.length === 0) {
|
|
234
|
+
throw new PersonaPatchError("write", path, 409, { error: "PERSONA_VERTICAL_REQUIRED" }, "Historical global/orphaned Personas cannot be edited.");
|
|
235
|
+
}
|
|
236
|
+
if (typeof persona.updatedAt !== "string" ||
|
|
237
|
+
new Date(persona.updatedAt).toISOString() !== new Date(expectedUpdatedAt).toISOString()) {
|
|
238
|
+
throw new PersonaPatchError("write", path, 409, {
|
|
239
|
+
error: "CONTEXT_ENTITY_UPDATE_CONFLICT",
|
|
240
|
+
expectedUpdatedAt,
|
|
241
|
+
actualUpdatedAt: persona.updatedAt,
|
|
242
|
+
}, "Re-read the Persona and reapply the intended patch.");
|
|
243
|
+
}
|
|
223
244
|
try {
|
|
224
245
|
return await client.post(path, {
|
|
225
|
-
id,
|
|
226
|
-
name,
|
|
246
|
+
id: persona.id,
|
|
247
|
+
name: persona.name,
|
|
248
|
+
organizationId,
|
|
249
|
+
verticalId: persona.verticalId,
|
|
250
|
+
expectedUpdatedAt,
|
|
227
251
|
...fieldsToWrite,
|
|
228
252
|
});
|
|
229
253
|
}
|
|
@@ -271,18 +295,29 @@ export function registerPersonaTools(server, client) {
|
|
|
271
295
|
"rewrite a persona. For partial edits, ALWAYS prefer the granular " +
|
|
272
296
|
"update_persona_* tools — they do safe read-modify-write merges and avoid " +
|
|
273
297
|
"wiping JSON columns you didn't intend to touch (each JSON column passed in " +
|
|
274
|
-
"this tool is replaced wholesale, so omitted sub-keys are LOST)."
|
|
298
|
+
"this tool is replaced wholesale, so omitted sub-keys are LOST). PRD-1415 requires " +
|
|
299
|
+
"one explicit organization and one explicit Vertical; global/orphaned Personas are rejected.", {
|
|
275
300
|
// core
|
|
276
301
|
id: z
|
|
277
302
|
.string()
|
|
278
303
|
.optional()
|
|
279
304
|
.describe("Persona ID to update an existing persona. Omit to create a new one."),
|
|
305
|
+
expected_updated_at: z
|
|
306
|
+
.string()
|
|
307
|
+
.datetime({ offset: true })
|
|
308
|
+
.optional()
|
|
309
|
+
.describe("Required on update: exact updatedAt from the Persona read this rewrite is based on. Omit on create."),
|
|
280
310
|
name: z.string().describe("Persona name (required)."),
|
|
281
311
|
organization_id: z
|
|
282
312
|
.string()
|
|
283
|
-
.
|
|
284
|
-
.
|
|
285
|
-
.describe("
|
|
313
|
+
.trim()
|
|
314
|
+
.min(1)
|
|
315
|
+
.describe("Required target organization ID. There is no implicit tenant and no global Persona."),
|
|
316
|
+
vertical_id: z
|
|
317
|
+
.string()
|
|
318
|
+
.trim()
|
|
319
|
+
.min(1)
|
|
320
|
+
.describe("Required Vertical ID in the same organization."),
|
|
286
321
|
description: z.string().optional().describe("Detailed persona description."),
|
|
287
322
|
// targeting
|
|
288
323
|
target_profile: z
|
|
@@ -386,6 +421,15 @@ export function registerPersonaTools(server, client) {
|
|
|
386
421
|
.describe("Snippets inserted at named points. scope: 'qualify' | 'outreach'; insertion_point: 'prefix' | 'phase_1_5' | 'phase_2_extra' | 'suffix'."),
|
|
387
422
|
}, async (params) => {
|
|
388
423
|
try {
|
|
424
|
+
if (!params.organization_id) {
|
|
425
|
+
throw new Error("organization_id is required; no implicit tenant or global Persona is allowed.");
|
|
426
|
+
}
|
|
427
|
+
if (!params.vertical_id) {
|
|
428
|
+
throw new Error("vertical_id is required; an orphan Persona cannot be created or updated.");
|
|
429
|
+
}
|
|
430
|
+
if (params.id !== undefined && params.expected_updated_at === undefined) {
|
|
431
|
+
throw new Error("expected_updated_at is required when updating a Persona. Re-read it first; stale writes are rejected with 409.");
|
|
432
|
+
}
|
|
389
433
|
if (params.id === undefined && params.schema_packs === undefined) {
|
|
390
434
|
throw new Error("schema_packs is required when creating a Persona. Call list_crm_schema_packs, then select exactly one base pack and at most one compatible sector pack.");
|
|
391
435
|
}
|
|
@@ -394,11 +438,15 @@ export function registerPersonaTools(server, client) {
|
|
|
394
438
|
if (selectionError)
|
|
395
439
|
throw new Error(selectionError);
|
|
396
440
|
}
|
|
397
|
-
const body = {
|
|
441
|
+
const body = {
|
|
442
|
+
name: params.name,
|
|
443
|
+
organizationId: params.organization_id,
|
|
444
|
+
verticalId: params.vertical_id,
|
|
445
|
+
};
|
|
398
446
|
if (params.id !== undefined)
|
|
399
447
|
body.id = params.id;
|
|
400
|
-
if (params.
|
|
401
|
-
body.
|
|
448
|
+
if (params.expected_updated_at !== undefined)
|
|
449
|
+
body.expectedUpdatedAt = params.expected_updated_at;
|
|
402
450
|
if (params.description !== undefined)
|
|
403
451
|
body.description = params.description;
|
|
404
452
|
if (params.target_profile !== undefined)
|
|
@@ -467,20 +515,21 @@ export function registerPersonaTools(server, client) {
|
|
|
467
515
|
"Reads the current Persona, validates exactly one workflow-compatible base and at most one " +
|
|
468
516
|
"compatible sector pack, then writes schemaPacks through the canonical Persona endpoint. " +
|
|
469
517
|
"The response includes the persisted Persona and readiness proofs for every linked group.", {
|
|
518
|
+
...optimisticPersonaMutationShape,
|
|
470
519
|
id: z.string().describe("Persona ID to update."),
|
|
471
520
|
schema_packs: z
|
|
472
521
|
.array(z.enum(CRM_SCHEMA_PACKS))
|
|
473
522
|
.min(1)
|
|
474
523
|
.describe("Complete replacement selection. Call list_crm_schema_packs first."),
|
|
475
|
-
}, async ({ id, schema_packs }) => {
|
|
524
|
+
}, async ({ organization_id, expected_updated_at, id, schema_packs }) => {
|
|
476
525
|
try {
|
|
477
526
|
const api = client ?? getClient();
|
|
478
|
-
const persona = await fetchPersona(id, api);
|
|
527
|
+
const persona = await fetchPersona(id, organization_id, api);
|
|
479
528
|
const workflowMode = persona.workflowMode === "company_first" ? "company_first" : "person_first";
|
|
480
529
|
const selectionError = crmSchemaPackSelectionError(schema_packs, workflowMode);
|
|
481
530
|
if (selectionError)
|
|
482
531
|
throw new Error(selectionError);
|
|
483
|
-
const data = await postPersonaUpdate(
|
|
532
|
+
const data = await postPersonaUpdate(persona, { schemaPacks: schema_packs }, organization_id, expected_updated_at, api);
|
|
484
533
|
return toolResult(data);
|
|
485
534
|
}
|
|
486
535
|
catch (error) {
|
|
@@ -494,6 +543,11 @@ export function registerPersonaTools(server, client) {
|
|
|
494
543
|
"qualification concerns (~37k chars instead of ~63k). Pass include_outreach=true to " +
|
|
495
544
|
"get the full payload — but for outreach-only reads prefer get_outreach_templates " +
|
|
496
545
|
"(a much smaller, dedicated read).", {
|
|
546
|
+
organization_id: z
|
|
547
|
+
.string()
|
|
548
|
+
.trim()
|
|
549
|
+
.min(1)
|
|
550
|
+
.describe("Required organization ID; no tenant is inferred."),
|
|
497
551
|
id: z.string().describe("Persona ID."),
|
|
498
552
|
include_outreach: z
|
|
499
553
|
.boolean()
|
|
@@ -502,9 +556,9 @@ export function registerPersonaTools(server, client) {
|
|
|
502
556
|
.describe("If true, keep the outreach trio in the response (outreachTemplates, " +
|
|
503
557
|
"outreachSignalRouting, outreachFields). Default false (SILO: outreach lives " +
|
|
504
558
|
"in its own tool)."),
|
|
505
|
-
}, async ({ id, include_outreach }) => {
|
|
559
|
+
}, async ({ organization_id, id, include_outreach }) => {
|
|
506
560
|
try {
|
|
507
|
-
const data = await (client ?? getClient()).get(`/api/persona/${encodeURIComponent(id)}
|
|
561
|
+
const data = await (client ?? getClient()).get(`/api/persona/${encodeURIComponent(id)}`, new URLSearchParams({ organizationId: organization_id }));
|
|
508
562
|
// SILO defense in depth: strip the outreach trio locally unless the
|
|
509
563
|
// caller explicitly opted in. Outreach data belongs to the dedicated
|
|
510
564
|
// outreach tools — never mix it with the persona read by default.
|
|
@@ -548,12 +602,13 @@ export function registerPersonaTools(server, client) {
|
|
|
548
602
|
// persona below to grab the outreach trio.
|
|
549
603
|
const groupPersona = (await (client ?? getClient()).get(`/api/lead-group/${encodeURIComponent(lead_group_id)}/persona`));
|
|
550
604
|
const personaId = groupPersona?.persona?.id;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
605
|
+
const organizationId = groupPersona?.organizationId;
|
|
606
|
+
if (!personaId || !organizationId) {
|
|
607
|
+
return toolError(`No usable tenant-scoped Persona is assigned to lead group "${lead_group_id}". ` +
|
|
608
|
+
`Assign one ACTIVE Persona linked to a Vertical before reading outreach templates.`);
|
|
554
609
|
}
|
|
555
610
|
// Step 2: fetch the full persona so we can project the trio.
|
|
556
|
-
const full = (await (client ?? getClient()).get(`/api/persona/${encodeURIComponent(personaId)}
|
|
611
|
+
const full = (await (client ?? getClient()).get(`/api/persona/${encodeURIComponent(personaId)}`, new URLSearchParams({ organizationId })));
|
|
557
612
|
// The API may return either a bare persona or {persona: {...}}.
|
|
558
613
|
const persona = full.persona ?? full;
|
|
559
614
|
const trio = {
|
|
@@ -573,32 +628,42 @@ export function registerPersonaTools(server, client) {
|
|
|
573
628
|
return handlePersonaToolError(error);
|
|
574
629
|
}
|
|
575
630
|
});
|
|
631
|
+
// ── change_persona_status ─────────────────────────────────────────────
|
|
632
|
+
server.tool("change_persona_status", "Change one tenant-scoped Persona between DRAFT, ACTIVE, and ARCHIVED. " +
|
|
633
|
+
"The exact expected_updated_at from the caller's read is mandatory. Activation requires an ACTIVE Vertical " +
|
|
634
|
+
"plus qualification-ready content; archive is blocked while durable references remain; ARCHIVED can only return to DRAFT.", {
|
|
635
|
+
...optimisticPersonaMutationShape,
|
|
636
|
+
id: z.string().min(1).describe("Persona ID."),
|
|
637
|
+
status: z.enum(["DRAFT", "ACTIVE", "ARCHIVED"]),
|
|
638
|
+
}, async ({ organization_id, expected_updated_at, id, status }) => {
|
|
639
|
+
try {
|
|
640
|
+
const query = new URLSearchParams({ organizationId: organization_id });
|
|
641
|
+
const data = await (client ?? getClient()).post(`/api/persona/${encodeURIComponent(id)}/status?${query.toString()}`, { expectedUpdatedAt: expected_updated_at, status });
|
|
642
|
+
return toolResult(data);
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
return handlePersonaToolError(error);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
576
648
|
// ── list_personas ──────────────────────────────────────────────────────
|
|
577
|
-
server.tool("list_personas", "List all
|
|
578
|
-
"and
|
|
649
|
+
server.tool("list_personas", "List all usable Persona records in one explicit organization. Historical global/orphaned " +
|
|
650
|
+
"Personas are excluded and available only through the reviewed migration inventory. Use this to discover persona IDs before calling " +
|
|
579
651
|
"get_persona, upsert_persona (for updates), or assigning a persona to a lead group. " +
|
|
580
652
|
"Returns a COMPACT view by default (id, name, organizationId, truncated description) " +
|
|
581
653
|
"to avoid blowing up context — full persona objects can be heavy (~10k chars each). " +
|
|
582
654
|
"Pass verbose=true to get the full objects, or call get_persona(id) for a single full persona.", {
|
|
583
655
|
organization_id: z
|
|
584
656
|
.string()
|
|
585
|
-
.
|
|
586
|
-
.
|
|
587
|
-
|
|
588
|
-
.boolean()
|
|
589
|
-
.optional()
|
|
590
|
-
.describe("If true (default), include global personas (organizationId === null) alongside org-scoped ones."),
|
|
657
|
+
.trim()
|
|
658
|
+
.min(1)
|
|
659
|
+
.describe("Required organization ID. The literal string 'null' and implicit tenant selection are forbidden."),
|
|
591
660
|
verbose: z
|
|
592
661
|
.boolean()
|
|
593
662
|
.optional()
|
|
594
663
|
.describe("If true, return full persona objects with every field. Defaults to false (compact view: id, name, organizationId, short description)."),
|
|
595
|
-
}, async ({ organization_id,
|
|
664
|
+
}, async ({ organization_id, verbose }) => {
|
|
596
665
|
try {
|
|
597
|
-
const params = new URLSearchParams();
|
|
598
|
-
if (organization_id !== undefined)
|
|
599
|
-
params.set("organizationId", organization_id);
|
|
600
|
-
if (include_globals !== undefined)
|
|
601
|
-
params.set("includeGlobals", String(include_globals));
|
|
666
|
+
const params = new URLSearchParams({ organizationId: organization_id });
|
|
602
667
|
const data = await (client ?? getClient()).get("/api/persona", params);
|
|
603
668
|
if (verbose)
|
|
604
669
|
return toolResult(data);
|
|
@@ -615,7 +680,9 @@ export function registerPersonaTools(server, client) {
|
|
|
615
680
|
return {
|
|
616
681
|
id: obj.id,
|
|
617
682
|
name: obj.name,
|
|
618
|
-
organizationId: obj.organizationId
|
|
683
|
+
organizationId: obj.organizationId,
|
|
684
|
+
verticalId: obj.verticalId,
|
|
685
|
+
status: obj.status,
|
|
619
686
|
...(desc !== undefined && {
|
|
620
687
|
description: desc.length > DESC_MAX ? `${desc.slice(0, DESC_MAX)}…` : desc,
|
|
621
688
|
}),
|
|
@@ -631,38 +698,6 @@ export function registerPersonaTools(server, client) {
|
|
|
631
698
|
return handlePersonaToolError(error);
|
|
632
699
|
}
|
|
633
700
|
});
|
|
634
|
-
// ── delete_persona ─────────────────────────────────────────────────────
|
|
635
|
-
server.tool("delete_persona", "Permanently delete a persona by ID. Irreversible. Fails if the persona is still assigned " +
|
|
636
|
-
"to any lead group or referenced by an active campaign. Always confirm with the user " +
|
|
637
|
-
"before calling this tool.", {
|
|
638
|
-
id: z.string().describe("Persona ID to delete."),
|
|
639
|
-
}, async ({ id }) => {
|
|
640
|
-
try {
|
|
641
|
-
const data = await (client ?? getClient()).delete(`/api/persona/${encodeURIComponent(id)}`);
|
|
642
|
-
return toolResult(data);
|
|
643
|
-
}
|
|
644
|
-
catch (error) {
|
|
645
|
-
return handlePersonaToolError(error);
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
// ── move_persona ───────────────────────────────────────────────────────
|
|
649
|
-
server.tool("move_persona", "Move a persona to a different organization (or make it global by passing null). " +
|
|
650
|
-
"Admin permission required. Useful when promoting an org-scoped persona to a shared " +
|
|
651
|
-
"global template, or reassigning between orgs.", {
|
|
652
|
-
id: z.string().describe("Persona ID to move."),
|
|
653
|
-
organization_id: z
|
|
654
|
-
.string()
|
|
655
|
-
.nullable()
|
|
656
|
-
.describe("Target organization ID (Clerk org ID). Pass null to make the persona global."),
|
|
657
|
-
}, async ({ id, organization_id }) => {
|
|
658
|
-
try {
|
|
659
|
-
const data = await (client ?? getClient()).post(`/api/persona/${encodeURIComponent(id)}/move`, { organizationId: organization_id });
|
|
660
|
-
return toolResult(data);
|
|
661
|
-
}
|
|
662
|
-
catch (error) {
|
|
663
|
-
return handlePersonaToolError(error);
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
701
|
// ════════════════════════════════════════════════════════════════════════
|
|
667
702
|
// GRANULAR PATCH TOOLS — read-modify-write, scoped to one section each
|
|
668
703
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -670,6 +705,7 @@ export function registerPersonaTools(server, client) {
|
|
|
670
705
|
server.tool("update_persona_identity", "Patch top-level scalar fields of a persona (name, description, " +
|
|
671
706
|
"output_language, workflow_mode, strict_match, enable_signals). " +
|
|
672
707
|
"Pass only the fields you want to change. Other persona sections are untouched.", {
|
|
708
|
+
...optimisticPersonaMutationShape,
|
|
673
709
|
id: z.string().describe("Persona ID."),
|
|
674
710
|
name: z.string().min(1).optional().describe("New persona name."),
|
|
675
711
|
description: z.string().nullable().optional().describe("Detailed description."),
|
|
@@ -691,7 +727,7 @@ export function registerPersonaTools(server, client) {
|
|
|
691
727
|
.boolean()
|
|
692
728
|
.optional()
|
|
693
729
|
.describe("Enable business signal detection for this persona."),
|
|
694
|
-
}, async ({ id, name, description, output_language, workflow_mode, strict_match, enable_signals, }) => {
|
|
730
|
+
}, async ({ organization_id, expected_updated_at, id, name, description, output_language, workflow_mode, strict_match, enable_signals, }) => {
|
|
695
731
|
try {
|
|
696
732
|
if (name === undefined &&
|
|
697
733
|
description === undefined &&
|
|
@@ -701,7 +737,7 @@ export function registerPersonaTools(server, client) {
|
|
|
701
737
|
enable_signals === undefined) {
|
|
702
738
|
return toolError("Provide at least one field to update.");
|
|
703
739
|
}
|
|
704
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
740
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
705
741
|
const body = {};
|
|
706
742
|
if (description !== undefined)
|
|
707
743
|
body.description = description;
|
|
@@ -713,7 +749,7 @@ export function registerPersonaTools(server, client) {
|
|
|
713
749
|
body.strictMatch = strict_match;
|
|
714
750
|
if (enable_signals !== undefined)
|
|
715
751
|
body.enableSignals = enable_signals;
|
|
716
|
-
const data = await postPersonaUpdate(
|
|
752
|
+
const data = await postPersonaUpdate({ ...current, name: name ?? current.name }, body, organization_id, expected_updated_at, client ?? getClient());
|
|
717
753
|
return toolResult(data);
|
|
718
754
|
}
|
|
719
755
|
catch (error) {
|
|
@@ -726,6 +762,7 @@ export function registerPersonaTools(server, client) {
|
|
|
726
762
|
"keys: job_titles, specialties, seniority_min, decision_maker, buying_power, " +
|
|
727
763
|
"target_company_types, min_company_size, interest_topics. Other targetProfile " +
|
|
728
764
|
"keys remain intact.", {
|
|
765
|
+
...optimisticPersonaMutationShape,
|
|
729
766
|
id: z.string().describe("Persona ID."),
|
|
730
767
|
patch: z
|
|
731
768
|
.record(z.unknown())
|
|
@@ -735,17 +772,17 @@ export function registerPersonaTools(server, client) {
|
|
|
735
772
|
.array(z.string())
|
|
736
773
|
.optional()
|
|
737
774
|
.describe("Keys to delete from targetProfile."),
|
|
738
|
-
}, async ({ id, patch, remove_keys }) => {
|
|
775
|
+
}, async ({ organization_id, expected_updated_at, id, patch, remove_keys }) => {
|
|
739
776
|
try {
|
|
740
777
|
if ((patch === undefined || Object.keys(patch).length === 0) &&
|
|
741
778
|
(remove_keys === undefined || remove_keys.length === 0)) {
|
|
742
779
|
return toolError("Provide at least one of: patch (non-empty), remove_keys (non-empty).");
|
|
743
780
|
}
|
|
744
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
781
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
745
782
|
const next = { ...asObject(current.targetProfile), ...(patch ?? {}) };
|
|
746
783
|
for (const k of remove_keys ?? [])
|
|
747
784
|
delete next[k];
|
|
748
|
-
const data = await postPersonaUpdate(
|
|
785
|
+
const data = await postPersonaUpdate(current, { targetProfile: next }, organization_id, expected_updated_at, client ?? getClient());
|
|
749
786
|
return toolResult(data);
|
|
750
787
|
}
|
|
751
788
|
catch (error) {
|
|
@@ -755,6 +792,7 @@ export function registerPersonaTools(server, client) {
|
|
|
755
792
|
// ── update_persona_disqualification ────────────────────────────────────
|
|
756
793
|
server.tool("update_persona_disqualification", "Patch the disqualificationCriteria JSON column (shallow merge). Common key: " +
|
|
757
794
|
"reasons (array of strings). Other keys remain intact.", {
|
|
795
|
+
...optimisticPersonaMutationShape,
|
|
758
796
|
id: z.string().describe("Persona ID."),
|
|
759
797
|
patch: z
|
|
760
798
|
.record(z.unknown())
|
|
@@ -764,17 +802,17 @@ export function registerPersonaTools(server, client) {
|
|
|
764
802
|
.array(z.string())
|
|
765
803
|
.optional()
|
|
766
804
|
.describe("Keys to delete from disqualificationCriteria."),
|
|
767
|
-
}, async ({ id, patch, remove_keys }) => {
|
|
805
|
+
}, async ({ organization_id, expected_updated_at, id, patch, remove_keys }) => {
|
|
768
806
|
try {
|
|
769
807
|
if ((patch === undefined || Object.keys(patch).length === 0) &&
|
|
770
808
|
(remove_keys === undefined || remove_keys.length === 0)) {
|
|
771
809
|
return toolError("Provide at least one of: patch (non-empty), remove_keys (non-empty).");
|
|
772
810
|
}
|
|
773
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
811
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
774
812
|
const next = { ...asObject(current.disqualificationCriteria), ...(patch ?? {}) };
|
|
775
813
|
for (const k of remove_keys ?? [])
|
|
776
814
|
delete next[k];
|
|
777
|
-
const data = await postPersonaUpdate(
|
|
815
|
+
const data = await postPersonaUpdate(current, { disqualificationCriteria: next }, organization_id, expected_updated_at, client ?? getClient());
|
|
778
816
|
return toolResult(data);
|
|
779
817
|
}
|
|
780
818
|
catch (error) {
|
|
@@ -785,6 +823,7 @@ export function registerPersonaTools(server, client) {
|
|
|
785
823
|
server.tool("update_persona_active_tools", "Replace the activeTools list wholesale, OR add/remove specific entries while " +
|
|
786
824
|
"preserving the rest. Use 'tools' to fully replace; use 'add' / 'remove' for " +
|
|
787
825
|
"incremental edits (deduped).", {
|
|
826
|
+
...optimisticPersonaMutationShape,
|
|
788
827
|
id: z.string().describe("Persona ID."),
|
|
789
828
|
tools: z
|
|
790
829
|
.array(z.string())
|
|
@@ -798,7 +837,7 @@ export function registerPersonaTools(server, client) {
|
|
|
798
837
|
.array(z.string())
|
|
799
838
|
.optional()
|
|
800
839
|
.describe("Tools to remove (no-op if absent)."),
|
|
801
|
-
}, async ({ id, tools, add, remove }) => {
|
|
840
|
+
}, async ({ organization_id, expected_updated_at, id, tools, add, remove }) => {
|
|
802
841
|
try {
|
|
803
842
|
if (tools === undefined && add === undefined && remove === undefined) {
|
|
804
843
|
return toolError("Provide one of: tools, add, remove.");
|
|
@@ -806,7 +845,7 @@ export function registerPersonaTools(server, client) {
|
|
|
806
845
|
if (tools !== undefined && (add !== undefined || remove !== undefined)) {
|
|
807
846
|
return toolError("'tools' is mutually exclusive with 'add' / 'remove'.");
|
|
808
847
|
}
|
|
809
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
848
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
810
849
|
let next;
|
|
811
850
|
if (tools !== undefined) {
|
|
812
851
|
next = tools;
|
|
@@ -819,7 +858,7 @@ export function registerPersonaTools(server, client) {
|
|
|
819
858
|
set.delete(t);
|
|
820
859
|
next = Array.from(set);
|
|
821
860
|
}
|
|
822
|
-
const data = await postPersonaUpdate(
|
|
861
|
+
const data = await postPersonaUpdate(current, { activeTools: next }, organization_id, expected_updated_at, client ?? getClient());
|
|
823
862
|
return toolResult(data);
|
|
824
863
|
}
|
|
825
864
|
catch (error) {
|
|
@@ -832,6 +871,7 @@ export function registerPersonaTools(server, client) {
|
|
|
832
871
|
"{criteria, examples, weight}. When passing a structured patch, sub-fields are " +
|
|
833
872
|
"merged into the existing structured tier (if any) — pass only what you want " +
|
|
834
873
|
"to change. Passing a string REPLACES the tier entirely.", {
|
|
874
|
+
...optimisticPersonaMutationShape,
|
|
835
875
|
id: z.string().describe("Persona ID."),
|
|
836
876
|
tier: z
|
|
837
877
|
.enum(["hot", "warm", "cold", "disqualified"])
|
|
@@ -848,7 +888,7 @@ export function registerPersonaTools(server, client) {
|
|
|
848
888
|
.passthrough(),
|
|
849
889
|
])
|
|
850
890
|
.describe("New tier value. String replaces wholesale; object is merged."),
|
|
851
|
-
}, async ({ id, tier, criteria }) => {
|
|
891
|
+
}, async ({ organization_id, expected_updated_at, id, tier, criteria }) => {
|
|
852
892
|
try {
|
|
853
893
|
const fieldMap = {
|
|
854
894
|
hot: "hotCriteria",
|
|
@@ -857,7 +897,7 @@ export function registerPersonaTools(server, client) {
|
|
|
857
897
|
disqualified: "disqualifiedCriteria",
|
|
858
898
|
};
|
|
859
899
|
const field = fieldMap[tier];
|
|
860
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
900
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
861
901
|
let nextValue;
|
|
862
902
|
if (typeof criteria === "string") {
|
|
863
903
|
nextValue = criteria;
|
|
@@ -869,7 +909,7 @@ export function registerPersonaTools(server, client) {
|
|
|
869
909
|
: {};
|
|
870
910
|
nextValue = { ...base, ...criteria };
|
|
871
911
|
}
|
|
872
|
-
const data = await postPersonaUpdate(
|
|
912
|
+
const data = await postPersonaUpdate(current, { [field]: nextValue }, organization_id, expected_updated_at, client ?? getClient());
|
|
873
913
|
return toolResult(data);
|
|
874
914
|
}
|
|
875
915
|
catch (error) {
|
|
@@ -881,6 +921,7 @@ export function registerPersonaTools(server, client) {
|
|
|
881
921
|
"list. Lookups are by client `name` (case-sensitive exact match). Reads the " +
|
|
882
922
|
"current lookalikeClients array, mutates the matched entry, writes the full " +
|
|
883
923
|
"list back. Use this instead of upsert_persona to avoid wiping the others.", {
|
|
924
|
+
...optimisticPersonaMutationShape,
|
|
884
925
|
id: z.string().describe("Persona ID."),
|
|
885
926
|
action: z
|
|
886
927
|
.enum(["add", "replace", "remove"])
|
|
@@ -892,7 +933,7 @@ export function registerPersonaTools(server, client) {
|
|
|
892
933
|
client: lookalikeClient
|
|
893
934
|
.optional()
|
|
894
935
|
.describe("Client payload. Required for 'add' and 'replace'. Schema: {name, sector?, size?, segment?, comparison_criteria?}."),
|
|
895
|
-
}, async ({ id, action, name, client: lookalikeClientInput }) => {
|
|
936
|
+
}, async ({ organization_id, expected_updated_at, id, action, name, client: lookalikeClientInput }) => {
|
|
896
937
|
try {
|
|
897
938
|
if ((action === "add" || action === "replace") && !lookalikeClientInput) {
|
|
898
939
|
return toolError(`client is required for action '${action}'.`);
|
|
@@ -900,7 +941,7 @@ export function registerPersonaTools(server, client) {
|
|
|
900
941
|
if ((action === "replace" || action === "remove") && !name) {
|
|
901
942
|
return toolError(`name is required for action '${action}'.`);
|
|
902
943
|
}
|
|
903
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
944
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
904
945
|
const list = asArray(current.lookalikeClients).map((c) => ({ ...c }));
|
|
905
946
|
if (action === "add") {
|
|
906
947
|
const newName = lookalikeClientInput.name;
|
|
@@ -925,9 +966,7 @@ export function registerPersonaTools(server, client) {
|
|
|
925
966
|
list.length = 0;
|
|
926
967
|
list.push(...filtered);
|
|
927
968
|
}
|
|
928
|
-
const data = await postPersonaUpdate(
|
|
929
|
-
lookalikeClients: list,
|
|
930
|
-
}, client ?? getClient());
|
|
969
|
+
const data = await postPersonaUpdate(current, { lookalikeClients: list }, organization_id, expected_updated_at, client ?? getClient());
|
|
931
970
|
return toolResult(data);
|
|
932
971
|
}
|
|
933
972
|
catch (error) {
|
|
@@ -938,6 +977,7 @@ export function registerPersonaTools(server, client) {
|
|
|
938
977
|
server.tool("update_persona_pain_point", "Add, replace, or remove a single pain point without re-sending the whole list. " +
|
|
939
978
|
"Pain points have no stable id — entries are addressed by zero-based index. " +
|
|
940
979
|
"Beware: indices shift after a remove, so re-fetch before chaining edits.", {
|
|
980
|
+
...optimisticPersonaMutationShape,
|
|
941
981
|
id: z.string().describe("Persona ID."),
|
|
942
982
|
action: z
|
|
943
983
|
.enum(["add", "replace", "remove"])
|
|
@@ -951,7 +991,7 @@ export function registerPersonaTools(server, client) {
|
|
|
951
991
|
pain: painPoint
|
|
952
992
|
.optional()
|
|
953
993
|
.describe("Pain payload {title?, description?}. Required for 'add' and 'replace'."),
|
|
954
|
-
}, async ({ id, action, index, pain }) => {
|
|
994
|
+
}, async ({ organization_id, expected_updated_at, id, action, index, pain }) => {
|
|
955
995
|
try {
|
|
956
996
|
if ((action === "add" || action === "replace") && !pain) {
|
|
957
997
|
return toolError(`pain is required for action '${action}'.`);
|
|
@@ -959,7 +999,7 @@ export function registerPersonaTools(server, client) {
|
|
|
959
999
|
if ((action === "replace" || action === "remove") && index === undefined) {
|
|
960
1000
|
return toolError(`index is required for action '${action}'.`);
|
|
961
1001
|
}
|
|
962
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
1002
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
963
1003
|
const list = asArray(current.painPoints).map((p) => ({ ...p }));
|
|
964
1004
|
if (action === "add") {
|
|
965
1005
|
list.push(pain);
|
|
@@ -976,7 +1016,7 @@ export function registerPersonaTools(server, client) {
|
|
|
976
1016
|
}
|
|
977
1017
|
list.splice(index, 1);
|
|
978
1018
|
}
|
|
979
|
-
const data = await postPersonaUpdate(
|
|
1019
|
+
const data = await postPersonaUpdate(current, { painPoints: list }, organization_id, expected_updated_at, client ?? getClient());
|
|
980
1020
|
return toolResult(data);
|
|
981
1021
|
}
|
|
982
1022
|
catch (error) {
|
|
@@ -988,6 +1028,7 @@ export function registerPersonaTools(server, client) {
|
|
|
988
1028
|
"tone_instructions, context). Each is a separate scalar JSON column — passing " +
|
|
989
1029
|
"one replaces it (pass null to clear). Other prompt fields and persona sections " +
|
|
990
1030
|
"remain intact.", {
|
|
1031
|
+
...optimisticPersonaMutationShape,
|
|
991
1032
|
id: z.string().describe("Persona ID."),
|
|
992
1033
|
normalization_rules: z
|
|
993
1034
|
.string()
|
|
@@ -1009,7 +1050,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1009
1050
|
.nullable()
|
|
1010
1051
|
.optional()
|
|
1011
1052
|
.describe("Markdown appended to the ICP prompt. Null clears it."),
|
|
1012
|
-
}, async ({ id, normalization_rules, signal_guidance, tone_instructions, context, }) => {
|
|
1053
|
+
}, async ({ organization_id, expected_updated_at, id, normalization_rules, signal_guidance, tone_instructions, context, }) => {
|
|
1013
1054
|
try {
|
|
1014
1055
|
if (normalization_rules === undefined &&
|
|
1015
1056
|
signal_guidance === undefined &&
|
|
@@ -1017,7 +1058,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1017
1058
|
context === undefined) {
|
|
1018
1059
|
return toolError("Provide at least one field to update.");
|
|
1019
1060
|
}
|
|
1020
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
1061
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
1021
1062
|
const body = {};
|
|
1022
1063
|
if (normalization_rules !== undefined)
|
|
1023
1064
|
body.normalizationRules = normalization_rules;
|
|
@@ -1027,7 +1068,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1027
1068
|
body.toneInstructions = tone_instructions;
|
|
1028
1069
|
if (context !== undefined)
|
|
1029
1070
|
body.context = context;
|
|
1030
|
-
const data = await postPersonaUpdate(
|
|
1071
|
+
const data = await postPersonaUpdate(current, body, organization_id, expected_updated_at, client ?? getClient());
|
|
1031
1072
|
return toolResult(data);
|
|
1032
1073
|
}
|
|
1033
1074
|
catch (error) {
|
|
@@ -1040,6 +1081,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1040
1081
|
"decisionDrivers[]}. Reads current icpStrategy, shallow-merges the keys you " +
|
|
1041
1082
|
"pass, optionally removes specified keys. Arrays (mainPains, keyObjections, " +
|
|
1042
1083
|
"decisionDrivers) are REPLACED wholesale when passed.", {
|
|
1084
|
+
...optimisticPersonaMutationShape,
|
|
1043
1085
|
id: z.string().describe("Persona ID."),
|
|
1044
1086
|
dealType: z
|
|
1045
1087
|
.string()
|
|
@@ -1069,7 +1111,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1069
1111
|
.array(z.string())
|
|
1070
1112
|
.optional()
|
|
1071
1113
|
.describe("Keys to delete from icpStrategy."),
|
|
1072
|
-
}, async ({ id, dealType, dreamOutcome, mainPains, keyObjections, decisionDrivers, patch, remove_keys, }) => {
|
|
1114
|
+
}, async ({ organization_id, expected_updated_at, id, dealType, dreamOutcome, mainPains, keyObjections, decisionDrivers, patch, remove_keys, }) => {
|
|
1073
1115
|
try {
|
|
1074
1116
|
if (dealType === undefined &&
|
|
1075
1117
|
dreamOutcome === undefined &&
|
|
@@ -1080,7 +1122,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1080
1122
|
(remove_keys === undefined || remove_keys.length === 0)) {
|
|
1081
1123
|
return toolError("Provide at least one field to update.");
|
|
1082
1124
|
}
|
|
1083
|
-
const current = await fetchPersona(id, client ?? getClient());
|
|
1125
|
+
const current = await fetchPersona(id, organization_id, client ?? getClient());
|
|
1084
1126
|
const next = { ...asObject(current.icpStrategy) };
|
|
1085
1127
|
if (dealType !== undefined)
|
|
1086
1128
|
next.dealType = dealType;
|
|
@@ -1096,7 +1138,7 @@ export function registerPersonaTools(server, client) {
|
|
|
1096
1138
|
Object.assign(next, patch);
|
|
1097
1139
|
for (const k of remove_keys ?? [])
|
|
1098
1140
|
delete next[k];
|
|
1099
|
-
const data = await postPersonaUpdate(
|
|
1141
|
+
const data = await postPersonaUpdate(current, { icpStrategy: next }, organization_id, expected_updated_at, client ?? getClient());
|
|
1100
1142
|
return toolResult(data);
|
|
1101
1143
|
}
|
|
1102
1144
|
catch (error) {
|
package/dist/tools/pipeline.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
|
|
2
|
+
import { type LeadifyClient } from "../client.js";
|
|
3
|
+
type PipelineClient = Pick<LeadifyClient, "get" | "post">;
|
|
4
|
+
export declare function registerPipelineTools(server: McpServer, client?: PipelineClient): void;
|
|
5
|
+
export {};
|