@agifyai/leadify-mcp 8.5.1 → 8.5.3
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 +16 -1
- package/dist/server.js +2 -0
- package/dist/tools/campaigns.d.ts +3 -1
- package/dist/tools/campaigns.js +40 -6
- package/dist/tools/commercial_truth.d.ts +48 -0
- package/dist/tools/commercial_truth.js +1141 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
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,8 @@ 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
|
|
222
|
+
| `create_campaign` | Créer une campagne DRAFT mono-canal rattachée à un groupe de leads. `channel` (`LINKEDIN` ou `EMAIL`) est obligatoire ; `start_at`, `end_at` et `timezone` configurent la fenêtre métier. À `end_at`, Leadify ferme la campagne idempotemment, annule uniquement les jobs prouvablement pré-fournisseur et préserve les autres pour réconciliation. |
|
|
223
|
+
| `update_campaign_configuration` | Corriger le canal ou la fenêtre d'une campagne DRAFT encore ouverte, sans activation ni enrôlement. |
|
|
215
224
|
| `list_campaigns` | Lister compactement les campagnes d'une organisation explicitement sélectionnée et leur statut courant. |
|
|
216
225
|
| `get_campaign` | Récupérer les détails d'une campagne et ses KPIs temps réel. |
|
|
217
226
|
| `update_campaign_status` | Changer le statut d'une campagne (DRAFT, ACTIVE, PAUSED, COMPLETED). |
|
|
@@ -221,6 +230,12 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
221
230
|
| `signal_disable` | Désactiver un signal (faux positif / écarté manuellement). Flip de `state` uniquement. |
|
|
222
231
|
| `signal_delete` | Supprimer définitivement un signal (cas rare : donnée erronée, doublon). |
|
|
223
232
|
| `add_activity` | Journaliser une interaction prospect (LinkedIn, email, call) dans le feed du lead. |
|
|
233
|
+
| `crm_lookup_person` | Lire la personne, les deals et le stage CRM d'une cible explicite, sans écrire dans le CRM. |
|
|
234
|
+
| `email_read_thread` | Lire un thread email borné et ses réponses, exclusions de délivrabilité et ambiguïtés. |
|
|
235
|
+
| `linkedin_read_conversation` | Lire la conversation LinkedIn bornée d'un profil explicitement sélectionné. |
|
|
236
|
+
| `unipile_read_messages` | Lire les messages LinkedIn Unipile bornés d'un profil explicitement sélectionné. |
|
|
237
|
+
| `leadify_read_activity_feed` | Lire l'audit borné d'un lead ; le feed ne constitue jamais une preuve de réponse. |
|
|
238
|
+
| `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
239
|
| `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
240
|
| `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
241
|
| `list_canonical_relationships` | Lire les liens canoniques typés d'une organisation, dans les deux directions. |
|
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,7 @@
|
|
|
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
|
-
|
|
4
|
+
type CampaignWriteClient = Pick<LeadifyClient, "post">;
|
|
5
|
+
type CampaignMutationClient = Pick<LeadifyClient, "put">;
|
|
6
|
+
export declare function registerCampaignTools(server: McpServer, readClient?: CampaignReadClient, writeClient?: CampaignWriteClient, mutationClient?: CampaignMutationClient): void;
|
|
5
7
|
export {};
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -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, mutationClient) {
|
|
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,33 +88,67 @@ export function registerCampaignTools(server, readClient) {
|
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
90
|
// ── create_campaign ────────────────────────────────────────────────────
|
|
91
|
-
server.tool("create_campaign", "Create a
|
|
92
|
-
"
|
|
93
|
-
"
|
|
91
|
+
server.tool("create_campaign", "Create a mono-channel DRAFT campaign with an explicit channel, optional ISO-8601 start/end " +
|
|
92
|
+
"window, and IANA timezone. At endAt Leadify closes the campaign idempotently, cancels only " +
|
|
93
|
+
"pre-provider jobs, and preserves any provider-barrier job for reconciliation.", {
|
|
94
94
|
name: z.string().describe("Campaign name."),
|
|
95
95
|
lead_group_id: z.string().describe("ID of the lead group to associate."),
|
|
96
|
+
channel: z.enum(["LINKEDIN", "EMAIL"]).describe("Required effective delivery channel. Create separate campaigns for LinkedIn and email."),
|
|
97
|
+
start_at: z.string().datetime().optional().describe("Optional inclusive campaign start timestamp in ISO-8601 UTC."),
|
|
98
|
+
end_at: z.string().datetime().optional().describe("Optional exclusive campaign end timestamp in ISO-8601 UTC; must be after start_at."),
|
|
99
|
+
timezone: z.string().min(1).optional().describe("IANA timezone used for the campaign business window, for example Europe/Paris."),
|
|
96
100
|
description: z.string().optional().describe("Optional description."),
|
|
97
101
|
persona_id: z
|
|
98
102
|
.string()
|
|
99
103
|
.optional()
|
|
100
104
|
.describe("ID of the persona to assign (optional)."),
|
|
101
|
-
}, async ({ name, lead_group_id, description, persona_id }) => {
|
|
105
|
+
}, async ({ name, lead_group_id, channel, start_at, end_at, timezone, description, persona_id }) => {
|
|
102
106
|
try {
|
|
103
107
|
const body = {
|
|
104
108
|
name,
|
|
105
109
|
leadGroupId: lead_group_id,
|
|
110
|
+
channel,
|
|
106
111
|
};
|
|
112
|
+
if (start_at !== undefined)
|
|
113
|
+
body.startAt = start_at;
|
|
114
|
+
if (end_at !== undefined)
|
|
115
|
+
body.endAt = end_at;
|
|
116
|
+
if (timezone !== undefined)
|
|
117
|
+
body.timezone = timezone;
|
|
107
118
|
if (description !== undefined)
|
|
108
119
|
body.description = description;
|
|
109
120
|
if (persona_id !== undefined)
|
|
110
121
|
body.personaId = persona_id;
|
|
111
|
-
const data = await getClient().post("/api/campaign", body);
|
|
122
|
+
const data = await (writeClient ?? getClient()).post("/api/campaign", body);
|
|
112
123
|
return toolResult(data);
|
|
113
124
|
}
|
|
114
125
|
catch (error) {
|
|
115
126
|
return handleToolError(error);
|
|
116
127
|
}
|
|
117
128
|
});
|
|
129
|
+
server.tool("update_campaign_configuration", "Correct an open DRAFT campaign's channel or lifecycle window before activation. This tool refuses ACTIVE, PAUSED, COMPLETED, or closed campaigns and never activates or enrolls prospects.", {
|
|
130
|
+
id: z.string().describe("Campaign ID."),
|
|
131
|
+
channel: z.enum(["LINKEDIN", "EMAIL"]).optional(),
|
|
132
|
+
start_at: z.string().datetime().nullable().optional(),
|
|
133
|
+
end_at: z.string().datetime().nullable().optional(),
|
|
134
|
+
timezone: z.string().min(1).optional(),
|
|
135
|
+
}, async ({ id, channel, start_at, end_at, timezone }) => {
|
|
136
|
+
try {
|
|
137
|
+
const body = {};
|
|
138
|
+
if (channel !== undefined)
|
|
139
|
+
body.channel = channel;
|
|
140
|
+
if (start_at !== undefined)
|
|
141
|
+
body.startAt = start_at;
|
|
142
|
+
if (end_at !== undefined)
|
|
143
|
+
body.endAt = end_at;
|
|
144
|
+
if (timezone !== undefined)
|
|
145
|
+
body.timezone = timezone;
|
|
146
|
+
return toolResult(await (mutationClient ?? getClient()).put(`/api/campaign/${encodeURIComponent(id)}/configuration`, body));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
return handleToolError(error);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
118
152
|
// ── get_campaign ───────────────────────────────────────────────────────
|
|
119
153
|
server.tool("get_campaign", "Retrieve a campaign chosen from list_campaigns, scoped to the explicitly selected " +
|
|
120
154
|
"organization. Includes its normalized current status (DRAFT, ACTIVE, PAUSED, or COMPLETED) " +
|
|
@@ -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 {};
|