@agifyai/leadify-mcp 7.0.0 → 7.1.1
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/dist/tools/dataroom.js +28 -18
- package/dist/tools/leads.js +39 -18
- package/dist/tools/logs.js +12 -1
- package/dist/tools/pipeline.js +34 -3
- package/package.json +1 -1
package/dist/tools/dataroom.js
CHANGED
|
@@ -249,27 +249,37 @@ async function mergeAndPut(organizationId, dry_run, patcher, validationHint) {
|
|
|
249
249
|
// organization_id. The backend resolves the org server-side.
|
|
250
250
|
export function registerDataRoomTools(server) {
|
|
251
251
|
// ── get_data_room ──────────────────────────────────────────────────────
|
|
252
|
-
server.tool("get_data_room", "Retrieve
|
|
253
|
-
"
|
|
254
|
-
"before writing campaigns or messages. " +
|
|
255
|
-
"By default, `personas[]` is NO LONGER included in the response (slimed down to " +
|
|
256
|
-
"keep the LLM context small — the data room itself was 158k chars). " +
|
|
257
|
-
"Pass `include_personas=true` to restore the legacy full payload (escape hatch). " +
|
|
258
|
-
"For per-persona queries, prefer the `list_personas` tool.", {
|
|
252
|
+
server.tool("get_data_room", "Retrieve compact data-room metadata by default. Documents are paginated and their content " +
|
|
253
|
+
"is omitted unless explicitly requested for named document IDs.", {
|
|
259
254
|
organization_id: z.string().describe(ORG_ID_DESC),
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}, async ({ organization_id, include_personas }) => {
|
|
255
|
+
page: z.number().int().positive().optional().default(1).describe("Document page, starting at 1."),
|
|
256
|
+
limit: z.number().int().positive().max(50).optional().default(20).describe("Documents per page, max 50."),
|
|
257
|
+
category: z.string().optional().describe("Filter documents by category."),
|
|
258
|
+
search: z.string().optional().describe("Search document title and metadata."),
|
|
259
|
+
include_document_content_for_ids: z.array(z.string()).max(10).optional().describe("Explicit document IDs whose content is required."),
|
|
260
|
+
include_personas: z.boolean().optional().default(false).describe("Include personas only when explicitly required."),
|
|
261
|
+
}, async ({ organization_id, page, limit, category, search, include_document_content_for_ids, include_personas }) => {
|
|
268
262
|
try {
|
|
269
|
-
const
|
|
270
|
-
|
|
263
|
+
const state = await fetchDataRoom(organization_id, { includePersonas: include_personas === true });
|
|
264
|
+
const room = state.dataRoom && typeof state.dataRoom === "object" ? state.dataRoom : state;
|
|
265
|
+
const documents = Array.isArray(room.documents) ? room.documents : [];
|
|
266
|
+
const term = search?.toLowerCase();
|
|
267
|
+
const filtered = documents.filter((document) => {
|
|
268
|
+
const value = document && typeof document === "object" ? document : {};
|
|
269
|
+
if (category && value.category !== category)
|
|
270
|
+
return false;
|
|
271
|
+
return !term || JSON.stringify({ title: value.title, name: value.name, category: value.category }).toLowerCase().includes(term);
|
|
271
272
|
});
|
|
272
|
-
|
|
273
|
+
const start = (page - 1) * limit;
|
|
274
|
+
const requested = new Set(include_document_content_for_ids ?? []);
|
|
275
|
+
const items = filtered.slice(start, start + limit).map((document) => {
|
|
276
|
+
const value = document && typeof document === "object" ? document : {};
|
|
277
|
+
const compact = { id: value.id, title: value.title ?? value.name, category: value.category, createdAt: value.createdAt, updatedAt: value.updatedAt };
|
|
278
|
+
if (requested.has(String(value.id)))
|
|
279
|
+
compact.content = value.content;
|
|
280
|
+
return compact;
|
|
281
|
+
});
|
|
282
|
+
return toolResult({ company_info: room.companyInfo, documents: items, pagination: { page, limit, total: filtered.length, totalPages: Math.ceil(filtered.length / limit) } });
|
|
273
283
|
}
|
|
274
284
|
catch (error) {
|
|
275
285
|
return handleToolError(error);
|
package/dist/tools/leads.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getClient } from "../client.js";
|
|
3
3
|
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
const DEFAULT_LEAD_FIELDS = ["firstName", "lastName", "email", "company", "jobTitle", "linkedin"];
|
|
5
|
+
function projectLead(lead, fields) {
|
|
6
|
+
const source = lead && typeof lead === "object" ? lead : {};
|
|
7
|
+
const data = source.data && typeof source.data === "object" ? source.data : {};
|
|
8
|
+
const projected = { id: source.id };
|
|
9
|
+
for (const field of fields)
|
|
10
|
+
if (data[field] !== undefined)
|
|
11
|
+
projected[field] = data[field];
|
|
12
|
+
return projected;
|
|
13
|
+
}
|
|
14
|
+
function compactLeadsResponse(data, fields, includeSchema) {
|
|
15
|
+
const source = data && typeof data === "object" ? data : {};
|
|
16
|
+
const leads = Array.isArray(source.leads) ? source.leads : [];
|
|
17
|
+
const result = { leads: leads.map((lead) => projectLead(lead, fields)), pagination: source.pagination };
|
|
18
|
+
if (includeSchema)
|
|
19
|
+
result.schema = source.schema;
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
4
22
|
export function registerLeadTools(server) {
|
|
5
23
|
// ── add_leads ──────────────────────────────────────────────────────────
|
|
6
24
|
server.tool("add_leads", "Add one or more leads to a specific lead group in Leadify. Provide the group ID " +
|
|
@@ -42,11 +60,9 @@ export function registerLeadTools(server) {
|
|
|
42
60
|
});
|
|
43
61
|
// ── get_leads ──────────────────────────────────────────────────────────
|
|
44
62
|
server.tool("get_leads", "Search and list leads from a Leadify lead group with optional filtering, search, " +
|
|
45
|
-
"and
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"the group schema. Use this to browse, search, or filter leads. For retrieving " +
|
|
49
|
-
"a single lead by its ID, use get_lead instead.", {
|
|
63
|
+
"full-text search, saved views, and compact field projections. Returns lead ID plus " +
|
|
64
|
+
"common identity fields by default. Pass `fields` for a workflow-specific projection " +
|
|
65
|
+
"and `include_schema=true` only when the schema is explicitly needed.", {
|
|
50
66
|
group_id: z.string().describe("ID of the lead group to query."),
|
|
51
67
|
view_id: z
|
|
52
68
|
.string()
|
|
@@ -69,6 +85,8 @@ export function registerLeadTools(server) {
|
|
|
69
85
|
.string()
|
|
70
86
|
.optional()
|
|
71
87
|
.describe("Full-text search term applied across all fields."),
|
|
88
|
+
fields: z.array(z.string()).min(1).max(30).optional().describe("Lead data fields to return. Defaults to a compact identity projection."),
|
|
89
|
+
include_schema: z.boolean().optional().default(false).describe("Include the complete group schema only when explicitly needed."),
|
|
72
90
|
filters: z
|
|
73
91
|
.record(z.record(z.string()))
|
|
74
92
|
.optional()
|
|
@@ -76,7 +94,7 @@ export function registerLeadTools(server) {
|
|
|
76
94
|
"operator to value. Operators: equals, not_equals, contains, not_contains, " +
|
|
77
95
|
"gte, lte, is_true, is_false, is_null, is_not_null. " +
|
|
78
96
|
'Example: {"industry": {"contains": "tech"}, "company": {"is_not_null": "true"}}'),
|
|
79
|
-
}, async ({ group_id, view_id, page, limit, search, filters }) => {
|
|
97
|
+
}, async ({ group_id, view_id, page, limit, search, fields, include_schema, filters }) => {
|
|
80
98
|
try {
|
|
81
99
|
const params = new URLSearchParams();
|
|
82
100
|
params.set("groupId", group_id);
|
|
@@ -96,29 +114,32 @@ export function registerLeadTools(server) {
|
|
|
96
114
|
}
|
|
97
115
|
}
|
|
98
116
|
const data = await getClient().get("/get-leads", params);
|
|
99
|
-
return toolResult(data);
|
|
117
|
+
return toolResult(compactLeadsResponse(data, fields ?? DEFAULT_LEAD_FIELDS, include_schema));
|
|
100
118
|
}
|
|
101
119
|
catch (error) {
|
|
102
120
|
return handleToolError(error);
|
|
103
121
|
}
|
|
104
122
|
});
|
|
105
123
|
// ── get_lead ───────────────────────────────────────────────────────────
|
|
106
|
-
server.tool("get_lead", "Retrieve
|
|
107
|
-
"
|
|
108
|
-
"configured), and the group schema. Use this when you already have a lead ID " +
|
|
109
|
-
"and need its complete information. For searching or browsing multiple leads, " +
|
|
110
|
-
"use get_leads instead. " +
|
|
111
|
-
"Note: the group schema lives at `lead.group.schema` (no top-level `schema` " +
|
|
112
|
-
"field). System-level `select` field options (qualification_level, " +
|
|
113
|
-
"qualification_status, seniority, institution_type) are reconciled against the " +
|
|
114
|
-
"canonical registry, so all legitimate values are listed.", {
|
|
124
|
+
server.tool("get_lead", "Retrieve a compact projection for a single lead. Defaults to identity fields only; " +
|
|
125
|
+
"request fields for workflow-specific data and include_schema only when needed.", {
|
|
115
126
|
id: z.string().describe("The unique ID of the lead to retrieve."),
|
|
116
|
-
|
|
127
|
+
fields: z.array(z.string()).min(1).max(30).optional().describe("Lead data fields to return."),
|
|
128
|
+
include_schema: z.boolean().optional().default(false).describe("Include the group schema only when explicitly needed."),
|
|
129
|
+
}, async ({ id, fields, include_schema }) => {
|
|
117
130
|
try {
|
|
118
131
|
const params = new URLSearchParams();
|
|
119
132
|
params.set("id", id);
|
|
120
133
|
const data = await getClient().get("/get-lead", params);
|
|
121
|
-
|
|
134
|
+
const source = data && typeof data === "object" ? data : {};
|
|
135
|
+
const lead = source.lead;
|
|
136
|
+
const result = { lead: projectLead(lead, fields ?? DEFAULT_LEAD_FIELDS) };
|
|
137
|
+
if (include_schema && lead && typeof lead === "object") {
|
|
138
|
+
const group = lead.group;
|
|
139
|
+
if (group && typeof group === "object")
|
|
140
|
+
result.schema = group.schema;
|
|
141
|
+
}
|
|
142
|
+
return toolResult(result);
|
|
122
143
|
}
|
|
123
144
|
catch (error) {
|
|
124
145
|
return handleToolError(error);
|
package/dist/tools/logs.js
CHANGED
|
@@ -124,6 +124,7 @@ export function registerLogTools(server) {
|
|
|
124
124
|
.max(100)
|
|
125
125
|
.optional()
|
|
126
126
|
.describe("Results per page. Defaults to 50, max 100."),
|
|
127
|
+
include_message_for_log_ids: z.array(z.string()).max(10).optional().describe("Explicit log IDs whose complete message is required."),
|
|
127
128
|
sort_by: z
|
|
128
129
|
.enum(["createdAt", "level", "action", "campaignSlug"])
|
|
129
130
|
.optional()
|
|
@@ -158,7 +159,17 @@ export function registerLogTools(server) {
|
|
|
158
159
|
if (params.sort_order)
|
|
159
160
|
qp.set("sort_order", params.sort_order);
|
|
160
161
|
const data = await getClient().get("/get-lead-logs", qp);
|
|
161
|
-
|
|
162
|
+
const source = data && typeof data === "object" ? data : {};
|
|
163
|
+
const requested = new Set(params.include_message_for_log_ids ?? []);
|
|
164
|
+
const logs = Array.isArray(source.data) ? source.data : [];
|
|
165
|
+
const compact = logs.map((log) => {
|
|
166
|
+
const value = log && typeof log === "object" ? log : {};
|
|
167
|
+
const item = { id: value.id, leadId: value.leadId, campaignSlug: value.campaignSlug, campaignName: value.campaignName, action: value.action, level: value.level, createdAt: value.createdAt, errorCode: value.errorCode, retryCount: value.retryCount };
|
|
168
|
+
if (requested.has(String(value.id)))
|
|
169
|
+
item.message = value.message;
|
|
170
|
+
return item;
|
|
171
|
+
});
|
|
172
|
+
return toolResult({ logs: compact, pagination: source.pagination });
|
|
162
173
|
}
|
|
163
174
|
catch (error) {
|
|
164
175
|
return handleToolError(error);
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -4,7 +4,7 @@ import { toolResult, toolError, handleToolError } from "../types.js";
|
|
|
4
4
|
export function registerPipelineTools(server) {
|
|
5
5
|
// ── trigger_outreach ─────────────────────────────────────────────────────
|
|
6
6
|
server.tool("trigger_outreach", "Generate the outreach message SEQUENCE for a lead via the Trigger.dev agent " +
|
|
7
|
-
"(Leadify
|
|
7
|
+
"(Leadify v3 writer). The agent is a PURE MESSAGE WRITER: it produces a multi-touch " +
|
|
8
8
|
"sequence (card_connexion, card_linkedin_one/two/three, card_email_one/two/three plus " +
|
|
9
9
|
"their subjects), filtered to the channels the lead can actually receive. It does NOT " +
|
|
10
10
|
"score, tier, analyse or research — there is no card_analysis/card_research/score/tier " +
|
|
@@ -27,7 +27,14 @@ export function registerPipelineTools(server) {
|
|
|
27
27
|
"this is NOT a failure — the run keeps going server-side, follow it via the " +
|
|
28
28
|
"traceUrl. In dry-run (dryrun=true) the call is safe to retry, since nothing is " +
|
|
29
29
|
"persisted; for production writes (dryrun=false) prefer following the trace over " +
|
|
30
|
-
"blindly retrying to avoid duplicate generation."
|
|
30
|
+
"blindly retrying to avoid duplicate generation. " +
|
|
31
|
+
"CARDS SUBSET (cards_to_generate): pass an explicit list of card names to restrict " +
|
|
32
|
+
"the generation to a specific subset. Use this for single-touch campaigns or " +
|
|
33
|
+
"when the full 10-card cycle is not needed. Omit to generate the full sequence " +
|
|
34
|
+
"(default). Subject fields (card_email_one_subject, etc.) should always accompany " +
|
|
35
|
+
"their body card. Valid card names: card_connexion, card_linkedin_one, " +
|
|
36
|
+
"card_linkedin_two, card_linkedin_three, card_email_one, card_email_one_subject, " +
|
|
37
|
+
"card_email_two, card_email_two_subject, card_email_three, card_email_three_subject.", {
|
|
31
38
|
lead_id: z.string().describe("ID of the lead to generate a message for."),
|
|
32
39
|
dryrun: z
|
|
33
40
|
.boolean()
|
|
@@ -42,7 +49,28 @@ export function registerPipelineTools(server) {
|
|
|
42
49
|
.default(false)
|
|
43
50
|
.describe("true = bypass the 'already has a message?' check so you can re-generate. " +
|
|
44
51
|
"false (default) = normal behaviour: skip leads that already have card_message."),
|
|
45
|
-
|
|
52
|
+
cards_to_generate: z
|
|
53
|
+
.array(z.enum([
|
|
54
|
+
"card_connexion",
|
|
55
|
+
"card_linkedin_one",
|
|
56
|
+
"card_linkedin_two",
|
|
57
|
+
"card_linkedin_three",
|
|
58
|
+
"card_email_one",
|
|
59
|
+
"card_email_one_subject",
|
|
60
|
+
"card_email_two",
|
|
61
|
+
"card_email_two_subject",
|
|
62
|
+
"card_email_three",
|
|
63
|
+
"card_email_three_subject",
|
|
64
|
+
]))
|
|
65
|
+
.min(1)
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Optional subset of outreach cards to generate. When omitted, all cards are " +
|
|
68
|
+
"generated (default behaviour — full 10-card sequence). When provided, ONLY " +
|
|
69
|
+
"these cards are generated. Example for single-touch: " +
|
|
70
|
+
"['card_connexion', 'card_linkedin_one', 'card_email_one', 'card_email_one_subject']. " +
|
|
71
|
+
"Subject fields must always accompany their body card. Cards for channels the " +
|
|
72
|
+
"lead doesn't have (e.g. email when no email address) are silently dropped."),
|
|
73
|
+
}, async ({ lead_id, dryrun, force, cards_to_generate }) => {
|
|
46
74
|
try {
|
|
47
75
|
// Resolve leadGroupId server-side. The Trigger.dev task payload requires
|
|
48
76
|
// `leadGroupId`, but the lead_id alone is enough on the MCP side — we
|
|
@@ -68,6 +96,9 @@ export function registerPipelineTools(server) {
|
|
|
68
96
|
dryrun,
|
|
69
97
|
force,
|
|
70
98
|
};
|
|
99
|
+
if (cards_to_generate && cards_to_generate.length > 0) {
|
|
100
|
+
body.cardsToGenerate = cards_to_generate;
|
|
101
|
+
}
|
|
71
102
|
const data = (await getClient().post("/api/trigger-outreach", body));
|
|
72
103
|
// Decorate the response with a Trigger.dev run deep-link when the
|
|
73
104
|
// backend didn't ship one. Applies to BOTH success and failure paths
|