@wabery/cli 0.12.0 → 0.14.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.
@@ -1,4 +1,4 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { McpServer, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { WaberyApiClient, WaberyApiError } from "./api-client.js";
4
4
  import { createExampleConfig } from "./config-example.js";
@@ -16,6 +16,11 @@ const readinessRequirementSchema = z.enum([
16
16
  ]);
17
17
  const optInSchema = z
18
18
  .object({
19
+ categories: z
20
+ .array(z.enum(["GENERAL", "MARKETING", "UTILITY", "AUTHENTICATION"]))
21
+ .min(1)
22
+ .max(4)
23
+ .optional(),
19
24
  source: z.string().min(1).max(120),
20
25
  method: z.string().min(1).max(80).optional(),
21
26
  occurred_at: z.string().datetime().optional(),
@@ -23,9 +28,67 @@ const optInSchema = z
23
28
  evidence: z.record(z.string(), z.unknown()).optional(),
24
29
  })
25
30
  .optional();
26
- const SERVER_INSTRUCTIONS = `Use Wabery tools as a developer platform for WhatsApp, Instagram, and Messenger messaging: channels, routing, contacts, registration intents, conversations, message history, outbound messages, inbound media metadata, WhatsApp Flows, templates, automations, webhooks, and Meta Business Agent extensions.
31
+ const broadcastStatusSchema = z.enum([
32
+ "draft",
33
+ "preparing",
34
+ "ready",
35
+ "scheduled",
36
+ "sending",
37
+ "completed",
38
+ "canceled",
39
+ "failed",
40
+ ]);
41
+ const broadcastRecipientStatusSchema = z.enum([
42
+ "pending",
43
+ "queued",
44
+ "sending",
45
+ "sent",
46
+ "delivered",
47
+ "read",
48
+ "failed",
49
+ "skipped",
50
+ "canceled",
51
+ ]);
52
+ const broadcastParameterMappingSchema = z.object({
53
+ component: z.enum(["header", "body", "button"]),
54
+ index: z.number().int().min(0),
55
+ button_index: z.number().int().min(0).optional(),
56
+ parameter_type: z
57
+ .enum(["text", "image", "document", "video"])
58
+ .default("text"),
59
+ source: z.enum([
60
+ "name",
61
+ "phone",
62
+ "preferred_language",
63
+ "metadata",
64
+ "constant",
65
+ ]),
66
+ metadata_key: z.string().min(1).max(120).optional(),
67
+ value: z.string().max(2000).optional(),
68
+ fallback: z.string().max(2000).optional(),
69
+ });
70
+ const broadcastVariantSchema = z.object({
71
+ template_id: z.string().min(1),
72
+ language: z.string().min(2).max(35),
73
+ parameter_mappings: z.array(broadcastParameterMappingSchema).max(100),
74
+ });
75
+ const broadcastAudienceFilterSchema = z.object({
76
+ search: z.string().max(120).optional(),
77
+ tags: z.array(z.string().min(1).max(60)).max(50).optional(),
78
+ languages: z.array(z.string().min(2).max(35)).max(50).optional(),
79
+ is_blocked: z.boolean().optional(),
80
+ created_after: z.string().datetime().optional(),
81
+ created_before: z.string().datetime().optional(),
82
+ });
83
+ const toolOutputSchema = {
84
+ result: z
85
+ .record(z.string(), z.unknown())
86
+ .describe("The structured Wabery API result object. Its operation-specific fields are described by the tool and Wabery API documentation."),
87
+ };
88
+ const SERVER_INSTRUCTIONS = `Use Wabery tools as a developer platform for WhatsApp, Instagram, and Messenger messaging: channels, routing, contacts, registration intents, conversations, message history, outbound messages, inbound media metadata, WhatsApp Flows, templates, broadcasts, automations, webhooks, and Meta Business Agent extensions.
27
89
  Recommended order: read wabery_get_config_schema, select the project, call wabery_get_project_readiness, list channels only when more channel detail is needed, check Business Agent eligibility before adding business_agent config, create registration intents or enroll WhatsApp contacts as needed, store returned contact_id plus channel_id, validate and preview config diff, apply config only after review, publish flows only after explicit confirmation, then send messages or published flows with channel_id and contact_id/conversation_id.
28
90
  Always distinguish sandbox testing from production readiness: a project without its own connected phone number can often still be tested in the shared Wabery sandbox, but production inbound traffic requires a dedicated project channel. For WhatsApp templates and proactive reminders, inspect readiness before creation: template submission and proactive template sends require a dedicated non-sandbox WhatsApp channel, Meta business verification/account approval, a connected phone, and a Meta payment method. After sending a message, use wabery_get_message to inspect provider status and failures.
91
+ For broadcasts, use only approved WhatsApp templates on a dedicated channel and only contacts with valid category-specific consent. Create a draft, prepare exactly one audience source, poll wabery_get_broadcast until it is ready, inspect its counts and excluded recipients, and send or schedule only after explicit user confirmation. Preparation freezes the recipient snapshot; create a new draft when the intended audience changes. CSV files are uploaded through the dashboard or CLI; use wabery_list_contact_imports and wabery_get_contact_import to select a completed import.
29
92
  Use wabery_list_conversations, wabery_get_conversation, and wabery_list_conversation_messages to inspect history. Inbound WhatsApp media appears on message records/webhooks as media/assets metadata with Wabery signed URLs when retained; download files promptly because URLs are short-lived and retention is plan-based.
30
93
  Config apply creates or updates Wabery flows, automations, webhook settings, and eligible Meta Business Agent declarations. It never publishes flows to Meta. Automations and Business Agent connector tools can trigger flows through references to flow config keys.
31
94
  High-risk tools require confirmation_token values shown in each tool description.`;
@@ -40,10 +103,57 @@ const WRITE_NOTE = {
40
103
  function serverInstructions(writePolicy) {
41
104
  return `${SERVER_INSTRUCTIONS}\n${WRITE_NOTE[writePolicy]}`;
42
105
  }
106
+ const MCP_SENSITIVE_OUTPUT_KEY_SUFFIXES = [
107
+ "accesstoken",
108
+ "apikey",
109
+ "authorizationheader",
110
+ "authtoken",
111
+ "bearertoken",
112
+ "credential",
113
+ "credentials",
114
+ "idtoken",
115
+ "jwt",
116
+ "password",
117
+ "passwordhash",
118
+ "privatekey",
119
+ "providertoken",
120
+ "refreshtoken",
121
+ "secret",
122
+ "serviceproof",
123
+ "sessiontoken",
124
+ "signingkey",
125
+ "stacktrace",
126
+ ];
127
+ const MCP_INTERNAL_OUTPUT_KEYS = new Set([
128
+ "debugid",
129
+ "internaldebugid",
130
+ "requestid",
131
+ "traceid",
132
+ ]);
133
+ function isSensitiveMcpOutputKey(key) {
134
+ const normalizedKey = key.replaceAll(/[^a-zA-Z0-9]/g, "").toLowerCase();
135
+ return (MCP_INTERNAL_OUTPUT_KEYS.has(normalizedKey) ||
136
+ MCP_SENSITIVE_OUTPUT_KEY_SUFFIXES.some((suffix) => normalizedKey.endsWith(suffix)));
137
+ }
138
+ function sanitizeMcpOutput(data) {
139
+ if (Array.isArray(data)) {
140
+ return data.map((item) => sanitizeMcpOutput(item));
141
+ }
142
+ if (!data || typeof data !== "object") {
143
+ return data;
144
+ }
145
+ return Object.fromEntries(Object.entries(data).flatMap(([key, value]) => isSensitiveMcpOutputKey(key) ? [] : [[key, sanitizeMcpOutput(value)]]));
146
+ }
43
147
  function jsonResult(data) {
148
+ const sanitizedData = sanitizeMcpOutput(data);
44
149
  return {
45
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
46
- structuredContent: { result: data },
150
+ content: [
151
+ {
152
+ type: "text",
153
+ text: JSON.stringify(sanitizedData, null, 2),
154
+ },
155
+ ],
156
+ structuredContent: { result: sanitizedData },
47
157
  };
48
158
  }
49
159
  function errorResult(data) {
@@ -89,11 +199,85 @@ function checkWriteMode(action, token, expectedToken, writePolicy = "env") {
89
199
  }
90
200
  export function createWaberyMcpServer(client = new WaberyApiClient(), options = {}) {
91
201
  const writePolicy = options.writePolicy ?? "env";
92
- const requireWrite = (action, token, expectedToken) => checkWriteMode(action, token, expectedToken, writePolicy);
93
202
  const server = new McpServer({
94
203
  name: "wabery",
95
204
  version: options.serverVersion ?? "0.1.0",
96
205
  }, { instructions: serverInstructions(writePolicy) });
206
+ const registerTool = (name, config, callback) => server.registerTool(name, {
207
+ outputSchema: toolOutputSchema,
208
+ ...config,
209
+ }, callback);
210
+ const requireWrite = async (action, token, expectedToken) => {
211
+ const modeBlocked = checkWriteMode(action, undefined, undefined, writePolicy);
212
+ if (modeBlocked)
213
+ return modeBlocked;
214
+ if (!expectedToken)
215
+ return null;
216
+ if (server.server.getClientCapabilities()?.elicitation) {
217
+ const response = await server.server.elicitInput({
218
+ message: `Approve the high-risk Wabery action: ${action}`,
219
+ requestedSchema: {
220
+ type: "object",
221
+ properties: {
222
+ confirm: {
223
+ type: "boolean",
224
+ title: "Confirm action",
225
+ description: "Approve this external or destructive action",
226
+ default: false,
227
+ },
228
+ },
229
+ required: ["confirm"],
230
+ },
231
+ });
232
+ if (response.action === "accept" && response.content?.confirm === true) {
233
+ return null;
234
+ }
235
+ return errorResult({
236
+ action,
237
+ message: "The user declined or canceled the action.",
238
+ });
239
+ }
240
+ if (writePolicy === "enabled") {
241
+ return errorResult({
242
+ action,
243
+ message: "This hosted MCP client must support elicitation so Wabery can obtain real user approval.",
244
+ });
245
+ }
246
+ return checkWriteMode(action, token, expectedToken, writePolicy);
247
+ };
248
+ const requireBroadcastApproval = async (action, message, token, localConfirmationToken) => {
249
+ if (server.server.getClientCapabilities()?.elicitation) {
250
+ const response = await server.server.elicitInput({
251
+ message,
252
+ requestedSchema: {
253
+ type: "object",
254
+ properties: {
255
+ confirm: {
256
+ type: "boolean",
257
+ title: "Confirm broadcast action",
258
+ description: "Approve this external messaging action",
259
+ default: false,
260
+ },
261
+ },
262
+ required: ["confirm"],
263
+ },
264
+ });
265
+ if (response.action === "accept" && response.content?.confirm === true) {
266
+ return null;
267
+ }
268
+ return errorResult({
269
+ action,
270
+ message: "The user declined or canceled the broadcast action.",
271
+ });
272
+ }
273
+ if (writePolicy === "enabled") {
274
+ return errorResult({
275
+ action,
276
+ message: "This hosted MCP client must support elicitation so Wabery can obtain real user approval.",
277
+ });
278
+ }
279
+ return await requireWrite(action, token, localConfirmationToken);
280
+ };
97
281
  const resolveProjectId = async (projectId) => {
98
282
  if (projectId)
99
283
  return projectId;
@@ -164,7 +348,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
164
348
  },
165
349
  ],
166
350
  }));
167
- server.registerTool("wabery_check_connection", {
351
+ registerTool("wabery_check_connection", {
168
352
  title: "Check Wabery connection",
169
353
  description: "Verify the configured API key can reach Wabery projects, channels, and the config schema before making changes.",
170
354
  inputSchema: {},
@@ -175,7 +359,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
175
359
  openWorldHint: false,
176
360
  },
177
361
  }, async () => jsonResult(await checkWaberyConnection(client)));
178
- server.registerTool("wabery_get_config_example", {
362
+ registerTool("wabery_get_config_example", {
179
363
  title: "Get example Wabery config",
180
364
  description: "Return a starter wabery.config.json with one flow and one automation that triggers it with a send_flow step.",
181
365
  inputSchema: {
@@ -192,7 +376,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
192
376
  openWorldHint: false,
193
377
  },
194
378
  }, async ({ project_id }) => jsonResult(createExampleConfig(project_id)));
195
- server.registerTool("wabery_get_config_schema", {
379
+ registerTool("wabery_get_config_schema", {
196
380
  title: "Get Wabery config schema",
197
381
  description: "Return the JSON Schema for wabery.config.json. Use this before generating flows, automations, webhooks, or business_agent declarations.",
198
382
  inputSchema: {},
@@ -203,7 +387,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
203
387
  openWorldHint: false,
204
388
  },
205
389
  }, async () => jsonResult(await client.get("/config/schema")));
206
- server.registerTool("wabery_export_config", {
390
+ registerTool("wabery_export_config", {
207
391
  title: "Export Wabery config",
208
392
  description: "Export the organization's flows, automations, webhook settings, and Business Agent declarations as a config object.",
209
393
  inputSchema: {},
@@ -214,7 +398,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
214
398
  openWorldHint: false,
215
399
  },
216
400
  }, async () => jsonResult(await client.get("/config")));
217
- server.registerTool("wabery_validate_config", {
401
+ registerTool("wabery_validate_config", {
218
402
  title: "Validate Wabery config",
219
403
  description: "Validate a declarative config object without changing flows or automations.",
220
404
  inputSchema: {
@@ -227,7 +411,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
227
411
  openWorldHint: false,
228
412
  },
229
413
  }, async ({ config }) => apiResult(client.post("/config/validate", config)));
230
- server.registerTool("wabery_preview_config_diff", {
414
+ registerTool("wabery_preview_config_diff", {
231
415
  title: "Preview Wabery config diff",
232
416
  description: "Preview which flows, automations, webhook settings, Business Agents, connectors, and tools would be created, updated, deleted, or left unchanged.",
233
417
  inputSchema: {
@@ -240,7 +424,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
240
424
  openWorldHint: false,
241
425
  },
242
426
  }, async ({ config }) => apiResult(client.post("/config/diff", config)));
243
- server.registerTool("wabery_apply_config", {
427
+ registerTool("wabery_apply_config", {
244
428
  title: "Apply Wabery config",
245
429
  description: "Create, update, or remove Wabery flows, automations, webhook settings, and eligible Business Agent declarations from config. This does not publish flows to Meta; call wabery_publish_flow explicitly after review.",
246
430
  inputSchema: {
@@ -258,7 +442,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
258
442
  },
259
443
  }, async (input) => {
260
444
  const { config, confirmation_token } = input;
261
- const blocked = requireWrite("wabery_apply_config", confirmation_token, "apply_config");
445
+ const blocked = await requireWrite("wabery_apply_config", confirmation_token, "apply_config");
262
446
  if (blocked)
263
447
  return blocked;
264
448
  try {
@@ -282,7 +466,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
282
466
  throw error;
283
467
  }
284
468
  });
285
- server.registerTool("wabery_list_business_agents", {
469
+ registerTool("wabery_list_business_agents", {
286
470
  title: "List Meta Business Agents",
287
471
  description: "List Meta Business Agent records for the selected Wabery project.",
288
472
  inputSchema: {
@@ -297,7 +481,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
297
481
  }, async ({ channel_id }) => jsonResult(await client.get("/business-agents", {
298
482
  channel_id,
299
483
  })));
300
- server.registerTool("wabery_check_business_agent_eligibility", {
484
+ registerTool("wabery_check_business_agent_eligibility", {
301
485
  title: "Check Meta Business Agent eligibility",
302
486
  description: "Check whether a WhatsApp channel is eligible for Meta Business Agent.",
303
487
  inputSchema: {
@@ -312,7 +496,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
312
496
  }, async ({ channel_id }) => jsonResult(await client.get("/business-agents/eligibility", {
313
497
  channel_id,
314
498
  })));
315
- server.registerTool("wabery_apply_business_agent", {
499
+ registerTool("wabery_apply_business_agent", {
316
500
  title: "Apply Meta Business Agent settings",
317
501
  description: "Provision or update a Meta Business Agent for a WhatsApp channel. Prefer wabery_apply_config when managing the whole project declaratively.",
318
502
  inputSchema: {
@@ -335,12 +519,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
335
519
  },
336
520
  }, async (input) => {
337
521
  const { confirmation_token, ...body } = input;
338
- const blocked = requireWrite("wabery_apply_business_agent", confirmation_token, "apply_business_agent");
522
+ const blocked = await requireWrite("wabery_apply_business_agent", confirmation_token, "apply_business_agent");
339
523
  if (blocked)
340
524
  return blocked;
341
525
  return jsonResult(await client.put("/business-agents", body));
342
526
  });
343
- server.registerTool("wabery_get_business_agent_settings", {
527
+ registerTool("wabery_get_business_agent_settings", {
344
528
  title: "Get Meta Business Agent settings",
345
529
  description: "Retrieve live Meta Business Agent settings.",
346
530
  inputSchema: {
@@ -353,7 +537,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
353
537
  openWorldHint: true,
354
538
  },
355
539
  }, async ({ business_agent_id }) => jsonResult(await client.get(`/business-agents/${encodeURIComponent(business_agent_id)}/settings`)));
356
- server.registerTool("wabery_update_business_agent_settings", {
540
+ registerTool("wabery_update_business_agent_settings", {
357
541
  title: "Update Meta Business Agent settings",
358
542
  description: "Update live Meta Business Agent settings.",
359
543
  inputSchema: {
@@ -376,12 +560,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
376
560
  },
377
561
  }, async (input) => {
378
562
  const { business_agent_id, confirmation_token, ...body } = input;
379
- const blocked = requireWrite("wabery_update_business_agent_settings", confirmation_token, "update_business_agent_settings");
563
+ const blocked = await requireWrite("wabery_update_business_agent_settings", confirmation_token, "update_business_agent_settings");
380
564
  if (blocked)
381
565
  return blocked;
382
566
  return jsonResult(await client.put(`/business-agents/${encodeURIComponent(business_agent_id)}/settings`, body));
383
567
  });
384
- server.registerTool("wabery_list_business_agent_connectors", {
568
+ registerTool("wabery_list_business_agent_connectors", {
385
569
  title: "List Meta Business Agent connectors",
386
570
  description: "List connector records for Meta Business Agents in the selected Wabery project.",
387
571
  inputSchema: {
@@ -396,7 +580,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
396
580
  }, async ({ business_agent_id }) => jsonResult(await client.get("/business-agents/connectors", {
397
581
  business_agent_id,
398
582
  })));
399
- server.registerTool("wabery_create_business_agent_connector", {
583
+ registerTool("wabery_create_business_agent_connector", {
400
584
  title: "Create Meta Business Agent connector",
401
585
  description: "Create a connector on a provisioned Meta Business Agent. Prefer wabery_apply_config for declarative project-wide changes.",
402
586
  inputSchema: {
@@ -418,12 +602,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
418
602
  },
419
603
  }, async (input) => {
420
604
  const { confirmation_token, ...body } = input;
421
- const blocked = requireWrite("wabery_create_business_agent_connector", confirmation_token, "create_business_agent_connector");
605
+ const blocked = await requireWrite("wabery_create_business_agent_connector", confirmation_token, "create_business_agent_connector");
422
606
  if (blocked)
423
607
  return blocked;
424
608
  return jsonResult(await client.post("/business-agents/connectors", body));
425
609
  });
426
- server.registerTool("wabery_create_business_agent_connector_tool", {
610
+ registerTool("wabery_create_business_agent_connector_tool", {
427
611
  title: "Create Meta Business Agent connector tool",
428
612
  description: "Create a connector tool backed by a Wabery Flow, hosted function, API endpoint, or no-op placeholder.",
429
613
  inputSchema: {
@@ -453,12 +637,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
453
637
  },
454
638
  }, async (input) => {
455
639
  const { connector_id, confirmation_token, ...body } = input;
456
- const blocked = requireWrite("wabery_create_business_agent_connector_tool", confirmation_token, "create_business_agent_connector_tool");
640
+ const blocked = await requireWrite("wabery_create_business_agent_connector_tool", confirmation_token, "create_business_agent_connector_tool");
457
641
  if (blocked)
458
642
  return blocked;
459
643
  return jsonResult(await client.post(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools`, body));
460
644
  });
461
- server.registerTool("wabery_list_business_agent_connector_tools", {
645
+ registerTool("wabery_list_business_agent_connector_tools", {
462
646
  title: "List Meta Business Agent connector tools",
463
647
  description: "List tools for a Meta Business Agent connector.",
464
648
  inputSchema: {
@@ -471,7 +655,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
471
655
  openWorldHint: false,
472
656
  },
473
657
  }, async ({ connector_id }) => jsonResult(await client.get(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools`)));
474
- server.registerTool("wabery_get_business_agent_connector_tool", {
658
+ registerTool("wabery_get_business_agent_connector_tool", {
475
659
  title: "Get Meta Business Agent connector tool",
476
660
  description: "Fetch one Meta Business Agent connector tool.",
477
661
  inputSchema: {
@@ -485,7 +669,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
485
669
  openWorldHint: false,
486
670
  },
487
671
  }, async ({ connector_id, tool_id }) => jsonResult(await client.get(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools/${encodeURIComponent(tool_id)}`)));
488
- server.registerTool("wabery_update_business_agent_connector_tool", {
672
+ registerTool("wabery_update_business_agent_connector_tool", {
489
673
  title: "Update Meta Business Agent connector tool",
490
674
  description: "Update one Meta Business Agent connector tool.",
491
675
  inputSchema: {
@@ -513,12 +697,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
513
697
  },
514
698
  }, async (input) => {
515
699
  const { connector_id, tool_id, confirmation_token, ...body } = input;
516
- const blocked = requireWrite("wabery_update_business_agent_connector_tool", confirmation_token, "update_business_agent_connector_tool");
700
+ const blocked = await requireWrite("wabery_update_business_agent_connector_tool", confirmation_token, "update_business_agent_connector_tool");
517
701
  if (blocked)
518
702
  return blocked;
519
703
  return jsonResult(await client.patch(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools/${encodeURIComponent(tool_id)}`, body));
520
704
  });
521
- server.registerTool("wabery_delete_business_agent_connector_tool", {
705
+ registerTool("wabery_delete_business_agent_connector_tool", {
522
706
  title: "Delete Meta Business Agent connector tool",
523
707
  description: "Delete one Meta Business Agent connector tool.",
524
708
  inputSchema: {
@@ -536,16 +720,35 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
536
720
  openWorldHint: false,
537
721
  },
538
722
  }, async ({ connector_id, tool_id, confirmation_token }) => {
539
- const blocked = requireWrite("wabery_delete_business_agent_connector_tool", confirmation_token, "delete_business_agent_connector_tool");
723
+ const blocked = await requireWrite("wabery_delete_business_agent_connector_tool", confirmation_token, "delete_business_agent_connector_tool");
540
724
  if (blocked)
541
725
  return blocked;
542
726
  return jsonResult(await client.delete(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools/${encodeURIComponent(tool_id)}`));
543
727
  });
544
- server.registerTool("wabery_business_agent_thread_control", {
728
+ registerTool("wabery_get_business_agent_thread_control", {
729
+ title: "Get Meta Business Agent thread control",
730
+ description: "Inspect WhatsApp thread control for a channel/contact pair without changing its owner.",
731
+ inputSchema: {
732
+ channel_id: z.string().min(1),
733
+ contact_id: z.string().min(1),
734
+ business_agent_id: z.string().min(1).optional(),
735
+ thread_id: z.string().min(1).optional(),
736
+ },
737
+ annotations: {
738
+ readOnlyHint: true,
739
+ destructiveHint: false,
740
+ idempotentHint: true,
741
+ openWorldHint: true,
742
+ },
743
+ }, async (input) => jsonResult(await client.post("/business-agents/thread-control", {
744
+ ...input,
745
+ action: "get",
746
+ })));
747
+ registerTool("wabery_set_business_agent_thread_control", {
545
748
  title: "Set Meta Business Agent thread control",
546
- description: "Take, pass, or inspect WhatsApp thread control for a channel/contact pair.",
749
+ description: "Take or pass WhatsApp thread control for a channel/contact pair. Use the separate get tool to inspect current ownership.",
547
750
  inputSchema: {
548
- action: z.enum(["take", "pass", "get"]),
751
+ action: z.enum(["take", "pass"]),
549
752
  channel_id: z.string().min(1),
550
753
  contact_id: z.string().min(1),
551
754
  business_agent_id: z.string().min(1).optional(),
@@ -561,16 +764,16 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
561
764
  readOnlyHint: false,
562
765
  destructiveHint: false,
563
766
  idempotentHint: true,
564
- openWorldHint: false,
767
+ openWorldHint: true,
565
768
  },
566
769
  }, async (input) => {
567
770
  const { confirmation_token, ...body } = input;
568
- const blocked = requireWrite("wabery_business_agent_thread_control", confirmation_token, "thread_control");
771
+ const blocked = await requireWrite("wabery_set_business_agent_thread_control", confirmation_token, "thread_control");
569
772
  if (blocked)
570
773
  return blocked;
571
774
  return jsonResult(await client.post("/business-agents/thread-control", body));
572
775
  });
573
- server.registerTool("wabery_test_business_agent", {
776
+ registerTool("wabery_test_business_agent", {
574
777
  title: "Test Meta Business Agent",
575
778
  description: "Run a Meta Business Agent test scenario.",
576
779
  inputSchema: {
@@ -585,7 +788,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
585
788
  openWorldHint: true,
586
789
  },
587
790
  }, async (body) => jsonResult(await client.post("/business-agents/test", body)));
588
- server.registerTool("wabery_get_business_agent_eval", {
791
+ registerTool("wabery_get_business_agent_eval", {
589
792
  title: "Get Meta Business Agent eval summary",
590
793
  description: "Retrieve the Meta Business Agent eval summary.",
591
794
  inputSchema: {
@@ -600,7 +803,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
600
803
  }, async ({ business_agent_id }) => jsonResult(await client.get("/business-agents/eval", {
601
804
  business_agent_id,
602
805
  })));
603
- server.registerTool("wabery_list_business_agent_skills", {
806
+ registerTool("wabery_list_business_agent_skills", {
604
807
  title: "List Meta Business Agent skills",
605
808
  description: "List configured Meta Business Agent skills.",
606
809
  inputSchema: {
@@ -615,7 +818,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
615
818
  }, async ({ business_agent_id }) => jsonResult(await client.get("/business-agents/skills", {
616
819
  business_agent_id,
617
820
  })));
618
- server.registerTool("wabery_set_business_agent_skills", {
821
+ registerTool("wabery_set_business_agent_skills", {
619
822
  title: "Set Meta Business Agent skills",
620
823
  description: "Replace configured Meta Business Agent skills.",
621
824
  inputSchema: {
@@ -628,17 +831,17 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
628
831
  },
629
832
  annotations: {
630
833
  readOnlyHint: false,
631
- destructiveHint: false,
834
+ destructiveHint: true,
632
835
  idempotentHint: true,
633
836
  openWorldHint: true,
634
837
  },
635
838
  }, async ({ confirmation_token, ...body }) => {
636
- const blocked = requireWrite("wabery_set_business_agent_skills", confirmation_token, "set_business_agent_skills");
839
+ const blocked = await requireWrite("wabery_set_business_agent_skills", confirmation_token, "set_business_agent_skills");
637
840
  if (blocked)
638
841
  return blocked;
639
842
  return jsonResult(await client.put("/business-agents/skills", body));
640
843
  });
641
- server.registerTool("wabery_list_projects", {
844
+ registerTool("wabery_list_projects", {
642
845
  title: "List Wabery projects",
643
846
  description: "List projects and their project_id values. Use project_id when creating flows or enrolling WhatsApp contacts.",
644
847
  inputSchema: {},
@@ -650,7 +853,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
650
853
  },
651
854
  }, async () => jsonResult(await client.get("/projects")));
652
855
  if (options.projectSelection) {
653
- server.registerTool("wabery_get_selected_project", {
856
+ registerTool("wabery_get_selected_project", {
654
857
  title: "Get selected Wabery project",
655
858
  description: "Return the project currently selected for this MCP session. If no project has been selected, Wabery uses the credential's default project.",
656
859
  inputSchema: {},
@@ -665,14 +868,14 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
665
868
  selected_project_id: options.projectSelection?.get() ?? null,
666
869
  default_project_id: client.projectId ?? null,
667
870
  }));
668
- server.registerTool("wabery_select_project", {
871
+ registerTool("wabery_select_project", {
669
872
  title: "Select Wabery project",
670
873
  description: "Select the project that subsequent Wabery MCP tools should use in this session. Call wabery_list_projects first, then pass the project_id.",
671
874
  inputSchema: {
672
875
  project_id: z.string().min(1),
673
876
  },
674
877
  annotations: {
675
- readOnlyHint: true,
878
+ readOnlyHint: false,
676
879
  destructiveHint: false,
677
880
  idempotentHint: true,
678
881
  openWorldHint: false,
@@ -687,7 +890,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
687
890
  });
688
891
  });
689
892
  }
690
- server.registerTool("wabery_get_project_readiness", {
893
+ registerTool("wabery_get_project_readiness", {
691
894
  title: "Get Wabery project readiness",
692
895
  description: "Explain what the selected project can do now: sandbox testing, production inbound, flow publish/send, templates, proactive messages, routing, opt-in, and submission handling. Call before building, after wabery_apply_config, before template/reminder work, and before the final creator summary.",
693
896
  inputSchema: {
@@ -701,9 +904,9 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
701
904
  openWorldHint: false,
702
905
  },
703
906
  }, async ({ project_id, requirements }) => jsonResult(await getReadiness(project_id, requirements)));
704
- server.registerTool("wabery_get_project", {
907
+ registerTool("wabery_get_project", {
705
908
  title: "Get a Wabery project",
706
- description: "Fetch a single project, including its webhook_secret (revealed only here, never in wabery_list_projects).",
909
+ description: "Fetch a single project's user-safe configuration. Secrets and provider credentials are omitted from MCP results.",
707
910
  inputSchema: { project_id: z.string().min(1) },
708
911
  annotations: {
709
912
  readOnlyHint: true,
@@ -712,7 +915,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
712
915
  openWorldHint: false,
713
916
  },
714
917
  }, async ({ project_id }) => jsonResult(await client.get(`/projects/${encodeURIComponent(project_id)}`)));
715
- server.registerTool("wabery_update_project", {
918
+ registerTool("wabery_update_project", {
716
919
  title: "Update a Wabery project",
717
920
  description: "Update name, description, routing_mode, require_opt_in, or webhook config (url, signing on/off, or a bring-your-own secret) of an existing project. Set require_opt_in=false on a dedicated production number so inbound senders can be replied to and messaged without the invite/opt-in step.",
718
921
  inputSchema: {
@@ -732,14 +935,14 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
732
935
  openWorldHint: false,
733
936
  },
734
937
  }, async ({ project_id, ...body }) => {
735
- const blocked = requireWrite("wabery_update_project");
938
+ const blocked = await requireWrite("wabery_update_project");
736
939
  if (blocked)
737
940
  return blocked;
738
941
  return jsonResult(await client.patch(`/projects/${encodeURIComponent(project_id)}`, body));
739
942
  });
740
- server.registerTool("wabery_rotate_webhook_secret", {
943
+ registerTool("wabery_rotate_webhook_secret", {
741
944
  title: "Rotate a project's webhook secret",
742
- description: "Set a new signing secret (server-generated, or a provided one) and return it once. Invalidates the previous secret.",
945
+ description: "Set a new signing secret (server-generated, or a provided one) and invalidate the previous secret. MCP confirms the rotation but omits the secret value; retrieve or manage sensitive credentials in the Wabery dashboard.",
743
946
  inputSchema: {
744
947
  project_id: z.string().min(1),
745
948
  webhook_secret: z.string().min(16).max(256).optional(),
@@ -751,12 +954,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
751
954
  openWorldHint: false,
752
955
  },
753
956
  }, async ({ project_id, webhook_secret }) => {
754
- const blocked = requireWrite("wabery_rotate_webhook_secret");
957
+ const blocked = await requireWrite("wabery_rotate_webhook_secret");
755
958
  if (blocked)
756
959
  return blocked;
757
960
  return jsonResult(await client.post(`/projects/${encodeURIComponent(project_id)}/rotate-webhook-secret`, webhook_secret ? { webhook_secret } : undefined));
758
961
  });
759
- server.registerTool("wabery_list_channels", {
962
+ registerTool("wabery_list_channels", {
760
963
  title: "List Wabery channels",
761
964
  description: "List connected messaging channels. Use a WhatsApp channel_id when sending a published flow. For WhatsApp templates or proactive template sends, inspect publish_readiness and tell the creator when a dedicated phone number, verified business, approved account, connected phone, or Meta payment method is missing.",
762
965
  inputSchema: {},
@@ -767,7 +970,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
767
970
  openWorldHint: false,
768
971
  },
769
972
  }, async () => jsonResult(await client.get("/channels")));
770
- server.registerTool("wabery_get_channel", {
973
+ registerTool("wabery_get_channel", {
771
974
  title: "Get a Wabery channel",
772
975
  description: "Fetch a single channel, including routing_warning and WhatsApp publish_readiness. If can_submit_templates is false, explain the blockers before attempting template creation or proactive template sends.",
773
976
  inputSchema: { channel_id: z.string().min(1) },
@@ -778,7 +981,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
778
981
  openWorldHint: false,
779
982
  },
780
983
  }, async ({ channel_id }) => jsonResult(await client.get(`/channels/${encodeURIComponent(channel_id)}`)));
781
- server.registerTool("wabery_update_channel", {
984
+ registerTool("wabery_update_channel", {
782
985
  title: "Update a Wabery channel's routing",
783
986
  description: "Set a dedicated channel's routing_mode (NONE | FLOWS | EXTERNAL). Use EXTERNAL so inbound to this channel is forwarded to the project webhook. The shared sandbox channel routes per-participant and cannot be updated this way.",
784
987
  inputSchema: {
@@ -792,14 +995,14 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
792
995
  openWorldHint: false,
793
996
  },
794
997
  }, async ({ channel_id, routing_mode }) => {
795
- const blocked = requireWrite("wabery_update_channel");
998
+ const blocked = await requireWrite("wabery_update_channel");
796
999
  if (blocked)
797
1000
  return blocked;
798
1001
  return jsonResult(await client.patch(`/channels/${encodeURIComponent(channel_id)}`, {
799
1002
  routing_mode,
800
1003
  }));
801
1004
  });
802
- server.registerTool("wabery_create_registration_intent", {
1005
+ registerTool("wabery_create_registration_intent", {
803
1006
  title: "Create registration intent",
804
1007
  description: "Mint a single-use wa.me registration link so an end-customer can connect over WhatsApp.",
805
1008
  inputSchema: {
@@ -821,7 +1024,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
821
1024
  openWorldHint: false,
822
1025
  },
823
1026
  }, async ({ project_id, customer_reference, metadata }) => {
824
- const blocked = requireWrite("wabery_create_registration_intent");
1027
+ const blocked = await requireWrite("wabery_create_registration_intent");
825
1028
  if (blocked)
826
1029
  return blocked;
827
1030
  return jsonResult(await client.post("/registration-intents", {
@@ -830,7 +1033,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
830
1033
  metadata,
831
1034
  }));
832
1035
  });
833
- server.registerTool("wabery_get_registration_intent", {
1036
+ registerTool("wabery_get_registration_intent", {
834
1037
  title: "Get registration intent",
835
1038
  description: "Poll a registration intent's status. Works with a publishable key or a scoped secret key.",
836
1039
  inputSchema: {
@@ -843,7 +1046,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
843
1046
  openWorldHint: false,
844
1047
  },
845
1048
  }, async ({ intent_id }) => jsonResult(await client.get(`/registration-intents/${encodeURIComponent(intent_id)}`)));
846
- server.registerTool("wabery_get_limits", {
1049
+ registerTool("wabery_get_limits", {
847
1050
  title: "Get Wabery API limits",
848
1051
  description: "Return the enforced public API rate limit and monthly request quota for the configured API key.",
849
1052
  inputSchema: {},
@@ -854,7 +1057,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
854
1057
  openWorldHint: false,
855
1058
  },
856
1059
  }, async () => jsonResult(await client.get("/limits")));
857
- server.registerTool("wabery_list_flows", {
1060
+ registerTool("wabery_list_flows", {
858
1061
  title: "List Wabery flows",
859
1062
  description: "List flows, including ids, config keys, project ids, and Meta publish status.",
860
1063
  inputSchema: {},
@@ -865,7 +1068,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
865
1068
  openWorldHint: false,
866
1069
  },
867
1070
  }, async () => jsonResult(await client.get("/flows")));
868
- server.registerTool("wabery_get_flow", {
1071
+ registerTool("wabery_get_flow", {
869
1072
  title: "Get Wabery flow",
870
1073
  description: "Fetch one flow by id, including status and field schema.",
871
1074
  inputSchema: {
@@ -878,7 +1081,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
878
1081
  openWorldHint: false,
879
1082
  },
880
1083
  }, async ({ flow_id }) => jsonResult(await client.get(`/flows/${flow_id}`)));
881
- server.registerTool("wabery_publish_flow", {
1084
+ registerTool("wabery_publish_flow", {
882
1085
  title: "Publish Wabery flow",
883
1086
  description: "Publish a Wabery draft flow to Meta. Only call after validate/diff/apply and explicit user confirmation.",
884
1087
  inputSchema: {
@@ -890,17 +1093,17 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
890
1093
  },
891
1094
  annotations: {
892
1095
  readOnlyHint: false,
893
- destructiveHint: false,
1096
+ destructiveHint: true,
894
1097
  idempotentHint: false,
895
1098
  openWorldHint: true,
896
1099
  },
897
1100
  }, async ({ flow_id, confirmation_token }) => {
898
- const blocked = requireWrite("wabery_publish_flow", confirmation_token, `publish:${flow_id}`);
1101
+ const blocked = await requireWrite("wabery_publish_flow", confirmation_token, `publish:${flow_id}`);
899
1102
  if (blocked)
900
1103
  return blocked;
901
1104
  return apiResult(client.post(`/flows/${flow_id}/publish`));
902
1105
  });
903
- server.registerTool("wabery_enroll_contact", {
1106
+ registerTool("wabery_enroll_contact", {
904
1107
  title: "Enroll WhatsApp contact",
905
1108
  description: "Register a phone number against a Wabery project. Returns contact_id, which is the stable id used in flow submissions and future sends.",
906
1109
  inputSchema: {
@@ -919,7 +1122,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
919
1122
  .optional()
920
1123
  .describe("Your own user/customer id for correlation."),
921
1124
  metadata: metadataSchema,
922
- opt_in: optInSchema.describe("WhatsApp opt-in evidence. Include source, method, consent_text, and evidence where available."),
1125
+ opt_in: optInSchema.describe("WhatsApp opt-in evidence. For broadcasts include the applicable MARKETING or UTILITY category plus source, method, consent_text, and evidence."),
923
1126
  },
924
1127
  annotations: {
925
1128
  readOnlyHint: false,
@@ -928,12 +1131,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
928
1131
  openWorldHint: false,
929
1132
  },
930
1133
  }, async (input) => {
931
- const blocked = requireWrite("wabery_enroll_contact");
1134
+ const blocked = await requireWrite("wabery_enroll_contact");
932
1135
  if (blocked)
933
1136
  return blocked;
934
1137
  return jsonResult(await client.post("/contacts", input));
935
1138
  });
936
- server.registerTool("wabery_list_contacts", {
1139
+ registerTool("wabery_list_contacts", {
937
1140
  title: "List Wabery contacts",
938
1141
  description: "List enrolled contacts and their stable contact_id values.",
939
1142
  inputSchema: {
@@ -947,7 +1150,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
947
1150
  openWorldHint: false,
948
1151
  },
949
1152
  }, async ({ limit, starting_after }) => jsonResult(await client.get("/contacts", { limit, starting_after })));
950
- server.registerTool("wabery_get_contact", {
1153
+ registerTool("wabery_get_contact", {
951
1154
  title: "Get Wabery contact",
952
1155
  description: "Fetch one enrolled contact by stable contact_id before sending or unenrolling.",
953
1156
  inputSchema: {
@@ -960,7 +1163,34 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
960
1163
  openWorldHint: false,
961
1164
  },
962
1165
  }, async ({ contact_id }) => jsonResult(await client.get(`/contacts/${contact_id}`)));
963
- server.registerTool("wabery_unenroll_contact", {
1166
+ registerTool("wabery_list_contact_imports", {
1167
+ title: "List Wabery contact imports",
1168
+ description: "List CSV contact imports and their processing counts. Upload files in the dashboard or CLI, then use a completed import_id as a broadcast audience source.",
1169
+ inputSchema: {
1170
+ limit: z.number().int().min(1).max(100).optional(),
1171
+ starting_after: z.string().optional(),
1172
+ },
1173
+ annotations: {
1174
+ readOnlyHint: true,
1175
+ destructiveHint: false,
1176
+ idempotentHint: true,
1177
+ openWorldHint: false,
1178
+ },
1179
+ }, async (query) => apiResult(client.get("/contact-imports", query)));
1180
+ registerTool("wabery_get_contact_import", {
1181
+ title: "Get Wabery contact import",
1182
+ description: "Inspect one CSV contact import's status and row counts. Wait for completed before using its import_id to prepare a broadcast.",
1183
+ inputSchema: {
1184
+ import_id: z.string().min(1),
1185
+ },
1186
+ annotations: {
1187
+ readOnlyHint: true,
1188
+ destructiveHint: false,
1189
+ idempotentHint: true,
1190
+ openWorldHint: false,
1191
+ },
1192
+ }, async ({ import_id }) => apiResult(client.get(`/contact-imports/${import_id}`)));
1193
+ registerTool("wabery_unenroll_contact", {
964
1194
  title: "Unenroll Wabery contact",
965
1195
  description: "Remove a contact phone's sandbox authorization for this organization. Pass erase=true for irreversible org-scoped contact data deletion.",
966
1196
  inputSchema: {
@@ -978,12 +1208,234 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
978
1208
  openWorldHint: false,
979
1209
  },
980
1210
  }, async ({ contact_id, erase, confirmation_token }) => {
981
- const blocked = requireWrite("wabery_unenroll_contact", confirmation_token, erase ? `erase:${contact_id}` : undefined);
1211
+ const blocked = await requireWrite("wabery_unenroll_contact", confirmation_token, erase ? `erase:${contact_id}` : undefined);
982
1212
  if (blocked)
983
1213
  return blocked;
984
1214
  return jsonResult(await client.delete(`/contacts/${contact_id}${erase ? "?erase=true" : ""}`));
985
1215
  });
986
- server.registerTool("wabery_list_conversations", {
1216
+ registerTool("wabery_list_broadcasts", {
1217
+ title: "List Wabery broadcasts",
1218
+ description: "List broadcast campaigns and delivery counts. Filter by lifecycle status when monitoring preparation or delivery.",
1219
+ inputSchema: {
1220
+ limit: z.number().int().min(1).max(100).optional(),
1221
+ starting_after: z.string().optional(),
1222
+ status: broadcastStatusSchema.optional(),
1223
+ },
1224
+ annotations: {
1225
+ readOnlyHint: true,
1226
+ destructiveHint: false,
1227
+ idempotentHint: true,
1228
+ openWorldHint: false,
1229
+ },
1230
+ }, async (query) => apiResult(client.get("/broadcasts", query)));
1231
+ registerTool("wabery_get_broadcast", {
1232
+ title: "Get Wabery broadcast",
1233
+ description: "Fetch one broadcast with its lifecycle status and recipient counts. Poll while status is preparing, and review counts before requesting send or schedule confirmation.",
1234
+ inputSchema: {
1235
+ broadcast_id: z.string().min(1),
1236
+ },
1237
+ annotations: {
1238
+ readOnlyHint: true,
1239
+ destructiveHint: false,
1240
+ idempotentHint: true,
1241
+ openWorldHint: false,
1242
+ },
1243
+ }, async ({ broadcast_id }) => apiResult(client.get(`/broadcasts/${broadcast_id}`)));
1244
+ registerTool("wabery_create_broadcast", {
1245
+ title: "Create Wabery broadcast draft",
1246
+ description: "Create a draft from approved WhatsApp template variants. This does not select recipients or send messages. Use a dedicated WhatsApp channel and category-specific consent.",
1247
+ inputSchema: {
1248
+ name: z.string().min(1).max(120),
1249
+ channel_id: z.string().min(1),
1250
+ category: z.enum(["MARKETING", "UTILITY"]),
1251
+ template_name: z.string().min(1).max(512),
1252
+ fallback_language: z.string().min(2).max(35),
1253
+ variants: z.array(broadcastVariantSchema).min(1).max(50),
1254
+ },
1255
+ annotations: {
1256
+ readOnlyHint: false,
1257
+ destructiveHint: false,
1258
+ idempotentHint: false,
1259
+ openWorldHint: false,
1260
+ },
1261
+ }, async ({ name, channel_id, category, template_name, fallback_language, variants, }) => {
1262
+ const blocked = await requireWrite("wabery_create_broadcast");
1263
+ if (blocked)
1264
+ return blocked;
1265
+ return apiResult(client.post("/broadcasts", {
1266
+ name,
1267
+ channelId: channel_id,
1268
+ category,
1269
+ templateName: template_name,
1270
+ fallbackLanguage: fallback_language,
1271
+ variants: variants.map((variant) => ({
1272
+ templateId: variant.template_id,
1273
+ language: variant.language,
1274
+ parameterMappings: variant.parameter_mappings.map((mapping) => ({
1275
+ component: mapping.component,
1276
+ index: mapping.index,
1277
+ buttonIndex: mapping.button_index,
1278
+ parameterType: mapping.parameter_type,
1279
+ source: mapping.source,
1280
+ metadataKey: mapping.metadata_key,
1281
+ value: mapping.value,
1282
+ fallback: mapping.fallback,
1283
+ })),
1284
+ })),
1285
+ }));
1286
+ });
1287
+ registerTool("wabery_prepare_broadcast_audience", {
1288
+ title: "Prepare Wabery broadcast audience",
1289
+ description: "Resolve and freeze a draft broadcast's recipients from exactly one source: contact_ids, import_id, or filter. Preparation is asynchronous and does not send messages. Poll wabery_get_broadcast until ready, then inspect counts and recipients.",
1290
+ inputSchema: {
1291
+ broadcast_id: z.string().min(1),
1292
+ contact_ids: z.array(z.string().min(1)).max(1000).optional(),
1293
+ import_id: z.string().min(1).optional(),
1294
+ filter: broadcastAudienceFilterSchema.optional(),
1295
+ exclude_contact_ids: z.array(z.string().min(1)).max(1000).optional(),
1296
+ },
1297
+ annotations: {
1298
+ readOnlyHint: false,
1299
+ destructiveHint: false,
1300
+ idempotentHint: true,
1301
+ openWorldHint: false,
1302
+ },
1303
+ }, async ({ broadcast_id, contact_ids, import_id, filter, exclude_contact_ids, }) => {
1304
+ const blocked = await requireWrite("wabery_prepare_broadcast_audience");
1305
+ if (blocked)
1306
+ return blocked;
1307
+ const sources = [contact_ids, import_id, filter].filter((value) => value !== undefined);
1308
+ if (sources.length !== 1) {
1309
+ return {
1310
+ ...jsonResult({
1311
+ error: "invalid_audience_source",
1312
+ message: "Provide exactly one audience source: contact_ids, import_id, or filter.",
1313
+ }),
1314
+ isError: true,
1315
+ };
1316
+ }
1317
+ return apiResult(client.post(`/broadcasts/${broadcast_id}/audience`, {
1318
+ contactIds: contact_ids,
1319
+ importId: import_id,
1320
+ filter: filter
1321
+ ? {
1322
+ search: filter.search,
1323
+ tags: filter.tags,
1324
+ languages: filter.languages,
1325
+ isBlocked: filter.is_blocked,
1326
+ createdAfter: filter.created_after,
1327
+ createdBefore: filter.created_before,
1328
+ }
1329
+ : undefined,
1330
+ excludeContactIds: exclude_contact_ids ?? [],
1331
+ }));
1332
+ });
1333
+ registerTool("wabery_list_broadcast_recipients", {
1334
+ title: "List Wabery broadcast recipients",
1335
+ description: "Inspect the prepared recipient snapshot, language selection, skips, failures, and delivery state. Use before confirmation and while monitoring delivery.",
1336
+ inputSchema: {
1337
+ broadcast_id: z.string().min(1),
1338
+ limit: z.number().int().min(1).max(100).optional(),
1339
+ starting_after: z.string().optional(),
1340
+ status: broadcastRecipientStatusSchema.optional(),
1341
+ },
1342
+ annotations: {
1343
+ readOnlyHint: true,
1344
+ destructiveHint: false,
1345
+ idempotentHint: true,
1346
+ openWorldHint: false,
1347
+ },
1348
+ }, async ({ broadcast_id, ...query }) => apiResult(client.get(`/broadcasts/${broadcast_id}/recipients`, query)));
1349
+ registerTool("wabery_send_broadcast", {
1350
+ title: "Send Wabery broadcast",
1351
+ description: "Start sending a ready broadcast to its frozen recipient snapshot. This creates real external WhatsApp messages and requires explicit user confirmation with confirmation_token send_broadcast:<broadcast_id>.",
1352
+ inputSchema: {
1353
+ broadcast_id: z.string().min(1),
1354
+ confirmation_token: z
1355
+ .string()
1356
+ .optional()
1357
+ .describe("Required: send_broadcast:<broadcast_id>. Obtain explicit user confirmation after reviewing counts."),
1358
+ },
1359
+ annotations: {
1360
+ readOnlyHint: false,
1361
+ destructiveHint: true,
1362
+ idempotentHint: true,
1363
+ openWorldHint: true,
1364
+ },
1365
+ }, async ({ broadcast_id, confirmation_token }) => {
1366
+ const blocked = await requireBroadcastApproval("wabery_send_broadcast", `Send broadcast ${broadcast_id} to its prepared WhatsApp recipients now?`, confirmation_token, `send_broadcast:${broadcast_id}`);
1367
+ if (blocked)
1368
+ return blocked;
1369
+ return apiResult(client.post(`/broadcasts/${broadcast_id}/send`));
1370
+ });
1371
+ registerTool("wabery_schedule_broadcast", {
1372
+ title: "Schedule Wabery broadcast",
1373
+ description: "Schedule a ready broadcast for future delivery to its frozen recipient snapshot. Requires explicit user confirmation with confirmation_token schedule_broadcast:<broadcast_id>.",
1374
+ inputSchema: {
1375
+ broadcast_id: z.string().min(1),
1376
+ scheduled_at: z.string().datetime(),
1377
+ timezone: z.string().min(1).max(80),
1378
+ confirmation_token: z
1379
+ .string()
1380
+ .optional()
1381
+ .describe("Required: schedule_broadcast:<broadcast_id>. Obtain explicit user confirmation after reviewing counts and time."),
1382
+ },
1383
+ annotations: {
1384
+ readOnlyHint: false,
1385
+ destructiveHint: true,
1386
+ idempotentHint: true,
1387
+ openWorldHint: true,
1388
+ },
1389
+ }, async ({ broadcast_id, scheduled_at, timezone, confirmation_token }) => {
1390
+ const blocked = await requireBroadcastApproval("wabery_schedule_broadcast", `Schedule broadcast ${broadcast_id} for ${scheduled_at} (${timezone})?`, confirmation_token, `schedule_broadcast:${broadcast_id}`);
1391
+ if (blocked)
1392
+ return blocked;
1393
+ return apiResult(client.post(`/broadcasts/${broadcast_id}/schedule`, {
1394
+ scheduledAt: scheduled_at,
1395
+ timezone,
1396
+ }));
1397
+ });
1398
+ registerTool("wabery_cancel_broadcast", {
1399
+ title: "Cancel Wabery broadcast",
1400
+ description: "Cancel a draft, ready, scheduled, or sending broadcast and stop recipients that have not been dispatched. Already accepted messages cannot be recalled. Requires confirmation_token cancel_broadcast:<broadcast_id>.",
1401
+ inputSchema: {
1402
+ broadcast_id: z.string().min(1),
1403
+ confirmation_token: z
1404
+ .string()
1405
+ .optional()
1406
+ .describe("Required: cancel_broadcast:<broadcast_id>."),
1407
+ },
1408
+ annotations: {
1409
+ readOnlyHint: false,
1410
+ destructiveHint: true,
1411
+ idempotentHint: true,
1412
+ openWorldHint: false,
1413
+ },
1414
+ }, async ({ broadcast_id, confirmation_token }) => {
1415
+ const blocked = await requireBroadcastApproval("wabery_cancel_broadcast", `Cancel broadcast ${broadcast_id}? Messages already accepted by WhatsApp cannot be recalled.`, confirmation_token, `cancel_broadcast:${broadcast_id}`);
1416
+ if (blocked)
1417
+ return blocked;
1418
+ return apiResult(client.post(`/broadcasts/${broadcast_id}/cancel`));
1419
+ });
1420
+ registerTool("wabery_duplicate_broadcast", {
1421
+ title: "Duplicate Wabery broadcast",
1422
+ description: "Create a new draft with the source broadcast's channel, template variants, and parameter mappings. The recipient snapshot is not copied; prepare a new audience before sending.",
1423
+ inputSchema: {
1424
+ broadcast_id: z.string().min(1),
1425
+ },
1426
+ annotations: {
1427
+ readOnlyHint: false,
1428
+ destructiveHint: false,
1429
+ idempotentHint: false,
1430
+ openWorldHint: false,
1431
+ },
1432
+ }, async ({ broadcast_id }) => {
1433
+ const blocked = await requireWrite("wabery_duplicate_broadcast");
1434
+ if (blocked)
1435
+ return blocked;
1436
+ return apiResult(client.post(`/broadcasts/${broadcast_id}/duplicate`));
1437
+ });
1438
+ registerTool("wabery_list_conversations", {
987
1439
  title: "List Wabery conversations",
988
1440
  description: "List organization-scoped conversations for inbox, support, or analytics tooling.",
989
1441
  inputSchema: {
@@ -1002,7 +1454,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1002
1454
  ...query,
1003
1455
  active: query.active === undefined ? undefined : String(query.active),
1004
1456
  })));
1005
- server.registerTool("wabery_get_conversation", {
1457
+ registerTool("wabery_get_conversation", {
1006
1458
  title: "Get Wabery conversation",
1007
1459
  description: "Fetch one organization-scoped conversation by id.",
1008
1460
  inputSchema: {
@@ -1015,7 +1467,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1015
1467
  openWorldHint: false,
1016
1468
  },
1017
1469
  }, async ({ conversation_id }) => jsonResult(await client.get(`/conversations/${conversation_id}`)));
1018
- server.registerTool("wabery_list_conversation_messages", {
1470
+ registerTool("wabery_list_conversation_messages", {
1019
1471
  title: "List Wabery conversation messages",
1020
1472
  description: "Fetch message history for a conversation. Cached inbound WhatsApp media includes short-lived Wabery signed URLs; expires_at is the retention deadline.",
1021
1473
  inputSchema: {
@@ -1031,7 +1483,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1031
1483
  openWorldHint: false,
1032
1484
  },
1033
1485
  }, async ({ conversation_id, ...query }) => jsonResult(await client.get(`/conversations/${conversation_id}/messages`, query)));
1034
- server.registerTool("wabery_send_message", {
1486
+ registerTool("wabery_send_message", {
1035
1487
  title: "Send Wabery message",
1036
1488
  description: "Send a WhatsApp text, approved template, media, or interactive message. The body must match POST /messages and should include an idempotency_key for retries.",
1037
1489
  inputSchema: {
@@ -1039,17 +1491,17 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1039
1491
  },
1040
1492
  annotations: {
1041
1493
  readOnlyHint: false,
1042
- destructiveHint: false,
1494
+ destructiveHint: true,
1043
1495
  idempotentHint: false,
1044
1496
  openWorldHint: true,
1045
1497
  },
1046
1498
  }, async ({ body }) => {
1047
- const blocked = requireWrite("wabery_send_message");
1499
+ const blocked = await requireWrite("wabery_send_message");
1048
1500
  if (blocked)
1049
1501
  return blocked;
1050
1502
  return apiResult(client.post("/messages", body));
1051
1503
  });
1052
- server.registerTool("wabery_get_message", {
1504
+ registerTool("wabery_get_message", {
1053
1505
  title: "Get Wabery message",
1054
1506
  description: "Fetch one message by message_id to inspect queued/provider status, delivery state, and Meta failure details after wabery_send_message.",
1055
1507
  inputSchema: {
@@ -1062,7 +1514,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1062
1514
  openWorldHint: false,
1063
1515
  },
1064
1516
  }, async ({ message_id }) => jsonResult(await client.get(`/messages/${message_id}`)));
1065
- server.registerTool("wabery_list_templates", {
1517
+ registerTool("wabery_list_templates", {
1066
1518
  title: "List WhatsApp templates",
1067
1519
  description: "List WhatsApp message templates and approval statuses for dedicated WhatsApp channels.",
1068
1520
  inputSchema: {
@@ -1076,7 +1528,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1076
1528
  openWorldHint: false,
1077
1529
  },
1078
1530
  }, async (query) => jsonResult(await client.get("/templates", query)));
1079
- server.registerTool("wabery_get_template", {
1531
+ registerTool("wabery_get_template", {
1080
1532
  title: "Get WhatsApp template",
1081
1533
  description: "Fetch one WhatsApp template and its Meta approval status. Set refresh=true to force a live Meta status check.",
1082
1534
  inputSchema: {
@@ -1092,7 +1544,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1092
1544
  }, async ({ template_id, refresh }) => jsonResult(await client.get(`/templates/${template_id}`, {
1093
1545
  refresh: refresh ? "true" : undefined,
1094
1546
  })));
1095
- server.registerTool("wabery_create_template", {
1547
+ registerTool("wabery_create_template", {
1096
1548
  title: "Create WhatsApp template",
1097
1549
  description: "Create and submit a WhatsApp message template to Meta for approval. Only dedicated, non-sandbox WhatsApp channels with Meta-verified business accounts and a Meta payment method can submit templates. Check channel publish_readiness first.",
1098
1550
  inputSchema: {
@@ -1105,12 +1557,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1105
1557
  openWorldHint: true,
1106
1558
  },
1107
1559
  }, async ({ body }) => {
1108
- const blocked = requireWrite("wabery_create_template");
1560
+ const blocked = await requireWrite("wabery_create_template");
1109
1561
  if (blocked)
1110
1562
  return blocked;
1111
1563
  return apiResult(client.post("/templates", body));
1112
1564
  });
1113
- server.registerTool("wabery_wait_template", {
1565
+ registerTool("wabery_wait_template", {
1114
1566
  title: "Wait for WhatsApp template approval",
1115
1567
  description: "Poll a WhatsApp template until Meta reports APPROVED. Fails if Meta rejects/disables it or if timeout_seconds elapses.",
1116
1568
  inputSchema: {
@@ -1128,7 +1580,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1128
1580
  timeoutMs: (timeout_seconds ?? 86_400) * 1000,
1129
1581
  intervalMs: (interval_seconds ?? 60) * 1000,
1130
1582
  })));
1131
- server.registerTool("wabery_send_flow", {
1583
+ registerTool("wabery_send_flow", {
1132
1584
  title: "Send Wabery flow",
1133
1585
  description: "Send a published WhatsApp Flow to an enrolled contact, existing conversation, or phone number. Returns flow_token and dispatch info.",
1134
1586
  inputSchema: {
@@ -1146,17 +1598,17 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1146
1598
  },
1147
1599
  annotations: {
1148
1600
  readOnlyHint: false,
1149
- destructiveHint: false,
1601
+ destructiveHint: true,
1150
1602
  idempotentHint: false,
1151
1603
  openWorldHint: true,
1152
1604
  },
1153
1605
  }, async ({ flow_id, ...body }) => {
1154
- const blocked = requireWrite("wabery_send_flow");
1606
+ const blocked = await requireWrite("wabery_send_flow");
1155
1607
  if (blocked)
1156
1608
  return blocked;
1157
1609
  return apiResult(client.post(`/flows/${flow_id}/send`, body));
1158
1610
  });
1159
- server.registerTool("wabery_list_submissions", {
1611
+ registerTool("wabery_list_submissions", {
1160
1612
  title: "List Wabery submissions",
1161
1613
  description: "List completed WhatsApp Flow submissions for the organization.",
1162
1614
  inputSchema: {
@@ -1172,7 +1624,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1172
1624
  openWorldHint: false,
1173
1625
  },
1174
1626
  }, async (query) => jsonResult(await client.get("/submissions", query)));
1175
- server.registerTool("wabery_list_dispatches", {
1627
+ registerTool("wabery_list_dispatches", {
1176
1628
  title: "List Wabery dispatches",
1177
1629
  description: "List sent flow dispatches. Use this to inspect flow_token status across sends.",
1178
1630
  inputSchema: {
@@ -1189,7 +1641,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1189
1641
  openWorldHint: false,
1190
1642
  },
1191
1643
  }, async (query) => jsonResult(await client.get("/dispatches", query)));
1192
- server.registerTool("wabery_get_dispatch", {
1644
+ registerTool("wabery_get_dispatch", {
1193
1645
  title: "Get Wabery dispatch",
1194
1646
  description: "Get one flow dispatch by flow_token.",
1195
1647
  inputSchema: {
@@ -1203,7 +1655,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1203
1655
  },
1204
1656
  }, async ({ flow_token }) => jsonResult(await client.get(`/dispatches/${flow_token}`)));
1205
1657
  // ─── Hosted functions (serverless logic the agent authors) ───────────
1206
- server.registerTool("wabery_list_functions", {
1658
+ registerTool("wabery_list_functions", {
1207
1659
  title: "List hosted functions",
1208
1660
  description: "List the project's hosted functions (serverless handlers that run on send/tool triggers).",
1209
1661
  inputSchema: {},
@@ -1214,7 +1666,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1214
1666
  openWorldHint: false,
1215
1667
  },
1216
1668
  }, async () => jsonResult(await client.get("/functions")));
1217
- server.registerTool("wabery_get_function", {
1669
+ registerTool("wabery_get_function", {
1218
1670
  title: "Get a hosted function",
1219
1671
  description: "Fetch one hosted function including its current source.",
1220
1672
  inputSchema: { function_id: z.string().min(1) },
@@ -1225,7 +1677,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1225
1677
  openWorldHint: false,
1226
1678
  },
1227
1679
  }, async ({ function_id }) => jsonResult(await client.get(`/functions/${function_id}`)));
1228
- server.registerTool("wabery_create_function", {
1680
+ registerTool("wabery_create_function", {
1229
1681
  title: "Create a hosted function",
1230
1682
  description: "Create a hosted function (metadata only). Deploy source separately with wabery_deploy_function. trigger_type is an AutomationTriggerType, e.g. KEYWORD, WELCOME, ANY_MESSAGE.",
1231
1683
  inputSchema: {
@@ -1249,12 +1701,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1249
1701
  },
1250
1702
  }, async (input) => {
1251
1703
  const { confirmation_token, ...body } = input;
1252
- const blocked = requireWrite("wabery_create_function", confirmation_token, "create_function");
1704
+ const blocked = await requireWrite("wabery_create_function", confirmation_token, "create_function");
1253
1705
  if (blocked)
1254
1706
  return blocked;
1255
1707
  return jsonResult(await client.post("/functions", body));
1256
1708
  });
1257
- server.registerTool("wabery_deploy_function", {
1709
+ registerTool("wabery_deploy_function", {
1258
1710
  title: "Deploy a hosted function",
1259
1711
  description: "Bundle and upload TypeScript source for a hosted function. Returns { status, version_id?, diagnostics? } — read diagnostics on error, fix, and redeploy.",
1260
1712
  inputSchema: {
@@ -1272,12 +1724,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1272
1724
  openWorldHint: false,
1273
1725
  },
1274
1726
  }, async ({ function_id, source, confirmation_token }) => {
1275
- const blocked = requireWrite("wabery_deploy_function", confirmation_token, "deploy_function");
1727
+ const blocked = await requireWrite("wabery_deploy_function", confirmation_token, "deploy_function");
1276
1728
  if (blocked)
1277
1729
  return blocked;
1278
1730
  return jsonResult(await client.post(`/functions/${function_id}/deploy`, { source }));
1279
1731
  });
1280
- server.registerTool("wabery_test_function", {
1732
+ registerTool("wabery_test_function", {
1281
1733
  title: "Test a hosted function",
1282
1734
  description: "Invoke a hosted function with a sample message event (no real send) and return its result plus logs.",
1283
1735
  inputSchema: {
@@ -1295,12 +1747,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1295
1747
  openWorldHint: false,
1296
1748
  },
1297
1749
  }, async ({ function_id, text, confirmation_token }) => {
1298
- const blocked = requireWrite("wabery_test_function", confirmation_token, "test_function");
1750
+ const blocked = await requireWrite("wabery_test_function", confirmation_token, "test_function");
1299
1751
  if (blocked)
1300
1752
  return blocked;
1301
1753
  return jsonResult(await client.post(`/functions/${function_id}/test`, { text }));
1302
1754
  });
1303
- server.registerTool("wabery_tail_function_logs", {
1755
+ registerTool("wabery_tail_function_logs", {
1304
1756
  title: "Tail hosted function logs",
1305
1757
  description: "Recent invocations of a hosted function (status, cpu, error, captured logs) for debugging.",
1306
1758
  inputSchema: { function_id: z.string().min(1) },
@@ -1311,7 +1763,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1311
1763
  openWorldHint: false,
1312
1764
  },
1313
1765
  }, async ({ function_id }) => jsonResult(await client.get(`/functions/${function_id}/logs`)));
1314
- server.registerTool("wabery_delete_function", {
1766
+ registerTool("wabery_delete_function", {
1315
1767
  title: "Delete a hosted function",
1316
1768
  description: "Delete a hosted function and its deployed Worker.",
1317
1769
  inputSchema: {
@@ -1328,12 +1780,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1328
1780
  openWorldHint: false,
1329
1781
  },
1330
1782
  }, async ({ function_id, confirmation_token }) => {
1331
- const blocked = requireWrite("wabery_delete_function", confirmation_token, "delete_function");
1783
+ const blocked = await requireWrite("wabery_delete_function", confirmation_token, "delete_function");
1332
1784
  if (blocked)
1333
1785
  return blocked;
1334
1786
  return jsonResult(await client.delete(`/functions/${function_id}`));
1335
1787
  });
1336
- server.registerTool("wabery_update_function", {
1788
+ registerTool("wabery_update_function", {
1337
1789
  title: "Update a hosted function",
1338
1790
  description: "Update a hosted function: rename, set expose_as_mcp_tool (make it callable by external agents), toggle is_active, or set input_schema (the tool schema used for agent/MCP calls).",
1339
1791
  inputSchema: {
@@ -1355,12 +1807,12 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1355
1807
  },
1356
1808
  }, async (input) => {
1357
1809
  const { function_id, confirmation_token, ...body } = input;
1358
- const blocked = requireWrite("wabery_update_function", confirmation_token, "update_function");
1810
+ const blocked = await requireWrite("wabery_update_function", confirmation_token, "update_function");
1359
1811
  if (blocked)
1360
1812
  return blocked;
1361
1813
  return jsonResult(await client.patch(`/functions/${function_id}`, body));
1362
1814
  });
1363
- server.registerTool("wabery_invoke_function", {
1815
+ registerTool("wabery_invoke_function", {
1364
1816
  title: "Invoke a hosted function as a tool",
1365
1817
  description: "Call a hosted function as a tool (mcp_call) with structured arguments and return its result. Optionally target a specific contact.",
1366
1818
  inputSchema: {
@@ -1379,7 +1831,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1379
1831
  openWorldHint: true,
1380
1832
  },
1381
1833
  }, async ({ function_id, arguments: args, contact_id, confirmation_token, }) => {
1382
- const blocked = requireWrite("wabery_invoke_function", confirmation_token, "invoke_function");
1834
+ const blocked = await requireWrite("wabery_invoke_function", confirmation_token, "invoke_function");
1383
1835
  if (blocked)
1384
1836
  return blocked;
1385
1837
  return jsonResult(await client.post(`/functions/${function_id}/invoke`, {