@wabery/cli 0.19.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,27 @@ import { z } from "zod";
3
3
  import { WaberyApiClient, WaberyApiError } from "./api-client.js";
4
4
  import { createExampleConfig } from "./config-example.js";
5
5
  import { checkWaberyConnection } from "./diagnostics.js";
6
+ import { executeMetaManagementOperation, META_MANAGEMENT_OPERATIONS, resolveMetaManagementOperation, } from "./meta-business-agent-operations.js";
6
7
  const configSchema = z.record(z.string(), z.unknown());
8
+ const metaSettingsShape = {
9
+ rollout: z.object({ enabled: z.boolean() }).optional(),
10
+ ai_audience: z.enum(["ALLOWLISTED_ONLY", "EVERYONE"]).optional(),
11
+ handoff: z
12
+ .object({
13
+ enabled: z.boolean(),
14
+ message: z.string().optional(),
15
+ message_selection: z.enum(["DEFAULT", "AGENT", "CUSTOM"]).optional(),
16
+ })
17
+ .optional(),
18
+ followup: z
19
+ .object({
20
+ enabled: z.boolean(),
21
+ followup_interval_in_seconds: z.number().int().optional(),
22
+ message: z.string().optional(),
23
+ })
24
+ .optional(),
25
+ never_say_phrases: z.array(z.string()).optional(),
26
+ };
7
27
  const metadataSchema = z.record(z.string(), z.unknown()).optional();
8
28
  const groupIdSchema = z
9
29
  .string()
@@ -216,7 +236,10 @@ const WRITE_NOTE = {
216
236
  enabled: "Write tools are enabled for this session. Your OAuth scopes decide what you may change, so attempt the write and let the server reject it if a scope is missing.",
217
237
  };
218
238
  function serverInstructions(writePolicy) {
219
- return `${SERVER_INSTRUCTIONS}\n${WRITE_NOTE[writePolicy]}`;
239
+ const instructions = process.env.WABERY_META_BUSINESS_AGENTS_OPERATE_ENABLED === "true"
240
+ ? SERVER_INSTRUCTIONS
241
+ : SERVER_INSTRUCTIONS.replace(", and Business Agent configuration", "").replace("Use routing and thread-control tools for human handoff. ", "Use routing tools for human handoff. ");
242
+ return `${instructions}\n${WRITE_NOTE[writePolicy]}`;
220
243
  }
221
244
  const MCP_SENSITIVE_OUTPUT_KEY_SUFFIXES = [
222
245
  "accesstoken",
@@ -320,6 +343,10 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
320
343
  version: options.serverVersion ?? "0.1.0",
321
344
  }, { instructions: serverInstructions(writePolicy) });
322
345
  const registerTool = (name, config, callback) => {
346
+ if (name.includes("business_agent") &&
347
+ process.env.WABERY_META_BUSINESS_AGENTS_OPERATE_ENABLED !== "true") {
348
+ return undefined;
349
+ }
323
350
  options.onToolRegistered?.({
324
351
  name,
325
352
  title: config.title,
@@ -518,7 +545,7 @@ OpenAPI: https://api.wabery.com/v1/openapi.json
518
545
  CLI: npm i -g @wabery/cli (https://www.npmjs.com/package/@wabery/cli)
519
546
  MCP: https://mcp.wabery.com
520
547
  Sandbox: shared WhatsApp test number, no Meta setup
521
- Free plan: $0, one Live project, 500 messages, no credit card
548
+ Free plan: $0, one Live project, 1,000 messages, no credit card
522
549
  API keys: create them yourself at https://app.wabery.com/access?tab=api-keys
523
550
  `,
524
551
  },
@@ -572,7 +599,7 @@ REST version is currently scheduled for sunset.
572
599
 
573
600
  Canonical URL: https://wabery.com/pricing.md
574
601
 
575
- Free: $0, 1 Live project, 500 messages, Sandbox, no credit card.
602
+ Free: $0, 1 Live project, 1,000 messages, Sandbox, no credit card.
576
603
  Pro: $15/month for two Live projects and 50K messages.
577
604
  Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
578
605
  `,
@@ -711,6 +738,94 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
711
738
  throw error;
712
739
  }
713
740
  });
741
+ registerTool("wabery_list_business_agent_operations", {
742
+ title: "Discover Meta management operations",
743
+ description: "Use this when you need to discover documented Meta management operations supported by this client. This is management access for a creator, never customer-facing agent tools. Server authorization and feature availability still apply; local contract coverage is not live verification.",
744
+ inputSchema: {},
745
+ annotations: {
746
+ readOnlyHint: true,
747
+ destructiveHint: false,
748
+ idempotentHint: true,
749
+ openWorldHint: false,
750
+ },
751
+ }, async () => jsonResult({
752
+ operations: META_MANAGEMENT_OPERATIONS,
753
+ live_verified: false,
754
+ }));
755
+ const metaOperationNames = (read) => META_MANAGEMENT_OPERATIONS.filter((item) => (item.method === "GET") === read).map((item) => item.name);
756
+ registerTool("wabery_read_business_agent_operation", {
757
+ title: "Read Meta management data",
758
+ description: "Use this when you need to read a named operation from wabery_list_business_agent_operations. Supply path identifiers in parameters (business_agent_id, connector_id, tool_id, remote_id) and documented filters in query. Connector/tool IDs are Wabery binding IDs; other resource IDs are Meta IDs. This cannot invoke a write or run a tool.",
759
+ inputSchema: {
760
+ operation: z.enum(metaOperationNames(true)),
761
+ parameters: z.record(z.string()).optional(),
762
+ query: z.record(z.union([z.string(), z.number()])).optional(),
763
+ },
764
+ annotations: {
765
+ readOnlyHint: true,
766
+ destructiveHint: false,
767
+ idempotentHint: true,
768
+ openWorldHint: true,
769
+ },
770
+ }, async ({ operation, ...input }) => {
771
+ if (resolveMetaManagementOperation(operation).method !== "GET")
772
+ throw new Error("Read operation required");
773
+ return jsonResult(await executeMetaManagementOperation(client, operation, input));
774
+ });
775
+ registerTool("wabery_write_business_agent_operation", {
776
+ title: "Change Meta management configuration",
777
+ description: "Use this when you need one named management write after explicit creator approval. Parameters contain scoped IDs; body contains the documented Wabery API request. Test/eval/tool-run may cause real backend writes: isolate connectors first. No automatic retries after uncertain writes. For files.upload provide body.file_name and body.file_base64 (up to 1 MiB decoded); use CLI/SDK for larger files. Never provide this management tool to a customer-facing agent.",
778
+ inputSchema: {
779
+ operation: z.enum(metaOperationNames(false)),
780
+ parameters: z.record(z.string()).optional(),
781
+ query: z.record(z.union([z.string(), z.number()])).optional(),
782
+ body: z.record(z.unknown()).optional(),
783
+ operation_id: z
784
+ .string()
785
+ .min(1)
786
+ .max(200)
787
+ .optional()
788
+ .describe("Required caller-owned ID for onboard, settings, and connector mutations."),
789
+ confirmation_token: z
790
+ .string()
791
+ .optional()
792
+ .describe("Required confirmation: meta_management_write"),
793
+ },
794
+ annotations: {
795
+ readOnlyHint: false,
796
+ destructiveHint: true,
797
+ idempotentHint: false,
798
+ openWorldHint: true,
799
+ },
800
+ }, async ({ operation, confirmation_token, operation_id, ...input }) => {
801
+ const blocked = await requireWrite("wabery_write_business_agent_operation", confirmation_token, "meta_management_write");
802
+ if (blocked)
803
+ return blocked;
804
+ if (resolveMetaManagementOperation(operation).method === "GET")
805
+ throw new Error("Write operation required");
806
+ let body = input.body;
807
+ if (operation === "files.upload") {
808
+ const name = input.body?.file_name;
809
+ const encoded = input.body?.file_base64;
810
+ if (typeof name !== "string" ||
811
+ typeof encoded !== "string" ||
812
+ encoded.length > 1_398_104 ||
813
+ !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(encoded))
814
+ throw new Error("Provide file_name and valid base64 for a file of at most 1 MiB");
815
+ const bytes = Buffer.from(encoded, "base64");
816
+ if (bytes.length > 1_048_576)
817
+ throw new Error("MCP file limit is 1 MiB; use CLI or SDK");
818
+ const form = new FormData();
819
+ form.set("file_name", name);
820
+ form.set("file", new Blob([new Uint8Array(bytes)]), name);
821
+ body = form;
822
+ }
823
+ return jsonResult(await executeMetaManagementOperation(client, operation, {
824
+ ...input,
825
+ body,
826
+ operationId: operation_id,
827
+ }));
828
+ });
714
829
  registerTool("wabery_list_business_agents", {
715
830
  title: "List Meta Business Agents",
716
831
  description: "Use this when you need the selected project's Meta Business Agent records and business_agent_id values. Do not use it for live settings; use wabery_get_business_agent_settings.",
@@ -746,11 +861,8 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
746
861
  description: "Use this when you need to provision or overwrite Meta Business Agent settings for a channel_id from wabery_list_channels. Prefer wabery_apply_config for reviewed project-wide declarative changes.",
747
862
  inputSchema: {
748
863
  channel_id: z.string().min(1),
749
- enabled: z.boolean().optional(),
750
- persona: z.string().min(1).max(4096).optional(),
751
- language: z.string().min(2).max(16).optional(),
752
- handoff: z.record(z.string(), z.unknown()).optional(),
753
- knowledge: z.record(z.string(), z.unknown()).optional(),
864
+ operation_id: z.string().min(1).max(200),
865
+ ...metaSettingsShape,
754
866
  confirmation_token: z
755
867
  .string()
756
868
  .optional()
@@ -763,11 +875,11 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
763
875
  openWorldHint: true,
764
876
  },
765
877
  }, async (input) => {
766
- const { confirmation_token, ...body } = input;
878
+ const { confirmation_token, operation_id, ...body } = input;
767
879
  const blocked = await requireWrite("wabery_apply_business_agent", confirmation_token, "apply_business_agent");
768
880
  if (blocked)
769
881
  return blocked;
770
- return jsonResult(await client.put("/business-agents", body));
882
+ return jsonResult(await client.put("/business-agents", body, operation_id));
771
883
  });
772
884
  registerTool("wabery_get_business_agent_settings", {
773
885
  title: "Get Meta Business Agent settings",
@@ -787,11 +899,8 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
787
899
  description: "Use this when you need to overwrite live Meta settings for a business_agent_id from wabery_list_business_agents. Prefer wabery_apply_business_agent when provisioning by channel.",
788
900
  inputSchema: {
789
901
  business_agent_id: z.string().min(1),
790
- enabled: z.boolean().optional(),
791
- persona: z.string().min(1).max(4096).optional(),
792
- language: z.string().min(2).max(16).optional(),
793
- handoff: z.record(z.string(), z.unknown()).optional(),
794
- knowledge: z.record(z.string(), z.unknown()).optional(),
902
+ operation_id: z.string().min(1).max(200),
903
+ ...metaSettingsShape,
795
904
  confirmation_token: z
796
905
  .string()
797
906
  .optional()
@@ -804,11 +913,11 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
804
913
  openWorldHint: true,
805
914
  },
806
915
  }, async (input) => {
807
- const { business_agent_id, confirmation_token, ...body } = input;
916
+ const { business_agent_id, confirmation_token, operation_id, ...body } = input;
808
917
  const blocked = await requireWrite("wabery_update_business_agent_settings", confirmation_token, "update_business_agent_settings");
809
918
  if (blocked)
810
919
  return blocked;
811
- return jsonResult(await client.put(`/business-agents/${encodeURIComponent(business_agent_id)}/settings`, body));
920
+ return jsonResult(await client.put(`/business-agents/${encodeURIComponent(business_agent_id)}/settings`, body, operation_id));
812
921
  });
813
922
  registerTool("wabery_list_business_agent_connectors", {
814
923
  title: "List Meta Business Agent connectors",
@@ -830,6 +939,7 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
830
939
  description: "Use this when you need to create a connector in Meta for a provisioned business_agent_id from wabery_list_business_agents. Prefer wabery_apply_config for reviewed project-wide declarative changes.",
831
940
  inputSchema: {
832
941
  business_agent_id: z.string().min(1),
942
+ operation_id: z.string().min(1).max(200),
833
943
  name: z.string().min(1).max(120),
834
944
  type: z.string().min(1).max(80),
835
945
  config: z.record(z.string(), z.unknown()).optional(),
@@ -846,11 +956,11 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
846
956
  openWorldHint: true,
847
957
  },
848
958
  }, async (input) => {
849
- const { confirmation_token, ...body } = input;
959
+ const { confirmation_token, operation_id, ...body } = input;
850
960
  const blocked = await requireWrite("wabery_create_business_agent_connector", confirmation_token, "create_business_agent_connector");
851
961
  if (blocked)
852
962
  return blocked;
853
- return jsonResult(await client.post("/business-agents/connectors", body));
963
+ return jsonResult(await client.post("/business-agents/connectors", body, operation_id));
854
964
  });
855
965
  registerTool("wabery_create_business_agent_connector_tool", {
856
966
  title: "Create Meta Business Agent connector tool",
@@ -972,7 +1082,7 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
972
1082
  });
973
1083
  registerTool("wabery_get_business_agent_thread_control", {
974
1084
  title: "Get Meta Business Agent thread control",
975
- description: "Use this when you need to inspect current WhatsApp thread ownership for a channel_id from wabery_list_channels and contact_id from wabery_list_contacts. Use the set tool for handoff.",
1085
+ description: "Use this when you need Wabery's last observed thread-control state, which may be unknown or stale. Meta does not document an owner GET endpoint. Use channel_id and contact_id from this project; use the set tool for an explicit transition.",
976
1086
  inputSchema: {
977
1087
  channel_id: z.string().min(1),
978
1088
  contact_id: z.string().min(1),
@@ -980,26 +1090,27 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
980
1090
  thread_id: z.string().min(1).optional(),
981
1091
  },
982
1092
  annotations: {
983
- readOnlyHint: false,
1093
+ readOnlyHint: true,
984
1094
  destructiveHint: false,
985
- idempotentHint: false,
1095
+ idempotentHint: true,
986
1096
  openWorldHint: true,
987
1097
  },
988
- }, async (input) => jsonResult(await client.post("/business-agents/thread-control", {
1098
+ }, async (input) => jsonResult(await client.get("/business-agents/thread-control", {
989
1099
  ...input,
990
- action: "get",
991
1100
  })));
992
1101
  registerTool("wabery_set_business_agent_thread_control", {
993
1102
  title: "Set Meta Business Agent thread control",
994
1103
  description: "Use this when you need to take or pass WhatsApp thread control for a channel_id and contact_id, changing automation or human ownership. Inspect first with wabery_get_business_agent_thread_control.",
995
1104
  inputSchema: {
996
- action: z.enum(["take", "pass"]),
1105
+ operation_id: z.string().min(1).max(200),
1106
+ action: z.enum(["take", "release", "pass"]),
997
1107
  channel_id: z.string().min(1),
998
1108
  contact_id: z.string().min(1),
999
1109
  business_agent_id: z.string().min(1).optional(),
1000
- thread_id: z.string().min(1).optional(),
1110
+ to: z.string().min(1),
1111
+ pass_to_meta: z.boolean().optional(),
1001
1112
  reason: z.string().max(512).optional(),
1002
- payload: z.record(z.string(), z.unknown()).optional(),
1113
+ metadata: z.string().max(2000).optional(),
1003
1114
  confirmation_token: z
1004
1115
  .string()
1005
1116
  .optional()
@@ -1012,19 +1123,21 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
1012
1123
  openWorldHint: true,
1013
1124
  },
1014
1125
  }, async (input) => {
1015
- const { confirmation_token, ...body } = input;
1126
+ const { confirmation_token, operation_id, ...body } = input;
1016
1127
  const blocked = await requireWrite("wabery_set_business_agent_thread_control", confirmation_token, "thread_control");
1017
1128
  if (blocked)
1018
1129
  return blocked;
1019
- return jsonResult(await client.post("/business-agents/thread-control", body));
1130
+ return jsonResult(await client.post("/business-agents/thread-control", body, operation_id));
1020
1131
  });
1021
1132
  registerTool("wabery_test_business_agent", {
1022
1133
  title: "Test Meta Business Agent",
1023
1134
  description: "Use this when you need to submit a controlled test scenario to Meta for a business_agent_id from wabery_list_business_agents. This is a remote provider operation, not a read or a real-recipient message send.",
1024
1135
  inputSchema: {
1136
+ operation_id: z.string().min(1).max(200),
1025
1137
  business_agent_id: z.string().min(1),
1026
- input: z.string().min(1).optional(),
1027
- scenario: z.record(z.string(), z.unknown()).optional(),
1138
+ user_msg: z.string().min(1),
1139
+ conversation_id: z.string().min(1).optional(),
1140
+ isolated_connectors: z.literal(true),
1028
1141
  confirmation_token: z
1029
1142
  .string()
1030
1143
  .optional()
@@ -1036,17 +1149,23 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
1036
1149
  idempotentHint: false,
1037
1150
  openWorldHint: true,
1038
1151
  },
1039
- }, async ({ confirmation_token, ...body }) => {
1152
+ }, async ({ confirmation_token, operation_id, ...body }) => {
1040
1153
  const blocked = await requireWrite("wabery_test_business_agent", confirmation_token, "test_business_agent");
1041
1154
  if (blocked)
1042
1155
  return blocked;
1043
- return jsonResult(await client.post("/business-agents/test", body));
1156
+ return jsonResult(await client.post("/business-agents/test", body, operation_id));
1044
1157
  });
1045
1158
  registerTool("wabery_get_business_agent_eval", {
1046
- title: "Get Meta Business Agent eval summary",
1047
- description: "Use this when you need the live Meta evaluation summary for a business_agent_id from wabery_list_business_agents. Do not use it to run a test scenario.",
1159
+ title: "Read Meta Business Agent evaluation",
1160
+ description: "Use this when you need cases, a job, evaluation details, or summaries. Supply job_id for run, comma-separated eval_id for details, or comma-separated summary_id for summary. This does not start evaluation or invoke tools.",
1048
1161
  inputSchema: {
1049
1162
  business_agent_id: z.string().min(1),
1163
+ operation: z
1164
+ .enum(["cases", "run", "details", "summary"])
1165
+ .default("cases"),
1166
+ job_id: z.string().optional(),
1167
+ eval_id: z.string().optional(),
1168
+ summary_id: z.string().optional(),
1050
1169
  },
1051
1170
  annotations: {
1052
1171
  readOnlyHint: true,
@@ -1054,12 +1173,16 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
1054
1173
  idempotentHint: true,
1055
1174
  openWorldHint: true,
1056
1175
  },
1057
- }, async ({ business_agent_id }) => jsonResult(await client.get("/business-agents/eval", {
1176
+ }, async ({ business_agent_id, operation, job_id, eval_id, summary_id }) => jsonResult(await client.get("/business-agents/eval", {
1058
1177
  business_agent_id,
1178
+ operation,
1179
+ job_id,
1180
+ eval_id,
1181
+ summary_id,
1059
1182
  })));
1060
1183
  registerTool("wabery_list_business_agent_skills", {
1061
1184
  title: "List Meta Business Agent skills",
1062
- description: "Use this when you need the current Meta skill list for a business_agent_id from wabery_list_business_agents. Use the set tool only to replace the full list.",
1185
+ description: "Use this when you need individual Meta skills and review states. There is no bulk replacement API. Use skills.create/update/delete through the management operation tool.",
1063
1186
  inputSchema: {
1064
1187
  business_agent_id: z.string().min(1),
1065
1188
  },
@@ -1072,28 +1195,31 @@ Create API keys yourself at https://app.wabery.com/access?tab=api-keys.
1072
1195
  }, async ({ business_agent_id }) => jsonResult(await client.get("/business-agents/skills", {
1073
1196
  business_agent_id,
1074
1197
  })));
1075
- registerTool("wabery_set_business_agent_skills", {
1076
- title: "Set Meta Business Agent skills",
1077
- description: "Use this when you need to replace the entire Meta skill list for a business_agent_id from wabery_list_business_agents. Do not use it to append one skill without first preserving existing entries.",
1198
+ registerTool("wabery_create_business_agent_skill", {
1199
+ title: "Create one Meta Business Agent skill",
1200
+ description: "Use this when you need to create one skill without replacing existing skills. Its Meta review status must become active before relying on it.",
1078
1201
  inputSchema: {
1202
+ operation_id: z.string().min(1).max(200),
1079
1203
  business_agent_id: z.string().min(1),
1080
- skills: z.array(z.unknown()),
1204
+ title: z.string().max(64).optional(),
1205
+ description: z.string().max(1024).optional(),
1206
+ skill: z.string().max(20000).optional(),
1081
1207
  confirmation_token: z
1082
1208
  .string()
1083
1209
  .optional()
1084
- .describe("Required when write mode is enabled: set_business_agent_skills"),
1210
+ .describe("Required when write mode is enabled: create_business_agent_skill"),
1085
1211
  },
1086
1212
  annotations: {
1087
1213
  readOnlyHint: false,
1088
- destructiveHint: true,
1089
- idempotentHint: true,
1214
+ destructiveHint: false,
1215
+ idempotentHint: false,
1090
1216
  openWorldHint: true,
1091
1217
  },
1092
- }, async ({ confirmation_token, ...body }) => {
1093
- const blocked = await requireWrite("wabery_set_business_agent_skills", confirmation_token, "set_business_agent_skills");
1218
+ }, async ({ confirmation_token, operation_id, ...body }) => {
1219
+ const blocked = await requireWrite("wabery_create_business_agent_skill", confirmation_token, "create_business_agent_skill");
1094
1220
  if (blocked)
1095
1221
  return blocked;
1096
- return jsonResult(await client.put("/business-agents/skills", body));
1222
+ return jsonResult(await client.post("/business-agents/skills", body, operation_id));
1097
1223
  });
1098
1224
  registerTool("wabery_list_projects", {
1099
1225
  title: "List Wabery projects",