@wabery/cli 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -9
- package/dist/api-client.d.ts +3 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +8 -0
- package/dist/api-client.js.map +1 -1
- package/dist/cli.js +26 -4
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +405 -28
- package/dist/mcp-server.js.map +1 -1
- package/package.json +2 -2
package/dist/mcp-server.js
CHANGED
|
@@ -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,62 @@ const optInSchema = z
|
|
|
23
28
|
evidence: z.record(z.string(), z.unknown()).optional(),
|
|
24
29
|
})
|
|
25
30
|
.optional();
|
|
26
|
-
const
|
|
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 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
84
|
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
85
|
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.
|
|
86
|
+
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
87
|
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
88
|
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
89
|
High-risk tools require confirmation_token values shown in each tool description.`;
|
|
@@ -89,11 +147,81 @@ function checkWriteMode(action, token, expectedToken, writePolicy = "env") {
|
|
|
89
147
|
}
|
|
90
148
|
export function createWaberyMcpServer(client = new WaberyApiClient(), options = {}) {
|
|
91
149
|
const writePolicy = options.writePolicy ?? "env";
|
|
92
|
-
const requireWrite = (action, token, expectedToken) => checkWriteMode(action, token, expectedToken, writePolicy);
|
|
93
150
|
const server = new McpServer({
|
|
94
151
|
name: "wabery",
|
|
95
152
|
version: options.serverVersion ?? "0.1.0",
|
|
96
153
|
}, { instructions: serverInstructions(writePolicy) });
|
|
154
|
+
const requireWrite = async (action, token, expectedToken) => {
|
|
155
|
+
const modeBlocked = checkWriteMode(action, undefined, undefined, writePolicy);
|
|
156
|
+
if (modeBlocked)
|
|
157
|
+
return modeBlocked;
|
|
158
|
+
if (!expectedToken)
|
|
159
|
+
return null;
|
|
160
|
+
if (server.server.getClientCapabilities()?.elicitation) {
|
|
161
|
+
const response = await server.server.elicitInput({
|
|
162
|
+
message: `Approve the high-risk Wabery action: ${action}`,
|
|
163
|
+
requestedSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
confirm: {
|
|
167
|
+
type: "boolean",
|
|
168
|
+
title: "Confirm action",
|
|
169
|
+
description: "Approve this external or destructive action",
|
|
170
|
+
default: false,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
required: ["confirm"],
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
if (response.action === "accept" && response.content?.confirm === true) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return errorResult({
|
|
180
|
+
action,
|
|
181
|
+
message: "The user declined or canceled the action.",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (writePolicy === "enabled") {
|
|
185
|
+
return errorResult({
|
|
186
|
+
action,
|
|
187
|
+
message: "This hosted MCP client must support elicitation so Wabery can obtain real user approval.",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return checkWriteMode(action, token, expectedToken, writePolicy);
|
|
191
|
+
};
|
|
192
|
+
const requireBroadcastApproval = async (action, message, token, localConfirmationToken) => {
|
|
193
|
+
if (server.server.getClientCapabilities()?.elicitation) {
|
|
194
|
+
const response = await server.server.elicitInput({
|
|
195
|
+
message,
|
|
196
|
+
requestedSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
confirm: {
|
|
200
|
+
type: "boolean",
|
|
201
|
+
title: "Confirm broadcast action",
|
|
202
|
+
description: "Approve this external messaging action",
|
|
203
|
+
default: false,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
required: ["confirm"],
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
if (response.action === "accept" && response.content?.confirm === true) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return errorResult({
|
|
213
|
+
action,
|
|
214
|
+
message: "The user declined or canceled the broadcast action.",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (writePolicy === "enabled") {
|
|
218
|
+
return errorResult({
|
|
219
|
+
action,
|
|
220
|
+
message: "This hosted MCP client must support elicitation so Wabery can obtain real user approval.",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return await requireWrite(action, token, localConfirmationToken);
|
|
224
|
+
};
|
|
97
225
|
const resolveProjectId = async (projectId) => {
|
|
98
226
|
if (projectId)
|
|
99
227
|
return projectId;
|
|
@@ -258,7 +386,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
258
386
|
},
|
|
259
387
|
}, async (input) => {
|
|
260
388
|
const { config, confirmation_token } = input;
|
|
261
|
-
const blocked = requireWrite("wabery_apply_config", confirmation_token, "apply_config");
|
|
389
|
+
const blocked = await requireWrite("wabery_apply_config", confirmation_token, "apply_config");
|
|
262
390
|
if (blocked)
|
|
263
391
|
return blocked;
|
|
264
392
|
try {
|
|
@@ -335,7 +463,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
335
463
|
},
|
|
336
464
|
}, async (input) => {
|
|
337
465
|
const { confirmation_token, ...body } = input;
|
|
338
|
-
const blocked = requireWrite("wabery_apply_business_agent", confirmation_token, "apply_business_agent");
|
|
466
|
+
const blocked = await requireWrite("wabery_apply_business_agent", confirmation_token, "apply_business_agent");
|
|
339
467
|
if (blocked)
|
|
340
468
|
return blocked;
|
|
341
469
|
return jsonResult(await client.put("/business-agents", body));
|
|
@@ -376,7 +504,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
376
504
|
},
|
|
377
505
|
}, async (input) => {
|
|
378
506
|
const { business_agent_id, confirmation_token, ...body } = input;
|
|
379
|
-
const blocked = requireWrite("wabery_update_business_agent_settings", confirmation_token, "update_business_agent_settings");
|
|
507
|
+
const blocked = await requireWrite("wabery_update_business_agent_settings", confirmation_token, "update_business_agent_settings");
|
|
380
508
|
if (blocked)
|
|
381
509
|
return blocked;
|
|
382
510
|
return jsonResult(await client.put(`/business-agents/${encodeURIComponent(business_agent_id)}/settings`, body));
|
|
@@ -418,7 +546,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
418
546
|
},
|
|
419
547
|
}, async (input) => {
|
|
420
548
|
const { confirmation_token, ...body } = input;
|
|
421
|
-
const blocked = requireWrite("wabery_create_business_agent_connector", confirmation_token, "create_business_agent_connector");
|
|
549
|
+
const blocked = await requireWrite("wabery_create_business_agent_connector", confirmation_token, "create_business_agent_connector");
|
|
422
550
|
if (blocked)
|
|
423
551
|
return blocked;
|
|
424
552
|
return jsonResult(await client.post("/business-agents/connectors", body));
|
|
@@ -453,7 +581,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
453
581
|
},
|
|
454
582
|
}, async (input) => {
|
|
455
583
|
const { connector_id, confirmation_token, ...body } = input;
|
|
456
|
-
const blocked = requireWrite("wabery_create_business_agent_connector_tool", confirmation_token, "create_business_agent_connector_tool");
|
|
584
|
+
const blocked = await requireWrite("wabery_create_business_agent_connector_tool", confirmation_token, "create_business_agent_connector_tool");
|
|
457
585
|
if (blocked)
|
|
458
586
|
return blocked;
|
|
459
587
|
return jsonResult(await client.post(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools`, body));
|
|
@@ -513,7 +641,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
513
641
|
},
|
|
514
642
|
}, async (input) => {
|
|
515
643
|
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");
|
|
644
|
+
const blocked = await requireWrite("wabery_update_business_agent_connector_tool", confirmation_token, "update_business_agent_connector_tool");
|
|
517
645
|
if (blocked)
|
|
518
646
|
return blocked;
|
|
519
647
|
return jsonResult(await client.patch(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools/${encodeURIComponent(tool_id)}`, body));
|
|
@@ -536,7 +664,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
536
664
|
openWorldHint: false,
|
|
537
665
|
},
|
|
538
666
|
}, async ({ connector_id, tool_id, confirmation_token }) => {
|
|
539
|
-
const blocked = requireWrite("wabery_delete_business_agent_connector_tool", confirmation_token, "delete_business_agent_connector_tool");
|
|
667
|
+
const blocked = await requireWrite("wabery_delete_business_agent_connector_tool", confirmation_token, "delete_business_agent_connector_tool");
|
|
540
668
|
if (blocked)
|
|
541
669
|
return blocked;
|
|
542
670
|
return jsonResult(await client.delete(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools/${encodeURIComponent(tool_id)}`));
|
|
@@ -565,7 +693,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
565
693
|
},
|
|
566
694
|
}, async (input) => {
|
|
567
695
|
const { confirmation_token, ...body } = input;
|
|
568
|
-
const blocked = requireWrite("wabery_business_agent_thread_control", confirmation_token, "thread_control");
|
|
696
|
+
const blocked = await requireWrite("wabery_business_agent_thread_control", confirmation_token, "thread_control");
|
|
569
697
|
if (blocked)
|
|
570
698
|
return blocked;
|
|
571
699
|
return jsonResult(await client.post("/business-agents/thread-control", body));
|
|
@@ -633,7 +761,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
633
761
|
openWorldHint: true,
|
|
634
762
|
},
|
|
635
763
|
}, async ({ confirmation_token, ...body }) => {
|
|
636
|
-
const blocked = requireWrite("wabery_set_business_agent_skills", confirmation_token, "set_business_agent_skills");
|
|
764
|
+
const blocked = await requireWrite("wabery_set_business_agent_skills", confirmation_token, "set_business_agent_skills");
|
|
637
765
|
if (blocked)
|
|
638
766
|
return blocked;
|
|
639
767
|
return jsonResult(await client.put("/business-agents/skills", body));
|
|
@@ -732,7 +860,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
732
860
|
openWorldHint: false,
|
|
733
861
|
},
|
|
734
862
|
}, async ({ project_id, ...body }) => {
|
|
735
|
-
const blocked = requireWrite("wabery_update_project");
|
|
863
|
+
const blocked = await requireWrite("wabery_update_project");
|
|
736
864
|
if (blocked)
|
|
737
865
|
return blocked;
|
|
738
866
|
return jsonResult(await client.patch(`/projects/${encodeURIComponent(project_id)}`, body));
|
|
@@ -751,7 +879,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
751
879
|
openWorldHint: false,
|
|
752
880
|
},
|
|
753
881
|
}, async ({ project_id, webhook_secret }) => {
|
|
754
|
-
const blocked = requireWrite("wabery_rotate_webhook_secret");
|
|
882
|
+
const blocked = await requireWrite("wabery_rotate_webhook_secret");
|
|
755
883
|
if (blocked)
|
|
756
884
|
return blocked;
|
|
757
885
|
return jsonResult(await client.post(`/projects/${encodeURIComponent(project_id)}/rotate-webhook-secret`, webhook_secret ? { webhook_secret } : undefined));
|
|
@@ -792,7 +920,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
792
920
|
openWorldHint: false,
|
|
793
921
|
},
|
|
794
922
|
}, async ({ channel_id, routing_mode }) => {
|
|
795
|
-
const blocked = requireWrite("wabery_update_channel");
|
|
923
|
+
const blocked = await requireWrite("wabery_update_channel");
|
|
796
924
|
if (blocked)
|
|
797
925
|
return blocked;
|
|
798
926
|
return jsonResult(await client.patch(`/channels/${encodeURIComponent(channel_id)}`, {
|
|
@@ -821,7 +949,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
821
949
|
openWorldHint: false,
|
|
822
950
|
},
|
|
823
951
|
}, async ({ project_id, customer_reference, metadata }) => {
|
|
824
|
-
const blocked = requireWrite("wabery_create_registration_intent");
|
|
952
|
+
const blocked = await requireWrite("wabery_create_registration_intent");
|
|
825
953
|
if (blocked)
|
|
826
954
|
return blocked;
|
|
827
955
|
return jsonResult(await client.post("/registration-intents", {
|
|
@@ -895,7 +1023,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
895
1023
|
openWorldHint: true,
|
|
896
1024
|
},
|
|
897
1025
|
}, async ({ flow_id, confirmation_token }) => {
|
|
898
|
-
const blocked = requireWrite("wabery_publish_flow", confirmation_token, `publish:${flow_id}`);
|
|
1026
|
+
const blocked = await requireWrite("wabery_publish_flow", confirmation_token, `publish:${flow_id}`);
|
|
899
1027
|
if (blocked)
|
|
900
1028
|
return blocked;
|
|
901
1029
|
return apiResult(client.post(`/flows/${flow_id}/publish`));
|
|
@@ -919,7 +1047,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
919
1047
|
.optional()
|
|
920
1048
|
.describe("Your own user/customer id for correlation."),
|
|
921
1049
|
metadata: metadataSchema,
|
|
922
|
-
opt_in: optInSchema.describe("WhatsApp opt-in evidence.
|
|
1050
|
+
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
1051
|
},
|
|
924
1052
|
annotations: {
|
|
925
1053
|
readOnlyHint: false,
|
|
@@ -928,7 +1056,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
928
1056
|
openWorldHint: false,
|
|
929
1057
|
},
|
|
930
1058
|
}, async (input) => {
|
|
931
|
-
const blocked = requireWrite("wabery_enroll_contact");
|
|
1059
|
+
const blocked = await requireWrite("wabery_enroll_contact");
|
|
932
1060
|
if (blocked)
|
|
933
1061
|
return blocked;
|
|
934
1062
|
return jsonResult(await client.post("/contacts", input));
|
|
@@ -960,6 +1088,33 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
960
1088
|
openWorldHint: false,
|
|
961
1089
|
},
|
|
962
1090
|
}, async ({ contact_id }) => jsonResult(await client.get(`/contacts/${contact_id}`)));
|
|
1091
|
+
server.registerTool("wabery_list_contact_imports", {
|
|
1092
|
+
title: "List Wabery contact imports",
|
|
1093
|
+
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.",
|
|
1094
|
+
inputSchema: {
|
|
1095
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
1096
|
+
starting_after: z.string().optional(),
|
|
1097
|
+
},
|
|
1098
|
+
annotations: {
|
|
1099
|
+
readOnlyHint: true,
|
|
1100
|
+
destructiveHint: false,
|
|
1101
|
+
idempotentHint: true,
|
|
1102
|
+
openWorldHint: false,
|
|
1103
|
+
},
|
|
1104
|
+
}, async (query) => apiResult(client.get("/contact-imports", query)));
|
|
1105
|
+
server.registerTool("wabery_get_contact_import", {
|
|
1106
|
+
title: "Get Wabery contact import",
|
|
1107
|
+
description: "Inspect one CSV contact import's status and row counts. Wait for completed before using its import_id to prepare a broadcast.",
|
|
1108
|
+
inputSchema: {
|
|
1109
|
+
import_id: z.string().min(1),
|
|
1110
|
+
},
|
|
1111
|
+
annotations: {
|
|
1112
|
+
readOnlyHint: true,
|
|
1113
|
+
destructiveHint: false,
|
|
1114
|
+
idempotentHint: true,
|
|
1115
|
+
openWorldHint: false,
|
|
1116
|
+
},
|
|
1117
|
+
}, async ({ import_id }) => apiResult(client.get(`/contact-imports/${import_id}`)));
|
|
963
1118
|
server.registerTool("wabery_unenroll_contact", {
|
|
964
1119
|
title: "Unenroll Wabery contact",
|
|
965
1120
|
description: "Remove a contact phone's sandbox authorization for this organization. Pass erase=true for irreversible org-scoped contact data deletion.",
|
|
@@ -978,11 +1133,233 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
978
1133
|
openWorldHint: false,
|
|
979
1134
|
},
|
|
980
1135
|
}, async ({ contact_id, erase, confirmation_token }) => {
|
|
981
|
-
const blocked = requireWrite("wabery_unenroll_contact", confirmation_token, erase ? `erase:${contact_id}` : undefined);
|
|
1136
|
+
const blocked = await requireWrite("wabery_unenroll_contact", confirmation_token, erase ? `erase:${contact_id}` : undefined);
|
|
982
1137
|
if (blocked)
|
|
983
1138
|
return blocked;
|
|
984
1139
|
return jsonResult(await client.delete(`/contacts/${contact_id}${erase ? "?erase=true" : ""}`));
|
|
985
1140
|
});
|
|
1141
|
+
server.registerTool("wabery_list_broadcasts", {
|
|
1142
|
+
title: "List Wabery broadcasts",
|
|
1143
|
+
description: "List broadcast campaigns and delivery counts. Filter by lifecycle status when monitoring preparation or delivery.",
|
|
1144
|
+
inputSchema: {
|
|
1145
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
1146
|
+
starting_after: z.string().optional(),
|
|
1147
|
+
status: broadcastStatusSchema.optional(),
|
|
1148
|
+
},
|
|
1149
|
+
annotations: {
|
|
1150
|
+
readOnlyHint: true,
|
|
1151
|
+
destructiveHint: false,
|
|
1152
|
+
idempotentHint: true,
|
|
1153
|
+
openWorldHint: false,
|
|
1154
|
+
},
|
|
1155
|
+
}, async (query) => apiResult(client.get("/broadcasts", query)));
|
|
1156
|
+
server.registerTool("wabery_get_broadcast", {
|
|
1157
|
+
title: "Get Wabery broadcast",
|
|
1158
|
+
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.",
|
|
1159
|
+
inputSchema: {
|
|
1160
|
+
broadcast_id: z.string().min(1),
|
|
1161
|
+
},
|
|
1162
|
+
annotations: {
|
|
1163
|
+
readOnlyHint: true,
|
|
1164
|
+
destructiveHint: false,
|
|
1165
|
+
idempotentHint: true,
|
|
1166
|
+
openWorldHint: false,
|
|
1167
|
+
},
|
|
1168
|
+
}, async ({ broadcast_id }) => apiResult(client.get(`/broadcasts/${broadcast_id}`)));
|
|
1169
|
+
server.registerTool("wabery_create_broadcast", {
|
|
1170
|
+
title: "Create Wabery broadcast draft",
|
|
1171
|
+
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.",
|
|
1172
|
+
inputSchema: {
|
|
1173
|
+
name: z.string().min(1).max(120),
|
|
1174
|
+
channel_id: z.string().min(1),
|
|
1175
|
+
category: z.enum(["MARKETING", "UTILITY"]),
|
|
1176
|
+
template_name: z.string().min(1).max(512),
|
|
1177
|
+
fallback_language: z.string().min(2).max(35),
|
|
1178
|
+
variants: z.array(broadcastVariantSchema).min(1).max(50),
|
|
1179
|
+
},
|
|
1180
|
+
annotations: {
|
|
1181
|
+
readOnlyHint: false,
|
|
1182
|
+
destructiveHint: false,
|
|
1183
|
+
idempotentHint: false,
|
|
1184
|
+
openWorldHint: false,
|
|
1185
|
+
},
|
|
1186
|
+
}, async ({ name, channel_id, category, template_name, fallback_language, variants, }) => {
|
|
1187
|
+
const blocked = await requireWrite("wabery_create_broadcast");
|
|
1188
|
+
if (blocked)
|
|
1189
|
+
return blocked;
|
|
1190
|
+
return apiResult(client.post("/broadcasts", {
|
|
1191
|
+
name,
|
|
1192
|
+
channelId: channel_id,
|
|
1193
|
+
category,
|
|
1194
|
+
templateName: template_name,
|
|
1195
|
+
fallbackLanguage: fallback_language,
|
|
1196
|
+
variants: variants.map((variant) => ({
|
|
1197
|
+
templateId: variant.template_id,
|
|
1198
|
+
language: variant.language,
|
|
1199
|
+
parameterMappings: variant.parameter_mappings.map((mapping) => ({
|
|
1200
|
+
component: mapping.component,
|
|
1201
|
+
index: mapping.index,
|
|
1202
|
+
buttonIndex: mapping.button_index,
|
|
1203
|
+
parameterType: mapping.parameter_type,
|
|
1204
|
+
source: mapping.source,
|
|
1205
|
+
metadataKey: mapping.metadata_key,
|
|
1206
|
+
value: mapping.value,
|
|
1207
|
+
fallback: mapping.fallback,
|
|
1208
|
+
})),
|
|
1209
|
+
})),
|
|
1210
|
+
}));
|
|
1211
|
+
});
|
|
1212
|
+
server.registerTool("wabery_prepare_broadcast_audience", {
|
|
1213
|
+
title: "Prepare Wabery broadcast audience",
|
|
1214
|
+
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.",
|
|
1215
|
+
inputSchema: {
|
|
1216
|
+
broadcast_id: z.string().min(1),
|
|
1217
|
+
contact_ids: z.array(z.string().min(1)).max(1000).optional(),
|
|
1218
|
+
import_id: z.string().min(1).optional(),
|
|
1219
|
+
filter: broadcastAudienceFilterSchema.optional(),
|
|
1220
|
+
exclude_contact_ids: z.array(z.string().min(1)).max(1000).optional(),
|
|
1221
|
+
},
|
|
1222
|
+
annotations: {
|
|
1223
|
+
readOnlyHint: false,
|
|
1224
|
+
destructiveHint: false,
|
|
1225
|
+
idempotentHint: true,
|
|
1226
|
+
openWorldHint: false,
|
|
1227
|
+
},
|
|
1228
|
+
}, async ({ broadcast_id, contact_ids, import_id, filter, exclude_contact_ids, }) => {
|
|
1229
|
+
const blocked = await requireWrite("wabery_prepare_broadcast_audience");
|
|
1230
|
+
if (blocked)
|
|
1231
|
+
return blocked;
|
|
1232
|
+
const sources = [contact_ids, import_id, filter].filter((value) => value !== undefined);
|
|
1233
|
+
if (sources.length !== 1) {
|
|
1234
|
+
return {
|
|
1235
|
+
...jsonResult({
|
|
1236
|
+
error: "invalid_audience_source",
|
|
1237
|
+
message: "Provide exactly one audience source: contact_ids, import_id, or filter.",
|
|
1238
|
+
}),
|
|
1239
|
+
isError: true,
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
return apiResult(client.post(`/broadcasts/${broadcast_id}/audience`, {
|
|
1243
|
+
contactIds: contact_ids,
|
|
1244
|
+
importId: import_id,
|
|
1245
|
+
filter: filter
|
|
1246
|
+
? {
|
|
1247
|
+
search: filter.search,
|
|
1248
|
+
tags: filter.tags,
|
|
1249
|
+
languages: filter.languages,
|
|
1250
|
+
isBlocked: filter.is_blocked,
|
|
1251
|
+
createdAfter: filter.created_after,
|
|
1252
|
+
createdBefore: filter.created_before,
|
|
1253
|
+
}
|
|
1254
|
+
: undefined,
|
|
1255
|
+
excludeContactIds: exclude_contact_ids ?? [],
|
|
1256
|
+
}));
|
|
1257
|
+
});
|
|
1258
|
+
server.registerTool("wabery_list_broadcast_recipients", {
|
|
1259
|
+
title: "List Wabery broadcast recipients",
|
|
1260
|
+
description: "Inspect the prepared recipient snapshot, language selection, skips, failures, and delivery state. Use before confirmation and while monitoring delivery.",
|
|
1261
|
+
inputSchema: {
|
|
1262
|
+
broadcast_id: z.string().min(1),
|
|
1263
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
1264
|
+
starting_after: z.string().optional(),
|
|
1265
|
+
status: broadcastRecipientStatusSchema.optional(),
|
|
1266
|
+
},
|
|
1267
|
+
annotations: {
|
|
1268
|
+
readOnlyHint: true,
|
|
1269
|
+
destructiveHint: false,
|
|
1270
|
+
idempotentHint: true,
|
|
1271
|
+
openWorldHint: false,
|
|
1272
|
+
},
|
|
1273
|
+
}, async ({ broadcast_id, ...query }) => apiResult(client.get(`/broadcasts/${broadcast_id}/recipients`, query)));
|
|
1274
|
+
server.registerTool("wabery_send_broadcast", {
|
|
1275
|
+
title: "Send Wabery broadcast",
|
|
1276
|
+
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>.",
|
|
1277
|
+
inputSchema: {
|
|
1278
|
+
broadcast_id: z.string().min(1),
|
|
1279
|
+
confirmation_token: z
|
|
1280
|
+
.string()
|
|
1281
|
+
.optional()
|
|
1282
|
+
.describe("Required: send_broadcast:<broadcast_id>. Obtain explicit user confirmation after reviewing counts."),
|
|
1283
|
+
},
|
|
1284
|
+
annotations: {
|
|
1285
|
+
readOnlyHint: false,
|
|
1286
|
+
destructiveHint: true,
|
|
1287
|
+
idempotentHint: true,
|
|
1288
|
+
openWorldHint: true,
|
|
1289
|
+
},
|
|
1290
|
+
}, async ({ broadcast_id, confirmation_token }) => {
|
|
1291
|
+
const blocked = await requireBroadcastApproval("wabery_send_broadcast", `Send broadcast ${broadcast_id} to its prepared WhatsApp recipients now?`, confirmation_token, `send_broadcast:${broadcast_id}`);
|
|
1292
|
+
if (blocked)
|
|
1293
|
+
return blocked;
|
|
1294
|
+
return apiResult(client.post(`/broadcasts/${broadcast_id}/send`));
|
|
1295
|
+
});
|
|
1296
|
+
server.registerTool("wabery_schedule_broadcast", {
|
|
1297
|
+
title: "Schedule Wabery broadcast",
|
|
1298
|
+
description: "Schedule a ready broadcast for future delivery to its frozen recipient snapshot. Requires explicit user confirmation with confirmation_token schedule_broadcast:<broadcast_id>.",
|
|
1299
|
+
inputSchema: {
|
|
1300
|
+
broadcast_id: z.string().min(1),
|
|
1301
|
+
scheduled_at: z.string().datetime(),
|
|
1302
|
+
timezone: z.string().min(1).max(80),
|
|
1303
|
+
confirmation_token: z
|
|
1304
|
+
.string()
|
|
1305
|
+
.optional()
|
|
1306
|
+
.describe("Required: schedule_broadcast:<broadcast_id>. Obtain explicit user confirmation after reviewing counts and time."),
|
|
1307
|
+
},
|
|
1308
|
+
annotations: {
|
|
1309
|
+
readOnlyHint: false,
|
|
1310
|
+
destructiveHint: true,
|
|
1311
|
+
idempotentHint: true,
|
|
1312
|
+
openWorldHint: true,
|
|
1313
|
+
},
|
|
1314
|
+
}, async ({ broadcast_id, scheduled_at, timezone, confirmation_token }) => {
|
|
1315
|
+
const blocked = await requireBroadcastApproval("wabery_schedule_broadcast", `Schedule broadcast ${broadcast_id} for ${scheduled_at} (${timezone})?`, confirmation_token, `schedule_broadcast:${broadcast_id}`);
|
|
1316
|
+
if (blocked)
|
|
1317
|
+
return blocked;
|
|
1318
|
+
return apiResult(client.post(`/broadcasts/${broadcast_id}/schedule`, {
|
|
1319
|
+
scheduledAt: scheduled_at,
|
|
1320
|
+
timezone,
|
|
1321
|
+
}));
|
|
1322
|
+
});
|
|
1323
|
+
server.registerTool("wabery_cancel_broadcast", {
|
|
1324
|
+
title: "Cancel Wabery broadcast",
|
|
1325
|
+
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>.",
|
|
1326
|
+
inputSchema: {
|
|
1327
|
+
broadcast_id: z.string().min(1),
|
|
1328
|
+
confirmation_token: z
|
|
1329
|
+
.string()
|
|
1330
|
+
.optional()
|
|
1331
|
+
.describe("Required: cancel_broadcast:<broadcast_id>."),
|
|
1332
|
+
},
|
|
1333
|
+
annotations: {
|
|
1334
|
+
readOnlyHint: false,
|
|
1335
|
+
destructiveHint: true,
|
|
1336
|
+
idempotentHint: true,
|
|
1337
|
+
openWorldHint: false,
|
|
1338
|
+
},
|
|
1339
|
+
}, async ({ broadcast_id, confirmation_token }) => {
|
|
1340
|
+
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}`);
|
|
1341
|
+
if (blocked)
|
|
1342
|
+
return blocked;
|
|
1343
|
+
return apiResult(client.post(`/broadcasts/${broadcast_id}/cancel`));
|
|
1344
|
+
});
|
|
1345
|
+
server.registerTool("wabery_duplicate_broadcast", {
|
|
1346
|
+
title: "Duplicate Wabery broadcast",
|
|
1347
|
+
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.",
|
|
1348
|
+
inputSchema: {
|
|
1349
|
+
broadcast_id: z.string().min(1),
|
|
1350
|
+
},
|
|
1351
|
+
annotations: {
|
|
1352
|
+
readOnlyHint: false,
|
|
1353
|
+
destructiveHint: false,
|
|
1354
|
+
idempotentHint: false,
|
|
1355
|
+
openWorldHint: false,
|
|
1356
|
+
},
|
|
1357
|
+
}, async ({ broadcast_id }) => {
|
|
1358
|
+
const blocked = await requireWrite("wabery_duplicate_broadcast");
|
|
1359
|
+
if (blocked)
|
|
1360
|
+
return blocked;
|
|
1361
|
+
return apiResult(client.post(`/broadcasts/${broadcast_id}/duplicate`));
|
|
1362
|
+
});
|
|
986
1363
|
server.registerTool("wabery_list_conversations", {
|
|
987
1364
|
title: "List Wabery conversations",
|
|
988
1365
|
description: "List organization-scoped conversations for inbox, support, or analytics tooling.",
|
|
@@ -1044,7 +1421,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1044
1421
|
openWorldHint: true,
|
|
1045
1422
|
},
|
|
1046
1423
|
}, async ({ body }) => {
|
|
1047
|
-
const blocked = requireWrite("wabery_send_message");
|
|
1424
|
+
const blocked = await requireWrite("wabery_send_message");
|
|
1048
1425
|
if (blocked)
|
|
1049
1426
|
return blocked;
|
|
1050
1427
|
return apiResult(client.post("/messages", body));
|
|
@@ -1105,7 +1482,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1105
1482
|
openWorldHint: true,
|
|
1106
1483
|
},
|
|
1107
1484
|
}, async ({ body }) => {
|
|
1108
|
-
const blocked = requireWrite("wabery_create_template");
|
|
1485
|
+
const blocked = await requireWrite("wabery_create_template");
|
|
1109
1486
|
if (blocked)
|
|
1110
1487
|
return blocked;
|
|
1111
1488
|
return apiResult(client.post("/templates", body));
|
|
@@ -1151,7 +1528,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1151
1528
|
openWorldHint: true,
|
|
1152
1529
|
},
|
|
1153
1530
|
}, async ({ flow_id, ...body }) => {
|
|
1154
|
-
const blocked = requireWrite("wabery_send_flow");
|
|
1531
|
+
const blocked = await requireWrite("wabery_send_flow");
|
|
1155
1532
|
if (blocked)
|
|
1156
1533
|
return blocked;
|
|
1157
1534
|
return apiResult(client.post(`/flows/${flow_id}/send`, body));
|
|
@@ -1249,7 +1626,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1249
1626
|
},
|
|
1250
1627
|
}, async (input) => {
|
|
1251
1628
|
const { confirmation_token, ...body } = input;
|
|
1252
|
-
const blocked = requireWrite("wabery_create_function", confirmation_token, "create_function");
|
|
1629
|
+
const blocked = await requireWrite("wabery_create_function", confirmation_token, "create_function");
|
|
1253
1630
|
if (blocked)
|
|
1254
1631
|
return blocked;
|
|
1255
1632
|
return jsonResult(await client.post("/functions", body));
|
|
@@ -1272,7 +1649,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1272
1649
|
openWorldHint: false,
|
|
1273
1650
|
},
|
|
1274
1651
|
}, async ({ function_id, source, confirmation_token }) => {
|
|
1275
|
-
const blocked = requireWrite("wabery_deploy_function", confirmation_token, "deploy_function");
|
|
1652
|
+
const blocked = await requireWrite("wabery_deploy_function", confirmation_token, "deploy_function");
|
|
1276
1653
|
if (blocked)
|
|
1277
1654
|
return blocked;
|
|
1278
1655
|
return jsonResult(await client.post(`/functions/${function_id}/deploy`, { source }));
|
|
@@ -1295,7 +1672,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1295
1672
|
openWorldHint: false,
|
|
1296
1673
|
},
|
|
1297
1674
|
}, async ({ function_id, text, confirmation_token }) => {
|
|
1298
|
-
const blocked = requireWrite("wabery_test_function", confirmation_token, "test_function");
|
|
1675
|
+
const blocked = await requireWrite("wabery_test_function", confirmation_token, "test_function");
|
|
1299
1676
|
if (blocked)
|
|
1300
1677
|
return blocked;
|
|
1301
1678
|
return jsonResult(await client.post(`/functions/${function_id}/test`, { text }));
|
|
@@ -1328,7 +1705,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1328
1705
|
openWorldHint: false,
|
|
1329
1706
|
},
|
|
1330
1707
|
}, async ({ function_id, confirmation_token }) => {
|
|
1331
|
-
const blocked = requireWrite("wabery_delete_function", confirmation_token, "delete_function");
|
|
1708
|
+
const blocked = await requireWrite("wabery_delete_function", confirmation_token, "delete_function");
|
|
1332
1709
|
if (blocked)
|
|
1333
1710
|
return blocked;
|
|
1334
1711
|
return jsonResult(await client.delete(`/functions/${function_id}`));
|
|
@@ -1355,7 +1732,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1355
1732
|
},
|
|
1356
1733
|
}, async (input) => {
|
|
1357
1734
|
const { function_id, confirmation_token, ...body } = input;
|
|
1358
|
-
const blocked = requireWrite("wabery_update_function", confirmation_token, "update_function");
|
|
1735
|
+
const blocked = await requireWrite("wabery_update_function", confirmation_token, "update_function");
|
|
1359
1736
|
if (blocked)
|
|
1360
1737
|
return blocked;
|
|
1361
1738
|
return jsonResult(await client.patch(`/functions/${function_id}`, body));
|
|
@@ -1379,7 +1756,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
|
|
|
1379
1756
|
openWorldHint: true,
|
|
1380
1757
|
},
|
|
1381
1758
|
}, async ({ function_id, arguments: args, contact_id, confirmation_token, }) => {
|
|
1382
|
-
const blocked = requireWrite("wabery_invoke_function", confirmation_token, "invoke_function");
|
|
1759
|
+
const blocked = await requireWrite("wabery_invoke_function", confirmation_token, "invoke_function");
|
|
1383
1760
|
if (blocked)
|
|
1384
1761
|
return blocked;
|
|
1385
1762
|
return jsonResult(await client.post(`/functions/${function_id}/invoke`, {
|