@agifyai/leadify-mcp 8.6.10 → 8.7.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.
package/README.md CHANGED
@@ -232,6 +232,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
232
232
  | `create_lead_group` | Créer un groupe et choisir son type canonique optionnel (`GENERIC`, `COMPANY` ou `PERSON`) via `entity_kind`. |
233
233
  | `update_lead_group` | Modifier un groupe, y compris son `entity_kind`, avec réconciliation du schéma côté Leadify. |
234
234
  | `add_leads` | Ajouter un ou plusieurs leads à un groupe. |
235
+ | `upsert_person_leads_with_employment` | Créer ou réconcilier atomiquement des Person Leads, PERSON, EMPLOYED_BY, projections et historique, sans effet commercial. |
235
236
  | `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination, dont le critère composé `event_filter`. |
236
237
  | `get_lead` | Récupérer les détails complets d'un lead par son ID. |
237
238
  | `update_lead` | Mettre à jour un ou plusieurs champs d'un lead existant. |
@@ -255,7 +256,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
255
256
  | `delete_campaign_log` | Supprimer une entrée de log de campagne. |
256
257
  | `update_campaign_stats` | Mettre à jour les statistiques d'email d'une campagne pour un groupe de leads. |
257
258
  | `create_campaign` | Créer une campagne DRAFT mono-canal rattachée à un Lead Group qui possède déjà sa paire Persona–Offre. Aucun `persona_id` ou contexte indépendant n’est accepté au niveau Campagne. `channel` (`LINKEDIN` ou `EMAIL`) est obligatoire ; `start_at` inclusif, `end_at` exclusif et `timezone` IANA configurent la fenêtre métier. À `end_at`, Leadify met la campagne en pause réversible (`WINDOW_END`) sans la clôturer. |
258
- | `update_campaign_configuration` | Modifier le nom, la description et la fenêtre de toute campagne encore `OPEN`; le canal reste modifiable uniquement en DRAFT. Prolonger `end_at` dans le futur ou le supprimer relance automatiquement une pause `WINDOW_END` après validation du fournisseur, mais jamais une pause manuelle. La fenêtre demandée reste enregistrée si cette validation échoue. La Persona est héritée du Lead Group et n’est pas modifiable au niveau Campagne. |
259
+ | `update_campaign` / `update_campaign_configuration` | Modifier le nom, la description et la fenêtre de toute campagne encore `OPEN`; le canal reste modifiable uniquement en DRAFT. Prolonger `end_at` dans le futur ou le supprimer relance automatiquement une pause `WINDOW_END` après validation du fournisseur, mais jamais une pause manuelle. La fenêtre demandée reste enregistrée si cette validation échoue. La Persona est héritée du Lead Group et n’est pas modifiable au niveau Campagne. |
259
260
  | `delete_campaign` | Supprimer irréversiblement une campagne `DRAFT` ou `PAUSED` encore `OPEN`, avec la confirmation explicite `delete_campaign`. Une campagne active ou clôturée est refusée, et aucun envoi n’est déclenché. |
260
261
  | `list_campaigns` | Lister compactement les campagnes d'une organisation explicitement sélectionnée, avec état effectif, fenêtre, motif de pause et clôture. |
261
262
  | `get_campaign` | Récupérer les détails d'une campagne, son état effectif, sa clôture et son éventuel rapport final figé, ainsi que ses KPIs temps réel. |
package/dist/server.js CHANGED
@@ -19,6 +19,7 @@ import { registerCanonicalBackfillTools } from "./tools/canonical_backfill.js";
19
19
  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
+ import { registerPersonEmploymentTools } from "./tools/person_employment.js";
22
23
  import { MCP_SERVER_NAME, MCP_VERSION } from "./version.js";
23
24
  export function createServer() {
24
25
  const server = new McpServer({
@@ -32,6 +33,7 @@ export function createServer() {
32
33
  registerLeadTools(server);
33
34
  registerRelationshipTools(server);
34
35
  registerCanonicalBackfillTools(server);
36
+ registerPersonEmploymentTools(server);
35
37
  registerEventTools(server);
36
38
  registerCommercialTruthTools(server);
37
39
  registerSchemaTools(server);
@@ -130,39 +130,44 @@ export function registerCampaignTools(server, readClient, writeClient, mutationC
130
130
  return handleToolError(error);
131
131
  }
132
132
  });
133
- server.tool("update_campaign_configuration", "Edit the lifecycle window of any OPEN campaign; changing channel remains limited to DRAFT. " +
133
+ const registerCampaignUpdateTool = (name, description) => {
134
+ server.tool(name, description, {
135
+ id: z.string().describe("Campaign ID."),
136
+ channel: z.enum(["LINKEDIN", "EMAIL"]).optional().describe("Delivery channel; mutable only while status is DRAFT."),
137
+ start_at: z.string().datetime().nullable().optional().describe("Inclusive UTC start timestamp, or null to remove the lower bound."),
138
+ end_at: z.string().datetime().nullable().optional().describe("Exclusive UTC end timestamp, or null to remove the upper bound. A future value or null can auto-resume only a WINDOW_END pause."),
139
+ timezone: z.string().min(1).optional().describe("IANA timezone used for the business window, for example Europe/Paris."),
140
+ name: z.string().min(1).optional().describe("New campaign name."),
141
+ description: z.string().nullable().optional().describe("New campaign description, or null to clear it."),
142
+ }, async ({ id, channel, start_at, end_at, timezone, name, description }) => {
143
+ try {
144
+ const body = {};
145
+ if (channel !== undefined)
146
+ body.channel = channel;
147
+ if (start_at !== undefined)
148
+ body.startAt = start_at;
149
+ if (end_at !== undefined)
150
+ body.endAt = end_at;
151
+ if (timezone !== undefined)
152
+ body.timezone = timezone;
153
+ if (name !== undefined)
154
+ body.name = name;
155
+ if (description !== undefined)
156
+ body.description = description;
157
+ return toolResult(await (mutationClient ?? getClient()).put(`/api/campaign/${encodeURIComponent(id)}/configuration`, body));
158
+ }
159
+ catch (error) {
160
+ return handleToolError(error);
161
+ }
162
+ });
163
+ };
164
+ const updateCampaignDescription = "Edit the name, description, and lifecycle window of any OPEN campaign; changing channel remains limited to DRAFT. " +
134
165
  "Extending endAt into the future or removing it automatically resumes only a campaign paused " +
135
166
  "because WINDOW_END, after provider validation and immediate replanning. A MANUAL pause never " +
136
167
  "auto-resumes. The requested window remains saved when provider validation fails, and a CLOSED " +
137
- "campaign is terminal.", {
138
- id: z.string().describe("Campaign ID."),
139
- channel: z.enum(["LINKEDIN", "EMAIL"]).optional().describe("Delivery channel; mutable only while status is DRAFT."),
140
- start_at: z.string().datetime().nullable().optional().describe("Inclusive UTC start timestamp, or null to remove the lower bound."),
141
- end_at: z.string().datetime().nullable().optional().describe("Exclusive UTC end timestamp, or null to remove the upper bound. A future value or null can auto-resume only a WINDOW_END pause."),
142
- timezone: z.string().min(1).optional().describe("IANA timezone used for the business window, for example Europe/Paris."),
143
- name: z.string().min(1).optional().describe("New campaign name."),
144
- description: z.string().nullable().optional().describe("New campaign description, or null to clear it."),
145
- }, async ({ id, channel, start_at, end_at, timezone, name, description }) => {
146
- try {
147
- const body = {};
148
- if (channel !== undefined)
149
- body.channel = channel;
150
- if (start_at !== undefined)
151
- body.startAt = start_at;
152
- if (end_at !== undefined)
153
- body.endAt = end_at;
154
- if (timezone !== undefined)
155
- body.timezone = timezone;
156
- if (name !== undefined)
157
- body.name = name;
158
- if (description !== undefined)
159
- body.description = description;
160
- return toolResult(await (mutationClient ?? getClient()).put(`/api/campaign/${encodeURIComponent(id)}/configuration`, body));
161
- }
162
- catch (error) {
163
- return handleToolError(error);
164
- }
165
- });
168
+ "campaign is terminal. Persona is inherited from the Lead Group and cannot be changed here.";
169
+ registerCampaignUpdateTool("update_campaign", updateCampaignDescription);
170
+ registerCampaignUpdateTool("update_campaign_configuration", updateCampaignDescription);
166
171
  server.tool("delete_campaign", "Irreversibly delete one OPEN campaign that is DRAFT or PAUSED. Active and permanently closed campaigns are refused. This operation never sends outreach. Always confirm with the user before calling this tool.", {
167
172
  id: z.string().describe("Campaign ID."),
168
173
  confirmation: z.literal("delete_campaign").describe("Exact acknowledgement required before deleting the campaign."),
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPersonEmploymentTools(server: McpServer, client?: import("../client.js").LeadifyClient): void;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { getClient } from "../client.js";
3
+ import { handleToolError, toolResult } from "../types.js";
4
+ export function registerPersonEmploymentTools(server, client = getClient()) {
5
+ server.tool("upsert_person_leads_with_employment", "Atomically create or reconcile Person Leads with their canonical PERSON identity, EMPLOYED_BY relationship, parent_lead/child_leads projections, evidence and append-only card_history. company_lead_id must be exact and already project an Account plus Legal Entity or compatible Establishment. Each batch entry is independently transactional and idempotent. This is the only supported Person write path for sourcing; never fall back to add_leads, canonical backfill, or separate relationship writes. It never qualifies, enriches contact details, sends outreach, creates campaigns, or activates anything.", {
6
+ people: z.array(z.object({
7
+ organization_id: z.string().trim().min(1),
8
+ person_group_id: z.string().trim().min(1),
9
+ company_lead_id: z.string().trim().min(1),
10
+ person_lead_id: z.string().trim().min(1).optional(),
11
+ fields: z.record(z.unknown()),
12
+ card_history_append: z.string().trim().min(1).max(100_000),
13
+ evidence_ids: z.array(z.string().trim().min(1)).min(1).max(100),
14
+ idempotency_key: z.string().trim().min(1).max(220),
15
+ }).strict()).min(1).max(100),
16
+ }, async ({ people }) => {
17
+ try {
18
+ return toolResult(await client.post("/api/person-employment/upsert", {
19
+ people: people.map((person) => ({
20
+ organizationId: person.organization_id,
21
+ personGroupId: person.person_group_id,
22
+ companyLeadId: person.company_lead_id,
23
+ personLeadId: person.person_lead_id,
24
+ fields: person.fields,
25
+ cardHistoryAppend: person.card_history_append,
26
+ evidenceIds: person.evidence_ids,
27
+ idempotencyKey: person.idempotency_key,
28
+ })),
29
+ }));
30
+ }
31
+ catch (error) {
32
+ return handleToolError(error);
33
+ }
34
+ });
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "8.6.10",
3
+ "version": "8.7.0",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",