@agifyai/leadify-mcp 9.2.0 → 9.3.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 +18 -0
- package/dist/server.js +2 -0
- package/dist/tools/outreach_exclusion.d.ts +3 -0
- package/dist/tools/outreach_exclusion.js +89 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -234,6 +234,9 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
234
234
|
| `add_leads` | Import plat générique vers un groupe : ne crée aucune relation d’emploi, aucun rattachement parent ni identité canonique. Ne jamais l’utiliser pour rattacher une Personne à une Company (voir `upsert_person_leads_with_employment`). |
|
|
235
235
|
| `upsert_person_leads_with_employment` | Créer ou réconcilier atomiquement des Person Leads, PERSON, EMPLOYED_BY, projections et historique, sans effet commercial. |
|
|
236
236
|
| `create_person_employment_evidence` / `get_person_employment_evidence` | Créer puis relire une preuve d’emploi individuelle tenant-scopée ; transmettre l’`evidence.id` retourné tel quel à l’upsert Person. |
|
|
237
|
+
| `exclude_account` | Exclure une entreprise (établissement) et toutes ses personnes liées : cause protégée `ACCOUNT_EXCLUDED`, motif et URL source obligatoires, `level: "establishment"`. |
|
|
238
|
+
| `exclude_lead` / `reintegrate_lead` | Exclure ou réintégrer une personne avec les mêmes commandes que l'interface ; raison obligatoire, aucun motif protégé levé. |
|
|
239
|
+
| `get_outreach_state` | Lire causes actives, version et date de 1 à 100 fiches. |
|
|
237
240
|
| `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination, dont le critère composé `event_filter`. |
|
|
238
241
|
| `get_lead` | Récupérer les détails complets d'un lead par son ID. |
|
|
239
242
|
| `update_lead` | Mettre à jour un ou plusieurs champs d'un lead existant. |
|
|
@@ -375,6 +378,21 @@ Aucune mutation de sourcing ne qualifie le lead, n’enrichit les contacts
|
|
|
375
378
|
aucun canal externe : le readback porte `noSend: true` et
|
|
376
379
|
`externalActivation: 0`.
|
|
377
380
|
|
|
381
|
+
### Exclusions de prospection (PRD-2404)
|
|
382
|
+
|
|
383
|
+
Une exclusion ne s'écrit jamais en texte (Context Workspace, notes, persona) ni dans les
|
|
384
|
+
champs `excluded`, `hide_from_campaign` ou `outreach_status` : le serveur refuse
|
|
385
|
+
ces champs (409 `PROTECTED_OUTREACH_FIELD`). Utiliser `exclude_account` pour un
|
|
386
|
+
client existant ou un compte à ne pas contacter : motif et `source_url` publique
|
|
387
|
+
obligatoires, les personnes liées sont exclues tant que le lien existe et leurs
|
|
388
|
+
campagnes s'arrêtent. Leadify ne distingue pas les services : la réponse dit
|
|
389
|
+
toujours `level: "establishment"`. `exclude_lead` et `reintegrate_lead` agissent
|
|
390
|
+
sur une personne ; la réintégration ne lève jamais une opposition, une protection
|
|
391
|
+
de délivrabilité ni l'exclusion d'une entreprise. Chaque écriture accepte
|
|
392
|
+
`idempotency_key` et `expected_version`, et rend l'état relu ;
|
|
393
|
+
`get_outreach_state` relit 1 à 100 fiches. Routes serveur : `/api/outreach/*`
|
|
394
|
+
(PRD-2337), auteur tracé dans Leadify : propriétaire de la clé, source Agent.
|
|
395
|
+
|
|
378
396
|
### Création d’organisation et qualification (PRD-2398)
|
|
379
397
|
|
|
380
398
|
Dans Leadify, **Réglages → Clés API → Modifier** permet de changer les actions
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerQualificationRunTools } from "./tools/qualification_runs.js";
|
|
2
|
+
import { registerOutreachExclusionTools } from "./tools/outreach_exclusion.js";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { registerAuthTools } from "./tools/auth.js";
|
|
4
5
|
import { registerLeadTools } from "./tools/leads.js";
|
|
@@ -53,5 +54,6 @@ export function createServer() {
|
|
|
53
54
|
registerPersonaTools(server);
|
|
54
55
|
registerPipelineTools(server);
|
|
55
56
|
registerQualificationRunTools(server);
|
|
57
|
+
registerOutreachExclusionTools(server);
|
|
56
58
|
return server;
|
|
57
59
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
// PRD-2404 — vraies exclusions de prospection, branchées sur /api/outreach
|
|
5
|
+
// (PRD-2337) : mêmes commandes que les boutons de l'interface Leadify.
|
|
6
|
+
const NEVER_IN_TEXT = "Never write an exclusion as text (Context Workspace, company brain, notes, persona) and never write excluded, " +
|
|
7
|
+
"hide_from_campaign or outreach_status on a lead: text excludes nothing and Leadify refuses those fields (409 PROTECTED_OUTREACH_FIELD).";
|
|
8
|
+
const id = z.string().trim().min(1).max(200);
|
|
9
|
+
const writeShape = {
|
|
10
|
+
organization_id: id.describe("Organization that owns the lead. Must be in the API key's scope."),
|
|
11
|
+
idempotency_key: z.string().trim().min(8).max(200).optional().describe("Stable key for this decision. Retrying with the same key replays the first result; reusing it for another lead or reason returns 409 OUTREACH_IDEMPOTENCY_KEY_REUSED."),
|
|
12
|
+
expected_version: z.number().int().min(0).optional().describe("state.version read with get_outreach_state. If the state changed meanwhile, the server refuses with 409 OUTREACH_VERSION_CONFLICT: re-read, then decide again."),
|
|
13
|
+
};
|
|
14
|
+
const reason = z.string().trim().min(3).max(2000);
|
|
15
|
+
export function registerOutreachExclusionTools(server, client = getClient()) {
|
|
16
|
+
server.tool("exclude_account", "Exclude a company (a hospital, clinic or other establishment) from all prospecting: sets the protected cause " +
|
|
17
|
+
"ACCOUNT_EXCLUDED on the company lead, and every person linked to it is excluded too (now and when linked later) and " +
|
|
18
|
+
"their running campaigns stop. Use it for existing customers, competitors or accounts the client must not contact. " +
|
|
19
|
+
"reason and source_url (the public page proving it) are required. Leadify has no department level: the response " +
|
|
20
|
+
"always says level=\"establishment\", with levelNote when service was given. Idempotent. An agent cannot lift this " +
|
|
21
|
+
"exclusion; only a human in Leadify can. " + NEVER_IN_TEXT, {
|
|
22
|
+
...writeShape,
|
|
23
|
+
company_lead_id: id.describe("Lead id of the company (COMPANY group)."),
|
|
24
|
+
reason: reason.describe("Why the account is excluded, e.g. 'Déjà client RDS Diag'."),
|
|
25
|
+
source_url: z.string().trim().url().max(2000).refine(value => /^https?:\/\//i.test(value), "http(s) URL required")
|
|
26
|
+
.describe("Public http(s) page proving the reason."),
|
|
27
|
+
service: z.string().trim().min(1).max(200).optional().describe("Department targeted, if any. Recorded in the note; the exclusion still covers the whole establishment."),
|
|
28
|
+
}, async ({ organization_id, company_lead_id, reason, source_url, service, idempotency_key, expected_version }) => {
|
|
29
|
+
try {
|
|
30
|
+
return toolResult(await client.post("/api/outreach/accounts/exclude", {
|
|
31
|
+
organizationId: organization_id, companyLeadId: company_lead_id, reason, sourceUrl: source_url, service,
|
|
32
|
+
idempotencyKey: idempotency_key, expectedVersion: expected_version, origin: "AGENT",
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return handleToolError(error);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
server.tool("exclude_lead", "Exclude one person from prospecting: no message is drafted, enrolled or sent, and their running campaigns stop. " +
|
|
40
|
+
"Same command as the Exclude button in Leadify; the history shows the API key owner as author. caused_by is MANUAL " +
|
|
41
|
+
"(default) or TARGET_NOT_FIT (out of target). For a company use exclude_account instead (this tool refuses company leads " +
|
|
42
|
+
"with 409 OUTREACH_COMPANY_USE_ACCOUNT_ROUTE). Returns the re-read state. " + NEVER_IN_TEXT, {
|
|
43
|
+
...writeShape,
|
|
44
|
+
lead_id: id.describe("Lead id of the person."),
|
|
45
|
+
caused_by: z.enum(["MANUAL", "TARGET_NOT_FIT"]).default("MANUAL"),
|
|
46
|
+
reason: reason.describe("Why the person is excluded; shown in the Leadify history."),
|
|
47
|
+
}, async ({ organization_id, lead_id, caused_by, reason, idempotency_key, expected_version }) => {
|
|
48
|
+
try {
|
|
49
|
+
return toolResult(await client.post("/api/outreach/leads/exclude", {
|
|
50
|
+
organizationId: organization_id, leadId: lead_id, causedBy: caused_by, reason,
|
|
51
|
+
idempotencyKey: idempotency_key, expectedVersion: expected_version, origin: "AGENT",
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return handleToolError(error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
server.tool("reintegrate_lead", "Allow again a person excluded by hand (MANUAL, out of target, or a migrated historical flag). A justification is " +
|
|
59
|
+
"required. It never lifts an opt-out, a deliverability protection or a company exclusion: if one remains, state.status " +
|
|
60
|
+
"stays \"excluded\" and state.causes says why. No campaign is restarted. Returns the re-read state.", {
|
|
61
|
+
...writeShape,
|
|
62
|
+
lead_id: id.describe("Lead id of the person."),
|
|
63
|
+
reason: reason.describe("Why the person may be contacted again; shown in the Leadify history."),
|
|
64
|
+
}, async ({ organization_id, lead_id, reason, idempotency_key, expected_version }) => {
|
|
65
|
+
try {
|
|
66
|
+
return toolResult(await client.post("/api/outreach/leads/reintegrate", {
|
|
67
|
+
organizationId: organization_id, leadId: lead_id, reason,
|
|
68
|
+
idempotencyKey: idempotency_key, expectedVersion: expected_version, origin: "AGENT",
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return handleToolError(error);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
server.tool("get_outreach_state", "Read the prospecting state of 1 to 100 leads (people or companies): status allowed or excluded, active causes with " +
|
|
76
|
+
"their origin and note, version (pass it as expected_version to a write), lastTransitionAt and accountSources " +
|
|
77
|
+
"(companies whose exclusion the person inherits). Leads absent from the organization are listed in missingLeadIds. " +
|
|
78
|
+
"Read this before deciding, instead of looking for exclusions in text.", {
|
|
79
|
+
organization_id: id,
|
|
80
|
+
lead_ids: z.array(id).min(1).max(100),
|
|
81
|
+
}, { readOnlyHint: true }, async ({ organization_id, lead_ids }) => {
|
|
82
|
+
try {
|
|
83
|
+
return toolResult(await client.post("/api/outreach/state", { organizationId: organization_id, leadIds: lead_ids }));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return handleToolError(error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|