@agifyai/leadify-mcp 3.6.3 → 4.0.0

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.
@@ -40,31 +40,6 @@ const signalTierRule = z.object({
40
40
  signal_id: z.string().optional(),
41
41
  tier: z.enum(["hot", "warm", "cold"]).optional(),
42
42
  });
43
- const outreachTemplate = z
44
- .object({
45
- id: z.string().optional(),
46
- channel: z.string().optional(),
47
- signal: z
48
- .string()
49
- .optional()
50
- .describe("Must exist in outreach_signal_routing.signals."),
51
- field_name: z.string().optional(),
52
- signal_context: z.string().optional(),
53
- purpose: z.string().optional(),
54
- example: z.string().optional(),
55
- max_length: z.number().optional(),
56
- label: z.string().optional(),
57
- subject: z.string().optional(),
58
- body: z.string().optional(),
59
- })
60
- .passthrough();
61
- const outreachField = z
62
- .object({
63
- key: z.string().optional(),
64
- label: z.string().optional(),
65
- source: z.string().optional(),
66
- })
67
- .passthrough();
68
43
  const extraPromptPhase = z.object({
69
44
  scope: z.enum(["qualify", "outreach"]).optional(),
70
45
  insertion_point: z
@@ -72,10 +47,6 @@ const extraPromptPhase = z.object({
72
47
  .describe("Where in the prompt to inject this snippet."),
73
48
  content: z.string().describe("Snippet content."),
74
49
  });
75
- const senderOverride = z.object({
76
- name: z.string().describe("Sender display name (required)."),
77
- title: z.string().optional(),
78
- });
79
50
  // ICP strategy — mental map consumed verbatim by the outreach agent preflight.
80
51
  const icpStrategyShape = {
81
52
  dealType: z.string().optional(),
@@ -107,32 +78,6 @@ function mapCardSection(s) {
107
78
  out.instructions = s.instructions;
108
79
  return out;
109
80
  }
110
- function mapOutreachTemplate(t) {
111
- const out = {};
112
- if (t.id !== undefined)
113
- out.id = t.id;
114
- if (t.channel !== undefined)
115
- out.channel = t.channel;
116
- if (t.signal !== undefined)
117
- out.signal = t.signal;
118
- if (t.field_name !== undefined)
119
- out.fieldName = t.field_name;
120
- if (t.signal_context !== undefined)
121
- out.signalContext = t.signal_context;
122
- if (t.purpose !== undefined)
123
- out.purpose = t.purpose;
124
- if (t.example !== undefined)
125
- out.example = t.example;
126
- if (t.max_length !== undefined)
127
- out.maxLength = t.max_length;
128
- if (t.label !== undefined)
129
- out.label = t.label;
130
- if (t.subject !== undefined)
131
- out.subject = t.subject;
132
- if (t.body !== undefined)
133
- out.body = t.body;
134
- return out;
135
- }
136
81
  function mapSignalTierRule(r) {
137
82
  const out = {};
138
83
  if (r.signal_id !== undefined)
@@ -314,10 +259,6 @@ export function registerPersonaTools(server) {
314
259
  .record(z.unknown())
315
260
  .optional()
316
261
  .describe("Exclusion rules. Common keys: reasons (array of strings)."),
317
- messaging_guidelines: z
318
- .record(z.unknown())
319
- .optional()
320
- .describe("Message approach. Common keys: tone, language, can_mention, must_not_mention, value_propositions."),
321
262
  active_tools: z
322
263
  .array(z.string())
323
264
  .optional()
@@ -351,11 +292,6 @@ export function registerPersonaTools(server) {
351
292
  .boolean()
352
293
  .optional()
353
294
  .describe("If true, reject non-perfectly-aligned leads."),
354
- agent_domain: z
355
- .string()
356
- .nullable()
357
- .optional()
358
- .describe("Agent identity string (e.g. 'for medtech companies')."),
359
295
  workflow_mode: z
360
296
  .enum(["company_first", "person_first"])
361
297
  .nullable()
@@ -398,37 +334,6 @@ export function registerPersonaTools(server) {
398
334
  })
399
335
  .optional()
400
336
  .describe("Rules mapping signal → tier (hot/warm/cold)."),
401
- // outreach
402
- outreach_signal_routing: z
403
- .object({
404
- signal_source_field: z
405
- .string()
406
- .optional()
407
- .describe("Card field to read (e.g. card_signals)."),
408
- default_signal: z
409
- .string()
410
- .optional()
411
- .describe("Must exist in 'signals' array."),
412
- signals: z
413
- .array(z.string())
414
- .optional()
415
- .describe("Exhaustive list of recognized signals."),
416
- })
417
- .optional()
418
- .describe("Extract signal source from card lead + default signal. All three sub-fields are optional."),
419
- outreach_templates: z
420
- .array(outreachTemplate)
421
- .optional()
422
- .describe("Outreach templates by channel. Each template: {id, channel, signal, field_name, signal_context, purpose, example, max_length, label, subject, body}."),
423
- outreach_fields: z
424
- .array(outreachField)
425
- .optional()
426
- .describe("Variables referenceable in templates. Each: {key, label, source}."),
427
- tone_instructions: z
428
- .string()
429
- .nullable()
430
- .optional()
431
- .describe("Free-form markdown for voice / tone guidance."),
432
337
  context: z
433
338
  .string()
434
339
  .nullable()
@@ -438,38 +343,8 @@ export function registerPersonaTools(server) {
438
343
  .array(extraPromptPhase)
439
344
  .optional()
440
345
  .describe("Snippets inserted at named points. scope: 'qualify' | 'outreach'; insertion_point: 'prefix' | 'phase_1_5' | 'phase_2_extra' | 'suffix'."),
441
- // sender
442
- sender_override: senderOverride
443
- .nullable()
444
- .optional()
445
- .describe("Override SDR identity at persona level ({name, title})."),
446
346
  }, async (params) => {
447
347
  try {
448
- // SILO guard: the upsert escape hatch must NOT be used for outreach
449
- // data. Outreach is its own concern with its own granular tools
450
- // (update_persona_outreach_template, update_persona_outreach_signal_routing).
451
- // Reject early with a clear pointer so the caller knows what to use.
452
- if (params.outreach_signal_routing !== undefined ||
453
- params.outreach_templates !== undefined ||
454
- params.outreach_fields !== undefined) {
455
- return toolError("SILO: upsert_persona does NOT accept outreach fields. " +
456
- "Use the dedicated outreach tools instead: " +
457
- "update_persona_outreach_template (add/replace/remove a template), " +
458
- "update_persona_outreach_signal_routing (patch the signal routing block). " +
459
- "For a read of the trio, use get_outreach_templates({ lead_group_id }).", {
460
- rejectedParams: [
461
- ...(params.outreach_signal_routing !== undefined
462
- ? ["outreach_signal_routing"]
463
- : []),
464
- ...(params.outreach_templates !== undefined
465
- ? ["outreach_templates"]
466
- : []),
467
- ...(params.outreach_fields !== undefined
468
- ? ["outreach_fields"]
469
- : []),
470
- ],
471
- });
472
- }
473
348
  const body = { name: params.name };
474
349
  if (params.id !== undefined)
475
350
  body.id = params.id;
@@ -481,8 +356,6 @@ export function registerPersonaTools(server) {
481
356
  body.targetProfile = params.target_profile;
482
357
  if (params.disqualification_criteria !== undefined)
483
358
  body.disqualificationCriteria = params.disqualification_criteria;
484
- if (params.messaging_guidelines !== undefined)
485
- body.messagingGuidelines = params.messaging_guidelines;
486
359
  if (params.active_tools !== undefined)
487
360
  body.activeTools = params.active_tools;
488
361
  if (params.hot_criteria !== undefined)
@@ -501,8 +374,6 @@ export function registerPersonaTools(server) {
501
374
  body.icpStrategy = params.icp_strategy;
502
375
  if (params.strict_match !== undefined)
503
376
  body.strictMatch = params.strict_match;
504
- if (params.agent_domain !== undefined)
505
- body.agentDomain = params.agent_domain;
506
377
  if (params.workflow_mode !== undefined)
507
378
  body.workflowMode = params.workflow_mode;
508
379
  if (params.output_language !== undefined)
@@ -528,21 +399,11 @@ export function registerPersonaTools(server) {
528
399
  rules: (params.signal_tier_rules.rules ?? []).map(mapSignalTierRule),
529
400
  };
530
401
  }
531
- // SILO: outreach_* params are accepted on the schema (so users discover
532
- // they exist here) but ALWAYS rejected by the guard at the top of this
533
- // handler. Outreach data flows through update_persona_outreach_template
534
- // and update_persona_outreach_signal_routing only. The unreachable
535
- // mapping code below was removed — TypeScript narrows the params to
536
- // `never` after the guard, so the old branches would never run anyway.
537
- if (params.tone_instructions !== undefined)
538
- body.toneInstructions = params.tone_instructions;
539
402
  if (params.context !== undefined)
540
403
  body.context = params.context;
541
404
  if (params.extra_prompt_phases !== undefined)
542
405
  body.extraPromptPhases =
543
406
  params.extra_prompt_phases.map(mapExtraPromptPhase);
544
- if (params.sender_override !== undefined)
545
- body.senderOverride = params.sender_override;
546
407
  const data = await getClient().post("/api/persona", body);
547
408
  return toolResult(data);
548
409
  }
@@ -730,17 +591,12 @@ export function registerPersonaTools(server) {
730
591
  // GRANULAR PATCH TOOLS — read-modify-write, scoped to one section each
731
592
  // ════════════════════════════════════════════════════════════════════════
732
593
  // ── update_persona_identity ────────────────────────────────────────────
733
- server.tool("update_persona_identity", "Patch top-level scalar fields of a persona (name, description, agent_domain, " +
734
- "output_language, workflow_mode, strict_match, enable_signals, sender_override). " +
594
+ server.tool("update_persona_identity", "Patch top-level scalar fields of a persona (name, description, " +
595
+ "output_language, workflow_mode, strict_match, enable_signals). " +
735
596
  "Pass only the fields you want to change. Other persona sections are untouched.", {
736
597
  id: z.string().describe("Persona ID."),
737
598
  name: z.string().min(1).optional().describe("New persona name."),
738
599
  description: z.string().nullable().optional().describe("Detailed description."),
739
- agent_domain: z
740
- .string()
741
- .nullable()
742
- .optional()
743
- .describe("Agent identity string (e.g. 'for medtech companies')."),
744
600
  output_language: z
745
601
  .string()
746
602
  .nullable()
@@ -759,28 +615,20 @@ export function registerPersonaTools(server) {
759
615
  .boolean()
760
616
  .optional()
761
617
  .describe("Enable business signal detection for this persona."),
762
- sender_override: senderOverride
763
- .nullable()
764
- .optional()
765
- .describe("Override SDR identity at persona level ({name, title}). Pass null to clear."),
766
- }, async ({ id, name, description, agent_domain, output_language, workflow_mode, strict_match, enable_signals, sender_override, }) => {
618
+ }, async ({ id, name, description, output_language, workflow_mode, strict_match, enable_signals, }) => {
767
619
  try {
768
620
  if (name === undefined &&
769
621
  description === undefined &&
770
- agent_domain === undefined &&
771
622
  output_language === undefined &&
772
623
  workflow_mode === undefined &&
773
624
  strict_match === undefined &&
774
- enable_signals === undefined &&
775
- sender_override === undefined) {
625
+ enable_signals === undefined) {
776
626
  return toolError("Provide at least one field to update.");
777
627
  }
778
628
  const current = await fetchPersona(id);
779
629
  const body = {};
780
630
  if (description !== undefined)
781
631
  body.description = description;
782
- if (agent_domain !== undefined)
783
- body.agentDomain = agent_domain;
784
632
  if (output_language !== undefined)
785
633
  body.outputLanguage = output_language;
786
634
  if (workflow_mode !== undefined)
@@ -789,8 +637,6 @@ export function registerPersonaTools(server) {
789
637
  body.strictMatch = strict_match;
790
638
  if (enable_signals !== undefined)
791
639
  body.enableSignals = enable_signals;
792
- if (sender_override !== undefined)
793
- body.senderOverride = sender_override;
794
640
  const data = await postPersonaUpdate(id, name ?? current.name, body);
795
641
  return toolResult(data);
796
642
  }
@@ -859,35 +705,6 @@ export function registerPersonaTools(server) {
859
705
  return handlePersonaToolError(error);
860
706
  }
861
707
  });
862
- // ── update_persona_messaging ───────────────────────────────────────────
863
- server.tool("update_persona_messaging", "Patch the messagingGuidelines JSON column (shallow merge). Common keys: tone, " +
864
- "language, can_mention, must_not_mention, value_propositions. Other keys remain intact.", {
865
- id: z.string().describe("Persona ID."),
866
- patch: z
867
- .record(z.unknown())
868
- .optional()
869
- .describe("Object whose keys are merged into existing messagingGuidelines (shallow)."),
870
- remove_keys: z
871
- .array(z.string())
872
- .optional()
873
- .describe("Keys to delete from messagingGuidelines."),
874
- }, async ({ id, patch, remove_keys }) => {
875
- try {
876
- if ((patch === undefined || Object.keys(patch).length === 0) &&
877
- (remove_keys === undefined || remove_keys.length === 0)) {
878
- return toolError("Provide at least one of: patch (non-empty), remove_keys (non-empty).");
879
- }
880
- const current = await fetchPersona(id);
881
- const next = { ...asObject(current.messagingGuidelines), ...(patch ?? {}) };
882
- for (const k of remove_keys ?? [])
883
- delete next[k];
884
- const data = await postPersonaUpdate(id, current.name, { messagingGuidelines: next });
885
- return toolResult(data);
886
- }
887
- catch (error) {
888
- return handlePersonaToolError(error);
889
- }
890
- });
891
708
  // ── update_persona_active_tools ────────────────────────────────────────
892
709
  server.tool("update_persona_active_tools", "Replace the activeTools list wholesale, OR add/remove specific entries while " +
893
710
  "preserving the rest. Use 'tools' to fully replace; use 'add' / 'remove' for " +
@@ -1090,151 +907,6 @@ export function registerPersonaTools(server) {
1090
907
  return handlePersonaToolError(error);
1091
908
  }
1092
909
  });
1093
- // ── update_persona_outreach_template ───────────────────────────────────
1094
- server.tool("update_persona_outreach_template", "Add, replace, or remove a single outreach template without re-sending the whole " +
1095
- "list. Lookups are by template `id` (recommended) or by composite key " +
1096
- "(channel + signal + field_name) when no id is set. WARNING: if the persona's " +
1097
- "outreachSignalRouting.signals[] is configured, every template's `signal` must " +
1098
- "be in that list — the API rejects with 422 otherwise.", {
1099
- id: z.string().describe("Persona ID."),
1100
- action: z
1101
- .enum(["add", "replace", "remove"])
1102
- .describe("add = append; replace = overwrite by id (or composite key); remove = delete by id (or composite key)."),
1103
- template_id: z
1104
- .string()
1105
- .optional()
1106
- .describe("Template id for replace/remove. Preferred lookup key."),
1107
- lookup_channel: z
1108
- .string()
1109
- .optional()
1110
- .describe("Fallback lookup: channel. Used together with lookup_signal + lookup_field_name when template_id is absent."),
1111
- lookup_signal: z
1112
- .string()
1113
- .optional()
1114
- .describe("Fallback lookup: signal."),
1115
- lookup_field_name: z
1116
- .string()
1117
- .optional()
1118
- .describe("Fallback lookup: field_name."),
1119
- template: outreachTemplate
1120
- .optional()
1121
- .describe("Template payload. Required for 'add' and 'replace'. Schema: {id, channel, signal, field_name, signal_context, purpose, example, max_length, label, subject, body}."),
1122
- }, async ({ id, action, template_id, lookup_channel, lookup_signal, lookup_field_name, template, }) => {
1123
- try {
1124
- if ((action === "add" || action === "replace") && !template) {
1125
- return toolError(`template is required for action '${action}'.`);
1126
- }
1127
- const matches = (t) => {
1128
- if (template_id)
1129
- return t.id === template_id;
1130
- if (lookup_channel || lookup_signal || lookup_field_name) {
1131
- return ((lookup_channel === undefined || t.channel === lookup_channel) &&
1132
- (lookup_signal === undefined || t.signal === lookup_signal) &&
1133
- (lookup_field_name === undefined || t.fieldName === lookup_field_name));
1134
- }
1135
- return false;
1136
- };
1137
- if (action !== "add" && !template_id && !lookup_channel && !lookup_signal && !lookup_field_name) {
1138
- return toolError("Provide template_id (preferred) or composite lookup (lookup_channel + lookup_signal + lookup_field_name).");
1139
- }
1140
- const current = await fetchPersona(id);
1141
- const list = asArray(current.outreachTemplates).map((t) => ({ ...t }));
1142
- if (action === "add") {
1143
- list.push(mapOutreachTemplate(template));
1144
- }
1145
- else if (action === "replace") {
1146
- const idx = list.findIndex(matches);
1147
- if (idx === -1) {
1148
- return toolError("No matching template found for the given lookup.");
1149
- }
1150
- list[idx] = mapOutreachTemplate(template);
1151
- }
1152
- else {
1153
- const before = list.length;
1154
- const filtered = list.filter((t) => !matches(t));
1155
- if (filtered.length === before) {
1156
- return toolError("No matching template found for the given lookup.");
1157
- }
1158
- list.length = 0;
1159
- list.push(...filtered);
1160
- }
1161
- const data = await postPersonaUpdate(id, current.name, {
1162
- outreachTemplates: list,
1163
- });
1164
- return toolResult(data);
1165
- }
1166
- catch (error) {
1167
- return handlePersonaToolError(error);
1168
- }
1169
- });
1170
- // ── update_persona_outreach_signal_routing ─────────────────────────────
1171
- server.tool("update_persona_outreach_signal_routing", "Patch the outreachSignalRouting block. signal_source_field and default_signal " +
1172
- "are scalar — passing one replaces it. signals[] is replaced wholesale when " +
1173
- "passed; use add_signals / remove_signals to splice instead. " +
1174
- "WARNING: default_signal must be present in signals[] (API rejects otherwise). " +
1175
- "Removing a signal that's still referenced by an outreachTemplate.signal will " +
1176
- "also be rejected.", {
1177
- id: z.string().describe("Persona ID."),
1178
- signal_source_field: z
1179
- .string()
1180
- .optional()
1181
- .describe("Card field to read (e.g. card_signals)."),
1182
- default_signal: z
1183
- .string()
1184
- .optional()
1185
- .describe("Default signal — must exist in signals[]."),
1186
- signals: z
1187
- .array(z.string())
1188
- .optional()
1189
- .describe("Full replacement of the signals list. Mutually exclusive with add_signals / remove_signals."),
1190
- add_signals: z
1191
- .array(z.string())
1192
- .optional()
1193
- .describe("Signals to add (deduped). Mutually exclusive with signals."),
1194
- remove_signals: z
1195
- .array(z.string())
1196
- .optional()
1197
- .describe("Signals to remove. Mutually exclusive with signals."),
1198
- }, async ({ id, signal_source_field, default_signal, signals, add_signals, remove_signals, }) => {
1199
- try {
1200
- if (signal_source_field === undefined &&
1201
- default_signal === undefined &&
1202
- signals === undefined &&
1203
- add_signals === undefined &&
1204
- remove_signals === undefined) {
1205
- return toolError("Provide at least one field to update.");
1206
- }
1207
- if (signals !== undefined &&
1208
- (add_signals !== undefined || remove_signals !== undefined)) {
1209
- return toolError("'signals' is mutually exclusive with add_signals / remove_signals.");
1210
- }
1211
- const current = await fetchPersona(id);
1212
- const existing = asObject(current.outreachSignalRouting);
1213
- const next = { ...existing };
1214
- if (signal_source_field !== undefined)
1215
- next.signalSourceField = signal_source_field;
1216
- if (default_signal !== undefined)
1217
- next.defaultSignal = default_signal;
1218
- if (signals !== undefined) {
1219
- next.signals = signals;
1220
- }
1221
- else if (add_signals !== undefined || remove_signals !== undefined) {
1222
- const set = new Set(asArray(existing.signals));
1223
- for (const s of add_signals ?? [])
1224
- set.add(s);
1225
- for (const s of remove_signals ?? [])
1226
- set.delete(s);
1227
- next.signals = Array.from(set);
1228
- }
1229
- const data = await postPersonaUpdate(id, current.name, {
1230
- outreachSignalRouting: next,
1231
- });
1232
- return toolResult(data);
1233
- }
1234
- catch (error) {
1235
- return handlePersonaToolError(error);
1236
- }
1237
- });
1238
910
  // ── update_persona_prompt_engineering ──────────────────────────────────
1239
911
  server.tool("update_persona_prompt_engineering", "Patch free-form markdown prompt fields (normalization_rules, signal_guidance, " +
1240
912
  "tone_instructions, context). Each is a separate scalar JSON column — passing " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "3.6.3",
3
+ "version": "4.0.0",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",