@wabery/cli 0.14.0 → 0.14.1

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.
@@ -85,13 +85,13 @@ const toolOutputSchema = {
85
85
  .record(z.string(), z.unknown())
86
86
  .describe("The structured Wabery API result object. Its operation-specific fields are described by the tool and Wabery API documentation."),
87
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.
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.
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.
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.
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.
94
- High-risk tools require confirmation_token values shown in each tool description.`;
88
+ const SERVER_INSTRUCTIONS = `Wabery is a developer platform for building and operating WhatsApp AI agents. Start discovery with wabery_list_projects, select the intended project, then call wabery_get_project_readiness; use channel readiness only for phone-number/provider detail.
89
+ Use Wabery for WhatsApp contacts, opt-in, conversations and message history, approved templates, Flows, broadcasts, routing/human handoff, signed webhooks, hosted functions, and Meta Business Agent configuration.
90
+ Keep sandbox and production readiness distinct: the shared sandbox supports controlled tests; production inbound, proactive templates, and broadcasts require an eligible dedicated WhatsApp channel. Enroll only controlled or explicitly opted-in contacts and retain the returned contact_id and channel_id.
91
+ Read conversations and message history before replying. Validate and preview config before apply; config apply may replace resources but never publishes a Flow. Draft and validate Flows before separately confirming publish or send.
92
+ For broadcasts, create a draft with approved templates, prepare exactly one audience source, poll until ready, review recipients/exclusions, then obtain explicit confirmation before send or schedule. Preparation freezes the audience.
93
+ Use routing and thread-control tools for automation ownership and human handoff. Treat hosted function test/invoke as potentially side-effecting code.
94
+ Obtain explicit user confirmation immediately before any external or irreversible action, including sends, publishes, deletions, secret rotation, provider updates, or function execution. After sending, call wabery_get_message, wabery_get_dispatch, or wabery_get_broadcast to report provider status; acceptance is not delivery.`;
95
95
  // The write gate differs by transport, and the model has to be told the truth
96
96
  // about the session it is actually in: a hosted OAuth client (ChatGPT, Claude)
97
97
  // cannot set an env var, so advertising the env gate there makes it refuse to
@@ -113,6 +113,7 @@ const MCP_SENSITIVE_OUTPUT_KEY_SUFFIXES = [
113
113
  "credentials",
114
114
  "idtoken",
115
115
  "jwt",
116
+ "oauthtoken",
116
117
  "password",
117
118
  "passwordhash",
118
119
  "privatekey",
@@ -350,7 +351,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
350
351
  }));
351
352
  registerTool("wabery_check_connection", {
352
353
  title: "Check Wabery connection",
353
- description: "Verify the configured API key can reach Wabery projects, channels, and the config schema before making changes.",
354
+ description: "Use this when you need to verify that the current credential can reach Wabery projects, WhatsApp channels, and the config schema before other work.",
354
355
  inputSchema: {},
355
356
  annotations: {
356
357
  readOnlyHint: true,
@@ -361,7 +362,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
361
362
  }, async () => jsonResult(await checkWaberyConnection(client)));
362
363
  registerTool("wabery_get_config_example", {
363
364
  title: "Get example Wabery config",
364
- description: "Return a starter wabery.config.json with one flow and one automation that triggers it with a send_flow step.",
365
+ description: "Use this when you need a starter wabery.config.json with one WhatsApp Flow and one send_flow automation; optional project_id comes from wabery_list_projects.",
365
366
  inputSchema: {
366
367
  project_id: z
367
368
  .string()
@@ -378,7 +379,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
378
379
  }, async ({ project_id }) => jsonResult(createExampleConfig(project_id)));
379
380
  registerTool("wabery_get_config_schema", {
380
381
  title: "Get Wabery config schema",
381
- description: "Return the JSON Schema for wabery.config.json. Use this before generating flows, automations, webhooks, or business_agent declarations.",
382
+ description: "Use this when you need the current JSON Schema before generating a wabery.config.json with Flows, automations, webhooks, or business_agent declarations.",
382
383
  inputSchema: {},
383
384
  annotations: {
384
385
  readOnlyHint: true,
@@ -389,7 +390,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
389
390
  }, async () => jsonResult(await client.get("/config/schema")));
390
391
  registerTool("wabery_export_config", {
391
392
  title: "Export Wabery config",
392
- description: "Export the organization's flows, automations, webhook settings, and Business Agent declarations as a config object.",
393
+ description: "Use this when you need the selected project's current Flows, automations, webhook settings, and Business Agent declarations as declarative config. Do not use it to inspect live provider status.",
393
394
  inputSchema: {},
394
395
  annotations: {
395
396
  readOnlyHint: true,
@@ -400,7 +401,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
400
401
  }, async () => jsonResult(await client.get("/config")));
401
402
  registerTool("wabery_validate_config", {
402
403
  title: "Validate Wabery config",
403
- description: "Validate a declarative config object without changing flows or automations.",
404
+ description: "Use this when you need to validate a complete declarative config without changing resources. Use wabery_preview_config_diff next to inspect effects.",
404
405
  inputSchema: {
405
406
  config: configSchema.describe("The full wabery.config.json object."),
406
407
  },
@@ -413,7 +414,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
413
414
  }, async ({ config }) => apiResult(client.post("/config/validate", config)));
414
415
  registerTool("wabery_preview_config_diff", {
415
416
  title: "Preview Wabery config diff",
416
- description: "Preview which flows, automations, webhook settings, Business Agents, connectors, and tools would be created, updated, deleted, or left unchanged.",
417
+ description: "Use this when you need to preview what a complete config would create, update, delete, or leave unchanged without applying it. Use wabery_apply_config only after reviewing this diff.",
417
418
  inputSchema: {
418
419
  config: configSchema.describe("The full wabery.config.json object."),
419
420
  },
@@ -426,7 +427,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
426
427
  }, async ({ config }) => apiResult(client.post("/config/diff", config)));
427
428
  registerTool("wabery_apply_config", {
428
429
  title: "Apply Wabery config",
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.",
430
+ description: "Use this when you need to apply a reviewed complete config, creating, overwriting, or removing Wabery Flows, automations, webhooks, and eligible Business Agent resources. Do not use it for one resource update when a narrow update tool fits. It never publishes Flows; use wabery_publish_flow separately.",
430
431
  inputSchema: {
431
432
  config: configSchema.describe("The full wabery.config.json object."),
432
433
  confirmation_token: z
@@ -438,7 +439,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
438
439
  readOnlyHint: false,
439
440
  destructiveHint: true,
440
441
  idempotentHint: true,
441
- openWorldHint: false,
442
+ openWorldHint: true,
442
443
  },
443
444
  }, async (input) => {
444
445
  const { config, confirmation_token } = input;
@@ -468,7 +469,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
468
469
  });
469
470
  registerTool("wabery_list_business_agents", {
470
471
  title: "List Meta Business Agents",
471
- description: "List Meta Business Agent records for the selected Wabery project.",
472
+ 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.",
472
473
  inputSchema: {
473
474
  channel_id: z.string().min(1).optional(),
474
475
  },
@@ -483,22 +484,22 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
483
484
  })));
484
485
  registerTool("wabery_check_business_agent_eligibility", {
485
486
  title: "Check Meta Business Agent eligibility",
486
- description: "Check whether a WhatsApp channel is eligible for Meta Business Agent.",
487
+ description: "Use this when you need to query Meta eligibility for a WhatsApp channel_id from wabery_list_channels before provisioning a Business Agent. This also caches the provider result in Wabery.",
487
488
  inputSchema: {
488
489
  channel_id: z.string().min(1),
489
490
  },
490
491
  annotations: {
491
- readOnlyHint: true,
492
- destructiveHint: false,
492
+ readOnlyHint: false,
493
+ destructiveHint: true,
493
494
  idempotentHint: true,
494
- openWorldHint: false,
495
+ openWorldHint: true,
495
496
  },
496
497
  }, async ({ channel_id }) => jsonResult(await client.get("/business-agents/eligibility", {
497
498
  channel_id,
498
499
  })));
499
500
  registerTool("wabery_apply_business_agent", {
500
501
  title: "Apply Meta Business Agent settings",
501
- description: "Provision or update a Meta Business Agent for a WhatsApp channel. Prefer wabery_apply_config when managing the whole project declaratively.",
502
+ 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.",
502
503
  inputSchema: {
503
504
  channel_id: z.string().min(1),
504
505
  enabled: z.boolean().optional(),
@@ -513,9 +514,9 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
513
514
  },
514
515
  annotations: {
515
516
  readOnlyHint: false,
516
- destructiveHint: false,
517
+ destructiveHint: true,
517
518
  idempotentHint: true,
518
- openWorldHint: false,
519
+ openWorldHint: true,
519
520
  },
520
521
  }, async (input) => {
521
522
  const { confirmation_token, ...body } = input;
@@ -526,7 +527,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
526
527
  });
527
528
  registerTool("wabery_get_business_agent_settings", {
528
529
  title: "Get Meta Business Agent settings",
529
- description: "Retrieve live Meta Business Agent settings.",
530
+ description: "Use this when you need live Meta settings for a business_agent_id from wabery_list_business_agents. Do not use it for the cached Wabery record.",
530
531
  inputSchema: {
531
532
  business_agent_id: z.string().min(1),
532
533
  },
@@ -539,7 +540,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
539
540
  }, async ({ business_agent_id }) => jsonResult(await client.get(`/business-agents/${encodeURIComponent(business_agent_id)}/settings`)));
540
541
  registerTool("wabery_update_business_agent_settings", {
541
542
  title: "Update Meta Business Agent settings",
542
- description: "Update live Meta Business Agent settings.",
543
+ 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.",
543
544
  inputSchema: {
544
545
  business_agent_id: z.string().min(1),
545
546
  enabled: z.boolean().optional(),
@@ -554,7 +555,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
554
555
  },
555
556
  annotations: {
556
557
  readOnlyHint: false,
557
- destructiveHint: false,
558
+ destructiveHint: true,
558
559
  idempotentHint: true,
559
560
  openWorldHint: true,
560
561
  },
@@ -567,7 +568,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
567
568
  });
568
569
  registerTool("wabery_list_business_agent_connectors", {
569
570
  title: "List Meta Business Agent connectors",
570
- description: "List connector records for Meta Business Agents in the selected Wabery project.",
571
+ description: "Use this when you need connector records and connector_id values for the selected project, optionally filtered by a business_agent_id from wabery_list_business_agents.",
571
572
  inputSchema: {
572
573
  business_agent_id: z.string().min(1).optional(),
573
574
  },
@@ -582,7 +583,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
582
583
  })));
583
584
  registerTool("wabery_create_business_agent_connector", {
584
585
  title: "Create Meta Business Agent connector",
585
- description: "Create a connector on a provisioned Meta Business Agent. Prefer wabery_apply_config for declarative project-wide changes.",
586
+ 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.",
586
587
  inputSchema: {
587
588
  business_agent_id: z.string().min(1),
588
589
  name: z.string().min(1).max(120),
@@ -596,9 +597,9 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
596
597
  },
597
598
  annotations: {
598
599
  readOnlyHint: false,
599
- destructiveHint: false,
600
+ destructiveHint: true,
600
601
  idempotentHint: true,
601
- openWorldHint: false,
602
+ openWorldHint: true,
602
603
  },
603
604
  }, async (input) => {
604
605
  const { confirmation_token, ...body } = input;
@@ -609,7 +610,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
609
610
  });
610
611
  registerTool("wabery_create_business_agent_connector_tool", {
611
612
  title: "Create Meta Business Agent connector tool",
612
- description: "Create a connector tool backed by a Wabery Flow, hosted function, API endpoint, or no-op placeholder.",
613
+ description: "Use this when you need to create a Meta connector tool for a connector_id from wabery_list_business_agent_connectors. backing_type is FLOW, HOSTED_FUNCTION, API_ENDPOINT, or NOOP; use the matching id from wabery_list_flows or wabery_list_functions.",
613
614
  inputSchema: {
614
615
  connector_id: z.string().min(1),
615
616
  name: z.string().min(1).max(120),
@@ -631,9 +632,9 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
631
632
  },
632
633
  annotations: {
633
634
  readOnlyHint: false,
634
- destructiveHint: false,
635
+ destructiveHint: true,
635
636
  idempotentHint: true,
636
- openWorldHint: false,
637
+ openWorldHint: true,
637
638
  },
638
639
  }, async (input) => {
639
640
  const { connector_id, confirmation_token, ...body } = input;
@@ -644,7 +645,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
644
645
  });
645
646
  registerTool("wabery_list_business_agent_connector_tools", {
646
647
  title: "List Meta Business Agent connector tools",
647
- description: "List tools for a Meta Business Agent connector.",
648
+ description: "Use this when you need local connector-tool records and tool_id values for a connector_id from wabery_list_business_agent_connectors.",
648
649
  inputSchema: {
649
650
  connector_id: z.string().min(1),
650
651
  },
@@ -657,7 +658,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
657
658
  }, async ({ connector_id }) => jsonResult(await client.get(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools`)));
658
659
  registerTool("wabery_get_business_agent_connector_tool", {
659
660
  title: "Get Meta Business Agent connector tool",
660
- description: "Fetch one Meta Business Agent connector tool.",
661
+ description: "Use this when you need one connector tool by connector_id and tool_id from wabery_list_business_agent_connector_tools.",
661
662
  inputSchema: {
662
663
  connector_id: z.string().min(1),
663
664
  tool_id: z.string().min(1),
@@ -671,7 +672,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
671
672
  }, async ({ connector_id, tool_id }) => jsonResult(await client.get(`/business-agents/connectors/${encodeURIComponent(connector_id)}/tools/${encodeURIComponent(tool_id)}`)));
672
673
  registerTool("wabery_update_business_agent_connector_tool", {
673
674
  title: "Update Meta Business Agent connector tool",
674
- description: "Update one Meta Business Agent connector tool.",
675
+ description: "Use this when you need to overwrite a Meta connector tool identified by connector_id and tool_id from wabery_list_business_agent_connector_tools. Use create only for a new tool.",
675
676
  inputSchema: {
676
677
  connector_id: z.string().min(1),
677
678
  tool_id: z.string().min(1),
@@ -691,9 +692,9 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
691
692
  },
692
693
  annotations: {
693
694
  readOnlyHint: false,
694
- destructiveHint: false,
695
+ destructiveHint: true,
695
696
  idempotentHint: true,
696
- openWorldHint: false,
697
+ openWorldHint: true,
697
698
  },
698
699
  }, async (input) => {
699
700
  const { connector_id, tool_id, confirmation_token, ...body } = input;
@@ -704,7 +705,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
704
705
  });
705
706
  registerTool("wabery_delete_business_agent_connector_tool", {
706
707
  title: "Delete Meta Business Agent connector tool",
707
- description: "Delete one Meta Business Agent connector tool.",
708
+ description: "Use this when you need to permanently delete a Meta connector tool identified by connector_id and tool_id from wabery_list_business_agent_connector_tools.",
708
709
  inputSchema: {
709
710
  connector_id: z.string().min(1),
710
711
  tool_id: z.string().min(1),
@@ -717,7 +718,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
717
718
  readOnlyHint: false,
718
719
  destructiveHint: true,
719
720
  idempotentHint: true,
720
- openWorldHint: false,
721
+ openWorldHint: true,
721
722
  },
722
723
  }, async ({ connector_id, tool_id, confirmation_token }) => {
723
724
  const blocked = await requireWrite("wabery_delete_business_agent_connector_tool", confirmation_token, "delete_business_agent_connector_tool");
@@ -727,7 +728,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
727
728
  });
728
729
  registerTool("wabery_get_business_agent_thread_control", {
729
730
  title: "Get Meta Business Agent thread control",
730
- description: "Inspect WhatsApp thread control for a channel/contact pair without changing its owner.",
731
+ 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.",
731
732
  inputSchema: {
732
733
  channel_id: z.string().min(1),
733
734
  contact_id: z.string().min(1),
@@ -746,7 +747,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
746
747
  })));
747
748
  registerTool("wabery_set_business_agent_thread_control", {
748
749
  title: "Set Meta Business Agent thread control",
749
- description: "Take or pass WhatsApp thread control for a channel/contact pair. Use the separate get tool to inspect current ownership.",
750
+ 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.",
750
751
  inputSchema: {
751
752
  action: z.enum(["take", "pass"]),
752
753
  channel_id: z.string().min(1),
@@ -762,7 +763,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
762
763
  },
763
764
  annotations: {
764
765
  readOnlyHint: false,
765
- destructiveHint: false,
766
+ destructiveHint: true,
766
767
  idempotentHint: true,
767
768
  openWorldHint: true,
768
769
  },
@@ -775,22 +776,31 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
775
776
  });
776
777
  registerTool("wabery_test_business_agent", {
777
778
  title: "Test Meta Business Agent",
778
- description: "Run a Meta Business Agent test scenario.",
779
+ 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.",
779
780
  inputSchema: {
780
781
  business_agent_id: z.string().min(1),
781
782
  input: z.string().min(1).optional(),
782
783
  scenario: z.record(z.string(), z.unknown()).optional(),
784
+ confirmation_token: z
785
+ .string()
786
+ .optional()
787
+ .describe("Required when write mode is enabled: test_business_agent"),
783
788
  },
784
789
  annotations: {
785
- readOnlyHint: true,
790
+ readOnlyHint: false,
786
791
  destructiveHint: false,
787
792
  idempotentHint: false,
788
793
  openWorldHint: true,
789
794
  },
790
- }, async (body) => jsonResult(await client.post("/business-agents/test", body)));
795
+ }, async ({ confirmation_token, ...body }) => {
796
+ const blocked = await requireWrite("wabery_test_business_agent", confirmation_token, "test_business_agent");
797
+ if (blocked)
798
+ return blocked;
799
+ return jsonResult(await client.post("/business-agents/test", body));
800
+ });
791
801
  registerTool("wabery_get_business_agent_eval", {
792
802
  title: "Get Meta Business Agent eval summary",
793
- description: "Retrieve the Meta Business Agent eval summary.",
803
+ 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.",
794
804
  inputSchema: {
795
805
  business_agent_id: z.string().min(1),
796
806
  },
@@ -805,7 +815,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
805
815
  })));
806
816
  registerTool("wabery_list_business_agent_skills", {
807
817
  title: "List Meta Business Agent skills",
808
- description: "List configured Meta Business Agent skills.",
818
+ 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.",
809
819
  inputSchema: {
810
820
  business_agent_id: z.string().min(1),
811
821
  },
@@ -820,7 +830,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
820
830
  })));
821
831
  registerTool("wabery_set_business_agent_skills", {
822
832
  title: "Set Meta Business Agent skills",
823
- description: "Replace configured Meta Business Agent skills.",
833
+ 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.",
824
834
  inputSchema: {
825
835
  business_agent_id: z.string().min(1),
826
836
  skills: z.array(z.unknown()),
@@ -843,7 +853,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
843
853
  });
844
854
  registerTool("wabery_list_projects", {
845
855
  title: "List Wabery projects",
846
- description: "List projects and their project_id values. Use project_id when creating flows or enrolling WhatsApp contacts.",
856
+ description: "Use this when you need to discover Wabery projects and their project_id values before selecting a project or passing an explicit project_id.",
847
857
  inputSchema: {},
848
858
  annotations: {
849
859
  readOnlyHint: true,
@@ -855,7 +865,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
855
865
  if (options.projectSelection) {
856
866
  registerTool("wabery_get_selected_project", {
857
867
  title: "Get selected Wabery project",
858
- description: "Return the project currently selected for this MCP session. If no project has been selected, Wabery uses the credential's default project.",
868
+ description: "Use this when you need to confirm which project this MCP session will use. A null selection means the credential's default project remains active.",
859
869
  inputSchema: {},
860
870
  annotations: {
861
871
  readOnlyHint: true,
@@ -870,7 +880,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
870
880
  }));
871
881
  registerTool("wabery_select_project", {
872
882
  title: "Select Wabery project",
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.",
883
+ description: "Use this when you need subsequent tools in this MCP session to target a project_id returned by wabery_list_projects. This changes session state, not the project itself.",
874
884
  inputSchema: {
875
885
  project_id: z.string().min(1),
876
886
  },
@@ -892,7 +902,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
892
902
  }
893
903
  registerTool("wabery_get_project_readiness", {
894
904
  title: "Get Wabery project readiness",
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.",
905
+ description: "Use this when you need capability readiness for the selected project or a project_id from wabery_list_projects: sandbox_test, production_inbound, flow_publish, flow_send, templates, proactive_messages, or submission_handling. Use channel readiness only for provider-specific phone details.",
896
906
  inputSchema: {
897
907
  project_id: z.string().min(1).optional(),
898
908
  requirements: z.array(readinessRequirementSchema).optional(),
@@ -906,7 +916,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
906
916
  }, async ({ project_id, requirements }) => jsonResult(await getReadiness(project_id, requirements)));
907
917
  registerTool("wabery_get_project", {
908
918
  title: "Get a Wabery project",
909
- description: "Fetch a single project's user-safe configuration. Secrets and provider credentials are omitted from MCP results.",
919
+ description: "Use this when you need one project's user-safe settings by project_id from wabery_list_projects. Do not use it for capability readiness; use wabery_get_project_readiness.",
910
920
  inputSchema: { project_id: z.string().min(1) },
911
921
  annotations: {
912
922
  readOnlyHint: true,
@@ -917,7 +927,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
917
927
  }, async ({ project_id }) => jsonResult(await client.get(`/projects/${encodeURIComponent(project_id)}`)));
918
928
  registerTool("wabery_update_project", {
919
929
  title: "Update a Wabery project",
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.",
930
+ description: "Use this when you need to overwrite project name, description, routing_mode, require_opt_in, or signed-webhook settings for a project_id from wabery_list_projects. Prefer wabery_apply_config for reviewed project-wide declarative changes.",
921
931
  inputSchema: {
922
932
  project_id: z.string().min(1),
923
933
  name: z.string().min(1).max(100).optional(),
@@ -927,25 +937,33 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
927
937
  webhook_url: z.string().url().nullable().optional(),
928
938
  webhook_signing_enabled: z.boolean().optional(),
929
939
  webhook_secret: z.string().min(16).max(256).optional(),
940
+ confirmation_token: z
941
+ .string()
942
+ .optional()
943
+ .describe("Required when write mode is enabled: update_project"),
930
944
  },
931
945
  annotations: {
932
946
  readOnlyHint: false,
933
- destructiveHint: false,
947
+ destructiveHint: true,
934
948
  idempotentHint: false,
935
- openWorldHint: false,
949
+ openWorldHint: true,
936
950
  },
937
- }, async ({ project_id, ...body }) => {
938
- const blocked = await requireWrite("wabery_update_project");
951
+ }, async ({ project_id, confirmation_token, ...body }) => {
952
+ const blocked = await requireWrite("wabery_update_project", confirmation_token, "update_project");
939
953
  if (blocked)
940
954
  return blocked;
941
955
  return jsonResult(await client.patch(`/projects/${encodeURIComponent(project_id)}`, body));
942
956
  });
943
957
  registerTool("wabery_rotate_webhook_secret", {
944
958
  title: "Rotate a project's webhook 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.",
959
+ description: "Use this when you need to invalidate and replace the signed-webhook secret for a project_id from wabery_list_projects. The MCP result never returns the new secret; manage it in the Wabery dashboard.",
946
960
  inputSchema: {
947
961
  project_id: z.string().min(1),
948
962
  webhook_secret: z.string().min(16).max(256).optional(),
963
+ confirmation_token: z
964
+ .string()
965
+ .optional()
966
+ .describe("Required when write mode is enabled: rotate_webhook_secret"),
949
967
  },
950
968
  annotations: {
951
969
  readOnlyHint: false,
@@ -953,15 +971,15 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
953
971
  idempotentHint: false,
954
972
  openWorldHint: false,
955
973
  },
956
- }, async ({ project_id, webhook_secret }) => {
957
- const blocked = await requireWrite("wabery_rotate_webhook_secret");
974
+ }, async ({ project_id, webhook_secret, confirmation_token }) => {
975
+ const blocked = await requireWrite("wabery_rotate_webhook_secret", confirmation_token, "rotate_webhook_secret");
958
976
  if (blocked)
959
977
  return blocked;
960
978
  return jsonResult(await client.post(`/projects/${encodeURIComponent(project_id)}/rotate-webhook-secret`, webhook_secret ? { webhook_secret } : undefined));
961
979
  });
962
980
  registerTool("wabery_list_channels", {
963
981
  title: "List Wabery channels",
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.",
982
+ description: "Use this when you need WhatsApp channel_id values, connection state, routing warnings, or provider publish_readiness. Use project readiness first for capability-level discovery.",
965
983
  inputSchema: {},
966
984
  annotations: {
967
985
  readOnlyHint: true,
@@ -972,7 +990,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
972
990
  }, async () => jsonResult(await client.get("/channels")));
973
991
  registerTool("wabery_get_channel", {
974
992
  title: "Get a Wabery channel",
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.",
993
+ description: "Use this when you need routing_warning and WhatsApp publish_readiness for a channel_id from wabery_list_channels. Do not use it as a substitute for project-level readiness.",
976
994
  inputSchema: { channel_id: z.string().min(1) },
977
995
  annotations: {
978
996
  readOnlyHint: true,
@@ -983,19 +1001,23 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
983
1001
  }, async ({ channel_id }) => jsonResult(await client.get(`/channels/${encodeURIComponent(channel_id)}`)));
984
1002
  registerTool("wabery_update_channel", {
985
1003
  title: "Update a Wabery channel's routing",
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.",
1004
+ description: "Use this when you need to overwrite a dedicated channel's routing_mode with NONE, FLOWS, or EXTERNAL using a channel_id from wabery_list_channels. Do not use it for the shared sandbox; update project routing instead.",
987
1005
  inputSchema: {
988
1006
  channel_id: z.string().min(1),
989
1007
  routing_mode: z.enum(["NONE", "FLOWS", "EXTERNAL"]),
1008
+ confirmation_token: z
1009
+ .string()
1010
+ .optional()
1011
+ .describe("Required when write mode is enabled: update_channel"),
990
1012
  },
991
1013
  annotations: {
992
1014
  readOnlyHint: false,
993
- destructiveHint: false,
1015
+ destructiveHint: true,
994
1016
  idempotentHint: true,
995
- openWorldHint: false,
1017
+ openWorldHint: true,
996
1018
  },
997
- }, async ({ channel_id, routing_mode }) => {
998
- const blocked = await requireWrite("wabery_update_channel");
1019
+ }, async ({ channel_id, routing_mode, confirmation_token }) => {
1020
+ const blocked = await requireWrite("wabery_update_channel", confirmation_token, "update_channel");
999
1021
  if (blocked)
1000
1022
  return blocked;
1001
1023
  return jsonResult(await client.patch(`/channels/${encodeURIComponent(channel_id)}`, {
@@ -1004,7 +1026,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1004
1026
  });
1005
1027
  registerTool("wabery_create_registration_intent", {
1006
1028
  title: "Create registration intent",
1007
- description: "Mint a single-use wa.me registration link so an end-customer can connect over WhatsApp.",
1029
+ description: "Use this when you need a single-use wa.me link for an end customer to register with a project_id from wabery_list_projects. This creates the link but does not contact the customer.",
1008
1030
  inputSchema: {
1009
1031
  project_id: z
1010
1032
  .string()
@@ -1035,7 +1057,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1035
1057
  });
1036
1058
  registerTool("wabery_get_registration_intent", {
1037
1059
  title: "Get registration intent",
1038
- description: "Poll a registration intent's status. Works with a publishable key or a scoped secret key.",
1060
+ description: "Use this when you need to poll a registration intent by intent_id returned by wabery_create_registration_intent. Do not create another intent merely to check status.",
1039
1061
  inputSchema: {
1040
1062
  intent_id: z.string().min(1),
1041
1063
  },
@@ -1048,7 +1070,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1048
1070
  }, async ({ intent_id }) => jsonResult(await client.get(`/registration-intents/${encodeURIComponent(intent_id)}`)));
1049
1071
  registerTool("wabery_get_limits", {
1050
1072
  title: "Get Wabery API limits",
1051
- description: "Return the enforced public API rate limit and monthly request quota for the configured API key.",
1073
+ description: "Use this when you need the current credential's enforced public API rate limit and monthly request quota. Do not use it for product-plan feature readiness.",
1052
1074
  inputSchema: {},
1053
1075
  annotations: {
1054
1076
  readOnlyHint: true,
@@ -1059,7 +1081,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1059
1081
  }, async () => jsonResult(await client.get("/limits")));
1060
1082
  registerTool("wabery_list_flows", {
1061
1083
  title: "List Wabery flows",
1062
- description: "List flows, including ids, config keys, project ids, and Meta publish status.",
1084
+ description: "Use this when you need WhatsApp Flow ids, config keys, project ids, or Meta publish statuses before get, publish, send, dispatch, or submission operations.",
1063
1085
  inputSchema: {},
1064
1086
  annotations: {
1065
1087
  readOnlyHint: true,
@@ -1070,7 +1092,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1070
1092
  }, async () => jsonResult(await client.get("/flows")));
1071
1093
  registerTool("wabery_get_flow", {
1072
1094
  title: "Get Wabery flow",
1073
- description: "Fetch one flow by id, including status and field schema.",
1095
+ description: "Use this when you need one WhatsApp Flow's draft, field schema, and publish status by flow_id from wabery_list_flows.",
1074
1096
  inputSchema: {
1075
1097
  flow_id: z.string().min(1),
1076
1098
  },
@@ -1083,7 +1105,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1083
1105
  }, async ({ flow_id }) => jsonResult(await client.get(`/flows/${flow_id}`)));
1084
1106
  registerTool("wabery_publish_flow", {
1085
1107
  title: "Publish Wabery flow",
1086
- description: "Publish a Wabery draft flow to Meta. Only call after validate/diff/apply and explicit user confirmation.",
1108
+ description: "Use this when you need to publish a reviewed draft WhatsApp Flow to Meta using a flow_id from wabery_list_flows. Do not use it to save a draft; use config apply, and obtain explicit confirmation first.",
1087
1109
  inputSchema: {
1088
1110
  flow_id: z.string().min(1),
1089
1111
  confirmation_token: z
@@ -1105,7 +1127,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1105
1127
  });
1106
1128
  registerTool("wabery_enroll_contact", {
1107
1129
  title: "Enroll WhatsApp contact",
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.",
1130
+ description: "Use this when you need to register a controlled or explicitly opted-in WhatsApp phone number for a project_id from wabery_list_projects. It returns stable contact_id and channel_id values for later sends.",
1109
1131
  inputSchema: {
1110
1132
  phone: z.string().min(5).describe("E.164 phone number."),
1111
1133
  project_id: z.string().min(1),
@@ -1126,7 +1148,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1126
1148
  },
1127
1149
  annotations: {
1128
1150
  readOnlyHint: false,
1129
- destructiveHint: false,
1151
+ destructiveHint: true,
1130
1152
  idempotentHint: true,
1131
1153
  openWorldHint: false,
1132
1154
  },
@@ -1138,7 +1160,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1138
1160
  });
1139
1161
  registerTool("wabery_list_contacts", {
1140
1162
  title: "List Wabery contacts",
1141
- description: "List enrolled contacts and their stable contact_id values.",
1163
+ description: "Use this when you need enrolled WhatsApp contacts, stable contact_id values, and opt-in metadata before a targeted send or audience preparation.",
1142
1164
  inputSchema: {
1143
1165
  limit: z.number().int().min(1).max(100).optional(),
1144
1166
  starting_after: z.string().optional(),
@@ -1152,7 +1174,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1152
1174
  }, async ({ limit, starting_after }) => jsonResult(await client.get("/contacts", { limit, starting_after })));
1153
1175
  registerTool("wabery_get_contact", {
1154
1176
  title: "Get Wabery contact",
1155
- description: "Fetch one enrolled contact by stable contact_id before sending or unenrolling.",
1177
+ description: "Use this when you need one enrolled WhatsApp contact and consent state by contact_id from wabery_list_contacts or wabery_enroll_contact.",
1156
1178
  inputSchema: {
1157
1179
  contact_id: z.string().min(1),
1158
1180
  },
@@ -1165,7 +1187,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1165
1187
  }, async ({ contact_id }) => jsonResult(await client.get(`/contacts/${contact_id}`)));
1166
1188
  registerTool("wabery_list_contact_imports", {
1167
1189
  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.",
1190
+ description: "Use this when you need CSV import_id values and processing counts after uploading through the dashboard or CLI. Only a completed import belongs in wabery_prepare_broadcast_audience.",
1169
1191
  inputSchema: {
1170
1192
  limit: z.number().int().min(1).max(100).optional(),
1171
1193
  starting_after: z.string().optional(),
@@ -1179,7 +1201,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1179
1201
  }, async (query) => apiResult(client.get("/contact-imports", query)));
1180
1202
  registerTool("wabery_get_contact_import", {
1181
1203
  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.",
1204
+ description: "Use this when you need status and row counts for an import_id from wabery_list_contact_imports. Wait for completed before broadcast preparation.",
1183
1205
  inputSchema: {
1184
1206
  import_id: z.string().min(1),
1185
1207
  },
@@ -1192,7 +1214,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1192
1214
  }, async ({ import_id }) => apiResult(client.get(`/contact-imports/${import_id}`)));
1193
1215
  registerTool("wabery_unenroll_contact", {
1194
1216
  title: "Unenroll Wabery contact",
1195
- description: "Remove a contact phone's sandbox authorization for this organization. Pass erase=true for irreversible org-scoped contact data deletion.",
1217
+ description: "Use this when you need to remove sandbox authorization for a contact_id from wabery_list_contacts. Set erase=true only for confirmed, irreversible organization-scoped contact-data deletion.",
1196
1218
  inputSchema: {
1197
1219
  contact_id: z.string().min(1),
1198
1220
  erase: z.boolean().optional(),
@@ -1215,7 +1237,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1215
1237
  });
1216
1238
  registerTool("wabery_list_broadcasts", {
1217
1239
  title: "List Wabery broadcasts",
1218
- description: "List broadcast campaigns and delivery counts. Filter by lifecycle status when monitoring preparation or delivery.",
1240
+ description: "Use this when you need WhatsApp broadcast_id values, lifecycle states, or aggregate delivery counts. Filter by status when monitoring preparation or delivery.",
1219
1241
  inputSchema: {
1220
1242
  limit: z.number().int().min(1).max(100).optional(),
1221
1243
  starting_after: z.string().optional(),
@@ -1230,7 +1252,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1230
1252
  }, async (query) => apiResult(client.get("/broadcasts", query)));
1231
1253
  registerTool("wabery_get_broadcast", {
1232
1254
  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.",
1255
+ description: "Use this when you need lifecycle status and recipient counts for a broadcast_id from wabery_list_broadcasts. Poll while preparing and review counts before send or schedule confirmation.",
1234
1256
  inputSchema: {
1235
1257
  broadcast_id: z.string().min(1),
1236
1258
  },
@@ -1243,7 +1265,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1243
1265
  }, async ({ broadcast_id }) => apiResult(client.get(`/broadcasts/${broadcast_id}`)));
1244
1266
  registerTool("wabery_create_broadcast", {
1245
1267
  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.",
1268
+ description: "Use this when you need a new WhatsApp broadcast draft from approved template variants and a channel_id from wabery_list_channels. This does not select recipients or send; use prepare next.",
1247
1269
  inputSchema: {
1248
1270
  name: z.string().min(1).max(120),
1249
1271
  channel_id: z.string().min(1),
@@ -1286,7 +1308,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1286
1308
  });
1287
1309
  registerTool("wabery_prepare_broadcast_audience", {
1288
1310
  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.",
1311
+ description: "Use this when you need to start asynchronous audience resolution and overwrite the frozen recipient snapshot for a draft broadcast_id. Supply exactly one source: contact_ids from wabery_list_contacts, a completed import_id, or filter. This does not send; poll wabery_get_broadcast.",
1290
1312
  inputSchema: {
1291
1313
  broadcast_id: z.string().min(1),
1292
1314
  contact_ids: z.array(z.string().min(1)).max(1000).optional(),
@@ -1296,7 +1318,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1296
1318
  },
1297
1319
  annotations: {
1298
1320
  readOnlyHint: false,
1299
- destructiveHint: false,
1321
+ destructiveHint: true,
1300
1322
  idempotentHint: true,
1301
1323
  openWorldHint: false,
1302
1324
  },
@@ -1332,7 +1354,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1332
1354
  });
1333
1355
  registerTool("wabery_list_broadcast_recipients", {
1334
1356
  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.",
1357
+ description: "Use this when you need the frozen recipients, language selection, exclusions, failures, or delivery state for a prepared broadcast_id from wabery_list_broadcasts.",
1336
1358
  inputSchema: {
1337
1359
  broadcast_id: z.string().min(1),
1338
1360
  limit: z.number().int().min(1).max(100).optional(),
@@ -1348,7 +1370,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1348
1370
  }, async ({ broadcast_id, ...query }) => apiResult(client.get(`/broadcasts/${broadcast_id}/recipients`, query)));
1349
1371
  registerTool("wabery_send_broadcast", {
1350
1372
  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>.",
1373
+ description: "Use this when you need to start real WhatsApp delivery for a ready broadcast_id after reviewing its frozen audience and counts. Do not use it for preparation; explicit confirmation is required.",
1352
1374
  inputSchema: {
1353
1375
  broadcast_id: z.string().min(1),
1354
1376
  confirmation_token: z
@@ -1370,7 +1392,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1370
1392
  });
1371
1393
  registerTool("wabery_schedule_broadcast", {
1372
1394
  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>.",
1395
+ description: "Use this when you need future WhatsApp delivery for a ready broadcast_id after reviewing its frozen audience, scheduled_at, and IANA timezone. Do not use it for immediate delivery; explicit confirmation is required.",
1374
1396
  inputSchema: {
1375
1397
  broadcast_id: z.string().min(1),
1376
1398
  scheduled_at: z.string().datetime(),
@@ -1397,7 +1419,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1397
1419
  });
1398
1420
  registerTool("wabery_cancel_broadcast", {
1399
1421
  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>.",
1422
+ description: "Use this when you need to cancel a broadcast_id and stop recipients not yet dispatched. Already accepted WhatsApp messages cannot be recalled; explicit confirmation is required.",
1401
1423
  inputSchema: {
1402
1424
  broadcast_id: z.string().min(1),
1403
1425
  confirmation_token: z
@@ -1419,7 +1441,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1419
1441
  });
1420
1442
  registerTool("wabery_duplicate_broadcast", {
1421
1443
  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.",
1444
+ description: "Use this when you need a new draft copied from a broadcast_id's channel, template variants, and mappings. The audience is never copied; prepare a new controlled audience.",
1423
1445
  inputSchema: {
1424
1446
  broadcast_id: z.string().min(1),
1425
1447
  },
@@ -1437,7 +1459,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1437
1459
  });
1438
1460
  registerTool("wabery_list_conversations", {
1439
1461
  title: "List Wabery conversations",
1440
- description: "List organization-scoped conversations for inbox, support, or analytics tooling.",
1462
+ description: "Use this when you need organization-scoped WhatsApp conversation_id values for inbox review, support, or message-history lookup. Filter by contact_id when known.",
1441
1463
  inputSchema: {
1442
1464
  contact_id: z.string().optional(),
1443
1465
  active: z.boolean().optional(),
@@ -1456,7 +1478,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1456
1478
  })));
1457
1479
  registerTool("wabery_get_conversation", {
1458
1480
  title: "Get Wabery conversation",
1459
- description: "Fetch one organization-scoped conversation by id.",
1481
+ description: "Use this when you need one organization-scoped WhatsApp conversation by conversation_id from wabery_list_conversations.",
1460
1482
  inputSchema: {
1461
1483
  conversation_id: z.string().min(1),
1462
1484
  },
@@ -1469,7 +1491,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1469
1491
  }, async ({ conversation_id }) => jsonResult(await client.get(`/conversations/${conversation_id}`)));
1470
1492
  registerTool("wabery_list_conversation_messages", {
1471
1493
  title: "List Wabery conversation messages",
1472
- description: "Fetch message history for a conversation. Cached inbound WhatsApp media includes short-lived Wabery signed URLs; expires_at is the retention deadline.",
1494
+ description: "Use this when you need message history for a conversation_id from wabery_list_conversations. Retained inbound media may include short-lived signed URLs; expires_at is the retention deadline.",
1473
1495
  inputSchema: {
1474
1496
  conversation_id: z.string().min(1),
1475
1497
  limit: z.number().int().min(1).max(100).optional(),
@@ -1484,10 +1506,16 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1484
1506
  },
1485
1507
  }, async ({ conversation_id, ...query }) => jsonResult(await client.get(`/conversations/${conversation_id}/messages`, query)));
1486
1508
  registerTool("wabery_send_message", {
1487
- title: "Send Wabery message",
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.",
1509
+ title: "Send WhatsApp message",
1510
+ description: "Use this when you need to send one real WhatsApp text, approved template, media, or interactive message. Do not use it to send a Flow; use wabery_send_flow. The body must identify channel and recipient using ids from list tools and should include idempotency_key; explicit confirmation is required.",
1489
1511
  inputSchema: {
1490
- body: z.record(z.string(), z.unknown()),
1512
+ body: z
1513
+ .record(z.string(), z.unknown())
1514
+ .describe("POST /messages body. Include channel_id plus contact_id, conversation_id, or to; include type-specific content and an idempotency_key for safe retries."),
1515
+ confirmation_token: z
1516
+ .string()
1517
+ .optional()
1518
+ .describe("Required when write mode is enabled: send_message"),
1491
1519
  },
1492
1520
  annotations: {
1493
1521
  readOnlyHint: false,
@@ -1495,15 +1523,15 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1495
1523
  idempotentHint: false,
1496
1524
  openWorldHint: true,
1497
1525
  },
1498
- }, async ({ body }) => {
1499
- const blocked = await requireWrite("wabery_send_message");
1526
+ }, async ({ body, confirmation_token }) => {
1527
+ const blocked = await requireWrite("wabery_send_message", confirmation_token, "send_message");
1500
1528
  if (blocked)
1501
1529
  return blocked;
1502
1530
  return apiResult(client.post("/messages", body));
1503
1531
  });
1504
1532
  registerTool("wabery_get_message", {
1505
1533
  title: "Get Wabery message",
1506
- description: "Fetch one message by message_id to inspect queued/provider status, delivery state, and Meta failure details after wabery_send_message.",
1534
+ description: "Use this when you need queued, provider, delivery, or failure status for a message_id returned by wabery_send_message or message history. An accepted send is not proof of delivery.",
1507
1535
  inputSchema: {
1508
1536
  message_id: z.string().min(1),
1509
1537
  },
@@ -1516,7 +1544,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1516
1544
  }, async ({ message_id }) => jsonResult(await client.get(`/messages/${message_id}`)));
1517
1545
  registerTool("wabery_list_templates", {
1518
1546
  title: "List WhatsApp templates",
1519
- description: "List WhatsApp message templates and approval statuses for dedicated WhatsApp channels.",
1547
+ description: "Use this when you need WhatsApp template_id values and approval states, optionally for a channel_id from wabery_list_channels, before template sends or broadcasts.",
1520
1548
  inputSchema: {
1521
1549
  channel_id: z.string().optional(),
1522
1550
  status: z.string().optional(),
@@ -1530,59 +1558,65 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1530
1558
  }, async (query) => jsonResult(await client.get("/templates", query)));
1531
1559
  registerTool("wabery_get_template", {
1532
1560
  title: "Get WhatsApp template",
1533
- description: "Fetch one WhatsApp template and its Meta approval status. Set refresh=true to force a live Meta status check.",
1561
+ description: "Use this when you need one template by template_id from wabery_list_templates. Set refresh=true only for a live Meta check that also updates Wabery's cached status.",
1534
1562
  inputSchema: {
1535
1563
  template_id: z.string().min(1),
1536
1564
  refresh: z.boolean().optional(),
1537
1565
  },
1538
1566
  annotations: {
1539
- readOnlyHint: true,
1567
+ readOnlyHint: false,
1540
1568
  destructiveHint: false,
1541
1569
  idempotentHint: true,
1542
- openWorldHint: false,
1570
+ openWorldHint: true,
1543
1571
  },
1544
1572
  }, async ({ template_id, refresh }) => jsonResult(await client.get(`/templates/${template_id}`, {
1545
1573
  refresh: refresh ? "true" : undefined,
1546
1574
  })));
1547
1575
  registerTool("wabery_create_template", {
1548
1576
  title: "Create WhatsApp template",
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.",
1577
+ description: "Use this when you need to submit a new WhatsApp template to Meta for approval after checking channel publish_readiness. Do not use it to inspect or send an existing template; explicit confirmation is required.",
1550
1578
  inputSchema: {
1551
- body: z.record(z.string(), z.unknown()),
1579
+ body: z
1580
+ .record(z.string(), z.unknown())
1581
+ .describe("Template submission body including a dedicated WhatsApp channel_id, name, category, language, and components accepted by POST /templates."),
1582
+ confirmation_token: z
1583
+ .string()
1584
+ .optional()
1585
+ .describe("Required when write mode is enabled: create_template"),
1552
1586
  },
1553
1587
  annotations: {
1554
1588
  readOnlyHint: false,
1555
- destructiveHint: false,
1589
+ destructiveHint: true,
1556
1590
  idempotentHint: false,
1557
1591
  openWorldHint: true,
1558
1592
  },
1559
- }, async ({ body }) => {
1560
- const blocked = await requireWrite("wabery_create_template");
1593
+ }, async ({ body, confirmation_token }) => {
1594
+ const blocked = await requireWrite("wabery_create_template", confirmation_token, "create_template");
1561
1595
  if (blocked)
1562
1596
  return blocked;
1563
1597
  return apiResult(client.post("/templates", body));
1564
1598
  });
1565
1599
  registerTool("wabery_wait_template", {
1566
1600
  title: "Wait for WhatsApp template approval",
1567
- description: "Poll a WhatsApp template until Meta reports APPROVED. Fails if Meta rejects/disables it or if timeout_seconds elapses.",
1601
+ description: "Use this when you need to poll Meta for a template_id from wabery_list_templates until APPROVED or timeout. This repeatedly refreshes Wabery's cached provider status; do not use it for a single cached read.",
1568
1602
  inputSchema: {
1569
1603
  template_id: z.string().min(1),
1570
1604
  timeout_seconds: z.number().int().min(1).max(86_400).optional(),
1571
1605
  interval_seconds: z.number().int().min(1).max(300).optional(),
1572
1606
  },
1573
1607
  annotations: {
1574
- readOnlyHint: true,
1608
+ readOnlyHint: false,
1575
1609
  destructiveHint: false,
1576
1610
  idempotentHint: true,
1577
- openWorldHint: false,
1611
+ openWorldHint: true,
1578
1612
  },
1579
1613
  }, async ({ template_id, timeout_seconds, interval_seconds }) => jsonResult(await waitUntilTemplateApproved(client, template_id, {
1580
1614
  timeoutMs: (timeout_seconds ?? 86_400) * 1000,
1581
1615
  intervalMs: (interval_seconds ?? 60) * 1000,
1582
1616
  })));
1583
1617
  registerTool("wabery_send_flow", {
1584
- title: "Send Wabery flow",
1585
- description: "Send a published WhatsApp Flow to an enrolled contact, existing conversation, or phone number. Returns flow_token and dispatch info.",
1618
+ title: "Send WhatsApp Flow",
1619
+ description: "Use this when you need to send one published WhatsApp Flow using flow_id and channel_id from list tools plus exactly one controlled contact_id, conversation_id, or phone number. Do not use wabery_send_message for a Flow; explicit confirmation is required.",
1586
1620
  inputSchema: {
1587
1621
  flow_id: z.string().min(1),
1588
1622
  channel_id: z.string().min(1),
@@ -1595,6 +1629,10 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1595
1629
  footer_text: z.string().max(60).optional(),
1596
1630
  first_screen: z.string().optional(),
1597
1631
  screen_data: z.record(z.string(), z.unknown()).optional(),
1632
+ confirmation_token: z
1633
+ .string()
1634
+ .optional()
1635
+ .describe("Required when write mode is enabled: send_flow:<flow_id>"),
1598
1636
  },
1599
1637
  annotations: {
1600
1638
  readOnlyHint: false,
@@ -1602,15 +1640,15 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1602
1640
  idempotentHint: false,
1603
1641
  openWorldHint: true,
1604
1642
  },
1605
- }, async ({ flow_id, ...body }) => {
1606
- const blocked = await requireWrite("wabery_send_flow");
1643
+ }, async ({ flow_id, confirmation_token, ...body }) => {
1644
+ const blocked = await requireWrite("wabery_send_flow", confirmation_token, `send_flow:${flow_id}`);
1607
1645
  if (blocked)
1608
1646
  return blocked;
1609
1647
  return apiResult(client.post(`/flows/${flow_id}/send`, body));
1610
1648
  });
1611
1649
  registerTool("wabery_list_submissions", {
1612
1650
  title: "List Wabery submissions",
1613
- description: "List completed WhatsApp Flow submissions for the organization.",
1651
+ description: "Use this when you need completed WhatsApp Flow submissions, optionally filtered by project agent_id or flow_id from wabery_list_flows.",
1614
1652
  inputSchema: {
1615
1653
  agent_id: z.string().optional(),
1616
1654
  flow_id: z.string().optional(),
@@ -1626,7 +1664,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1626
1664
  }, async (query) => jsonResult(await client.get("/submissions", query)));
1627
1665
  registerTool("wabery_list_dispatches", {
1628
1666
  title: "List Wabery dispatches",
1629
- description: "List sent flow dispatches. Use this to inspect flow_token status across sends.",
1667
+ description: "Use this when you need sent WhatsApp Flow dispatches and flow_token values, optionally filtered by flow_id or contact_id. Use wabery_get_dispatch for one token.",
1630
1668
  inputSchema: {
1631
1669
  flow_id: z.string().optional(),
1632
1670
  contact_id: z.string().optional(),
@@ -1643,7 +1681,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1643
1681
  }, async (query) => jsonResult(await client.get("/dispatches", query)));
1644
1682
  registerTool("wabery_get_dispatch", {
1645
1683
  title: "Get Wabery dispatch",
1646
- description: "Get one flow dispatch by flow_token.",
1684
+ description: "Use this when you need provider and completion status for one WhatsApp Flow dispatch by flow_token returned by wabery_send_flow or wabery_list_dispatches.",
1647
1685
  inputSchema: {
1648
1686
  flow_token: z.string().min(1),
1649
1687
  },
@@ -1657,7 +1695,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1657
1695
  // ─── Hosted functions (serverless logic the agent authors) ───────────
1658
1696
  registerTool("wabery_list_functions", {
1659
1697
  title: "List hosted functions",
1660
- description: "List the project's hosted functions (serverless handlers that run on send/tool triggers).",
1698
+ description: "Use this when you need hosted function_id values, deployment state, or exposure settings for the selected project before get, deploy, test, invoke, update, or delete operations.",
1661
1699
  inputSchema: {},
1662
1700
  annotations: {
1663
1701
  readOnlyHint: true,
@@ -1668,7 +1706,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1668
1706
  }, async () => jsonResult(await client.get("/functions")));
1669
1707
  registerTool("wabery_get_function", {
1670
1708
  title: "Get a hosted function",
1671
- description: "Fetch one hosted function including its current source.",
1709
+ description: "Use this when you need metadata and current source for a function_id from wabery_list_functions. Do not use it to execute the function.",
1672
1710
  inputSchema: { function_id: z.string().min(1) },
1673
1711
  annotations: {
1674
1712
  readOnlyHint: true,
@@ -1679,7 +1717,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1679
1717
  }, async ({ function_id }) => jsonResult(await client.get(`/functions/${function_id}`)));
1680
1718
  registerTool("wabery_create_function", {
1681
1719
  title: "Create a hosted function",
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.",
1720
+ description: "Use this when you need a new hosted-function record without deploying source. trigger_type accepts values such as KEYWORD, WELCOME, or ANY_MESSAGE; use wabery_deploy_function next.",
1683
1721
  inputSchema: {
1684
1722
  slug: z
1685
1723
  .string()
@@ -1708,7 +1746,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1708
1746
  });
1709
1747
  registerTool("wabery_deploy_function", {
1710
1748
  title: "Deploy a hosted function",
1711
- description: "Bundle and upload TypeScript source for a hosted function. Returns { status, version_id?, diagnostics? } — read diagnostics on error, fix, and redeploy.",
1749
+ description: "Use this when you need to bundle and overwrite the active TypeScript deployment for a function_id from wabery_list_functions. Do not use it to execute code; use test or invoke after reviewing diagnostics.",
1712
1750
  inputSchema: {
1713
1751
  function_id: z.string().min(1),
1714
1752
  source: z.string().min(1),
@@ -1719,7 +1757,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1719
1757
  },
1720
1758
  annotations: {
1721
1759
  readOnlyHint: false,
1722
- destructiveHint: false,
1760
+ destructiveHint: true,
1723
1761
  idempotentHint: true,
1724
1762
  openWorldHint: false,
1725
1763
  },
@@ -1731,7 +1769,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1731
1769
  });
1732
1770
  registerTool("wabery_test_function", {
1733
1771
  title: "Test a hosted function",
1734
- description: "Invoke a hosted function with a sample message event (no real send) and return its result plus logs.",
1772
+ description: "Use this when you need to execute a function_id with a synthetic message event and inspect its result and logs. Do not use it as a side-effect-free preview: function code can persist data or call external APIs; use wabery_invoke_function for a real structured tool call.",
1735
1773
  inputSchema: {
1736
1774
  function_id: z.string().min(1),
1737
1775
  text: z.string().max(4000).optional(),
@@ -1742,9 +1780,9 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1742
1780
  },
1743
1781
  annotations: {
1744
1782
  readOnlyHint: false,
1745
- destructiveHint: false,
1746
- idempotentHint: true,
1747
- openWorldHint: false,
1783
+ destructiveHint: true,
1784
+ idempotentHint: false,
1785
+ openWorldHint: true,
1748
1786
  },
1749
1787
  }, async ({ function_id, text, confirmation_token }) => {
1750
1788
  const blocked = await requireWrite("wabery_test_function", confirmation_token, "test_function");
@@ -1754,7 +1792,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1754
1792
  });
1755
1793
  registerTool("wabery_tail_function_logs", {
1756
1794
  title: "Tail hosted function logs",
1757
- description: "Recent invocations of a hosted function (status, cpu, error, captured logs) for debugging.",
1795
+ description: "Use this when you need recent invocation status, CPU, errors, or captured logs for a function_id from wabery_list_functions. This does not execute the function.",
1758
1796
  inputSchema: { function_id: z.string().min(1) },
1759
1797
  annotations: {
1760
1798
  readOnlyHint: true,
@@ -1765,7 +1803,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1765
1803
  }, async ({ function_id }) => jsonResult(await client.get(`/functions/${function_id}/logs`)));
1766
1804
  registerTool("wabery_delete_function", {
1767
1805
  title: "Delete a hosted function",
1768
- description: "Delete a hosted function and its deployed Worker.",
1806
+ description: "Use this when you need to permanently delete a function_id from wabery_list_functions and its deployed Worker. Do not use update to simulate deletion.",
1769
1807
  inputSchema: {
1770
1808
  function_id: z.string().min(1),
1771
1809
  confirmation_token: z
@@ -1787,7 +1825,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1787
1825
  });
1788
1826
  registerTool("wabery_update_function", {
1789
1827
  title: "Update a hosted function",
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).",
1828
+ description: "Use this when you need to overwrite metadata for a function_id from wabery_list_functions: name, expose_as_mcp_tool, is_active, or input_schema. Do not use it to deploy source or invoke code.",
1791
1829
  inputSchema: {
1792
1830
  function_id: z.string().min(1),
1793
1831
  name: z.string().min(1).max(120).optional(),
@@ -1801,7 +1839,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1801
1839
  },
1802
1840
  annotations: {
1803
1841
  readOnlyHint: false,
1804
- destructiveHint: false,
1842
+ destructiveHint: true,
1805
1843
  idempotentHint: true,
1806
1844
  openWorldHint: false,
1807
1845
  },
@@ -1814,7 +1852,7 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1814
1852
  });
1815
1853
  registerTool("wabery_invoke_function", {
1816
1854
  title: "Invoke a hosted function as a tool",
1817
- description: "Call a hosted function as a tool (mcp_call) with structured arguments and return its result. Optionally target a specific contact.",
1855
+ description: "Use this when you need a real structured mcp_call to an exposed function_id from wabery_list_functions, optionally with contact_id from wabery_list_contacts. Do not use it for synthetic testing; arbitrary function code can persist data, call APIs, or send messages.",
1818
1856
  inputSchema: {
1819
1857
  function_id: z.string().min(1),
1820
1858
  arguments: z.record(z.string(), z.unknown()).optional(),
@@ -1826,8 +1864,8 @@ export function createWaberyMcpServer(client = new WaberyApiClient(), options =
1826
1864
  },
1827
1865
  annotations: {
1828
1866
  readOnlyHint: false,
1829
- destructiveHint: false,
1830
- idempotentHint: true,
1867
+ destructiveHint: true,
1868
+ idempotentHint: false,
1831
1869
  openWorldHint: true,
1832
1870
  },
1833
1871
  }, async ({ function_id, arguments: args, contact_id, confirmation_token, }) => {