@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 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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "9.12.0",
3
+ "version": "9.13.0",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",