@agifyai/leadify-mcp 8.5.0 → 8.5.2

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
@@ -102,6 +102,14 @@ Mode watch :
102
102
  npm run dev
103
103
  ```
104
104
 
105
+ Canari de vérité commerciale en lecture seule :
106
+
107
+ ```bash
108
+ LEADIFY_CANARY_ORGANIZATION_ID=org-id npm run canary:commercial
109
+ ```
110
+
111
+ Le canari sélectionne une organisation et un groupe accessibles, relit la même source deux fois, ne publie aucun message et n'ajoute aucune activité. Les variables `LEADIFY_CANARY_LEAD_GROUP_ID`, `LEADIFY_CANARY_EMAIL`, `LEADIFY_CANARY_LINKEDIN_URL` et `LEADIFY_CANARY_LEAD_ID` permettent de fixer une cible déjà autorisée ; la clé et le contenu des conversations ne sont jamais affichés.
112
+
105
113
  ### Architecture
106
114
 
107
115
  ```
@@ -211,7 +219,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
211
219
  | `get_campaign_logs` | Récupérer les logs de campagne avec filtres et pagination. |
212
220
  | `delete_campaign_log` | Supprimer une entrée de log de campagne. |
213
221
  | `update_campaign_stats` | Mettre à jour les statistiques d'email d'une campagne pour un groupe de leads. |
214
- | `create_campaign` | Créer une nouvelle campagne rattachée à un groupe de leads (statut DRAFT). |
222
+ | `create_campaign` | Créer une campagne DRAFT mono-canal rattachée à un groupe de leads. `channel` (`LINKEDIN` ou `EMAIL`) est obligatoire : aucun défaut LinkedIn implicite. La fenêtre de campagne (`startAt`/`endAt`) et le contrat de clôture ne sont pas encore supportés par l'API Leadify ; ne pas utiliser cet outil pour une campagne bornée dans le temps avant cette livraison. |
215
223
  | `list_campaigns` | Lister compactement les campagnes d'une organisation explicitement sélectionnée et leur statut courant. |
216
224
  | `get_campaign` | Récupérer les détails d'une campagne et ses KPIs temps réel. |
217
225
  | `update_campaign_status` | Changer le statut d'une campagne (DRAFT, ACTIVE, PAUSED, COMPLETED). |
@@ -221,6 +229,12 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
221
229
  | `signal_disable` | Désactiver un signal (faux positif / écarté manuellement). Flip de `state` uniquement. |
222
230
  | `signal_delete` | Supprimer définitivement un signal (cas rare : donnée erronée, doublon). |
223
231
  | `add_activity` | Journaliser une interaction prospect (LinkedIn, email, call) dans le feed du lead. |
232
+ | `crm_lookup_person` | Lire la personne, les deals et le stage CRM d'une cible explicite, sans écrire dans le CRM. |
233
+ | `email_read_thread` | Lire un thread email borné et ses réponses, exclusions de délivrabilité et ambiguïtés. |
234
+ | `linkedin_read_conversation` | Lire la conversation LinkedIn bornée d'un profil explicitement sélectionné. |
235
+ | `unipile_read_messages` | Lire les messages LinkedIn Unipile bornés d'un profil explicitement sélectionné. |
236
+ | `leadify_read_activity_feed` | Lire l'audit borné d'un lead ; le feed ne constitue jamais une preuve de réponse. |
237
+ | `get_commercial_truth` | Réconcilier CRM, email, LinkedIn, Unipile et activité avec un statut, une fraîcheur et une action recommandée ; aucune écriture ni envoi. |
224
238
  | `compile_context_pack` | Compiler en lecture seule un Context Pack canonique depuis une cible explicite (`organization`, `lead_group`, `campaign`, `account`, `person`) ou un couple lead + campagne pour `WRITE_OUTREACH`. Retourne publications courantes, sources, freshness, contradictions et provenance. N’utilise jamais la Data Room legacy ni ne choisit de cible implicite. |
225
239
  | `get_context_workspace` | Lire le Context Workspace canonique versionné (`company_brain` ou `gtm_playbook`) d'une organisation explicitement sélectionnée. Retourne le brouillon et la dernière version publiée. |
226
240
  | `list_canonical_relationships` | Lire les liens canoniques typés d'une organisation, dans les deux directions. |
@@ -259,4 +273,5 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
259
273
  | `set_outreach_connection_request` | Toggle du flag connectionRequestEnabled (LinkedIn invite vs cold DM). |
260
274
  | `trigger_outreach` | Générer un message d'outreach via l'agent Trigger.dev (dry-run par défaut). `force=true` pour re-générer sur un lead déjà traité. |
261
275
  | `append_fine_tuning` | Appendre du contenu à une section du Fine Tuning (nonNegotiableRules, pitfalls, structure, examples). Toujours en mode append — garantie contractuelle. |
276
+ | `set_fine_tuning_output_config` | Remplacer uniquement la configuration métier de sorties d’un Fine Tuning (`linkedinConnection`, `linkedinMessage`, `email`, activations et quantités). Préserve Markdown, langues et fallback ; ne génère, ne planifie, n’active ni n’envoie aucun outreach. |
262
277
  | `pipeline_next_lead` | Sélectionner le prochain lead à traiter (score descendant, sans message). Exclusion des IDs déjà vus, limit 1-5. |
package/dist/server.js CHANGED
@@ -15,6 +15,7 @@ import { registerFineTuningTools } from "./tools/fine_tuning.js";
15
15
  import { registerContextWorkspaceTools } from "./tools/context_workspace.js";
16
16
  import { registerLeadViewTools } from "./tools/views.js";
17
17
  import { registerRelationshipTools } from "./tools/relationships.js";
18
+ import { registerCommercialTruthTools } from "./tools/commercial_truth.js";
18
19
  import { MCP_SERVER_NAME, MCP_VERSION } from "./version.js";
19
20
  export function createServer() {
20
21
  const server = new McpServer({
@@ -27,6 +28,7 @@ export function createServer() {
27
28
  registerLeadViewTools(server);
28
29
  registerLeadTools(server);
29
30
  registerRelationshipTools(server);
31
+ registerCommercialTruthTools(server);
30
32
  registerSchemaTools(server);
31
33
  registerLogTools(server);
32
34
  registerCampaignTools(server);
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { type LeadifyClient } from "../client.js";
3
3
  type CampaignReadClient = Pick<LeadifyClient, "get">;
4
- export declare function registerCampaignTools(server: McpServer, readClient?: CampaignReadClient): void;
4
+ type CampaignWriteClient = Pick<LeadifyClient, "post">;
5
+ export declare function registerCampaignTools(server: McpServer, readClient?: CampaignReadClient, writeClient?: CampaignWriteClient): void;
5
6
  export {};
@@ -19,7 +19,7 @@ function assertCampaignBelongsToOrganization(data, organizationId) {
19
19
  }
20
20
  return campaign;
21
21
  }
22
- export function registerCampaignTools(server, readClient) {
22
+ export function registerCampaignTools(server, readClient, writeClient) {
23
23
  // ── update_campaign_stats ──────────────────────────────────────────────
24
24
  server.tool("update_campaign_stats", "Update email campaign statistics for a lead group. If the given campaignSlug " +
25
25
  "doesn't exist in the group's stats yet, it is created; otherwise provided fields " +
@@ -88,27 +88,31 @@ export function registerCampaignTools(server, readClient) {
88
88
  }
89
89
  });
90
90
  // ── create_campaign ────────────────────────────────────────────────────
91
- server.tool("create_campaign", "Create a new campaign linked to an existing lead group. The campaign is initialized " +
92
- "in DRAFT status and can be configured with a persona. Returns the new campaign ID. " +
93
- "Use update_campaign_status afterwards to move it to ACTIVE when ready.", {
91
+ server.tool("create_campaign", "Create a mono-channel DRAFT campaign linked to an existing lead group. channel is required " +
92
+ "so no campaign silently defaults to LinkedIn. The current Leadify API does not yet persist " +
93
+ "a campaign start/end window or closure contract: do not use this tool for a time-bounded " +
94
+ "campaign until that API support is released. Returns the new campaign ID; use " +
95
+ "update_campaign_status only after the campaign is fully configured and explicitly approved.", {
94
96
  name: z.string().describe("Campaign name."),
95
97
  lead_group_id: z.string().describe("ID of the lead group to associate."),
98
+ channel: z.enum(["LINKEDIN", "EMAIL"]).describe("Required effective delivery channel. Create separate campaigns for LinkedIn and email."),
96
99
  description: z.string().optional().describe("Optional description."),
97
100
  persona_id: z
98
101
  .string()
99
102
  .optional()
100
103
  .describe("ID of the persona to assign (optional)."),
101
- }, async ({ name, lead_group_id, description, persona_id }) => {
104
+ }, async ({ name, lead_group_id, channel, description, persona_id }) => {
102
105
  try {
103
106
  const body = {
104
107
  name,
105
108
  leadGroupId: lead_group_id,
109
+ channel,
106
110
  };
107
111
  if (description !== undefined)
108
112
  body.description = description;
109
113
  if (persona_id !== undefined)
110
114
  body.personaId = persona_id;
111
- const data = await getClient().post("/api/campaign", body);
115
+ const data = await (writeClient ?? getClient()).post("/api/campaign", body);
112
116
  return toolResult(data);
113
117
  }
114
118
  catch (error) {
@@ -0,0 +1,48 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type LeadifyClient } from "../client.js";
3
+ type CommercialReadClient = Pick<LeadifyClient, "get">;
4
+ type RecordValue = Record<string, unknown>;
5
+ export type CommercialSource = "crm" | "email" | "linkedin" | "unipile" | "leadify_activity";
6
+ export type CommercialDirection = "inbound" | "outbound" | "system" | "mixed" | "unknown";
7
+ export type CommercialStatus = "client" | "prospect" | "in_crm" | "not_in_crm" | "replied" | "contacted" | "excluded" | "audit_only" | "no_observed_activity" | "no_action" | "review_required" | "handoff_required" | "unknown";
8
+ export interface CommercialFreshness {
9
+ observedAt: string | null;
10
+ state: "fresh" | "stale" | "unknown";
11
+ ageMs?: number;
12
+ }
13
+ export interface CommercialProjection {
14
+ source: CommercialSource | "reconciled";
15
+ date: string | null;
16
+ direction: CommercialDirection;
17
+ status: CommercialStatus;
18
+ optOut: boolean;
19
+ bounce: boolean;
20
+ suppression: boolean;
21
+ handoff: boolean;
22
+ freshness: CommercialFreshness;
23
+ ambiguities: string[];
24
+ }
25
+ export interface CommercialSourceSnapshot {
26
+ source: CommercialSource;
27
+ configured: boolean;
28
+ projection: CommercialProjection;
29
+ pagination: RecordValue;
30
+ evidence: RecordValue;
31
+ payload: RecordValue;
32
+ }
33
+ export interface CommercialReconciliation {
34
+ status: CommercialStatus;
35
+ recommendedAction: string;
36
+ responded: boolean;
37
+ inCrm: boolean;
38
+ crmStage: "client" | "prospect" | "none" | "unknown";
39
+ optOut: boolean;
40
+ bounce: boolean;
41
+ suppression: boolean;
42
+ handoff: boolean;
43
+ freshness: CommercialFreshness;
44
+ ambiguities: string[];
45
+ }
46
+ export declare function reconcileCommercialSources(sources: CommercialSourceSnapshot[]): CommercialReconciliation;
47
+ export declare function registerCommercialTruthTools(server: McpServer, client?: CommercialReadClient): void;
48
+ export {};