@agifyai/leadify-mcp 1.4.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/README.md +117 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +62 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +34 -0
- package/dist/tools/activities.d.ts +2 -0
- package/dist/tools/activities.js +103 -0
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +16 -0
- package/dist/tools/campaigns.d.ts +2 -0
- package/dist/tools/campaigns.js +144 -0
- package/dist/tools/dataroom.d.ts +2 -0
- package/dist/tools/dataroom.js +82 -0
- package/dist/tools/datasources.d.ts +2 -0
- package/dist/tools/datasources.js +144 -0
- package/dist/tools/lead_groups.d.ts +2 -0
- package/dist/tools/lead_groups.js +172 -0
- package/dist/tools/leads.d.ts +2 -0
- package/dist/tools/leads.js +188 -0
- package/dist/tools/logs.d.ts +2 -0
- package/dist/tools/logs.js +181 -0
- package/dist/tools/organizations.d.ts +2 -0
- package/dist/tools/organizations.js +17 -0
- package/dist/tools/outreach_settings.d.ts +2 -0
- package/dist/tools/outreach_settings.js +397 -0
- package/dist/tools/personas.d.ts +2 -0
- package/dist/tools/personas.js +1268 -0
- package/dist/tools/schema.d.ts +2 -0
- package/dist/tools/schema.js +130 -0
- package/dist/tools/signals.d.ts +2 -0
- package/dist/tools/signals.js +50 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +38 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Leadify MCP Server
|
|
2
|
+
|
|
3
|
+
Serveur MCP (Model Context Protocol) pour l'API Leadify. Expose tous les endpoints REST de Leadify sous forme de tools MCP, utilisables depuis Claude Desktop, Claude Code ou tout client compatible MCP.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Le serveur est publié sur npm sous `@agifyai/leadify-mcp`. Aucun clone, aucun build local : `npx` télécharge la dernière version à chaque démarrage.
|
|
8
|
+
|
|
9
|
+
### Claude Desktop
|
|
10
|
+
|
|
11
|
+
Ouvrir le fichier de configuration :
|
|
12
|
+
- **macOS** : `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
13
|
+
- **Windows** : `%APPDATA%\Claude\claude_desktop_config.json`
|
|
14
|
+
|
|
15
|
+
Ajouter une entrée `"leadify"` dans `"mcpServers"` :
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"leadify": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@agifyai/leadify-mcp@latest"],
|
|
23
|
+
"env": {
|
|
24
|
+
"LEADIFY_API_KEY": "votre-clé-api"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Redémarrer Claude Desktop. Les tools apparaissent (icône marteau).
|
|
32
|
+
|
|
33
|
+
### Claude Code
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude mcp add leadify npx -y @agifyai/leadify-mcp@latest -e LEADIFY_API_KEY=votre-clé-api
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Mise à jour automatique
|
|
40
|
+
|
|
41
|
+
Le tag `@latest` force `npx` à vérifier la version publiée à chaque lancement de session MCP. Quand un nouveau tool est mergé sur `main` et publié, il est dispo à la session suivante — pas de `git pull`, pas de rebuild.
|
|
42
|
+
|
|
43
|
+
## Développement
|
|
44
|
+
|
|
45
|
+
Pour bosser sur le MCP en local :
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone git@github.com:AgifyAI/mcp_leadify.git
|
|
49
|
+
cd mcp_leadify
|
|
50
|
+
npm install
|
|
51
|
+
npm run build
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pointer Claude Code sur la build locale :
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
claude mcp add leadify-dev node /chemin/absolu/vers/mcp_leadify/dist/index.js -e LEADIFY_API_KEY=votre-clé-api
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Publier une nouvelle version
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm version patch # ou minor / major
|
|
64
|
+
git push && git push --tags
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
GitHub Actions build et publie sur npm automatiquement (workflow `.github/workflows/publish.yml`). Si la version dans `package.json` n'a pas bougé, le workflow skip — pas de doublon.
|
|
68
|
+
|
|
69
|
+
## Tools disponibles
|
|
70
|
+
|
|
71
|
+
| Tool | Description |
|
|
72
|
+
|------|-------------|
|
|
73
|
+
| `test_api_key` | Vérifier que la clé API configurée est valide (health check). |
|
|
74
|
+
| `add_leads` | Ajouter un ou plusieurs leads à un groupe. |
|
|
75
|
+
| `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination. |
|
|
76
|
+
| `get_lead` | Récupérer les détails complets d'un lead par son ID. |
|
|
77
|
+
| `update_lead` | Mettre à jour un ou plusieurs champs d'un lead existant. |
|
|
78
|
+
| `delete_leads` | Supprimer définitivement des leads par leurs IDs. |
|
|
79
|
+
| `update_schema` | Ajouter ou modifier les définitions de champs d'un groupe. |
|
|
80
|
+
| `delete_columns` | Supprimer des colonnes du schéma et des données d'un groupe. |
|
|
81
|
+
| `update_hidden_columns` | Afficher ou masquer des colonnes dans la vue tableau (réversible). |
|
|
82
|
+
| `add_campaign_log` | Enregistrer une entrée de log de campagne pour un lead. |
|
|
83
|
+
| `get_campaign_logs` | Récupérer les logs de campagne avec filtres et pagination. |
|
|
84
|
+
| `delete_campaign_log` | Supprimer une entrée de log de campagne. |
|
|
85
|
+
| `update_campaign_stats` | Mettre à jour les statistiques d'email d'une campagne pour un groupe de leads. |
|
|
86
|
+
| `create_campaign` | Créer une nouvelle campagne rattachée à un groupe de leads (statut DRAFT). |
|
|
87
|
+
| `get_campaign` | Récupérer les détails d'une campagne et ses KPIs temps réel. |
|
|
88
|
+
| `update_campaign_status` | Changer le statut d'une campagne (DRAFT, ACTIVE, PAUSED, COMPLETED). |
|
|
89
|
+
| `export_campaign` | Exporter les statistiques complètes d'une campagne en CSV. |
|
|
90
|
+
| `add_signal` | Ajouter un signal de business intelligence à un lead (INFO, CRITICAL, GOLDEN). |
|
|
91
|
+
| `add_activity` | Journaliser une interaction prospect (LinkedIn, email, call) dans le feed du lead. |
|
|
92
|
+
| `get_data_room` | Récupérer la data room complète : infos société, documents, personas. |
|
|
93
|
+
| `update_data_room` | Mettre à jour les informations société (companyInfo) de la data room. |
|
|
94
|
+
| `add_data_room_document` | Ajouter un document textuel à la data room avec catégorie. |
|
|
95
|
+
| `upsert_persona` | Créer ou mettre à jour un persona (ciblage, messaging, outils actifs). |
|
|
96
|
+
| `get_persona` | Récupérer un persona par son ID. |
|
|
97
|
+
| `get_lead_group_persona` | Récupérer le persona assigné à un groupe de leads. |
|
|
98
|
+
| `list_data_sources` | Lister toutes les sources de données configurées (par pays puis nom). |
|
|
99
|
+
| `create_data_source` | Créer une nouvelle source de données (admin uniquement). |
|
|
100
|
+
| `update_data_source` | Modifier une source de données existante (admin uniquement). |
|
|
101
|
+
| `delete_data_source` | Supprimer définitivement une source de données (admin uniquement). |
|
|
102
|
+
| `get_outreach_settings` | Récupérer la config outreach d'un lead group (positioning, sequence, rules, URLs, case studies). |
|
|
103
|
+
| `update_outreach_settings` | Update full-form (escape hatch) — remplace les blocs JSON entièrement. Préférer les tools granulaires ci-dessous. |
|
|
104
|
+
| `update_outreach_positioning` | Patch partiel du positioning (dream / fear / whyNow individuellement). |
|
|
105
|
+
| `update_outreach_sequence_slot` | Patch d'un seul slot de séquence (connexion, linkedin.one/two/three, email.one/two/three) sans toucher les autres. |
|
|
106
|
+
| `update_outreach_rules` | Patch du bloc rules : forbidden/priority (replace), format.* (deep-merge). |
|
|
107
|
+
| `update_outreach_case_study` | Ajouter / remplacer / supprimer un case study par index, sans re-envoyer la liste. |
|
|
108
|
+
| `update_outreach_urls` | Patch booking_url et/ou website_url uniquement. |
|
|
109
|
+
| `set_outreach_connection_request` | Toggle du flag connectionRequestEnabled (LinkedIn invite vs cold DM). |
|
|
110
|
+
|
|
111
|
+
## Développement
|
|
112
|
+
|
|
113
|
+
Mode watch pour le développement :
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm run dev
|
|
117
|
+
```
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class LeadifyClient {
|
|
2
|
+
private readonly baseUrl;
|
|
3
|
+
private readonly apiKey;
|
|
4
|
+
constructor();
|
|
5
|
+
private request;
|
|
6
|
+
get(path: string, params?: URLSearchParams): Promise<unknown>;
|
|
7
|
+
post(path: string, body: unknown): Promise<unknown>;
|
|
8
|
+
put(path: string, body: unknown): Promise<unknown>;
|
|
9
|
+
delete(path: string): Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function getClient(): LeadifyClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { LeadifyApiError } from "./types.js";
|
|
2
|
+
export class LeadifyClient {
|
|
3
|
+
baseUrl;
|
|
4
|
+
apiKey;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.baseUrl = "https://api-leadify.agify.fr";
|
|
7
|
+
const key = process.env.LEADIFY_API_KEY;
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw new Error("LEADIFY_API_KEY environment variable is required. " +
|
|
10
|
+
"Set it before starting the server.");
|
|
11
|
+
}
|
|
12
|
+
this.apiKey = key;
|
|
13
|
+
}
|
|
14
|
+
async request(method, url, body) {
|
|
15
|
+
const headers = {
|
|
16
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
17
|
+
};
|
|
18
|
+
const init = { method, headers };
|
|
19
|
+
if (body !== undefined && (method === "POST" || method === "PUT")) {
|
|
20
|
+
headers["Content-Type"] = "application/json";
|
|
21
|
+
init.body = JSON.stringify(body);
|
|
22
|
+
}
|
|
23
|
+
const response = await fetch(url.toString(), init);
|
|
24
|
+
let data;
|
|
25
|
+
try {
|
|
26
|
+
data = await response.json();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
data = { raw: await response.text() };
|
|
30
|
+
}
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new LeadifyApiError(response.status, data);
|
|
33
|
+
}
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
async get(path, params) {
|
|
37
|
+
const url = new URL(path, this.baseUrl);
|
|
38
|
+
if (params) {
|
|
39
|
+
params.forEach((value, key) => url.searchParams.set(key, value));
|
|
40
|
+
}
|
|
41
|
+
return this.request("GET", url);
|
|
42
|
+
}
|
|
43
|
+
async post(path, body) {
|
|
44
|
+
const url = new URL(path, this.baseUrl);
|
|
45
|
+
return this.request("POST", url, body);
|
|
46
|
+
}
|
|
47
|
+
async put(path, body) {
|
|
48
|
+
const url = new URL(path, this.baseUrl);
|
|
49
|
+
return this.request("PUT", url, body);
|
|
50
|
+
}
|
|
51
|
+
async delete(path) {
|
|
52
|
+
const url = new URL(path, this.baseUrl);
|
|
53
|
+
return this.request("DELETE", url);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let _client = null;
|
|
57
|
+
export function getClient() {
|
|
58
|
+
if (!_client) {
|
|
59
|
+
_client = new LeadifyClient();
|
|
60
|
+
}
|
|
61
|
+
return _client;
|
|
62
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
}
|
|
9
|
+
main().catch((error) => {
|
|
10
|
+
console.error("Fatal error:", error);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerAuthTools } from "./tools/auth.js";
|
|
3
|
+
import { registerLeadTools } from "./tools/leads.js";
|
|
4
|
+
import { registerSchemaTools } from "./tools/schema.js";
|
|
5
|
+
import { registerLogTools } from "./tools/logs.js";
|
|
6
|
+
import { registerCampaignTools } from "./tools/campaigns.js";
|
|
7
|
+
import { registerSignalTools } from "./tools/signals.js";
|
|
8
|
+
import { registerActivityTools } from "./tools/activities.js";
|
|
9
|
+
import { registerDataRoomTools } from "./tools/dataroom.js";
|
|
10
|
+
import { registerDataSourceTools } from "./tools/datasources.js";
|
|
11
|
+
import { registerPersonaTools } from "./tools/personas.js";
|
|
12
|
+
import { registerLeadGroupTools } from "./tools/lead_groups.js";
|
|
13
|
+
import { registerOrganizationTools } from "./tools/organizations.js";
|
|
14
|
+
import { registerOutreachSettingsTools } from "./tools/outreach_settings.js";
|
|
15
|
+
export function createServer() {
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: "leadify",
|
|
18
|
+
version: "1.4.1",
|
|
19
|
+
});
|
|
20
|
+
registerAuthTools(server);
|
|
21
|
+
registerOrganizationTools(server);
|
|
22
|
+
registerLeadGroupTools(server);
|
|
23
|
+
registerLeadTools(server);
|
|
24
|
+
registerSchemaTools(server);
|
|
25
|
+
registerLogTools(server);
|
|
26
|
+
registerCampaignTools(server);
|
|
27
|
+
registerSignalTools(server);
|
|
28
|
+
registerActivityTools(server);
|
|
29
|
+
registerDataRoomTools(server);
|
|
30
|
+
registerDataSourceTools(server);
|
|
31
|
+
registerPersonaTools(server);
|
|
32
|
+
registerOutreachSettingsTools(server);
|
|
33
|
+
return server;
|
|
34
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
const ACTIVITY_TYPES = [
|
|
5
|
+
"LINKEDIN_INVITATION_SENT",
|
|
6
|
+
"LINKEDIN_INVITATION_ACCEPTED",
|
|
7
|
+
"LINKEDIN_MESSAGE_SENT",
|
|
8
|
+
"LINKEDIN_MESSAGE_RECEIVED",
|
|
9
|
+
"LINKEDIN_INMAIL_SENT",
|
|
10
|
+
"LINKEDIN_MESSAGE_READ",
|
|
11
|
+
"EMAIL_SENT",
|
|
12
|
+
"EMAIL_RECEIVED",
|
|
13
|
+
"EMAIL_SENT_MANUAL",
|
|
14
|
+
"PHONE_CALL",
|
|
15
|
+
"LINKEDIN_MESSAGE",
|
|
16
|
+
"MESSAGE",
|
|
17
|
+
];
|
|
18
|
+
export function registerActivityTools(server) {
|
|
19
|
+
// ── add_activity ───────────────────────────────────────────────────────
|
|
20
|
+
server.tool("add_activity", "Log a prospect interaction as an activity on a lead's live feed — LinkedIn " +
|
|
21
|
+
"invitations, messages, InMails, calls, emails sent or received. Supply unipile " +
|
|
22
|
+
"chat/account IDs to thread follow-up messages together. Use this to keep the " +
|
|
23
|
+
"lead timeline in sync with what happens in external tools.", {
|
|
24
|
+
lead_id: z.string().describe("ID of the target lead."),
|
|
25
|
+
type: z.enum(ACTIVITY_TYPES).describe("Type of activity."),
|
|
26
|
+
title: z
|
|
27
|
+
.string()
|
|
28
|
+
.describe("Short title of the activity (e.g. 'Invitation envoyée à Dr. Martin')."),
|
|
29
|
+
preview: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Message preview or detailed description."),
|
|
33
|
+
date: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Activity date in ISO 8601 format. Defaults to now."),
|
|
37
|
+
sender_name: z.string().optional().describe("Name of the sender."),
|
|
38
|
+
sender_email: z
|
|
39
|
+
.string()
|
|
40
|
+
.email()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Sender email (for email activities)."),
|
|
43
|
+
recipient_email: z
|
|
44
|
+
.string()
|
|
45
|
+
.email()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Recipient email (for email activities)."),
|
|
48
|
+
unipile_chat_id: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Unipile conversation ID (to link follow-up messages)."),
|
|
52
|
+
unipile_account_id: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Unipile account ID that performed this action."),
|
|
56
|
+
provider_profile_url: z
|
|
57
|
+
.string()
|
|
58
|
+
.url()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("URL of the prospect's LinkedIn profile."),
|
|
61
|
+
campaign_id: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Campaign ID to attach this activity to."),
|
|
65
|
+
metadata: z
|
|
66
|
+
.record(z.unknown())
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Supplementary metadata as a JSON object."),
|
|
69
|
+
}, async (params) => {
|
|
70
|
+
try {
|
|
71
|
+
const body = {
|
|
72
|
+
leadId: params.lead_id,
|
|
73
|
+
type: params.type,
|
|
74
|
+
title: params.title,
|
|
75
|
+
};
|
|
76
|
+
if (params.preview !== undefined)
|
|
77
|
+
body.preview = params.preview;
|
|
78
|
+
if (params.date !== undefined)
|
|
79
|
+
body.date = params.date;
|
|
80
|
+
if (params.sender_name !== undefined)
|
|
81
|
+
body.senderName = params.sender_name;
|
|
82
|
+
if (params.sender_email !== undefined)
|
|
83
|
+
body.senderEmail = params.sender_email;
|
|
84
|
+
if (params.recipient_email !== undefined)
|
|
85
|
+
body.recipientEmail = params.recipient_email;
|
|
86
|
+
if (params.unipile_chat_id !== undefined)
|
|
87
|
+
body.unipileChatId = params.unipile_chat_id;
|
|
88
|
+
if (params.unipile_account_id !== undefined)
|
|
89
|
+
body.unipileAccountId = params.unipile_account_id;
|
|
90
|
+
if (params.provider_profile_url !== undefined)
|
|
91
|
+
body.providerProfileUrl = params.provider_profile_url;
|
|
92
|
+
if (params.campaign_id !== undefined)
|
|
93
|
+
body.campaignId = params.campaign_id;
|
|
94
|
+
if (params.metadata !== undefined)
|
|
95
|
+
body.metadata = params.metadata;
|
|
96
|
+
const data = await getClient().post("/add-activity", body);
|
|
97
|
+
return toolResult(data);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return handleToolError(error);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getClient } from "../client.js";
|
|
2
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
3
|
+
export function registerAuthTools(server) {
|
|
4
|
+
server.tool("test_api_key", "Verify that the configured Leadify API key is valid and has not been revoked. " +
|
|
5
|
+
"Returns a success message if the key is active. Use this as a health check " +
|
|
6
|
+
"before performing other operations, or to diagnose authentication errors " +
|
|
7
|
+
"(401/403) from other tools.", {}, async () => {
|
|
8
|
+
try {
|
|
9
|
+
const data = await getClient().get("/test-api-key");
|
|
10
|
+
return toolResult(data);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return handleToolError(error);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
export function registerCampaignTools(server) {
|
|
5
|
+
// ── update_campaign_stats ──────────────────────────────────────────────
|
|
6
|
+
server.tool("update_campaign_stats", "Update email campaign statistics for a lead group. If the given campaignSlug " +
|
|
7
|
+
"doesn't exist in the group's stats yet, it is created; otherwise provided fields " +
|
|
8
|
+
"merge with existing values. Use this to push up-to-date counters from an external " +
|
|
9
|
+
"sender (opens, clicks, replies, etc.). Only fields you pass are updated.", {
|
|
10
|
+
lead_group_id: z.string().describe("ID of the lead group."),
|
|
11
|
+
campaign_slug: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("Unique campaign slug identifier (e.g. 'q1-2025')."),
|
|
14
|
+
lead_total: z.number().int().optional().describe("Total leads in the campaign."),
|
|
15
|
+
lead_to_launch: z.number().int().optional().describe("Leads to launch."),
|
|
16
|
+
lead_ready_to_send: z.number().int().optional().describe("Leads ready to send."),
|
|
17
|
+
lead_in_progress: z.number().int().optional().describe("Leads in progress."),
|
|
18
|
+
lead_completed: z
|
|
19
|
+
.number()
|
|
20
|
+
.int()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Leads with completed processing."),
|
|
23
|
+
sent_count: z.number().int().optional().describe("Number of emails sent."),
|
|
24
|
+
delivered_count: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Number of emails delivered."),
|
|
29
|
+
opened_count: z.number().int().optional().describe("Number of emails opened."),
|
|
30
|
+
clicked_count: z.number().int().optional().describe("Number of clicks in emails."),
|
|
31
|
+
replied_count: z.number().int().optional().describe("Number of replies received."),
|
|
32
|
+
interested_count: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Number of leads marked as interested."),
|
|
37
|
+
}, async (params) => {
|
|
38
|
+
try {
|
|
39
|
+
const body = {
|
|
40
|
+
leadGroupId: params.lead_group_id,
|
|
41
|
+
campaignSlug: params.campaign_slug,
|
|
42
|
+
};
|
|
43
|
+
if (params.lead_total !== undefined)
|
|
44
|
+
body.leadTotal = params.lead_total;
|
|
45
|
+
if (params.lead_to_launch !== undefined)
|
|
46
|
+
body.leadToLaunch = params.lead_to_launch;
|
|
47
|
+
if (params.lead_ready_to_send !== undefined)
|
|
48
|
+
body.leadReadyToSend = params.lead_ready_to_send;
|
|
49
|
+
if (params.lead_in_progress !== undefined)
|
|
50
|
+
body.leadInProgress = params.lead_in_progress;
|
|
51
|
+
if (params.lead_completed !== undefined)
|
|
52
|
+
body.leadCompleted = params.lead_completed;
|
|
53
|
+
if (params.sent_count !== undefined)
|
|
54
|
+
body.sentCount = params.sent_count;
|
|
55
|
+
if (params.delivered_count !== undefined)
|
|
56
|
+
body.deliveredCount = params.delivered_count;
|
|
57
|
+
if (params.opened_count !== undefined)
|
|
58
|
+
body.openedCount = params.opened_count;
|
|
59
|
+
if (params.clicked_count !== undefined)
|
|
60
|
+
body.clickedCount = params.clicked_count;
|
|
61
|
+
if (params.replied_count !== undefined)
|
|
62
|
+
body.repliedCount = params.replied_count;
|
|
63
|
+
if (params.interested_count !== undefined)
|
|
64
|
+
body.interestedCount = params.interested_count;
|
|
65
|
+
const data = await getClient().post("/update-campaign-stats", body);
|
|
66
|
+
return toolResult(data);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return handleToolError(error);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// ── create_campaign ────────────────────────────────────────────────────
|
|
73
|
+
server.tool("create_campaign", "Create a new campaign linked to an existing lead group. The campaign is initialized " +
|
|
74
|
+
"in DRAFT status and can be configured with a persona. Returns the new campaign ID. " +
|
|
75
|
+
"Use update_campaign_status afterwards to move it to ACTIVE when ready.", {
|
|
76
|
+
name: z.string().describe("Campaign name."),
|
|
77
|
+
lead_group_id: z.string().describe("ID of the lead group to associate."),
|
|
78
|
+
description: z.string().optional().describe("Optional description."),
|
|
79
|
+
persona_id: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("ID of the persona to assign (optional)."),
|
|
83
|
+
}, async ({ name, lead_group_id, description, persona_id }) => {
|
|
84
|
+
try {
|
|
85
|
+
const body = {
|
|
86
|
+
name,
|
|
87
|
+
leadGroupId: lead_group_id,
|
|
88
|
+
};
|
|
89
|
+
if (description !== undefined)
|
|
90
|
+
body.description = description;
|
|
91
|
+
if (persona_id !== undefined)
|
|
92
|
+
body.personaId = persona_id;
|
|
93
|
+
const data = await getClient().post("/api/campaign", body);
|
|
94
|
+
return toolResult(data);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return handleToolError(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// ── get_campaign ───────────────────────────────────────────────────────
|
|
101
|
+
server.tool("get_campaign", "Retrieve full details for a campaign by its ID, including real-time calculated KPIs " +
|
|
102
|
+
"(invitations sent, accept rate, messages sent, emails sent, reply rate, golden signals). " +
|
|
103
|
+
"Use this to check a campaign's current performance.", {
|
|
104
|
+
id: z.string().describe("Campaign ID."),
|
|
105
|
+
}, async ({ id }) => {
|
|
106
|
+
try {
|
|
107
|
+
const data = await getClient().get(`/api/campaign/${encodeURIComponent(id)}`);
|
|
108
|
+
return toolResult(data);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return handleToolError(error);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// ── update_campaign_status ─────────────────────────────────────────────
|
|
115
|
+
server.tool("update_campaign_status", "Change a campaign's workflow status. Allowed values: DRAFT, ACTIVE, PAUSED, COMPLETED. " +
|
|
116
|
+
"Moving to ACTIVE starts outbound execution; PAUSED halts it; COMPLETED marks the " +
|
|
117
|
+
"campaign as finished. Always confirm with the user before calling this tool.", {
|
|
118
|
+
id: z.string().describe("Campaign ID."),
|
|
119
|
+
status: z
|
|
120
|
+
.enum(["DRAFT", "ACTIVE", "PAUSED", "COMPLETED"])
|
|
121
|
+
.describe("New campaign status."),
|
|
122
|
+
}, async ({ id, status }) => {
|
|
123
|
+
try {
|
|
124
|
+
const data = await getClient().put(`/api/campaign/${encodeURIComponent(id)}/status`, { status });
|
|
125
|
+
return toolResult(data);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return handleToolError(error);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// ── export_campaign ────────────────────────────────────────────────────
|
|
132
|
+
server.tool("export_campaign", "Export a campaign's full statistics and metrics as CSV. Returns the raw CSV text. " +
|
|
133
|
+
"Use this for external analysis or reporting.", {
|
|
134
|
+
id: z.string().describe("Campaign ID."),
|
|
135
|
+
}, async ({ id }) => {
|
|
136
|
+
try {
|
|
137
|
+
const data = await getClient().get(`/api/campaign/${encodeURIComponent(id)}/export`);
|
|
138
|
+
return toolResult(data);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return handleToolError(error);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
const DOCUMENT_CATEGORIES = [
|
|
5
|
+
"PRODUCT_CATALOG",
|
|
6
|
+
"CONGRESS_LIST",
|
|
7
|
+
"TARGET_LIST",
|
|
8
|
+
"COMPANY_INFO",
|
|
9
|
+
"COMPETITOR_INFO",
|
|
10
|
+
"CAMPAIGN_BRIEF",
|
|
11
|
+
"OTHER",
|
|
12
|
+
];
|
|
13
|
+
const ORG_ID_DESC = "Optional Clerk organization ID to target a specific org's data room. " +
|
|
14
|
+
"Defaults to the caller's own org. Requires admin access for writes.";
|
|
15
|
+
export function registerDataRoomTools(server) {
|
|
16
|
+
// ── get_data_room ──────────────────────────────────────────────────────
|
|
17
|
+
server.tool("get_data_room", "Retrieve the organization's full data room: company info, all documents, and every " +
|
|
18
|
+
"persona defined for the workspace. Use this as the entry point when you need to " +
|
|
19
|
+
"understand the company's context before writing campaigns or messages.", {
|
|
20
|
+
organization_id: z.string().optional().describe(ORG_ID_DESC),
|
|
21
|
+
}, async ({ organization_id }) => {
|
|
22
|
+
try {
|
|
23
|
+
const params = organization_id
|
|
24
|
+
? new URLSearchParams({ organizationId: organization_id })
|
|
25
|
+
: undefined;
|
|
26
|
+
const data = await getClient().get("/api/data-room", params);
|
|
27
|
+
return toolResult(data);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return handleToolError(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
// ── update_data_room ───────────────────────────────────────────────────
|
|
34
|
+
server.tool("update_data_room", "Update the organization's company info in the data room. Accepts any freeform " +
|
|
35
|
+
"fields under companyInfo — common ones: name, sector, description, size, website, " +
|
|
36
|
+
"products, valueProposition, competitors, targetRegions, sender. Only the fields " +
|
|
37
|
+
"you pass are updated.", {
|
|
38
|
+
company_info: z
|
|
39
|
+
.record(z.unknown())
|
|
40
|
+
.describe("Freeform object of company details. Common fields: name, sector, description, " +
|
|
41
|
+
"size, website, products, valueProposition, competitors, targetRegions, sender."),
|
|
42
|
+
organization_id: z.string().optional().describe(ORG_ID_DESC),
|
|
43
|
+
}, async ({ company_info, organization_id }) => {
|
|
44
|
+
try {
|
|
45
|
+
const body = { companyInfo: company_info };
|
|
46
|
+
if (organization_id)
|
|
47
|
+
body.organizationId = organization_id;
|
|
48
|
+
const data = await getClient().put("/api/data-room", body);
|
|
49
|
+
return toolResult(data);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return handleToolError(error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
// ── add_data_room_document ─────────────────────────────────────────────
|
|
56
|
+
server.tool("add_data_room_document", "Add a textual/markdown document to the data room under a predefined category. " +
|
|
57
|
+
"Use this to store product catalogs, congress lists, target lists, competitor briefs, " +
|
|
58
|
+
"or campaign briefs that personas and agents can reference.", {
|
|
59
|
+
title: z.string().describe("Document title."),
|
|
60
|
+
category: z
|
|
61
|
+
.enum(DOCUMENT_CATEGORIES)
|
|
62
|
+
.describe("Document classification."),
|
|
63
|
+
content: z
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Text or markdown content."),
|
|
67
|
+
organization_id: z.string().optional().describe(ORG_ID_DESC),
|
|
68
|
+
}, async ({ title, category, content, organization_id }) => {
|
|
69
|
+
try {
|
|
70
|
+
const body = { title, category };
|
|
71
|
+
if (content !== undefined)
|
|
72
|
+
body.content = content;
|
|
73
|
+
if (organization_id)
|
|
74
|
+
body.organizationId = organization_id;
|
|
75
|
+
const data = await getClient().post("/api/data-room/document", body);
|
|
76
|
+
return toolResult(data);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return handleToolError(error);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|