@agifyai/leadify-mcp 9.12.0 → 9.13.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 +19 -0
- package/dist/server.js +2 -0
- package/dist/tools/outreach_review.d.ts +14 -0
- package/dist/tools/outreach_review.js +62 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -276,6 +276,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
276
276
|
| `get_capture_coverage` | Boîtes mail et comptes LinkedIn réellement captés, avec leur état. |
|
|
277
277
|
| `get_follow_up_cadence` | Rythme de relance de l'organisation (lecture seule). |
|
|
278
278
|
| `get_prospect_approach` | Avant d'écrire à une personne : son Approche (quotidien, problématique, ce qu'elle veut, ce qu'on lui apporte). |
|
|
279
|
+
| `list_outreach_review_queue` | La file « À valider » de Leadify (page `/to-review`) en un appel, en lecture seule : filtres campagne, canal, type de message et état (`TO_REVIEW` par défaut), `total`, `counts`, `truncated`. Le texte des messages seulement avec `include_text`. |
|
|
279
280
|
| `preview_move_leads` | Essai à blanc du déplacement de fiches vers un autre groupe de la même organisation : sélection par identifiants, vue ou filtre (500 fiches au plus par appel, lots ensuite), refus par fiche avec motif, doublons dans le groupe cible, champs de schéma et relations à ajouter, blocages et empreinte du plan. |
|
|
280
281
|
| `move_leads` | Appliquer un déplacement prévisualisé ; exige l'empreinte de la prévisualisation et une clé d'idempotence. La fiche garde son identifiant et tout son historique ; jamais de fusion. |
|
|
281
282
|
| `undo_move_leads` / `get_lead_move` | Annuler ou relire une opération de déplacement par son identifiant. |
|
|
@@ -495,6 +496,24 @@ plus long se termine par `… [truncated: N characters in total]`. `list_follow_
|
|
|
495
496
|
accepte `include_text` pour ajouter le message entier (`lastMessage.text`) à côté
|
|
496
497
|
de son extrait de 500 caractères.
|
|
497
498
|
|
|
499
|
+
### File « À valider » (PRD-2815)
|
|
500
|
+
|
|
501
|
+
`list_outreach_review_queue` lit en un appel la file de la page `/to-review` :
|
|
502
|
+
les messages de campagne qui attendent une décision humaine, dans l'ordre de la
|
|
503
|
+
page, au plus 500, sans pagination. Filtres : `campaign_id`, `channel` (`EMAIL`,
|
|
504
|
+
`LINKEDIN`), `message_type_id` et `state` (`TO_REVIEW` par défaut, `VALIDATED`,
|
|
505
|
+
`CORRECTED`, `REFUSED`, ou `ALL`). Les identifiants des filtres viennent de
|
|
506
|
+
`filters.campaigns` et `filters.messageTypes`. Chaque message garde l'essentiel
|
|
507
|
+
(prospect, entreprise, campagne, canal, étape, type, départ prévu, créneau
|
|
508
|
+
manqué, statut, objet) ; la révision, la date de création et les identifiants de
|
|
509
|
+
campagne et de type, déjà dans `filters`, sont retirés. Sans le texte des
|
|
510
|
+
messages, la file réelle de 73 messages tient en environ 30 000 caractères, sous
|
|
511
|
+
la limite de 50 000 d'Hermès ; `include_text: true` ajoute `body`, le message
|
|
512
|
+
entier (environ 500 caractères de plus par message) : au-delà d'une quarantaine
|
|
513
|
+
de messages, filtrer d'abord. Si `truncated` vaut `true`, filtrer par campagne.
|
|
514
|
+
Aucun outil ne valide, corrige ou refuse : cela reste humain. Route :
|
|
515
|
+
`GET /api/outreach-review/queue` (permission `read`).
|
|
516
|
+
|
|
498
517
|
### Déplacer des prospects vers un autre groupe (PRD-2478)
|
|
499
518
|
|
|
500
519
|
Pour réorganiser une base, **déplacer** les fiches plutôt que les copier
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { registerAgentOutreachTools } from "./tools/agent_outreach.js";
|
|
|
2
2
|
import { registerQualificationRunTools } from "./tools/qualification_runs.js";
|
|
3
3
|
import { registerOutreachExclusionTools } from "./tools/outreach_exclusion.js";
|
|
4
4
|
import { registerProspectionTrackingTools } from "./tools/prospection_tracking.js";
|
|
5
|
+
import { registerOutreachReviewTools } from "./tools/outreach_review.js";
|
|
5
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
7
|
import { registerAuthTools } from "./tools/auth.js";
|
|
7
8
|
import { registerLeadTools } from "./tools/leads.js";
|
|
@@ -63,5 +64,6 @@ export function createServer() {
|
|
|
63
64
|
registerQualificationRunTools(server);
|
|
64
65
|
registerOutreachExclusionTools(server);
|
|
65
66
|
registerProspectionTrackingTools(server);
|
|
67
|
+
registerOutreachReviewTools(server);
|
|
66
68
|
return server;
|
|
67
69
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type LeadifyClient } from "../client.js";
|
|
3
|
+
export declare const OUTREACH_REVIEW_TOOL_NAMES: readonly ["list_outreach_review_queue"];
|
|
4
|
+
export declare const OUTREACH_REVIEW_STATES: readonly ["ALL", "TO_REVIEW", "VALIDATED", "CORRECTED", "REFUSED"];
|
|
5
|
+
export declare const OUTREACH_REVIEW_CHANNELS: readonly ["EMAIL", "LINKEDIN"];
|
|
6
|
+
type Client = Pick<LeadifyClient, "get">;
|
|
7
|
+
/**
|
|
8
|
+
* One queue item without what only a write would need (revision, createdAt) and
|
|
9
|
+
* without the campaign and message type ids, which filters.campaigns and
|
|
10
|
+
* filters.messageTypes carry once. The body only with include_text.
|
|
11
|
+
*/
|
|
12
|
+
export declare function projectReviewItem(item: unknown, includeText: boolean): Record<string, unknown>;
|
|
13
|
+
export declare function registerOutreachReviewTools(server: McpServer, client?: Client): void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { handleToolError } from "../types.js";
|
|
4
|
+
// PRD-2815 — la file « À valider » de Leadify (page /to-review) en lecture seule,
|
|
5
|
+
// par la route serveur GET /api/outreach-review/queue. Valider, corriger ou
|
|
6
|
+
// refuser reste humain : aucune écriture ici.
|
|
7
|
+
export const OUTREACH_REVIEW_TOOL_NAMES = ["list_outreach_review_queue"];
|
|
8
|
+
export const OUTREACH_REVIEW_STATES = ["ALL", "TO_REVIEW", "VALIDATED", "CORRECTED", "REFUSED"];
|
|
9
|
+
export const OUTREACH_REVIEW_CHANNELS = ["EMAIL", "LINKEDIN"];
|
|
10
|
+
const id = z.string().trim().min(1).max(200);
|
|
11
|
+
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
12
|
+
const object = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
13
|
+
/** One line of JSON: an agent runtime sets aside tool results above 50,000 characters. */
|
|
14
|
+
const compact = (data) => ({ content: [{ type: "text", text: JSON.stringify(data) }] });
|
|
15
|
+
/**
|
|
16
|
+
* One queue item without what only a write would need (revision, createdAt) and
|
|
17
|
+
* without the campaign and message type ids, which filters.campaigns and
|
|
18
|
+
* filters.messageTypes carry once. The body only with include_text.
|
|
19
|
+
*/
|
|
20
|
+
export function projectReviewItem(item, includeText) {
|
|
21
|
+
const i = object(item);
|
|
22
|
+
const result = {
|
|
23
|
+
id: i.id, leadId: i.leadId, leadName: i.leadName, companyName: i.companyName, campaignName: i.campaignName,
|
|
24
|
+
channel: i.channel, step: i.step, stepKind: i.stepKind, messageTypeName: object(i.messageType).name ?? null,
|
|
25
|
+
plannedDepartureAt: i.plannedDepartureAt, missedSlot: i.missedSlot, status: i.status, editedByHuman: i.editedByHuman,
|
|
26
|
+
subject: i.subject,
|
|
27
|
+
};
|
|
28
|
+
if (includeText)
|
|
29
|
+
result.body = i.body;
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
export function registerOutreachReviewTools(server, client = getClient()) {
|
|
33
|
+
server.tool("list_outreach_review_queue", "The Leadify 'À valider' queue (page /to-review) in ONE call: the campaign messages waiting for a human decision, in " +
|
|
34
|
+
"the page's order. Read-only: validating, correcting or refusing a message stays human, in Leadify. Answer: " +
|
|
35
|
+
"{organizationId, state, limit, total, counts {all, toReview, validated, corrected, refused}, truncated, items, " +
|
|
36
|
+
"filters {campaigns [{id, name}], messageTypes [{id, name}]}, notContactedToday {count, reasons}}. Each item: id, " +
|
|
37
|
+
"leadId, leadName, companyName, campaignName, channel, step, stepKind, messageTypeName, plannedDepartureAt, " +
|
|
38
|
+
"missedSlot, status (TO_REVIEW, APPROVED, SCHEDULED, SENT, REJECTED), editedByHuman, subject. To stay well under " +
|
|
39
|
+
"50,000 characters, the message text is left out by default (about 30,000 characters for 73 messages); " +
|
|
40
|
+
"include_text=true adds body, the whole message, about 500 characters more per message (about 68,000 for those 73): " +
|
|
41
|
+
"above about 40 messages, narrow with campaign_id, channel or message_type_id first. At most 500 messages, no pagination: when truncated " +
|
|
42
|
+
"is true, filter by campaign_id. Pass the ids found in filters.campaigns and filters.messageTypes.", {
|
|
43
|
+
organization_id: id.describe("Organization to read. Must be in the API key's scope."),
|
|
44
|
+
state: z.enum(OUTREACH_REVIEW_STATES).optional().describe("TO_REVIEW (default): waiting for validation. VALIDATED, CORRECTED, REFUSED: decided in the last 7 days. ALL: TO_REVIEW plus the messages validated, scheduled, sent or refused in the last 7 days."),
|
|
45
|
+
campaign_id: id.optional().describe("Only this campaign (filters.campaigns[].id)."),
|
|
46
|
+
channel: z.enum(OUTREACH_REVIEW_CHANNELS).optional().describe("EMAIL or LINKEDIN."),
|
|
47
|
+
message_type_id: id.optional().describe("Only this message type (filters.messageTypes[].id)."),
|
|
48
|
+
include_text: z.boolean().optional().describe("Also return body, the whole message text. Off by default."),
|
|
49
|
+
}, READ_ONLY, (async ({ organization_id, state, campaign_id, channel, message_type_id, include_text }) => {
|
|
50
|
+
try {
|
|
51
|
+
const query = new URLSearchParams(Object.entries({ organizationId: organization_id, campaignId: campaign_id, channel, messageTypeId: message_type_id, state: state ?? "TO_REVIEW" })
|
|
52
|
+
.filter((entry) => entry[1] !== undefined));
|
|
53
|
+
const { items, ...rest } = object(await client.get("/api/outreach-review/queue", query));
|
|
54
|
+
const { organizationId, state: readState, limit, total, counts, truncated, ...others } = rest;
|
|
55
|
+
return compact({ organizationId, state: readState, limit, total, counts, truncated,
|
|
56
|
+
items: Array.isArray(items) ? items.map(item => projectReviewItem(item, include_text === true)) : items, ...others });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return handleToolError(error);
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
}
|