@agifyai/leadify-mcp 9.6.0 → 9.7.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 +39 -10
- package/dist/tools/events.d.ts +6 -0
- package/dist/tools/events.js +71 -22
- package/dist/tools/leads.js +8 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,8 +195,12 @@ Conséquences pratiques :
|
|
|
195
195
|
|
|
196
196
|
## Sémantique des événements
|
|
197
197
|
|
|
198
|
-
Une participation
|
|
199
|
-
|
|
198
|
+
Une participation porte sur une entreprise ou sur une personne (PRD-2475,
|
|
199
|
+
serveur PRD-2473). Un `account_id` ou le `lead_id` d’un prospect Entreprise
|
|
200
|
+
écrit, comme avant, sur l’entreprise canonique (`Account`). Le `lead_id` d’un
|
|
201
|
+
prospect Personne, ou un `person_id` (`canonicalIdentity.id` d’un prospect Personne, `kind: PERSON`), écrit une
|
|
202
|
+
participation propre à la personne canonique : elle la suit dans tous ses
|
|
203
|
+
groupes. Les outils d’events d’entreprise fonctionnent comme avant. `get_leads.event_filter` et
|
|
200
204
|
`list_event_participations` ajoutent explicitement `VERIFIED` lorsque le statut
|
|
201
205
|
n’est pas fourni. Pour relire des faits `UNVERIFIED`, `DISPUTED` ou `REFUTED`,
|
|
202
206
|
l’agent doit demander ces statuts explicitement. Une donnée incertaine ou
|
|
@@ -211,6 +215,31 @@ des sélecteurs courants sans effacer son historique. Les champs legacy
|
|
|
211
215
|
restent disponibles pour compatibilité/export, mais ne sont jamais synchronisés
|
|
212
216
|
depuis ce modèle.
|
|
213
217
|
|
|
218
|
+
Événements d’une personne :
|
|
219
|
+
|
|
220
|
+
- Rôles d’une personne : `REGISTERED` (inscrit), `ATTENDED` (présent),
|
|
221
|
+
`INVITED` (invité), `SPEAKER`, `ORGANIZER`, `OTHER`. Les trois premiers sont
|
|
222
|
+
refusés pour une entreprise. Inscrit, présent et invité sont trois faits
|
|
223
|
+
distincts : une inscription ne prouve jamais une présence.
|
|
224
|
+
- Une liste d’inscrits fournie par le client est une preuve valable de
|
|
225
|
+
l’inscription, y compris pour une personne désinscrite ensuite ou inscrite
|
|
226
|
+
plusieurs fois : rôle `REGISTERED` et `client_document { file_name,
|
|
227
|
+
received_on, provided_by }`, sans adresse web. `VERIFIED` exige une adresse
|
|
228
|
+
web ou un `client_document`.
|
|
229
|
+
- Un event de l’employeur n’est jamais une participation de la personne. Lue
|
|
230
|
+
avec le `lead_id` d’une personne, `list_event_participations` rend ses
|
|
231
|
+
participations propres (`origin: PERSON`) puis celles de son employeur
|
|
232
|
+
(`origin: EMPLOYER`). Sans `target`, `person_ids` ni `lead_id` de personne,
|
|
233
|
+
la liste ne rend que les entreprises, comme avant ; `target: PERSON` ou
|
|
234
|
+
`person_ids` lisent les personnes.
|
|
235
|
+
- `get_leads.event_filter.subject` vaut `EMPLOYER` (défaut, comportement
|
|
236
|
+
historique) ou `PERSON` (seules les personnes qui portent elles-mêmes la
|
|
237
|
+
participation, par exemple les inscrits d’un webinar).
|
|
238
|
+
- Les events ont un type facultatif `event_type` : `WEBINAR`, `CONGRESS`,
|
|
239
|
+
`TRADE_SHOW`, `OTHER` (filtre `event_types` dans `list_events`).
|
|
240
|
+
`get_event` rend `participationCounts { companies, persons }`.
|
|
241
|
+
- L’absence d’enregistrement ne prouve rien.
|
|
242
|
+
|
|
214
243
|
Il n’existe plus de score de confiance événementiel : le statut et ses preuves
|
|
215
244
|
portent seuls la validation utile. `Event.description` explique l’audience et
|
|
216
245
|
l’utilité de l’événement ; `activity_summary` explique ce que l’entreprise y
|
|
@@ -244,17 +273,17 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
244
273
|
| `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. |
|
|
245
274
|
| `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. |
|
|
246
275
|
| `undo_move_leads` / `get_lead_move` | Annuler ou relire une opération de déplacement par son identifiant. |
|
|
247
|
-
| `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination, dont le critère composé `event_filter
|
|
276
|
+
| `get_leads` | Rechercher et lister des leads avec filtres, recherche et pagination, dont le critère composé `event_filter` (`subject` : employeur ou personne). |
|
|
248
277
|
| `get_lead` | Récupérer les détails complets d'un lead par son ID. |
|
|
249
278
|
| `update_lead` | Mettre à jour un ou plusieurs champs d'un lead existant. |
|
|
250
279
|
| `clear_lead_field` | Effacer irréversiblement une valeur stockée, sans modifier le schéma du groupe. Exige la confirmation explicite `clear_lead_field`. |
|
|
251
|
-
| `list_events` | Lister les événements tenant-scoped ; les événements archivés sont masqués par défaut. |
|
|
252
|
-
| `get_event` | Lire un événement
|
|
253
|
-
| `create_event` | Créer un événement
|
|
254
|
-
| `update_event` | Modifier ou archiver sans suppression un événement partagé, avec version attendue. |
|
|
255
|
-
| `list_event_participations` | Lister les participations d’entreprise ;
|
|
256
|
-
| `get_event_participation` | Lire un snapshot courant et son historique ; `VERIFIED` par défaut, statut incertain à demander explicitement. |
|
|
257
|
-
| `record_event_participations` | Valider à blanc ou enregistrer un lot idempotent de 1 à 100 observations/corrections. |
|
|
280
|
+
| `list_events` | Lister les événements tenant-scoped, filtrables par type (`event_types`) ; les événements archivés sont masqués par défaut. |
|
|
281
|
+
| `get_event` | Lire un événement partagé, son type, son état d’archivage et ses nombres d’entreprises et de personnes rattachées. |
|
|
282
|
+
| `create_event` | Créer un événement partagé, avec son type facultatif, et une clé d’idempotence. |
|
|
283
|
+
| `update_event` | Modifier (dont le type) ou archiver sans suppression un événement partagé, avec version attendue. |
|
|
284
|
+
| `list_event_participations` | Lister les participations d’entreprise et de personne, avec leur provenance ; filtres `target`, `person_ids` ; `VERIFIED` par défaut. |
|
|
285
|
+
| `get_event_participation` | Lire un snapshot courant d’entreprise ou de personne et son historique ; `VERIFIED` par défaut, statut incertain à demander explicitement. |
|
|
286
|
+
| `record_event_participations` | Valider à blanc ou enregistrer un lot idempotent de 1 à 100 observations/corrections d’entreprise ou de personne (inscrit, présent, invité, preuve « document du client »). |
|
|
258
287
|
| `preview_reset_sequence_messages` / `execute_reset_sequence_messages` | Prévisualiser puis effacer irréversiblement les messages générés d'un prospect, d'une liste explicite ou d'un groupe explicite. L'exécution exige le digest de preview et une clé d'idempotence. |
|
|
259
288
|
| `preview_reset_ai_fields` / `execute_reset_ai_fields` | Prévisualiser puis effacer irréversiblement les champs IA d'un prospect, d'une liste explicite ou d'un groupe explicite, sans toucher aux contacts, flags manuels, activités ou campagnes. |
|
|
260
289
|
| `delete_leads` | Supprimer définitivement des leads par leurs IDs. |
|
package/dist/tools/events.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const COMPANY_EVENT_ROLES: readonly ["EXHIBITOR", "SPONSOR", "SPEAKER", "PARTNER", "ORGANIZER", "CONTRIBUTOR", "PARTICIPANT", "UNKNOWN", "OTHER"];
|
|
4
|
+
export declare const PERSON_ONLY_EVENT_ROLES: readonly ["REGISTERED", "ATTENDED", "INVITED"];
|
|
5
|
+
export declare const PERSON_EVENT_ROLES: readonly ["REGISTERED", "ATTENDED", "INVITED", "SPEAKER", "ORGANIZER", "OTHER"];
|
|
6
|
+
export declare const eventRole: z.ZodEnum<["EXHIBITOR", "SPONSOR", "SPEAKER", "PARTNER", "ORGANIZER", "CONTRIBUTOR", "PARTICIPANT", "UNKNOWN", "OTHER", "REGISTERED", "ATTENDED", "INVITED"]>;
|
|
7
|
+
export declare const eventType: z.ZodEnum<["WEBINAR", "CONGRESS", "TRADE_SHOW", "OTHER"]>;
|
|
2
8
|
export declare function registerEventTools(server: McpServer, client?: import("../client.js").LeadifyClient): void;
|
package/dist/tools/events.js
CHANGED
|
@@ -7,7 +7,10 @@ const dateOnly = z.string()
|
|
|
7
7
|
const parsed = new Date(`${value}T00:00:00.000Z`);
|
|
8
8
|
return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
|
|
9
9
|
}, "Use a real calendar date in YYYY-MM-DD format.");
|
|
10
|
-
|
|
10
|
+
// Company roles are the nine historical ones. REGISTERED, ATTENDED and
|
|
11
|
+
// INVITED are three distinct facts about a person (PRD-2473 contract):
|
|
12
|
+
// a registration never proves attendance, an invitation proves neither.
|
|
13
|
+
export const COMPANY_EVENT_ROLES = [
|
|
11
14
|
"EXHIBITOR",
|
|
12
15
|
"SPONSOR",
|
|
13
16
|
"SPEAKER",
|
|
@@ -17,7 +20,13 @@ const role = z.enum([
|
|
|
17
20
|
"PARTICIPANT",
|
|
18
21
|
"UNKNOWN",
|
|
19
22
|
"OTHER",
|
|
20
|
-
]
|
|
23
|
+
];
|
|
24
|
+
export const PERSON_ONLY_EVENT_ROLES = ["REGISTERED", "ATTENDED", "INVITED"];
|
|
25
|
+
export const PERSON_EVENT_ROLES = ["REGISTERED", "ATTENDED", "INVITED", "SPEAKER", "ORGANIZER", "OTHER"];
|
|
26
|
+
export const eventRole = z.enum([...COMPANY_EVENT_ROLES, ...PERSON_ONLY_EVENT_ROLES]);
|
|
27
|
+
const role = eventRole;
|
|
28
|
+
export const eventType = z.enum(["WEBINAR", "CONGRESS", "TRADE_SHOW", "OTHER"]);
|
|
29
|
+
const target = z.enum(["COMPANY", "PERSON"]);
|
|
21
30
|
const status = z.enum(["UNVERIFIED", "VERIFIED", "DISPUTED", "REFUTED"]);
|
|
22
31
|
const nullableLabel = z.string().trim().min(1).max(500).nullable().optional();
|
|
23
32
|
const nullableNarrative = z.string().trim().min(1).max(5_000).nullable().optional();
|
|
@@ -35,10 +44,12 @@ const eventFields = {
|
|
|
35
44
|
official_url: nullableUrl,
|
|
36
45
|
last_verified_on: dateOnly.nullable().optional(),
|
|
37
46
|
};
|
|
47
|
+
const eventTypeDescription = "Event type: WEBINAR, CONGRESS, TRADE_SHOW or OTHER. Omitted means not specified.";
|
|
38
48
|
const createEventShape = {
|
|
39
49
|
organization_id: z.string().trim().min(1),
|
|
40
50
|
slug: z.string().trim().min(1).max(160).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
41
51
|
...eventFields,
|
|
52
|
+
event_type: eventType.optional().describe(eventTypeDescription),
|
|
42
53
|
idempotency_key: idempotencyKey,
|
|
43
54
|
};
|
|
44
55
|
const createEventInput = z.object(createEventShape).strict().superRefine((value, context) => {
|
|
@@ -59,12 +70,13 @@ const updateEventShape = {
|
|
|
59
70
|
venue: eventFields.venue,
|
|
60
71
|
official_url: eventFields.official_url,
|
|
61
72
|
last_verified_on: eventFields.last_verified_on,
|
|
73
|
+
event_type: eventType.nullable().optional().describe("New event type (WEBINAR, CONGRESS, TRADE_SHOW, OTHER), or null to mark it as not specified."),
|
|
62
74
|
archive: z.boolean().optional().default(false),
|
|
63
75
|
expected_version: z.number().int().positive(),
|
|
64
76
|
idempotency_key: idempotencyKey,
|
|
65
77
|
};
|
|
66
78
|
const updateEventInput = z.object(updateEventShape).strict().superRefine((value, context) => {
|
|
67
|
-
const mutable = ["name", "edition", "description", "starts_on", "ends_on", "city", "country_code", "venue", "official_url", "last_verified_on"];
|
|
79
|
+
const mutable = ["name", "edition", "description", "starts_on", "ends_on", "city", "country_code", "venue", "official_url", "last_verified_on", "event_type"];
|
|
68
80
|
const supplied = mutable.filter((key) => value[key] !== undefined);
|
|
69
81
|
if (value.archive && supplied.length > 0) {
|
|
70
82
|
context.addIssue({ code: z.ZodIssueCode.custom, path: ["archive"], message: "Archive and field updates must be separate idempotent operations." });
|
|
@@ -76,16 +88,23 @@ const updateEventInput = z.object(updateEventShape).strict().superRefine((value,
|
|
|
76
88
|
context.addIssue({ code: z.ZodIssueCode.custom, path: ["ends_on"], message: "ends_on must be on or after starts_on." });
|
|
77
89
|
}
|
|
78
90
|
});
|
|
91
|
+
const clientDocument = z.object({
|
|
92
|
+
file_name: z.string().trim().min(1).max(300).describe("Name of the file the client sent, e.g. the registrants spreadsheet."),
|
|
93
|
+
received_on: dateOnly.describe("Date the file was received, YYYY-MM-DD."),
|
|
94
|
+
provided_by: z.string().trim().min(1).max(300).describe("Who provided the file (client organization or contact)."),
|
|
95
|
+
}).strict().describe("Proof by a document provided by the client, without a web address. A client's registrants list is valid proof of registration (role REGISTERED), including for a person who later unsubscribed or registered several times. It is not proof of attendance.");
|
|
79
96
|
const participationItem = z.object({
|
|
80
97
|
event_id: z.string().trim().min(1),
|
|
81
|
-
account_id: z.string().trim().min(1).optional(),
|
|
82
|
-
lead_id: z.string().trim().min(1).optional(),
|
|
83
|
-
|
|
98
|
+
account_id: z.string().trim().min(1).optional().describe("Company target: canonical account id."),
|
|
99
|
+
lead_id: z.string().trim().min(1).optional().describe("Company lead (writes on its account) or Person lead (writes a personal participation on its canonical person)."),
|
|
100
|
+
person_id: z.string().trim().min(1).optional().describe("Person target: canonical person id: canonicalIdentity.id of a Person lead (canonicalIdentity.kind PERSON), as returned by get_lead."),
|
|
101
|
+
roles: z.array(role).min(1).max(20).describe("Company roles: EXHIBITOR, SPONSOR, SPEAKER, PARTNER, ORGANIZER, CONTRIBUTOR, PARTICIPANT, UNKNOWN, OTHER. Person roles: REGISTERED, ATTENDED, INVITED, SPEAKER, ORGANIZER, OTHER. REGISTERED, ATTENDED and INVITED are reserved to persons and are three distinct facts."),
|
|
84
102
|
role_details: z.array(z.string().trim().min(1).max(500)).max(50).optional().default([]),
|
|
85
103
|
stands: z.array(z.string().trim().min(1).max(200)).max(50).optional().default([]),
|
|
86
104
|
source_urls: z.array(z.string().trim().url().max(2048)).max(50).optional().default([]),
|
|
105
|
+
client_document: clientDocument.optional(),
|
|
87
106
|
source_provider: nullableLabel,
|
|
88
|
-
activity_summary: nullableNarrative.describe("Readable summary of what the company does, presents or targets at this event."),
|
|
107
|
+
activity_summary: nullableNarrative.describe("Readable summary of what the company or person does, presents or targets at this event."),
|
|
89
108
|
proof_excerpt: z.string().trim().min(1).max(20_000),
|
|
90
109
|
observed_on: dateOnly,
|
|
91
110
|
verified_on: dateOnly.nullable().optional(),
|
|
@@ -93,11 +112,24 @@ const participationItem = z.object({
|
|
|
93
112
|
correction_reason: z.string().trim().min(1).max(5_000).optional(),
|
|
94
113
|
idempotency_key: idempotencyKey,
|
|
95
114
|
}).strict().superRefine((value, context) => {
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
const targets = [value.account_id, value.lead_id, value.person_id].filter(Boolean).length;
|
|
116
|
+
if (targets !== 1) {
|
|
117
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: "Provide exactly one account_id, lead_id or person_id." });
|
|
98
118
|
}
|
|
99
|
-
if (value.
|
|
100
|
-
|
|
119
|
+
if (value.account_id) {
|
|
120
|
+
const refused = value.roles.filter((item) => PERSON_ONLY_EVENT_ROLES.includes(item));
|
|
121
|
+
if (refused.length) {
|
|
122
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["roles"], message: `Roles reserved to a person cannot be recorded on a company: ${refused.join(", ")}.` });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (value.person_id) {
|
|
126
|
+
const refused = value.roles.filter((item) => !PERSON_EVENT_ROLES.includes(item));
|
|
127
|
+
if (refused.length) {
|
|
128
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["roles"], message: `Roles not accepted for a person: ${refused.join(", ")}. Use REGISTERED, ATTENDED, INVITED, SPEAKER, ORGANIZER or OTHER.` });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (value.status === "VERIFIED" && value.source_urls.length === 0 && !value.client_document) {
|
|
132
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["source_urls"], message: "VERIFIED requires at least one source URL or a client_document." });
|
|
101
133
|
}
|
|
102
134
|
if (value.status === "VERIFIED" && !value.verified_on) {
|
|
103
135
|
context.addIssue({ code: z.ZodIssueCode.custom, path: ["verified_on"], message: "VERIFIED requires verified_on." });
|
|
@@ -118,10 +150,18 @@ function participationPayload(item) {
|
|
|
118
150
|
eventId: item.event_id,
|
|
119
151
|
accountId: item.account_id,
|
|
120
152
|
leadId: item.lead_id,
|
|
153
|
+
personId: item.person_id,
|
|
121
154
|
roles: item.roles,
|
|
122
155
|
roleDetails: item.role_details,
|
|
123
156
|
stands: item.stands,
|
|
124
157
|
sourceUrls: item.source_urls,
|
|
158
|
+
clientDocument: item.client_document
|
|
159
|
+
? {
|
|
160
|
+
fileName: item.client_document.file_name,
|
|
161
|
+
receivedOn: item.client_document.received_on,
|
|
162
|
+
providedBy: item.client_document.provided_by,
|
|
163
|
+
}
|
|
164
|
+
: undefined,
|
|
125
165
|
sourceProvider: item.source_provider,
|
|
126
166
|
activitySummary: item.activity_summary,
|
|
127
167
|
proofExcerpt: item.proof_excerpt,
|
|
@@ -160,16 +200,17 @@ function filterFactHistory(data, acceptedStatuses) {
|
|
|
160
200
|
return source;
|
|
161
201
|
}
|
|
162
202
|
export function registerEventTools(server, client = getClient()) {
|
|
163
|
-
server.tool("list_events", "List tenant-scoped events. Archived events are hidden unless include_archived=true. An event alone is not evidence that a company
|
|
203
|
+
server.tool("list_events", "List tenant-scoped events. Archived events are hidden unless include_archived=true. Filter by event_types (WEBINAR, CONGRESS, TRADE_SHOW, OTHER). An event alone is not evidence that a company or a person took part; cite participation only from a matching VERIFIED participation.", {
|
|
164
204
|
organization_id: z.string().trim().min(1),
|
|
165
205
|
include_archived: z.boolean().optional().default(false),
|
|
166
206
|
search: z.string().trim().min(1).max(200).optional(),
|
|
167
207
|
editions: z.array(z.string().trim().min(1).max(200)).max(100).optional(),
|
|
208
|
+
event_types: z.array(eventType).min(1).max(4).optional(),
|
|
168
209
|
starts_on_from: dateOnly.optional(),
|
|
169
210
|
starts_on_to: dateOnly.optional(),
|
|
170
211
|
limit: z.number().int().min(1).max(100).optional().default(50),
|
|
171
212
|
cursor: z.string().trim().min(1).optional(),
|
|
172
|
-
}, async ({ organization_id, include_archived, search, editions, starts_on_from, starts_on_to, limit, cursor }) => {
|
|
213
|
+
}, async ({ organization_id, include_archived, search, editions, event_types, starts_on_from, starts_on_to, limit, cursor }) => {
|
|
173
214
|
try {
|
|
174
215
|
const params = new URLSearchParams({
|
|
175
216
|
organizationId: organization_id,
|
|
@@ -179,6 +220,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
179
220
|
if (search)
|
|
180
221
|
params.set("search", search);
|
|
181
222
|
setCsv(params, "editions", editions);
|
|
223
|
+
setCsv(params, "eventTypes", event_types);
|
|
182
224
|
if (starts_on_from)
|
|
183
225
|
params.set("startsOnFrom", starts_on_from);
|
|
184
226
|
if (starts_on_to)
|
|
@@ -191,7 +233,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
191
233
|
return handleToolError(error);
|
|
192
234
|
}
|
|
193
235
|
});
|
|
194
|
-
server.tool("get_event", "Get one tenant-scoped event, including its archive state and
|
|
236
|
+
server.tool("get_event", "Get one tenant-scoped event, including its type, archive state and participationCounts { companies, persons }: the number of companies and of persons that carry a participation themselves. A count does not establish that any particular company or person attended, and a person is never counted through their employer.", {
|
|
195
237
|
organization_id: z.string().trim().min(1),
|
|
196
238
|
event_id: z.string().trim().min(1),
|
|
197
239
|
}, async ({ organization_id, event_id }) => {
|
|
@@ -202,7 +244,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
202
244
|
return handleToolError(error);
|
|
203
245
|
}
|
|
204
246
|
});
|
|
205
|
-
server.tool("create_event", "Create one reusable tenant-scoped event. The idempotency key makes an identical retry safe; changing the payload under the same key is rejected. Creating an event does not create company participation evidence.", createEventShape, async (params) => {
|
|
247
|
+
server.tool("create_event", "Create one reusable tenant-scoped event, optionally typed with event_type (WEBINAR, CONGRESS, TRADE_SHOW, OTHER). The idempotency key makes an identical retry safe; changing the payload under the same key is rejected. Creating an event does not create any company or person participation evidence.", createEventShape, async (params) => {
|
|
206
248
|
try {
|
|
207
249
|
const input = createEventInput.parse(params);
|
|
208
250
|
return toolResult(await client.post("/api/events", compact({
|
|
@@ -218,6 +260,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
218
260
|
venue: input.venue,
|
|
219
261
|
officialUrl: input.official_url,
|
|
220
262
|
lastVerifiedOn: input.last_verified_on,
|
|
263
|
+
eventType: input.event_type,
|
|
221
264
|
idempotencyKey: input.idempotency_key,
|
|
222
265
|
})));
|
|
223
266
|
}
|
|
@@ -225,7 +268,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
225
268
|
return handleToolError(error);
|
|
226
269
|
}
|
|
227
270
|
});
|
|
228
|
-
server.tool("update_event", "Update or archive one shared event with optimistic versioning and idempotency. Set archive=true as a separate operation with no field changes. This affects every company view that references the event; archiving is non-destructive and keeps all participation evidence.", updateEventShape, async (params) => {
|
|
271
|
+
server.tool("update_event", "Update or archive one shared event with optimistic versioning and idempotency, including its event_type (WEBINAR, CONGRESS, TRADE_SHOW, OTHER, or null for not specified). Set archive=true as a separate operation with no field changes. This affects every company and person view that references the event; archiving is non-destructive and keeps all participation evidence.", updateEventShape, async (params) => {
|
|
229
272
|
try {
|
|
230
273
|
const input = updateEventInput.parse(params);
|
|
231
274
|
const body = input.archive
|
|
@@ -247,6 +290,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
247
290
|
venue: input.venue,
|
|
248
291
|
officialUrl: input.official_url,
|
|
249
292
|
lastVerifiedOn: input.last_verified_on,
|
|
293
|
+
eventType: input.event_type,
|
|
250
294
|
expectedVersion: input.expected_version,
|
|
251
295
|
idempotencyKey: input.idempotency_key,
|
|
252
296
|
});
|
|
@@ -256,11 +300,13 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
256
300
|
return handleToolError(error);
|
|
257
301
|
}
|
|
258
302
|
});
|
|
259
|
-
server.tool("list_event_participations", "List current
|
|
303
|
+
server.tool("list_event_participations", "List current event participation snapshots of companies and persons in one tenant. Without target, person_ids or a Person lead_id, only company participations are returned (historical behavior); pass target=PERSON, person_ids or the lead_id of a Person lead to read persons. Each item carries target (COMPANY or PERSON), accountId, personId, clientDocument and, for a person, personLeads. With the lead_id of a Person lead, the result holds the person's own participations (origin PERSON) and those of their employer (origin EMPLOYER): an employer's event is never a participation of the person. REGISTERED (registered), ATTENDED (attended) and INVITED (invited) are distinct facts: a registration never proves attendance. Defaults explicitly to VERIFIED only. To review uncertain evidence or uncertain historical facts, pass statuses containing UNVERIFIED, DISPUTED or REFUTED; unrequested fact statuses are hidden and counted. Never cite uncertain data, or the absence of a record, as proof of attendance or non-attendance.", {
|
|
260
304
|
organization_id: z.string().trim().min(1),
|
|
261
305
|
event_ids: z.array(z.string().trim().min(1)).max(100).optional(),
|
|
262
306
|
account_ids: z.array(z.string().trim().min(1)).max(100).optional(),
|
|
263
|
-
|
|
307
|
+
person_ids: z.array(z.string().trim().min(1)).max(100).optional().describe("Canonical person ids: canonicalIdentity.id of Person leads (kind PERSON)."),
|
|
308
|
+
target: target.optional().describe("COMPANY or PERSON. Omitted: companies only, unless person_ids or a Person lead_id is given."),
|
|
309
|
+
lead_id: z.string().trim().min(1).optional().describe("Company lead: its account's participations. Person lead: its own participations (origin PERSON) and its employer's (origin EMPLOYER)."),
|
|
264
310
|
editions: z.array(z.string().trim().min(1).max(200)).max(100).optional(),
|
|
265
311
|
roles: z.array(role).max(20).optional(),
|
|
266
312
|
statuses: z.array(status).min(1).max(10).optional().default(["VERIFIED"]),
|
|
@@ -268,8 +314,8 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
268
314
|
starts_on_to: dateOnly.optional(),
|
|
269
315
|
include_facts: z.boolean().optional().default(false),
|
|
270
316
|
limit: z.number().int().min(1).max(100).optional().default(50),
|
|
271
|
-
cursor: z.string().trim().min(1).optional(),
|
|
272
|
-
}, async ({ organization_id, event_ids, account_ids, lead_id, editions, roles, statuses, starts_on_from, starts_on_to, include_facts, limit, cursor }) => {
|
|
317
|
+
cursor: z.string().trim().min(1).optional().describe("Opaque cursor returned by the previous page; pass it back unchanged."),
|
|
318
|
+
}, async ({ organization_id, event_ids, account_ids, person_ids, target, lead_id, editions, roles, statuses, starts_on_from, starts_on_to, include_facts, limit, cursor }) => {
|
|
273
319
|
try {
|
|
274
320
|
const params = new URLSearchParams({
|
|
275
321
|
organizationId: organization_id,
|
|
@@ -279,6 +325,9 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
279
325
|
});
|
|
280
326
|
setCsv(params, "eventIds", event_ids);
|
|
281
327
|
setCsv(params, "accountIds", account_ids);
|
|
328
|
+
setCsv(params, "personIds", person_ids);
|
|
329
|
+
if (target)
|
|
330
|
+
params.set("target", target);
|
|
282
331
|
if (lead_id)
|
|
283
332
|
params.set("leadId", lead_id);
|
|
284
333
|
setCsv(params, "editions", editions);
|
|
@@ -296,7 +345,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
296
345
|
return handleToolError(error);
|
|
297
346
|
}
|
|
298
347
|
});
|
|
299
|
-
server.tool("get_event_participation", "Get one explicitly selected current participation and its immutable fact history. Access defaults to VERIFIED only; pass statuses explicitly to review UNVERIFIED, DISPUTED or REFUTED facts. Uncertain evidence is not verified attendance, and missing data never means non-participation.", {
|
|
348
|
+
server.tool("get_event_participation", "Get one explicitly selected current participation of a company or a person and its immutable fact history, with target, accountId, personId, clientDocument and, for a person, personLeads. Access defaults to VERIFIED only; pass statuses explicitly to review UNVERIFIED, DISPUTED or REFUTED facts. REGISTERED is not ATTENDED. Uncertain evidence is not verified attendance, and missing data never means non-participation.", {
|
|
300
349
|
organization_id: z.string().trim().min(1),
|
|
301
350
|
participation_id: z.string().trim().min(1),
|
|
302
351
|
statuses: z.array(status).min(1).max(10).optional().default(["VERIFIED"]),
|
|
@@ -320,7 +369,7 @@ export function registerEventTools(server, client = getClient()) {
|
|
|
320
369
|
return handleToolError(error);
|
|
321
370
|
}
|
|
322
371
|
});
|
|
323
|
-
server.tool("record_event_participations", "Create participation evidence, add a corroborating source, or correct 1-100
|
|
372
|
+
server.tool("record_event_participations", "Create participation evidence, add a corroborating source, or correct 1-100 event observations of companies or persons. Set validate_only=true for a no-write dry-run. Each item targets exactly one account_id, lead_id or person_id and has its own idempotency key. A Company lead or account_id writes a company participation, exactly as before. The lead_id of a Person lead, or a person_id, writes a personal participation on the canonical person: it follows that person across every group. Person roles: REGISTERED, ATTENDED, INVITED, SPEAKER, ORGANIZER, OTHER; REGISTERED, ATTENDED and INVITED are refused for a company. Registered, attended and invited are three distinct facts: a registration never proves attendance. A registrants list provided by the client is valid proof of registration: record role REGISTERED with client_document { file_name, received_on, provided_by } and no URL, including for a person who later unsubscribed or registered several times. An employer's event is never a participation of the person: never record a person from their company's participation. activity_summary is the readable account of what the company or person does at the event; proof_excerpt remains the exact agent/audit evidence. Corrections append an immutable fact and require correction_reason; there is no delete operation. VERIFIED requires a source URL or a client_document, a proof excerpt and a verification date. Uncertain or absent data cannot be cited as attendance, and the absence of a record proves nothing.", {
|
|
324
373
|
organization_id: z.string().trim().min(1),
|
|
325
374
|
batch_id: z.string().trim().min(1).max(255),
|
|
326
375
|
validate_only: z.boolean().optional().default(false),
|
package/dist/tools/leads.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getClient } from "../client.js";
|
|
3
3
|
import { toolResult, handleToolError } from "../types.js";
|
|
4
|
+
import { eventRole } from "./events.js";
|
|
4
5
|
const DEFAULT_LEAD_FIELDS = ["firstName", "lastName", "email", "company", "jobTitle", "linkedin"];
|
|
5
6
|
const eventFilterSchema = z.object({
|
|
6
7
|
event_ids: z.array(z.string().trim().min(1)).max(100).optional(),
|
|
7
8
|
editions: z.array(z.string().trim().min(1).max(200)).max(100).optional(),
|
|
8
|
-
roles: z.array(
|
|
9
|
-
"EXHIBITOR", "SPONSOR", "SPEAKER", "PARTNER", "ORGANIZER",
|
|
10
|
-
"CONTRIBUTOR", "PARTICIPANT", "UNKNOWN", "OTHER",
|
|
11
|
-
])).max(20).optional(),
|
|
9
|
+
roles: z.array(eventRole).max(20).optional(),
|
|
12
10
|
statuses: z.array(z.enum(["UNVERIFIED", "VERIFIED", "DISPUTED", "REFUTED"])).min(1).max(10).optional(),
|
|
13
11
|
starts_on_from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
14
12
|
starts_on_to: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
13
|
+
subject: z.enum(["EMPLOYER", "PERSON"]).optional().describe("EMPLOYER (default, historical behavior): leads whose company carries the participation. PERSON: only Person leads that carry the participation themselves, e.g. the registrants of a webinar. An employer's event is never a participation of the person."),
|
|
15
14
|
}).strict().superRefine((value, context) => {
|
|
16
15
|
if (value.starts_on_from && value.starts_on_to && value.starts_on_to < value.starts_on_from) {
|
|
17
16
|
context.addIssue({ code: z.ZodIssueCode.custom, path: ["starts_on_to"], message: "starts_on_to must be on or after starts_on_from." });
|
|
@@ -169,7 +168,9 @@ export function registerLeadTools(server, client = getClient()) {
|
|
|
169
168
|
"full-text search, saved views, and compact field projections. Returns lead ID plus " +
|
|
170
169
|
"common identity fields by default. Pass `fields` for a workflow-specific projection " +
|
|
171
170
|
"and `include_schema=true` only when the schema is explicitly needed. event_filter " +
|
|
172
|
-
"uses one correlated
|
|
171
|
+
"uses one correlated participation and defaults explicitly to VERIFIED; subject=PERSON " +
|
|
172
|
+
"keeps only persons who carry the participation themselves, subject=EMPLOYER (default) " +
|
|
173
|
+
"matches through the company. A registration (REGISTERED) is not attendance (ATTENDED); " +
|
|
173
174
|
"uncertain or absent data must never be cited as attendance or non-attendance.", {
|
|
174
175
|
group_id: z.string().describe("ID of the lead group to query."),
|
|
175
176
|
view_id: z
|
|
@@ -202,7 +203,7 @@ export function registerLeadTools(server, client = getClient()) {
|
|
|
202
203
|
"operator to value. Operators: equals, not_equals, contains, not_contains, " +
|
|
203
204
|
"gte, lte, is_true, is_false, is_null, is_not_null. " +
|
|
204
205
|
'Example: {"industry": {"contains": "tech"}, "company": {"is_not_null": "true"}}'),
|
|
205
|
-
event_filter: eventFilterSchema.optional().describe("Structured
|
|
206
|
+
event_filter: eventFilterSchema.optional().describe("Structured event participation filter. subject chooses whose participation counts: EMPLOYER (default, the company's) or PERSON (the person's own). Values within one list use OR; event, edition, role, status and date dimensions use AND against the same participation. statuses defaults to [VERIFIED]. Pass uncertain statuses explicitly for review. There is no confidence score, negative operator or absence filter: the absence of a record proves nothing."),
|
|
206
207
|
}, async ({ group_id, view_id, page, limit, search, fields, include_schema, filters, event_filter }) => {
|
|
207
208
|
try {
|
|
208
209
|
const params = new URLSearchParams();
|
|
@@ -231,6 +232,7 @@ export function registerLeadTools(server, client = getClient()) {
|
|
|
231
232
|
statuses: checked.statuses ?? ["VERIFIED"],
|
|
232
233
|
...(checked.starts_on_from ? { startsOnFrom: checked.starts_on_from } : {}),
|
|
233
234
|
...(checked.starts_on_to ? { startsOnTo: checked.starts_on_to } : {}),
|
|
235
|
+
...(checked.subject ? { subject: checked.subject } : {}),
|
|
234
236
|
}));
|
|
235
237
|
}
|
|
236
238
|
const data = await client.get("/get-leads", params);
|