@agifyai/leadify-mcp 3.4.0 → 3.6.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/server.js +1 -1
- package/dist/tools/fine_tuning.js +51 -0
- package/dist/tools/pipeline.js +8 -19
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -16,7 +16,7 @@ import { registerPipelineTools } from "./tools/pipeline.js";
|
|
|
16
16
|
export function createServer() {
|
|
17
17
|
const server = new McpServer({
|
|
18
18
|
name: "leadify",
|
|
19
|
-
version: "3.
|
|
19
|
+
version: "3.6.0",
|
|
20
20
|
});
|
|
21
21
|
registerAuthTools(server);
|
|
22
22
|
registerOrganizationTools(server);
|
|
@@ -4,6 +4,17 @@ import { toolResult, handleToolError } from "../types.js";
|
|
|
4
4
|
// Dedicated tool for the Fine Tuning page (separate from OutreachSettings).
|
|
5
5
|
// Both concepts live in the DB (FineTuning + OutreachSettings) and the API
|
|
6
6
|
// keeps them strictly separated: this endpoint NEVER touches OutreachSettings.
|
|
7
|
+
//
|
|
8
|
+
// Two write tools coexist by design, with NON-OVERLAPPING contracts:
|
|
9
|
+
// - append_fine_tuning → ALWAYS appends, never replaces. Contractual guarantee
|
|
10
|
+
// for the iterative build-up pattern (skill operator
|
|
11
|
+
// accumulates rules and examples as the user iterates).
|
|
12
|
+
// - set_fine_tuning → WHOLESALE replace of ONE section. Use when you need
|
|
13
|
+
// to wipe + rewrite a section cleanly (e.g. after a big
|
|
14
|
+
// refactor, or to clear a section by passing empty
|
|
15
|
+
// content).
|
|
16
|
+
// The split exists so neither caller is ever confused about which behavior
|
|
17
|
+
// they triggered.
|
|
7
18
|
export function registerFineTuningTools(server) {
|
|
8
19
|
// ── get_fine_tuning ───────────────────────────────────────────────────
|
|
9
20
|
server.tool("get_fine_tuning", "Get the fine tuning configuration for a specific lead group. Returns exactly " +
|
|
@@ -30,4 +41,44 @@ export function registerFineTuningTools(server) {
|
|
|
30
41
|
return handleToolError(error);
|
|
31
42
|
}
|
|
32
43
|
});
|
|
44
|
+
// ── set_fine_tuning ───────────────────────────────────────────────────
|
|
45
|
+
// WHOLESALE replace of one Fine Tuning section. Distinct from
|
|
46
|
+
// append_fine_tuning (which is append-only, contractual). Both tools
|
|
47
|
+
// coexist intentionally — pick the right one for the intent.
|
|
48
|
+
server.tool("set_fine_tuning", "WHOLESALE replace ONE section of the fine tuning configuration for a lead group. " +
|
|
49
|
+
"The target section is fully overwritten with the new content. " +
|
|
50
|
+
"This is the destructive counterpart of append_fine_tuning: use it when you need " +
|
|
51
|
+
"to wipe and rewrite a section cleanly (big refactor, schema change, full clear). " +
|
|
52
|
+
"SIBLING SECTIONS ARE NOT TOUCHED. Only the named section is replaced; the other 3 " +
|
|
53
|
+
"Markdown sections, supportedLanguages, and fallbackLanguage stay intact. " +
|
|
54
|
+
"To clear a section, pass content=\"\". " +
|
|
55
|
+
"DO NOT use this for incremental edits — use append_fine_tuning instead so the " +
|
|
56
|
+
"iterative build-up pattern is preserved (no accidental wipe of accumulated rules " +
|
|
57
|
+
"or examples). " +
|
|
58
|
+
"The 4 replaceable sections are: nonNegotiableRules, pitfalls, structure, examples. " +
|
|
59
|
+
"Languages (supportedLanguages, fallbackLanguage) are managed separately via the " +
|
|
60
|
+
"Fine Tuning UI or by a dedicated languages tool — this endpoint does not touch them.", {
|
|
61
|
+
lead_group_id: z
|
|
62
|
+
.string()
|
|
63
|
+
.describe("ID of the lead group whose fine tuning section to overwrite."),
|
|
64
|
+
section: z
|
|
65
|
+
.enum(["nonNegotiableRules", "pitfalls", "structure", "examples"])
|
|
66
|
+
.describe("Which Fine Tuning section to replace. One of: " +
|
|
67
|
+
"nonNegotiableRules | pitfalls | structure | examples."),
|
|
68
|
+
content: z
|
|
69
|
+
.string()
|
|
70
|
+
.describe("New content for the section (Markdown). The section is replaced WHOLESALE — " +
|
|
71
|
+
"any previous content is lost. Pass an empty string to clear the section."),
|
|
72
|
+
}, async ({ lead_group_id, section, content }) => {
|
|
73
|
+
try {
|
|
74
|
+
const data = await getClient().put(`/api/lead-group/${encodeURIComponent(lead_group_id)}/fine-tuning`, { section, content });
|
|
75
|
+
return toolResult({
|
|
76
|
+
...(data && typeof data === "object" ? data : {}),
|
|
77
|
+
_note: `Wholesale replace of section '${section}' for lead group ${lead_group_id}.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return handleToolError(error);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
33
84
|
}
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -15,24 +15,14 @@ export function registerPipelineTools(server) {
|
|
|
15
15
|
"card_* sequence fields to lead.data). " +
|
|
16
16
|
"The 'force' flag (default false) bypasses the 'already has a message?' guard — " +
|
|
17
17
|
"useful for re-generating on an already-processed lead. " +
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"file pair in the Trigger.dev repo (pilots/{campaign_id}.md + .json), paired to the lead's " +
|
|
22
|
-
"group. The writer hard-fails if the pilot is missing on a real run. " +
|
|
23
|
-
"The lead's `leadGroupId` is resolved server-side from the lead (one extra " +
|
|
18
|
+
"The pilot pinning (which Trigger.dev pilot/copywriting ruleset to follow) is resolved " +
|
|
19
|
+
"server-side from the lead's group — the caller never has to pass a campaign id. " +
|
|
20
|
+
"The lead's `leadGroupId` is also resolved server-side from the lead (one extra " +
|
|
24
21
|
"`get_lead` call); the caller only needs `lead_id`. " +
|
|
25
22
|
"The response is decorated with a Trigger.dev `traceUrl` deep-link when missing " +
|
|
26
23
|
"(https://cloud.trigger.dev/runs/{runId}), so you can always jump to the trace. " +
|
|
27
24
|
"Waits up to 120s for the remote agent to complete.", {
|
|
28
25
|
lead_id: z.string().describe("ID of the lead to generate a message for."),
|
|
29
|
-
campaign_id: z
|
|
30
|
-
.string()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe("OPTIONAL pilot slug driving the copywriting (e.g. 'agify-medtech-fr-v2'). NOT a DB " +
|
|
33
|
-
"campaign id. Must match a pilot file pair in the Trigger.dev repo whose .json is " +
|
|
34
|
-
"paired to this lead's group. The writer hard-fails if the pilot is missing. " +
|
|
35
|
-
"Omit to skip the pilot-pinning (e.g. for dry-runs or smoke tests)."),
|
|
36
26
|
dryrun: z
|
|
37
27
|
.boolean()
|
|
38
28
|
.optional()
|
|
@@ -46,7 +36,7 @@ export function registerPipelineTools(server) {
|
|
|
46
36
|
.default(false)
|
|
47
37
|
.describe("true = bypass the 'already has a message?' check so you can re-generate. " +
|
|
48
38
|
"false (default) = normal behaviour: skip leads that already have card_message."),
|
|
49
|
-
}, async ({ lead_id,
|
|
39
|
+
}, async ({ lead_id, dryrun, force }) => {
|
|
50
40
|
try {
|
|
51
41
|
// Resolve leadGroupId server-side. The Trigger.dev task payload requires
|
|
52
42
|
// `leadGroupId`, but the lead_id alone is enough on the MCP side — we
|
|
@@ -62,17 +52,16 @@ export function registerPipelineTools(server) {
|
|
|
62
52
|
`The lead was not found, or it has no groupId. ` +
|
|
63
53
|
`Check that the lead exists via get_lead.`);
|
|
64
54
|
}
|
|
55
|
+
// No campaign_id is sent — pilot pinning is resolved server-side from
|
|
56
|
+
// the lead's group. Keeping the field out of the MCP surface is a
|
|
57
|
+
// SILO move: the caller (human or LLM) does not need to know about
|
|
58
|
+
// pilot slugs, and there's no risk of passing a stale or wrong slug.
|
|
65
59
|
const body = {
|
|
66
60
|
leadId: lead_id,
|
|
67
61
|
leadGroupId,
|
|
68
62
|
dryrun,
|
|
69
63
|
force,
|
|
70
64
|
};
|
|
71
|
-
// campaignId is optional: only include it when the caller passed one.
|
|
72
|
-
// Backend defaults are left to handle the omitted case.
|
|
73
|
-
if (campaign_id !== undefined) {
|
|
74
|
-
body.campaignId = campaign_id;
|
|
75
|
-
}
|
|
76
65
|
const data = (await getClient().post("/api/trigger-outreach", body));
|
|
77
66
|
// Decorate the response with a Trigger.dev run deep-link when the
|
|
78
67
|
// backend didn't ship one. Applies to BOTH success and failure paths
|