@agifyai/leadify-mcp 8.5.5 → 8.6.2

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.
@@ -0,0 +1,528 @@
1
+ import { z } from "zod";
2
+ import { getClient } from "../client.js";
3
+ import { handleToolError, toolResult } from "../types.js";
4
+ const statusSchema = z.enum(["DRAFT", "ACTIVE", "ARCHIVED"]);
5
+ const shortText = z.string().trim().max(500);
6
+ const longText = z.string().trim().max(20_000);
7
+ const itemId = z.string().trim().min(1).max(100);
8
+ const textList = z.array(shortText).max(50);
9
+ const linkedIds = z.array(itemId).max(50);
10
+ const idealCustomerSchema = z.object({
11
+ summary: longText.optional(),
12
+ industries: textList.optional(),
13
+ companySizes: textList.optional(),
14
+ geographies: textList.optional(),
15
+ characteristics: textList.optional(),
16
+ }).strict();
17
+ const marketMetricSchema = z.object({
18
+ id: itemId,
19
+ label: shortText,
20
+ value: shortText,
21
+ explanation: longText.optional(),
22
+ }).strict();
23
+ const verticalUseCaseSchema = z.object({
24
+ id: itemId,
25
+ title: shortText,
26
+ description: longText.optional(),
27
+ customerCaseIds: linkedIds.optional(),
28
+ }).strict();
29
+ const verticalPainPointSchema = z.object({
30
+ id: itemId,
31
+ title: shortText,
32
+ description: longText.optional(),
33
+ useCaseIds: linkedIds.optional(),
34
+ customerCaseIds: linkedIds.optional(),
35
+ }).strict();
36
+ const qualificationSignalSchema = z.object({
37
+ id: itemId,
38
+ title: shortText,
39
+ example: longText.optional(),
40
+ meaning: longText.optional(),
41
+ }).strict();
42
+ const titledDetailSchema = z.object({
43
+ id: itemId,
44
+ title: shortText,
45
+ description: longText.optional(),
46
+ }).strict();
47
+ const objectionSchema = z.object({
48
+ id: itemId,
49
+ objection: longText,
50
+ response: longText.optional(),
51
+ }).strict();
52
+ const commercialLearningSchema = z.object({
53
+ id: itemId,
54
+ observation: longText,
55
+ implication: longText.optional(),
56
+ }).strict();
57
+ const offerClaimSchema = z.object({
58
+ id: itemId,
59
+ statement: longText,
60
+ details: longText.optional(),
61
+ }).strict();
62
+ const demonstratedResultSchema = z.object({
63
+ id: itemId,
64
+ label: shortText,
65
+ value: shortText,
66
+ details: longText.optional(),
67
+ }).strict();
68
+ const customerCaseSchema = z.object({
69
+ id: itemId,
70
+ companyName: shortText,
71
+ industry: shortText.optional(),
72
+ companyDescription: longText.optional(),
73
+ resultDetails: longText.optional(),
74
+ testimonial: longText.optional(),
75
+ }).strict();
76
+ function duplicates(values) {
77
+ const seen = new Set();
78
+ const duplicateValues = new Set();
79
+ for (const value of values) {
80
+ if (seen.has(value))
81
+ duplicateValues.add(value);
82
+ seen.add(value);
83
+ }
84
+ return [...duplicateValues];
85
+ }
86
+ const verticalContextContentObjectSchema = z.object({
87
+ sector: shortText.optional(),
88
+ sectorPositioning: longText.optional(),
89
+ commercialPitch: longText.optional(),
90
+ idealCustomer: idealCustomerSchema.optional(),
91
+ marketMetrics: z.array(marketMetricSchema).max(10).optional(),
92
+ customerCaseIds: z.array(itemId).max(7).optional(),
93
+ useCases: z.array(verticalUseCaseSchema).max(50).optional(),
94
+ painPoints: z.array(verticalPainPointSchema).max(50).optional(),
95
+ qualificationSignals: z.array(qualificationSignalSchema).max(50).optional(),
96
+ competitiveAdvantages: z.array(titledDetailSchema).max(50).optional(),
97
+ competitors: z.array(titledDetailSchema).max(50).optional(),
98
+ objections: z.array(objectionSchema).max(50).optional(),
99
+ allowedVocabulary: textList.optional(),
100
+ prohibitedVocabulary: textList.optional(),
101
+ legalConstraints: z.array(titledDetailSchema).max(50).optional(),
102
+ regulatoryInformation: longText.optional(),
103
+ commercialExperience: z.array(commercialLearningSchema).max(50).optional(),
104
+ }).strict();
105
+ export const verticalContextContentSchema = verticalContextContentObjectSchema.superRefine((content, context) => {
106
+ const itemCollections = [
107
+ ["marketMetrics", content.marketMetrics],
108
+ ["useCases", content.useCases],
109
+ ["painPoints", content.painPoints],
110
+ ["qualificationSignals", content.qualificationSignals],
111
+ ["competitiveAdvantages", content.competitiveAdvantages],
112
+ ["competitors", content.competitors],
113
+ ["objections", content.objections],
114
+ ["legalConstraints", content.legalConstraints],
115
+ ["commercialExperience", content.commercialExperience],
116
+ ];
117
+ for (const [field, items] of itemCollections) {
118
+ for (const duplicate of duplicates((items ?? []).map(item => item.id))) {
119
+ context.addIssue({ code: z.ZodIssueCode.custom, path: [field], message: `Duplicate item id: ${duplicate}` });
120
+ }
121
+ }
122
+ for (const duplicate of duplicates(content.customerCaseIds ?? [])) {
123
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["customerCaseIds"], message: `Duplicate customer case id: ${duplicate}` });
124
+ }
125
+ const selectedCustomerCases = new Set(content.customerCaseIds ?? []);
126
+ const useCaseIds = new Set((content.useCases ?? []).map(item => item.id));
127
+ for (const [index, useCase] of (content.useCases ?? []).entries()) {
128
+ for (const customerCaseId of useCase.customerCaseIds ?? []) {
129
+ if (!selectedCustomerCases.has(customerCaseId)) {
130
+ context.addIssue({
131
+ code: z.ZodIssueCode.custom,
132
+ path: ["useCases", index, "customerCaseIds"],
133
+ message: `Customer case ${customerCaseId} must first be selected on the Vertical`,
134
+ });
135
+ }
136
+ }
137
+ }
138
+ for (const [index, painPoint] of (content.painPoints ?? []).entries()) {
139
+ for (const useCaseId of painPoint.useCaseIds ?? []) {
140
+ if (!useCaseIds.has(useCaseId)) {
141
+ context.addIssue({
142
+ code: z.ZodIssueCode.custom,
143
+ path: ["painPoints", index, "useCaseIds"],
144
+ message: `Unknown Vertical use case id: ${useCaseId}`,
145
+ });
146
+ }
147
+ }
148
+ for (const customerCaseId of painPoint.customerCaseIds ?? []) {
149
+ if (!selectedCustomerCases.has(customerCaseId)) {
150
+ context.addIssue({
151
+ code: z.ZodIssueCode.custom,
152
+ path: ["painPoints", index, "customerCaseIds"],
153
+ message: `Customer case ${customerCaseId} must first be selected on the Vertical`,
154
+ });
155
+ }
156
+ }
157
+ }
158
+ });
159
+ export const offerContextContentSchema = z.object({
160
+ description: longText.optional(),
161
+ promise: longText.optional(),
162
+ capabilities: z.array(titledDetailSchema).max(50).optional(),
163
+ activationConditions: z.array(titledDetailSchema).max(50).optional(),
164
+ claims: z.array(offerClaimSchema).max(50).optional(),
165
+ demonstratedResults: z.array(demonstratedResultSchema).max(50).optional(),
166
+ customerCases: z.array(customerCaseSchema).max(50).optional(),
167
+ constraints: z.array(titledDetailSchema).max(50).optional(),
168
+ }).strict().superRefine((content, context) => {
169
+ const itemCollections = [
170
+ ["capabilities", content.capabilities],
171
+ ["activationConditions", content.activationConditions],
172
+ ["claims", content.claims],
173
+ ["demonstratedResults", content.demonstratedResults],
174
+ ["customerCases", content.customerCases],
175
+ ["constraints", content.constraints],
176
+ ];
177
+ for (const [field, items] of itemCollections) {
178
+ for (const duplicate of duplicates((items ?? []).map(item => item.id))) {
179
+ context.addIssue({ code: z.ZodIssueCode.custom, path: [field], message: `Duplicate item id: ${duplicate}` });
180
+ }
181
+ }
182
+ });
183
+ const expectedUpdatedAtSchema = z.string().datetime({ offset: true }).describe("Exact updatedAt from the read this mutation is based on. A stale value is rejected with 409.");
184
+ const previewScopeSchema = z.enum(["explicit", "lead_group", "campaign"]);
185
+ function previewBody(input) {
186
+ if (input.scope === "explicit") {
187
+ if (!input.vertical_id || !input.offer_id || !input.persona_id) {
188
+ throw new Error("explicit preview requires vertical_id, offer_id, and persona_id");
189
+ }
190
+ return {
191
+ scope: input.scope,
192
+ verticalId: input.vertical_id,
193
+ offerId: input.offer_id,
194
+ personaId: input.persona_id,
195
+ };
196
+ }
197
+ if (input.scope === "lead_group") {
198
+ if (!input.lead_group_id)
199
+ throw new Error("lead_group preview requires lead_group_id");
200
+ return { scope: input.scope, leadGroupId: input.lead_group_id };
201
+ }
202
+ if (!input.campaign_id)
203
+ throw new Error("campaign preview requires campaign_id");
204
+ return { scope: input.scope, campaignId: input.campaign_id };
205
+ }
206
+ function params(organizationId, status) {
207
+ return new URLSearchParams({
208
+ organizationId,
209
+ ...(status ? { status } : {}),
210
+ });
211
+ }
212
+ function path(resource, organizationId) {
213
+ return `/api/context-entities/${resource}?${new URLSearchParams({ organizationId }).toString()}`;
214
+ }
215
+ function entityPath(resource, id, organizationId, suffix = "") {
216
+ return `/api/context-entities/${resource}/${encodeURIComponent(id)}${suffix}?${new URLSearchParams({ organizationId }).toString()}`;
217
+ }
218
+ function object(value) {
219
+ return value && typeof value === "object" && !Array.isArray(value)
220
+ ? value
221
+ : {};
222
+ }
223
+ async function patchEntity(client, input) {
224
+ if (input.name === undefined &&
225
+ Object.keys(input.contentPatch ?? {}).length === 0 &&
226
+ (input.removeContentKeys?.length ?? 0) === 0) {
227
+ throw new Error("Provide a name, a non-empty content_patch, or remove_content_keys.");
228
+ }
229
+ const read = object(await client.get(entityPath(input.resource, input.id, input.organizationId)));
230
+ const entity = object(read[input.wrapper]);
231
+ const actualUpdatedAt = entity.updatedAt;
232
+ if (typeof actualUpdatedAt !== "string" ||
233
+ new Date(actualUpdatedAt).toISOString() !== new Date(input.expectedUpdatedAt).toISOString()) {
234
+ throw new Error(`CONTEXT_ENTITY_UPDATE_CONFLICT: expected ${input.expectedUpdatedAt}, current ${String(actualUpdatedAt)}. Re-read before retrying.`);
235
+ }
236
+ const parsedCurrent = input.resource === "verticals"
237
+ ? verticalContextContentSchema.safeParse(entity.content)
238
+ : offerContextContentSchema.safeParse(entity.content);
239
+ const content = {
240
+ // During the strict cutover, an invalid legacy object has no active keys to
241
+ // preserve. The first valid patch replaces it instead of smuggling old,
242
+ // now-forbidden JSON back through the MCP.
243
+ ...(parsedCurrent.success ? parsedCurrent.data : {}),
244
+ ...(input.contentPatch ?? {}),
245
+ };
246
+ for (const key of input.removeContentKeys ?? [])
247
+ delete content[key];
248
+ return client.patch(entityPath(input.resource, input.id, input.organizationId), {
249
+ expectedUpdatedAt: input.expectedUpdatedAt,
250
+ ...(input.name !== undefined ? { name: input.name } : {}),
251
+ ...(input.contentPatch !== undefined || (input.removeContentKeys?.length ?? 0) > 0
252
+ ? { content }
253
+ : {}),
254
+ });
255
+ }
256
+ export function registerContextEntityTools(server, client = getClient()) {
257
+ server.registerTool("list_verticals", {
258
+ title: "List organization Verticals",
259
+ description: "List compact tenant-scoped Verticals. Historical global/orphaned Personas are never returned. " +
260
+ "Always provide the organization explicitly; no tenant or Vertical is inferred.",
261
+ inputSchema: {
262
+ organization_id: z.string().trim().min(1),
263
+ status: statusSchema.optional(),
264
+ },
265
+ annotations: { readOnlyHint: true },
266
+ }, async ({ organization_id, status }) => {
267
+ try {
268
+ return toolResult(await client.get("/api/context-entities/verticals", params(organization_id, status)));
269
+ }
270
+ catch (error) {
271
+ return handleToolError(error);
272
+ }
273
+ });
274
+ server.registerTool("get_vertical", {
275
+ title: "Read one Vertical",
276
+ description: "Read one exact tenant-scoped Vertical, including its authoritative JSON, linked Personas, and linked Offers. " +
277
+ "The organization and Vertical IDs are both mandatory.",
278
+ inputSchema: {
279
+ organization_id: z.string().trim().min(1),
280
+ vertical_id: z.string().trim().min(1),
281
+ },
282
+ annotations: { readOnlyHint: true },
283
+ }, async ({ organization_id, vertical_id }) => {
284
+ try {
285
+ return toolResult(await client.get(`/api/context-entities/verticals/${encodeURIComponent(vertical_id)}`, params(organization_id)));
286
+ }
287
+ catch (error) {
288
+ return handleToolError(error);
289
+ }
290
+ });
291
+ server.registerTool("create_vertical", {
292
+ title: "Create a Vertical draft",
293
+ description: "Create one tenant-scoped Vertical in DRAFT. Content is simple authoritative JSON and must not contain " +
294
+ "formal evidence/source/proof/provenance fields. Its closed schema covers sector positioning, vocabulary, legal constraints and " +
295
+ "commercialExperience belong here; global counterparts remain in Company Brain with no fallback or override. " +
296
+ "This never publishes, activates, or selects a default Vertical.",
297
+ inputSchema: {
298
+ organization_id: z.string().trim().min(1),
299
+ name: z.string().trim().min(1).max(200),
300
+ content: verticalContextContentSchema.optional(),
301
+ },
302
+ }, async ({ organization_id, name, content }) => {
303
+ try {
304
+ return toolResult(await client.post(path("verticals", organization_id), {
305
+ name,
306
+ content: content ?? {},
307
+ }));
308
+ }
309
+ catch (error) {
310
+ return handleToolError(error);
311
+ }
312
+ });
313
+ server.registerTool("list_offers", {
314
+ title: "List organization Offers",
315
+ description: "List compact tenant-scoped Offers with relation counts. Always provide the organization explicitly; " +
316
+ "no Offer or tenant is inferred.",
317
+ inputSchema: {
318
+ organization_id: z.string().trim().min(1),
319
+ status: statusSchema.optional(),
320
+ },
321
+ annotations: { readOnlyHint: true },
322
+ }, async ({ organization_id, status }) => {
323
+ try {
324
+ return toolResult(await client.get("/api/context-entities/offers", params(organization_id, status)));
325
+ }
326
+ catch (error) {
327
+ return handleToolError(error);
328
+ }
329
+ });
330
+ server.registerTool("update_vertical", {
331
+ title: "Update one Vertical safely",
332
+ description: "Optimistically patch one Vertical name and/or top-level JSON keys. The tool re-reads, shallow-merges only the named keys, " +
333
+ "and sends the exact expected_updated_at. A concurrent change returns 409; no silent overwrite is allowed. " +
334
+ "Editing an ACTIVE Vertical returns it to DRAFT.",
335
+ inputSchema: {
336
+ organization_id: z.string().trim().min(1),
337
+ vertical_id: z.string().trim().min(1),
338
+ expected_updated_at: expectedUpdatedAtSchema,
339
+ name: z.string().trim().min(1).max(200).optional(),
340
+ // References may target untouched top-level collections. Structural
341
+ // validation happens here; the server validates the fully merged JSON.
342
+ content_patch: verticalContextContentObjectSchema.optional(),
343
+ remove_content_keys: z.array(z.string().min(1)).max(100).optional(),
344
+ },
345
+ }, async ({ organization_id, vertical_id, expected_updated_at, name, content_patch, remove_content_keys }) => {
346
+ try {
347
+ return toolResult(await patchEntity(client, {
348
+ resource: "verticals",
349
+ wrapper: "vertical",
350
+ organizationId: organization_id,
351
+ id: vertical_id,
352
+ expectedUpdatedAt: expected_updated_at,
353
+ name,
354
+ contentPatch: content_patch,
355
+ removeContentKeys: remove_content_keys,
356
+ }));
357
+ }
358
+ catch (error) {
359
+ return handleToolError(error);
360
+ }
361
+ });
362
+ server.registerTool("change_vertical_status", {
363
+ title: "Change one Vertical status",
364
+ description: "Change a Vertical between DRAFT, ACTIVE, and ARCHIVED using mandatory optimistic concurrency. " +
365
+ "Activation checks readiness; archive is blocked while references remain; ARCHIVED can only return to DRAFT.",
366
+ inputSchema: {
367
+ organization_id: z.string().trim().min(1),
368
+ vertical_id: z.string().trim().min(1),
369
+ expected_updated_at: expectedUpdatedAtSchema,
370
+ status: statusSchema,
371
+ },
372
+ }, async ({ organization_id, vertical_id, expected_updated_at, status }) => {
373
+ try {
374
+ return toolResult(await client.post(entityPath("verticals", vertical_id, organization_id, "/status"), { expectedUpdatedAt: expected_updated_at, status }));
375
+ }
376
+ catch (error) {
377
+ return handleToolError(error);
378
+ }
379
+ });
380
+ server.registerTool("get_offer", {
381
+ title: "Read one Offer",
382
+ description: "Read one exact tenant-scoped Offer, including its authoritative JSON, linked Verticals, and Lead Groups. " +
383
+ "The organization and Offer IDs are both mandatory.",
384
+ inputSchema: {
385
+ organization_id: z.string().trim().min(1),
386
+ offer_id: z.string().trim().min(1),
387
+ },
388
+ annotations: { readOnlyHint: true },
389
+ }, async ({ organization_id, offer_id }) => {
390
+ try {
391
+ return toolResult(await client.get(`/api/context-entities/offers/${encodeURIComponent(offer_id)}`, params(organization_id)));
392
+ }
393
+ catch (error) {
394
+ return handleToolError(error);
395
+ }
396
+ });
397
+ server.registerTool("create_offer", {
398
+ title: "Create an Offer draft",
399
+ description: "Create one tenant-scoped Offer in DRAFT. Content is simple authoritative JSON and must not contain " +
400
+ "formal evidence/source/proof/provenance fields. Its closed schema owns claims, demonstratedResults and customerCases; " +
401
+ "sector context and commercialExperience do not. This never publishes, activates, links, or selects an Offer.",
402
+ inputSchema: {
403
+ organization_id: z.string().trim().min(1),
404
+ name: z.string().trim().min(1).max(200),
405
+ content: offerContextContentSchema.optional(),
406
+ },
407
+ }, async ({ organization_id, name, content }) => {
408
+ try {
409
+ return toolResult(await client.post(path("offers", organization_id), {
410
+ name,
411
+ content: content ?? {},
412
+ }));
413
+ }
414
+ catch (error) {
415
+ return handleToolError(error);
416
+ }
417
+ });
418
+ server.registerTool("update_offer", {
419
+ title: "Update one Offer safely",
420
+ description: "Optimistically patch one Offer name and/or top-level JSON keys. The tool re-reads and shallow-merges only the named keys. " +
421
+ "A stale expected_updated_at returns 409, and editing an ACTIVE Offer returns it to DRAFT.",
422
+ inputSchema: {
423
+ organization_id: z.string().trim().min(1),
424
+ offer_id: z.string().trim().min(1),
425
+ expected_updated_at: expectedUpdatedAtSchema,
426
+ name: z.string().trim().min(1).max(200).optional(),
427
+ content_patch: offerContextContentSchema.optional(),
428
+ remove_content_keys: z.array(z.string().min(1)).max(100).optional(),
429
+ },
430
+ }, async ({ organization_id, offer_id, expected_updated_at, name, content_patch, remove_content_keys }) => {
431
+ try {
432
+ return toolResult(await patchEntity(client, {
433
+ resource: "offers",
434
+ wrapper: "offer",
435
+ organizationId: organization_id,
436
+ id: offer_id,
437
+ expectedUpdatedAt: expected_updated_at,
438
+ name,
439
+ contentPatch: content_patch,
440
+ removeContentKeys: remove_content_keys,
441
+ }));
442
+ }
443
+ catch (error) {
444
+ return handleToolError(error);
445
+ }
446
+ });
447
+ server.registerTool("change_offer_status", {
448
+ title: "Change one Offer status",
449
+ description: "Change an Offer between DRAFT, ACTIVE, and ARCHIVED using mandatory optimistic concurrency. " +
450
+ "Activation checks readiness; archive is blocked while references remain; ARCHIVED can only return to DRAFT.",
451
+ inputSchema: {
452
+ organization_id: z.string().trim().min(1),
453
+ offer_id: z.string().trim().min(1),
454
+ expected_updated_at: expectedUpdatedAtSchema,
455
+ status: statusSchema,
456
+ },
457
+ }, async ({ organization_id, offer_id, expected_updated_at, status }) => {
458
+ try {
459
+ return toolResult(await client.post(entityPath("offers", offer_id, organization_id, "/status"), { expectedUpdatedAt: expected_updated_at, status }));
460
+ }
461
+ catch (error) {
462
+ return handleToolError(error);
463
+ }
464
+ });
465
+ const verticalOfferMutationInput = {
466
+ organization_id: z.string().trim().min(1),
467
+ vertical_id: z.string().trim().min(1),
468
+ offer_id: z.string().trim().min(1),
469
+ expected_vertical_updated_at: expectedUpdatedAtSchema,
470
+ expected_offer_updated_at: expectedUpdatedAtSchema,
471
+ };
472
+ server.registerTool("link_vertical_offer", {
473
+ title: "Link one Offer to one Vertical",
474
+ description: "Create one exact tenant-scoped Vertical-Offer relation. Both objects must be ACTIVE and both timestamps must be current. " +
475
+ "The relation edit returns both objects to DRAFT for explicit revalidation.",
476
+ inputSchema: verticalOfferMutationInput,
477
+ }, async ({ organization_id, vertical_id, offer_id, expected_vertical_updated_at, expected_offer_updated_at }) => {
478
+ try {
479
+ return toolResult(await client.put(`${entityPath("verticals", vertical_id, organization_id, `/offers/${encodeURIComponent(offer_id)}`)}`, {
480
+ expectedVerticalUpdatedAt: expected_vertical_updated_at,
481
+ expectedOfferUpdatedAt: expected_offer_updated_at,
482
+ }));
483
+ }
484
+ catch (error) {
485
+ return handleToolError(error);
486
+ }
487
+ });
488
+ server.registerTool("unlink_vertical_offer", {
489
+ title: "Unlink one Offer from one Vertical",
490
+ description: "Remove one exact tenant-scoped Vertical-Offer relation with both current timestamps. " +
491
+ "The server refuses removal while a Lead Group depends on that pair and returns changed:false when already absent.",
492
+ inputSchema: verticalOfferMutationInput,
493
+ }, async ({ organization_id, vertical_id, offer_id, expected_vertical_updated_at, expected_offer_updated_at }) => {
494
+ try {
495
+ return toolResult(await client.delete(`${entityPath("verticals", vertical_id, organization_id, `/offers/${encodeURIComponent(offer_id)}`)}`, {
496
+ expectedVerticalUpdatedAt: expected_vertical_updated_at,
497
+ expectedOfferUpdatedAt: expected_offer_updated_at,
498
+ }));
499
+ }
500
+ catch (error) {
501
+ return handleToolError(error);
502
+ }
503
+ });
504
+ server.registerTool("preview_resolved_context", {
505
+ title: "Preview the exact resolved context",
506
+ description: "Resolve Company Brain, one ACTIVE Vertical, one linked ACTIVE Offer, one ACTIVE Persona, and Lead Group sender/CTA when the scope provides them. " +
507
+ "Use explicit for a free writer, lead_group for qualification, or campaign for inherited campaign context. " +
508
+ "The tool is read-only and returns a deterministic digest with dryRun:true, persisted:false, noSend:true, " +
509
+ "runtimeApplied:false, and externalActivation:0. No organization or resource is inferred.",
510
+ inputSchema: {
511
+ organization_id: z.string().trim().min(1),
512
+ scope: previewScopeSchema,
513
+ vertical_id: z.string().trim().min(1).optional(),
514
+ offer_id: z.string().trim().min(1).optional(),
515
+ persona_id: z.string().trim().min(1).optional(),
516
+ lead_group_id: z.string().trim().min(1).optional(),
517
+ campaign_id: z.string().trim().min(1).optional(),
518
+ },
519
+ annotations: { readOnlyHint: true },
520
+ }, async ({ organization_id, ...input }) => {
521
+ try {
522
+ return toolResult(await client.post(`/api/context-entities/preview?${new URLSearchParams({ organizationId: organization_id }).toString()}`, previewBody(input)));
523
+ }
524
+ catch (error) {
525
+ return handleToolError(error);
526
+ }
527
+ });
528
+ }