@agifyai/leadify-mcp 1.5.1 → 2.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.
- package/README.md +3 -0
- package/dist/server.js +2 -0
- package/dist/tools/dataroom.js +15 -12
- package/dist/tools/leads.js +3 -8
- package/dist/tools/pipeline.d.ts +2 -0
- package/dist/tools/pipeline.js +111 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -223,3 +223,6 @@ Conséquences pratiques :
|
|
|
223
223
|
| `update_outreach_case_study` | Ajouter / remplacer / supprimer un case study par index, sans re-envoyer la liste. |
|
|
224
224
|
| `update_outreach_urls` | Patch booking_url et/ou website_url uniquement. |
|
|
225
225
|
| `set_outreach_connection_request` | Toggle du flag connectionRequestEnabled (LinkedIn invite vs cold DM). |
|
|
226
|
+
| `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é. |
|
|
227
|
+
| `append_fine_tuning` | Appendre du contenu à une section du Fine Tuning (nonNegotiableRules, pitfalls, structure, examples). Toujours en mode append — garantie contractuelle. |
|
|
228
|
+
| `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
|
@@ -12,6 +12,7 @@ import { registerPersonaTools } from "./tools/personas.js";
|
|
|
12
12
|
import { registerLeadGroupTools } from "./tools/lead_groups.js";
|
|
13
13
|
import { registerOrganizationTools } from "./tools/organizations.js";
|
|
14
14
|
import { registerOutreachSettingsTools } from "./tools/outreach_settings.js";
|
|
15
|
+
import { registerPipelineTools } from "./tools/pipeline.js";
|
|
15
16
|
export function createServer() {
|
|
16
17
|
const server = new McpServer({
|
|
17
18
|
name: "leadify",
|
|
@@ -30,5 +31,6 @@ export function createServer() {
|
|
|
30
31
|
registerDataSourceTools(server);
|
|
31
32
|
registerPersonaTools(server);
|
|
32
33
|
registerOutreachSettingsTools(server);
|
|
34
|
+
registerPipelineTools(server);
|
|
33
35
|
return server;
|
|
34
36
|
}
|
package/dist/tools/dataroom.js
CHANGED
|
@@ -163,8 +163,11 @@ const DOCUMENT_CATEGORIES = [
|
|
|
163
163
|
"OTHER",
|
|
164
164
|
];
|
|
165
165
|
// ─── Common descriptions ───────────────────────────────────────────────────
|
|
166
|
-
const ORG_ID_DESC = "
|
|
167
|
-
"
|
|
166
|
+
const ORG_ID_DESC = "REQUIRED Clerk organization ID targeting the org whose data room you act on. " +
|
|
167
|
+
"Resolve it from the lead group you are working on (e.g. via list_organizations) " +
|
|
168
|
+
"and pass it explicitly. NEVER omit it: there is no safe default — the API key " +
|
|
169
|
+
"spans multiple orgs and an empty value would silently hit the wrong company's " +
|
|
170
|
+
"data room (read or write). Requires admin access for writes.";
|
|
168
171
|
const DRY_RUN_DESC = "If true, validate the merged payload + return the preview WITHOUT writing. " +
|
|
169
172
|
"Use to dry-fit a change before persisting.";
|
|
170
173
|
async function fetchDataRoom(organizationId) {
|
|
@@ -242,7 +245,7 @@ export function registerDataRoomTools(server) {
|
|
|
242
245
|
server.tool("get_data_room", "Retrieve the organization's full data room: company info (v2 schema), all documents, and " +
|
|
243
246
|
"every persona defined for the workspace. Use this as the entry point when you need to " +
|
|
244
247
|
"understand the company's context before writing campaigns or messages.", {
|
|
245
|
-
organization_id: z.string().
|
|
248
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
246
249
|
}, async ({ organization_id }) => {
|
|
247
250
|
try {
|
|
248
251
|
const data = await fetchDataRoom(organization_id);
|
|
@@ -361,7 +364,7 @@ export function registerDataRoomTools(server) {
|
|
|
361
364
|
company_info: z
|
|
362
365
|
.record(z.unknown())
|
|
363
366
|
.describe("Full v2 companyInfo payload (replaces the existing one). See describe_company_info_schema for the contract."),
|
|
364
|
-
organization_id: z.string().
|
|
367
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
365
368
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
366
369
|
}, async ({ company_info, organization_id, dry_run }) => {
|
|
367
370
|
try {
|
|
@@ -392,7 +395,7 @@ export function registerDataRoomTools(server) {
|
|
|
392
395
|
.max(CHAR_LIMITS.website)
|
|
393
396
|
.optional()
|
|
394
397
|
.describe("Company website URL (max 500)."),
|
|
395
|
-
organization_id: z.string().
|
|
398
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
396
399
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
397
400
|
}, async ({ name, website, organization_id, dry_run }) => {
|
|
398
401
|
try {
|
|
@@ -452,7 +455,7 @@ export function registerDataRoomTools(server) {
|
|
|
452
455
|
.max(10)
|
|
453
456
|
.optional()
|
|
454
457
|
.describe("Replaces constraints[] wholesale. For per-item edits use update_company_info_constraint."),
|
|
455
|
-
organization_id: z.string().
|
|
458
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
456
459
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
457
460
|
}, async ({ pitchOneLiner, marketSpecialty, stage, salesTeamSizeFR, geo, constraints, organization_id, dry_run, }) => {
|
|
458
461
|
try {
|
|
@@ -511,7 +514,7 @@ export function registerDataRoomTools(server) {
|
|
|
511
514
|
constraint: constraintSchema
|
|
512
515
|
.optional()
|
|
513
516
|
.describe("Constraint payload {label ≤150, type ∈ regulatory|brand|legal, note? ≤150}. Required for 'add' and 'replace'."),
|
|
514
|
-
organization_id: z.string().
|
|
517
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
515
518
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
516
519
|
}, async ({ action, index, constraint, organization_id, dry_run }) => {
|
|
517
520
|
try {
|
|
@@ -576,7 +579,7 @@ export function registerDataRoomTools(server) {
|
|
|
576
579
|
product: productSchema
|
|
577
580
|
.optional()
|
|
578
581
|
.describe("Product payload. Required for 'add' and 'replace'."),
|
|
579
|
-
organization_id: z.string().
|
|
582
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
580
583
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
581
584
|
}, async ({ action, name, product, organization_id, dry_run }) => {
|
|
582
585
|
try {
|
|
@@ -656,7 +659,7 @@ export function registerDataRoomTools(server) {
|
|
|
656
659
|
.max(CHAR_LIMITS.defaultBaseline)
|
|
657
660
|
.optional()
|
|
658
661
|
.describe("Default baseline (max 200)."),
|
|
659
|
-
organization_id: z.string().
|
|
662
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
660
663
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
661
664
|
}, async ({ pricingModel, ticketRange, salesCycleMonths, triggers, defaultBaseline, organization_id, dry_run, }) => {
|
|
662
665
|
try {
|
|
@@ -704,7 +707,7 @@ export function registerDataRoomTools(server) {
|
|
|
704
707
|
icp: productICPSchema
|
|
705
708
|
.optional()
|
|
706
709
|
.describe("ICP payload. Required for 'add' and 'replace'."),
|
|
707
|
-
organization_id: z.string().
|
|
710
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
708
711
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
709
712
|
}, async ({ action, productId, icp, organization_id, dry_run }) => {
|
|
710
713
|
try {
|
|
@@ -769,7 +772,7 @@ export function registerDataRoomTools(server) {
|
|
|
769
772
|
.array(z.string().max(CHAR_LIMITS.forbiddenWord))
|
|
770
773
|
.optional()
|
|
771
774
|
.describe("Replaces forbiddenWords[] wholesale (each ≤100)."),
|
|
772
|
-
organization_id: z.string().
|
|
775
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
773
776
|
dry_run: z.boolean().optional().describe(DRY_RUN_DESC),
|
|
774
777
|
}, async ({ keyMetrics, miniStories, forbiddenWords, organization_id, dry_run }) => {
|
|
775
778
|
try {
|
|
@@ -810,7 +813,7 @@ export function registerDataRoomTools(server) {
|
|
|
810
813
|
.string()
|
|
811
814
|
.optional()
|
|
812
815
|
.describe("Text or markdown content."),
|
|
813
|
-
organization_id: z.string().
|
|
816
|
+
organization_id: z.string().describe(ORG_ID_DESC),
|
|
814
817
|
}, async ({ title, category, content, organization_id }) => {
|
|
815
818
|
try {
|
|
816
819
|
const body = { title, category };
|
package/dist/tools/leads.js
CHANGED
|
@@ -62,12 +62,9 @@ export function registerLeadTools(server) {
|
|
|
62
62
|
.number()
|
|
63
63
|
.int()
|
|
64
64
|
.positive()
|
|
65
|
+
.max(50)
|
|
65
66
|
.optional()
|
|
66
|
-
.describe("Results per page. Defaults to 50."),
|
|
67
|
-
no_pagination: z
|
|
68
|
-
.boolean()
|
|
69
|
-
.optional()
|
|
70
|
-
.describe("If true, returns ALL leads in the group without pagination. Use with caution on large groups."),
|
|
67
|
+
.describe("Results per page. Defaults to 50, hard-capped at 50."),
|
|
71
68
|
search: z
|
|
72
69
|
.string()
|
|
73
70
|
.optional()
|
|
@@ -79,7 +76,7 @@ export function registerLeadTools(server) {
|
|
|
79
76
|
"operator to value. Operators: equals, not_equals, contains, not_contains, " +
|
|
80
77
|
"gte, lte, is_true, is_false, is_null, is_not_null. " +
|
|
81
78
|
'Example: {"industry": {"contains": "tech"}, "company": {"is_not_null": "true"}}'),
|
|
82
|
-
}, async ({ group_id, view_id, page, limit,
|
|
79
|
+
}, async ({ group_id, view_id, page, limit, search, filters }) => {
|
|
83
80
|
try {
|
|
84
81
|
const params = new URLSearchParams();
|
|
85
82
|
params.set("groupId", group_id);
|
|
@@ -89,8 +86,6 @@ export function registerLeadTools(server) {
|
|
|
89
86
|
params.set("page", String(page));
|
|
90
87
|
if (limit !== undefined)
|
|
91
88
|
params.set("limit", String(limit));
|
|
92
|
-
if (no_pagination)
|
|
93
|
-
params.set("noPagination", "true");
|
|
94
89
|
if (search)
|
|
95
90
|
params.set("search", search);
|
|
96
91
|
if (filters) {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
export function registerPipelineTools(server) {
|
|
5
|
+
// ── trigger_outreach ─────────────────────────────────────────────────────
|
|
6
|
+
server.tool("trigger_outreach", "Generate the outreach message SEQUENCE for a lead via the Trigger.dev agent " +
|
|
7
|
+
"(Leadify v2 writer). The agent is a PURE MESSAGE WRITER: it produces a multi-touch " +
|
|
8
|
+
"sequence (card_connexion, card_linkedin_one/two/three, card_email_one/two/three plus " +
|
|
9
|
+
"their subjects), filtered to the channels the lead can actually receive. It does NOT " +
|
|
10
|
+
"score, tier, analyse or research — there is no card_analysis/card_research/score/tier " +
|
|
11
|
+
"in the output (those belong to qualification, not writing). " +
|
|
12
|
+
"DRY-RUN BY DEFAULT (dryrun=true) — the agent runs identically but NEVER persists " +
|
|
13
|
+
"anything to the DB; the generated cards travel back in the tool output so you can " +
|
|
14
|
+
"iterate without polluting the DB. Use dryrun=false for production writes (persists the " +
|
|
15
|
+
"card_* sequence fields to lead.data). " +
|
|
16
|
+
"The 'force' flag (default false) bypasses the 'already has a message?' guard — " +
|
|
17
|
+
"useful for re-generating on an already-processed lead. " +
|
|
18
|
+
"Waits up to 120s for the remote agent to complete.", {
|
|
19
|
+
lead_id: z.string().describe("ID of the lead to generate a message for."),
|
|
20
|
+
dryrun: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.default(true)
|
|
24
|
+
.describe("true (default) = dry-run mode: agent runs but writes nothing to DB (generated " +
|
|
25
|
+
"cards returned in the tool output). " +
|
|
26
|
+
"false = production mode: agent persists the card_* sequence fields to lead.data."),
|
|
27
|
+
force: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.default(false)
|
|
31
|
+
.describe("true = bypass the 'already has a message?' check so you can re-generate. " +
|
|
32
|
+
"false (default) = normal behaviour: skip leads that already have card_message."),
|
|
33
|
+
}, async ({ lead_id, dryrun, force }) => {
|
|
34
|
+
try {
|
|
35
|
+
const data = await getClient().post("/api/trigger-outreach", {
|
|
36
|
+
leadId: lead_id,
|
|
37
|
+
dryrun,
|
|
38
|
+
force,
|
|
39
|
+
});
|
|
40
|
+
return toolResult(data);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return handleToolError(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// ── append_fine_tuning ───────────────────────────────────────────────────
|
|
47
|
+
server.tool("append_fine_tuning", "Append content to a Fine Tuning section for a lead group. " +
|
|
48
|
+
"ALWAYS in append mode — NEVER replaces existing content (contractual guarantee). " +
|
|
49
|
+
"Use this to incrementally build up rules and examples as you iterate with the user. " +
|
|
50
|
+
"Sections: nonNegotiableRules (imperative rules), pitfalls (traps to avoid), " +
|
|
51
|
+
"structure (imposed message structure), examples (validated message samples). " +
|
|
52
|
+
"For full rewrites, use the Leadify UI or the classic update endpoint.", {
|
|
53
|
+
lead_group_id: z.string().describe("ID of the lead group."),
|
|
54
|
+
section: z
|
|
55
|
+
.enum(["nonNegotiableRules", "pitfalls", "structure", "examples"])
|
|
56
|
+
.describe("Fine Tuning section to enrich."),
|
|
57
|
+
content: z
|
|
58
|
+
.string()
|
|
59
|
+
.describe("Content to APPEND (not replace). Concatenated after existing content."),
|
|
60
|
+
separator: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.default("\n\n---\n\n")
|
|
64
|
+
.describe("Separator between old and new content. Default: '\\n\\n---\\n\\n'."),
|
|
65
|
+
}, async ({ lead_group_id, section, content, separator }) => {
|
|
66
|
+
try {
|
|
67
|
+
const data = await getClient().post("/api/pipeline/append-fine-tuning", {
|
|
68
|
+
leadGroupId: lead_group_id,
|
|
69
|
+
section,
|
|
70
|
+
content,
|
|
71
|
+
separator,
|
|
72
|
+
});
|
|
73
|
+
return toolResult(data);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return handleToolError(error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// ── pipeline_next_lead ───────────────────────────────────────────────────
|
|
80
|
+
server.tool("pipeline_next_lead", "Select the next lead to process in the campaign pipeline. " +
|
|
81
|
+
"Returns the hottest leads (highest score) that don't yet have a message " +
|
|
82
|
+
"(card_message empty or missing). Exclude already-processed leads via exclude_lead_ids. " +
|
|
83
|
+
"Call this at the start of each pipeline iteration to get a fresh lead.", {
|
|
84
|
+
lead_group_id: z.string().describe("ID of the lead group to query."),
|
|
85
|
+
exclude_lead_ids: z
|
|
86
|
+
.array(z.string())
|
|
87
|
+
.optional()
|
|
88
|
+
.default([])
|
|
89
|
+
.describe("IDs of leads already processed in this session (to avoid duplicates)."),
|
|
90
|
+
limit: z
|
|
91
|
+
.number()
|
|
92
|
+
.int()
|
|
93
|
+
.min(1)
|
|
94
|
+
.max(5)
|
|
95
|
+
.optional()
|
|
96
|
+
.default(1)
|
|
97
|
+
.describe("Maximum number of leads to return (1-5, default 1)."),
|
|
98
|
+
}, async ({ lead_group_id, exclude_lead_ids, limit }) => {
|
|
99
|
+
try {
|
|
100
|
+
const data = await getClient().post("/api/pipeline/next-lead", {
|
|
101
|
+
leadGroupId: lead_group_id,
|
|
102
|
+
excludeLeadIds: exclude_lead_ids,
|
|
103
|
+
limit,
|
|
104
|
+
});
|
|
105
|
+
return toolResult(data);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return handleToolError(error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|