@agifyai/leadify-mcp 3.0.0 → 3.2.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/dist/tools/dataroom.js +25 -10
- package/dist/tools/fine_tuning.d.ts +2 -0
- package/dist/tools/fine_tuning.js +33 -0
- package/dist/tools/lead_groups.js +18 -3
- package/dist/tools/leads.js +5 -1
- package/dist/tools/outreach_settings.js +16 -3
- package/dist/tools/pipeline.js +28 -3
- package/package.json +1 -1
package/dist/tools/dataroom.js
CHANGED
|
@@ -170,11 +170,13 @@ const ORG_ID_DESC = "REQUIRED Clerk organization ID targeting the org whose data
|
|
|
170
170
|
"data room (read or write). Requires admin access for writes.";
|
|
171
171
|
const DRY_RUN_DESC = "If true, validate the merged payload + return the preview WITHOUT writing. " +
|
|
172
172
|
"Use to dry-fit a change before persisting.";
|
|
173
|
-
async function fetchDataRoom(organizationId) {
|
|
174
|
-
const params =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
async function fetchDataRoom(organizationId, opts) {
|
|
174
|
+
const params = new URLSearchParams();
|
|
175
|
+
if (organizationId)
|
|
176
|
+
params.set("organizationId", organizationId);
|
|
177
|
+
if (opts?.includePersonas)
|
|
178
|
+
params.set("includePersonas", "true");
|
|
179
|
+
const data = (await getClient().get("/api/data-room", params.toString() ? params : undefined));
|
|
178
180
|
return data ?? {};
|
|
179
181
|
}
|
|
180
182
|
function currentCompanyInfo(state) {
|
|
@@ -242,13 +244,26 @@ async function mergeAndPut(organizationId, dry_run, patcher, validationHint) {
|
|
|
242
244
|
// ─── Tool registrations ────────────────────────────────────────────────────
|
|
243
245
|
export function registerDataRoomTools(server) {
|
|
244
246
|
// ── get_data_room ──────────────────────────────────────────────────────
|
|
245
|
-
server.tool("get_data_room", "Retrieve the organization's
|
|
246
|
-
"
|
|
247
|
-
"
|
|
247
|
+
server.tool("get_data_room", "Retrieve the organization's data room: company info (v2 schema) and all documents. " +
|
|
248
|
+
"Use this as the entry point when you need to understand the company's context " +
|
|
249
|
+
"before writing campaigns or messages. " +
|
|
250
|
+
"By default, `personas[]` is NO LONGER included in the response (slimed down to " +
|
|
251
|
+
"keep the LLM context small — the data room itself was 158k chars). " +
|
|
252
|
+
"Pass `include_personas=true` to restore the legacy full payload (escape hatch). " +
|
|
253
|
+
"For per-persona queries, prefer the `list_personas` tool.", {
|
|
248
254
|
organization_id: z.string().describe(ORG_ID_DESC),
|
|
249
|
-
|
|
255
|
+
include_personas: z
|
|
256
|
+
.boolean()
|
|
257
|
+
.optional()
|
|
258
|
+
.default(false)
|
|
259
|
+
.describe("If true, include the full `personas[]` array in the response (legacy " +
|
|
260
|
+
"behaviour, ~150k chars). If false (default), personas are omitted — use " +
|
|
261
|
+
"`list_personas` for per-persona queries."),
|
|
262
|
+
}, async ({ organization_id, include_personas }) => {
|
|
250
263
|
try {
|
|
251
|
-
const data = await fetchDataRoom(organization_id
|
|
264
|
+
const data = await fetchDataRoom(organization_id, {
|
|
265
|
+
includePersonas: include_personas === true,
|
|
266
|
+
});
|
|
252
267
|
return toolResult(data);
|
|
253
268
|
}
|
|
254
269
|
catch (error) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
// Dedicated tool for the Fine Tuning page (separate from OutreachSettings).
|
|
5
|
+
// Both concepts live in the DB (FineTuning + OutreachSettings) and the API
|
|
6
|
+
// keeps them strictly separated: this endpoint NEVER touches OutreachSettings.
|
|
7
|
+
export function registerFineTuningTools(server) {
|
|
8
|
+
// ── get_fine_tuning ───────────────────────────────────────────────────
|
|
9
|
+
server.tool("get_fine_tuning", "Get the fine tuning configuration for a specific lead group. Returns exactly " +
|
|
10
|
+
"the 5 levers of the Fine Tuning page and nothing else: " +
|
|
11
|
+
"(1) nonNegotiableRules (Markdown), (2) pitfalls (Markdown), (3) structure (Markdown), " +
|
|
12
|
+
"(4) supportedLanguages (string[]), (5) examples (Markdown), plus fallbackLanguage. " +
|
|
13
|
+
"Read STRICTLY from the FineTuning model — zero OutreachSettings mixed in. " +
|
|
14
|
+
"Use this when editing the Fine Tuning page or when an agent needs the copywriting " +
|
|
15
|
+
"guardrails (rules + pitfalls + structure + examples + languages). " +
|
|
16
|
+
"DO NOT use get_outreach_settings for this: that endpoint returns 100× more payload " +
|
|
17
|
+
"and is for the Outreach agent preflight, not for fine tuning. " +
|
|
18
|
+
"Upsert-on-read: if no FineTuning row exists for the lead group, one is created " +
|
|
19
|
+
"with defaults, so the response shape is always stable.", {
|
|
20
|
+
lead_group_id: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe("ID of the lead group whose fine tuning configuration to fetch. " +
|
|
23
|
+
"Resolve via list_organizations + get_leads if needed."),
|
|
24
|
+
}, async ({ lead_group_id }) => {
|
|
25
|
+
try {
|
|
26
|
+
const data = await getClient().get(`/api/lead-group/${encodeURIComponent(lead_group_id)}/fine-tuning`);
|
|
27
|
+
return toolResult(data);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return handleToolError(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -158,11 +158,26 @@ export function registerLeadGroupTools(server) {
|
|
|
158
158
|
// ── get_lead_group_persona ─────────────────────────────────────────────
|
|
159
159
|
server.tool("get_lead_group_persona", "Retrieve the persona assigned to a specific lead group, with group context. " +
|
|
160
160
|
"Returns null persona if none is assigned. Use this to check what targeting " +
|
|
161
|
-
"rules apply to a group before generating messages."
|
|
161
|
+
"rules apply to a group before generating messages. " +
|
|
162
|
+
"By default, `persona` is SLIM ({id, name, organizationId}) — about ~150 chars " +
|
|
163
|
+
"instead of 69k — just enough to resolve the org / link the persona to a lead group. " +
|
|
164
|
+
"Pass `view=full` to get the complete persona (legacy behaviour). " +
|
|
165
|
+
"Pass `view=outreach` to get the outreach-specific projection.", {
|
|
162
166
|
lead_group_id: z.string().describe("Lead group ID."),
|
|
163
|
-
|
|
167
|
+
view: z
|
|
168
|
+
.enum(["slim", "outreach", "full"])
|
|
169
|
+
.optional()
|
|
170
|
+
.default("slim")
|
|
171
|
+
.describe("Persona projection. " +
|
|
172
|
+
"'slim' (default) = {id, name, organizationId} — fast, context-friendly. " +
|
|
173
|
+
"'outreach' = projection used by the outreach agent (messaging + targeting). " +
|
|
174
|
+
"'full' = complete persona payload (legacy 69k-char shape, escape hatch)."),
|
|
175
|
+
}, async ({ lead_group_id, view }) => {
|
|
164
176
|
try {
|
|
165
|
-
const
|
|
177
|
+
const params = new URLSearchParams();
|
|
178
|
+
if (view)
|
|
179
|
+
params.set("view", view);
|
|
180
|
+
const data = await getClient().get(`/api/lead-group/${encodeURIComponent(lead_group_id)}/persona`, params);
|
|
166
181
|
return toolResult(data);
|
|
167
182
|
}
|
|
168
183
|
catch (error) {
|
package/dist/tools/leads.js
CHANGED
|
@@ -107,7 +107,11 @@ export function registerLeadTools(server) {
|
|
|
107
107
|
"group membership, creation/update timestamps, related leads (if relations are " +
|
|
108
108
|
"configured), and the group schema. Use this when you already have a lead ID " +
|
|
109
109
|
"and need its complete information. For searching or browsing multiple leads, " +
|
|
110
|
-
"use get_leads instead."
|
|
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.", {
|
|
111
115
|
id: z.string().describe("The unique ID of the lead to retrieve."),
|
|
112
116
|
}, async ({ id }) => {
|
|
113
117
|
try {
|
|
@@ -78,11 +78,24 @@ export function registerOutreachSettingsTools(server) {
|
|
|
78
78
|
// ── get_outreach_settings ──────────────────────────────────────────────
|
|
79
79
|
server.tool("get_outreach_settings", "Get the outreach settings (positioning, sequence design, rules, URLs, case studies) " +
|
|
80
80
|
"for a specific lead group / campaign. The settings drive the [Leadify] Outreach agent " +
|
|
81
|
-
"preflight. GET auto-creates a row with defaults on first read."
|
|
81
|
+
"preflight. GET auto-creates a row with defaults on first read. " +
|
|
82
|
+
"Pass `sections` (CSV) to slice the response and avoid saturating the LLM context " +
|
|
83
|
+
"when you only need one part (e.g. 'positioning,urls'). Allowed: " +
|
|
84
|
+
"positioning, sequenceDesign, rules, caseStudies, urls, connection.", {
|
|
82
85
|
lead_group_id: z.string().describe("Lead group ID."),
|
|
83
|
-
|
|
86
|
+
sections: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Optional CSV of section names to return. " +
|
|
90
|
+
"Allowed values: positioning, sequenceDesign, rules, caseStudies, urls, connection. " +
|
|
91
|
+
"If omitted, the full row is returned (legacy behaviour). " +
|
|
92
|
+
"Use this to keep the LLM context small when you only need one section."),
|
|
93
|
+
}, async ({ lead_group_id, sections }) => {
|
|
84
94
|
try {
|
|
85
|
-
const
|
|
95
|
+
const params = new URLSearchParams();
|
|
96
|
+
if (sections)
|
|
97
|
+
params.set("sections", sections);
|
|
98
|
+
const data = await getClient().get(`/api/lead-group/${encodeURIComponent(lead_group_id)}/outreach-settings`, params);
|
|
86
99
|
return toolResult(data);
|
|
87
100
|
}
|
|
88
101
|
catch (error) {
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getClient } from "../client.js";
|
|
3
|
-
import { toolResult, handleToolError } from "../types.js";
|
|
3
|
+
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 " +
|
|
@@ -19,6 +19,10 @@ export function registerPipelineTools(server) {
|
|
|
19
19
|
"which copywriting pilot the writer follows. It is NOT a DB id — it must match a pilot " +
|
|
20
20
|
"authored in the Trigger.dev repo (pilots/{campaign_id}.md + .json), and that pilot's " +
|
|
21
21
|
".json is paired to the lead's group. The skill operator picks it; there is no default. " +
|
|
22
|
+
"The lead's `leadGroupId` is resolved server-side from the lead (one extra " +
|
|
23
|
+
"`get_lead` call); the caller only needs `lead_id`. " +
|
|
24
|
+
"The response is decorated with a Trigger.dev `traceUrl` deep-link when missing " +
|
|
25
|
+
"(https://cloud.trigger.dev/runs/{runId}), so you can always jump to the trace. " +
|
|
22
26
|
"Waits up to 120s for the remote agent to complete.", {
|
|
23
27
|
lead_id: z.string().describe("ID of the lead to generate a message for."),
|
|
24
28
|
campaign_id: z
|
|
@@ -41,12 +45,33 @@ export function registerPipelineTools(server) {
|
|
|
41
45
|
"false (default) = normal behaviour: skip leads that already have card_message."),
|
|
42
46
|
}, async ({ lead_id, campaign_id, dryrun, force }) => {
|
|
43
47
|
try {
|
|
44
|
-
|
|
48
|
+
// Resolve leadGroupId server-side. The Trigger.dev task payload requires
|
|
49
|
+
// `leadGroupId`, but the lead_id alone is enough on the MCP side — we
|
|
50
|
+
// fetch the lead to extract groupId instead of forcing the caller (human
|
|
51
|
+
// or LLM) to pass it manually. One extra HTTP call, dwarfed by the
|
|
52
|
+
// 10–60s Trigger.dev wait that follows.
|
|
53
|
+
const leadParams = new URLSearchParams();
|
|
54
|
+
leadParams.set("id", lead_id);
|
|
55
|
+
const leadRes = (await getClient().get("/get-lead", leadParams));
|
|
56
|
+
const leadGroupId = leadRes?.lead?.groupId;
|
|
57
|
+
if (!leadGroupId) {
|
|
58
|
+
return toolError(`Cannot resolve leadGroupId for lead "${lead_id}". ` +
|
|
59
|
+
`The lead was not found, or it has no groupId. ` +
|
|
60
|
+
`Check that the lead exists via get_lead.`);
|
|
61
|
+
}
|
|
62
|
+
const data = (await getClient().post("/api/trigger-outreach", {
|
|
45
63
|
leadId: lead_id,
|
|
64
|
+
leadGroupId,
|
|
46
65
|
campaignId: campaign_id,
|
|
47
66
|
dryrun,
|
|
48
67
|
force,
|
|
49
|
-
});
|
|
68
|
+
}));
|
|
69
|
+
// Decorate the response with a Trigger.dev run deep-link when the
|
|
70
|
+
// backend didn't ship one. Applies to BOTH success and failure paths
|
|
71
|
+
// so the skill operator can always jump to the trace.
|
|
72
|
+
if (data?.runId && !data.traceUrl) {
|
|
73
|
+
data.traceUrl = `https://cloud.trigger.dev/runs/${data.runId}`;
|
|
74
|
+
}
|
|
50
75
|
return toolResult(data);
|
|
51
76
|
}
|
|
52
77
|
catch (error) {
|