@agifyai/leadify-mcp 7.1.3 → 8.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
CHANGED
|
@@ -200,9 +200,11 @@ Conséquences pratiques :
|
|
|
200
200
|
| `signal_disable` | Désactiver un signal (faux positif / écarté manuellement). Flip de `state` uniquement. |
|
|
201
201
|
| `signal_delete` | Supprimer définitivement un signal (cas rare : donnée erronée, doublon). |
|
|
202
202
|
| `add_activity` | Journaliser une interaction prospect (LinkedIn, email, call) dans le feed du lead. |
|
|
203
|
-
| `
|
|
203
|
+
| `get_context_workspace` | Lire le Context Workspace canonique versionné (`company_brain` ou `gtm_playbook`) d'une organisation explicitement sélectionnée. Retourne le brouillon et la dernière version publiée. |
|
|
204
|
+
| `update_company_brain_sections` | Modifier uniquement les sections indiquées du Company Brain : le MCP relit le brouillon ou la dernière publication, fusionne les sections ciblées, puis sauvegarde avec révision attendue et clé d'idempotence. |
|
|
205
|
+
| `update_gtm_playbook_sections` | Modifier uniquement les sections indiquées du GTM Playbook, avec la même lecture-fusion-révision atomiquement contrôlée. |
|
|
206
|
+
| `publish_context_workspace` | Publier une révision prête du Context Workspace après confirmation explicite (`confirm_publish: true`). Admin de l'organisation requis ; les raisons de non-readiness sont renvoyées par le serveur. |
|
|
204
207
|
| `describe_company_info_schema` | Lire le schéma companyInfo v2 mirroré côté MCP (enums, max-lengths, sections, patch tool par section). À appeler avant la première update. |
|
|
205
|
-
| `update_data_room` | Update WHOLESALE (escape hatch) — remplace l'intégralité de companyInfo. Requis pour le bootstrap initial. Préférer les tools granulaires pour l'incrémental. |
|
|
206
208
|
| `update_company_info_identity` | Patch name + website. Reads + merges + PUTs le full v2 payload. |
|
|
207
209
|
| `update_company_info_snapshot` | Patch du bloc snapshot (pitchOneLiner, marketSpecialty, stage, salesTeamSizeFR, geo, constraints). geo merge shallow. |
|
|
208
210
|
| `update_company_info_constraint` | Add / replace / remove une seule contrainte dans snapshot.constraints[]. label + type enum + note≤150. Cap 10. |
|
|
@@ -210,7 +212,6 @@ Conséquences pratiques :
|
|
|
210
212
|
| `update_company_info_economics` | Patch du bloc economics (pricingModel, ticketRange, salesCycleMonths, triggers, defaultBaseline). |
|
|
211
213
|
| `update_company_info_product_icp` | Add / replace / remove un seul ICP dans productICPs[]. Lookup par productId (must match a products[].name). Cap 5. |
|
|
212
214
|
| `update_company_info_proof_wording` | Patch du bloc proofWording (keyMetrics, miniStories, forbiddenWords). |
|
|
213
|
-
| `add_data_room_document` | Ajouter un document textuel à la data room avec catégorie. |
|
|
214
215
|
| `upsert_persona` | Créer ou mettre à jour un persona (ciblage, messaging, outils actifs). |
|
|
215
216
|
| `get_persona` | Récupérer un persona par son ID. |
|
|
216
217
|
| `get_lead_group_persona` | Récupérer le persona assigné à un groupe de leads. |
|
package/dist/server.js
CHANGED
|
@@ -6,17 +6,17 @@ import { registerLogTools } from "./tools/logs.js";
|
|
|
6
6
|
import { registerCampaignTools } from "./tools/campaigns.js";
|
|
7
7
|
import { registerSignalTools } from "./tools/signals.js";
|
|
8
8
|
import { registerActivityTools } from "./tools/activities.js";
|
|
9
|
-
import { registerDataRoomTools } from "./tools/dataroom.js";
|
|
10
9
|
import { registerDataSourceTools } from "./tools/datasources.js";
|
|
11
10
|
import { registerPersonaTools } from "./tools/personas.js";
|
|
12
11
|
import { registerLeadGroupTools } from "./tools/lead_groups.js";
|
|
13
12
|
import { registerOrganizationTools } from "./tools/organizations.js";
|
|
14
13
|
import { registerPipelineTools } from "./tools/pipeline.js";
|
|
15
14
|
import { registerFineTuningTools } from "./tools/fine_tuning.js";
|
|
15
|
+
import { registerContextWorkspaceTools } from "./tools/context_workspace.js";
|
|
16
16
|
export function createServer() {
|
|
17
17
|
const server = new McpServer({
|
|
18
18
|
name: "leadify",
|
|
19
|
-
version: "
|
|
19
|
+
version: "8.0.0",
|
|
20
20
|
});
|
|
21
21
|
registerAuthTools(server);
|
|
22
22
|
registerOrganizationTools(server);
|
|
@@ -27,7 +27,7 @@ export function createServer() {
|
|
|
27
27
|
registerCampaignTools(server);
|
|
28
28
|
registerSignalTools(server);
|
|
29
29
|
registerActivityTools(server);
|
|
30
|
-
|
|
30
|
+
registerContextWorkspaceTools(server);
|
|
31
31
|
registerDataSourceTools(server);
|
|
32
32
|
registerPersonaTools(server);
|
|
33
33
|
registerPipelineTools(server);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface ContextWorkspaceClient {
|
|
3
|
+
get(path: string, params?: URLSearchParams): Promise<unknown>;
|
|
4
|
+
put(path: string, body: unknown): Promise<unknown>;
|
|
5
|
+
post(path: string, body: unknown): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
/** Bounded projection for agents: the active draft and latest published version only. */
|
|
8
|
+
export declare function projectContextWorkspaceRead(data: unknown): Record<string, unknown>;
|
|
9
|
+
export declare function registerContextWorkspaceTools(server: McpServer, client?: ContextWorkspaceClient): void;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { handleToolError, toolResult } from "../types.js";
|
|
4
|
+
const CONTEXT_KINDS = ["company_brain", "gtm_playbook"];
|
|
5
|
+
const MAX_READINESS_ISSUES = 20;
|
|
6
|
+
const kindSchema = z.enum(CONTEXT_KINDS);
|
|
7
|
+
const sectionSchema = z.object({
|
|
8
|
+
value: z.unknown(),
|
|
9
|
+
evidenceIds: z.array(z.string().min(1)).max(100),
|
|
10
|
+
approved: z.boolean(),
|
|
11
|
+
}).strict();
|
|
12
|
+
const sectionPatchSchema = z.record(z.string().min(1), sectionSchema).refine(sections => Object.keys(sections).length > 0, "At least one section is required");
|
|
13
|
+
function compactReadiness(readiness) {
|
|
14
|
+
if (!readiness || typeof readiness !== "object" || Array.isArray(readiness))
|
|
15
|
+
return readiness;
|
|
16
|
+
const source = readiness;
|
|
17
|
+
const issues = Array.isArray(source.issues) ? source.issues : [];
|
|
18
|
+
return {
|
|
19
|
+
ready: source.ready,
|
|
20
|
+
evaluated_at: source.evaluatedAt,
|
|
21
|
+
issue_count: issues.length,
|
|
22
|
+
issues: issues.slice(0, MAX_READINESS_ISSUES),
|
|
23
|
+
...(issues.length > MAX_READINESS_ISSUES
|
|
24
|
+
? { issues_truncated: true }
|
|
25
|
+
: {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function compactDraft(draft) {
|
|
29
|
+
if (!draft || typeof draft !== "object" || Array.isArray(draft))
|
|
30
|
+
return draft;
|
|
31
|
+
const source = draft;
|
|
32
|
+
return {
|
|
33
|
+
revision: source.revision,
|
|
34
|
+
base_version: source.baseVersion,
|
|
35
|
+
rollback_from_version: source.rollbackFromVersion,
|
|
36
|
+
readiness: compactReadiness(source.readiness),
|
|
37
|
+
content: source.content,
|
|
38
|
+
created_at: source.createdAt,
|
|
39
|
+
updated_at: source.updatedAt,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function compactPublished(published) {
|
|
43
|
+
if (!published || typeof published !== "object" || Array.isArray(published))
|
|
44
|
+
return published;
|
|
45
|
+
const source = published;
|
|
46
|
+
return {
|
|
47
|
+
version: source.version,
|
|
48
|
+
source_draft_id: source.sourceDraftId,
|
|
49
|
+
readiness: compactReadiness(source.readiness),
|
|
50
|
+
content: source.content,
|
|
51
|
+
published_at: source.publishedAt,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Bounded projection for agents: the active draft and latest published version only. */
|
|
55
|
+
export function projectContextWorkspaceRead(data) {
|
|
56
|
+
const source = data && typeof data === "object" && !Array.isArray(data)
|
|
57
|
+
? data
|
|
58
|
+
: {};
|
|
59
|
+
const published = Array.isArray(source.published) ? source.published : [];
|
|
60
|
+
return {
|
|
61
|
+
kind: source.kind,
|
|
62
|
+
draft: compactDraft(source.draft),
|
|
63
|
+
latest_published: compactPublished(published[0] ?? null),
|
|
64
|
+
published_version_count: published.length,
|
|
65
|
+
...(published.length > 1 ? { historical_versions_omitted: published.length - 1 } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function record(value) {
|
|
69
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
70
|
+
? value
|
|
71
|
+
: {};
|
|
72
|
+
}
|
|
73
|
+
function currentRevision(draft) {
|
|
74
|
+
const revision = record(draft).revision;
|
|
75
|
+
return typeof revision === "number" && Number.isInteger(revision) && revision >= 0
|
|
76
|
+
? revision
|
|
77
|
+
: 0;
|
|
78
|
+
}
|
|
79
|
+
function mergeSections(read, sections) {
|
|
80
|
+
const state = record(read);
|
|
81
|
+
const draft = record(state.draft);
|
|
82
|
+
const published = Array.isArray(state.published) ? state.published : [];
|
|
83
|
+
// Start a new draft from the latest published document, then change only the
|
|
84
|
+
// named sections. This preserves every untouched section across revisions.
|
|
85
|
+
const base = Object.keys(draft).length > 0
|
|
86
|
+
? record(draft.content)
|
|
87
|
+
: record(record(published[0]).content);
|
|
88
|
+
return { ...base, ...sections };
|
|
89
|
+
}
|
|
90
|
+
async function updateSections(client, input) {
|
|
91
|
+
const read = await client.get(`/api/context-workspace/${input.kind}`, new URLSearchParams({ organizationId: input.organizationId }));
|
|
92
|
+
const actualRevision = currentRevision(record(read).draft);
|
|
93
|
+
if (actualRevision !== input.expectedRevision) {
|
|
94
|
+
throw new Error(`Revision conflict before write: expected ${input.expectedRevision}, current ${actualRevision}. Re-read and retry without overwriting concurrent sections.`);
|
|
95
|
+
}
|
|
96
|
+
return client.put("/api/context-workspace/draft", {
|
|
97
|
+
organizationId: input.organizationId,
|
|
98
|
+
kind: input.kind,
|
|
99
|
+
content: mergeSections(read, input.sections),
|
|
100
|
+
expectedRevision: input.expectedRevision,
|
|
101
|
+
idempotencyKey: input.idempotencyKey,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function registerContextWorkspaceTools(server, client = getClient()) {
|
|
105
|
+
server.registerTool("get_context_workspace", {
|
|
106
|
+
title: "Read canonical versioned Context Workspace",
|
|
107
|
+
description: "Read the canonical Leadify Context Workspace for one explicitly selected organization and kind. " +
|
|
108
|
+
"Returns the current draft plus the latest published Company Brain or GTM Playbook, with bounded readiness metadata. " +
|
|
109
|
+
"Use this for every Leadify workflow. The Data Room is no longer exposed by this MCP.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
organization_id: z.string().trim().min(1).describe("Required organization ID. Always select it explicitly; there is no default tenant."),
|
|
112
|
+
kind: kindSchema.describe("Canonical workspace document: `company_brain` or `gtm_playbook`."),
|
|
113
|
+
},
|
|
114
|
+
annotations: { readOnlyHint: true },
|
|
115
|
+
}, async ({ organization_id, kind }) => {
|
|
116
|
+
try {
|
|
117
|
+
const params = new URLSearchParams({ organizationId: organization_id });
|
|
118
|
+
const data = await client.get(`/api/context-workspace/${kind}`, params);
|
|
119
|
+
return toolResult(projectContextWorkspaceRead(data));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return handleToolError(error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const sectionTools = [
|
|
126
|
+
{ name: "update_company_brain_sections", kind: "company_brain", title: "Update Company Brain sections" },
|
|
127
|
+
{ name: "update_gtm_playbook_sections", kind: "gtm_playbook", title: "Update GTM Playbook sections" },
|
|
128
|
+
];
|
|
129
|
+
for (const tool of sectionTools) {
|
|
130
|
+
server.registerTool(tool.name, {
|
|
131
|
+
title: tool.title,
|
|
132
|
+
description: "Update only the named canonical sections. The MCP re-reads the current draft (or latest published version), merges only those sections, " +
|
|
133
|
+
"and saves with optimistic concurrency. Unnamed sections are preserved; a concurrent revision is never overwritten.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
organization_id: z.string().trim().min(1).describe("Required organization ID; section writes require admin access."),
|
|
136
|
+
sections: sectionPatchSchema.describe("Only sections to change. Each section must include `value`, `evidenceIds`, and `approved`."),
|
|
137
|
+
expected_revision: z.number().int().nonnegative().describe("Current draft revision, or 0 when no draft exists. Re-read first."),
|
|
138
|
+
idempotency_key: z.string().trim().min(1).max(200).describe("Stable unique key for this exact section patch."),
|
|
139
|
+
},
|
|
140
|
+
annotations: { idempotentHint: true },
|
|
141
|
+
}, async ({ organization_id, sections, expected_revision, idempotency_key }) => {
|
|
142
|
+
try {
|
|
143
|
+
const data = await updateSections(client, { organizationId: organization_id, kind: tool.kind, sections, expectedRevision: expected_revision, idempotencyKey: idempotency_key });
|
|
144
|
+
return toolResult(compactDraft(data));
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return handleToolError(error);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
server.registerTool("publish_context_workspace", {
|
|
152
|
+
title: "Publish a ready Context Workspace draft",
|
|
153
|
+
description: "Publish exactly one ready draft revision of the canonical Context Workspace. This is a consequential action: it requires " +
|
|
154
|
+
"an organization-admin write key, the precise current draft revision, and `confirm_publish: true`. The server rejects incomplete " +
|
|
155
|
+
"content with its readiness reasons and rejects stale revisions without overwriting anything.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
organization_id: z.string().trim().min(1).describe("Required organization ID; publication is restricted to an admin key for this tenant."),
|
|
158
|
+
kind: kindSchema,
|
|
159
|
+
expected_revision: z.number().int().positive().describe("Exact ready draft revision to publish, obtained from `get_context_workspace`."),
|
|
160
|
+
idempotency_key: z.string().trim().min(1).max(200).describe("Stable unique key for this exact publication request."),
|
|
161
|
+
confirm_publish: z.literal(true).describe("Must be explicitly true after the user has confirmed this publication."),
|
|
162
|
+
},
|
|
163
|
+
annotations: { destructiveHint: true, idempotentHint: true },
|
|
164
|
+
}, async ({ organization_id, kind, expected_revision, idempotency_key }) => {
|
|
165
|
+
try {
|
|
166
|
+
const data = await client.post("/api/context-workspace/publish", {
|
|
167
|
+
organizationId: organization_id,
|
|
168
|
+
kind,
|
|
169
|
+
expectedRevision: expected_revision,
|
|
170
|
+
idempotencyKey: idempotency_key,
|
|
171
|
+
});
|
|
172
|
+
return toolResult(compactPublished(data));
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
return handleToolError(error);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -67,7 +67,7 @@ export function registerOrganizationTools(server) {
|
|
|
67
67
|
server.tool("discover_leadify_context", "Safe zero-ID entry point for Leadify. With no organization_id, lists accessible organizations " +
|
|
68
68
|
"and never chooses one when several are available. With an explicit organization_id, returns " +
|
|
69
69
|
"a compact list of its lead groups. Use the returned lead_group_id with lead, campaign, persona " +
|
|
70
|
-
"and
|
|
70
|
+
"and Context Workspace tools.", {
|
|
71
71
|
organization_id: z.string().optional().describe("Organization selected explicitly after discovery. Omit only for the first step."),
|
|
72
72
|
limit: z.number().int().min(1).max(100).optional().default(50).describe("Maximum lead groups returned after selection."),
|
|
73
73
|
}, async ({ organization_id, limit }) => {
|
|
@@ -92,7 +92,7 @@ export function registerOrganizationTools(server) {
|
|
|
92
92
|
leadGroups: groups.slice(0, limit).map((group) => ({ id: group.id, name: group.name, organizationId: group.organizationId, leadCount: group._count?.leads ?? 0, published: group.published ?? false })),
|
|
93
93
|
total: groups.length,
|
|
94
94
|
truncated: groups.length > limit,
|
|
95
|
-
next: "Choose a lead_group_id, then use get_leads,
|
|
95
|
+
next: "Choose a lead_group_id, then use get_leads, get_context_workspace, or get_lead_group_persona.",
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
catch (error) {
|