wacrm/mcp-server/src/tools/broadcast.ts
Arnas Donauskas 78f2f79d3b Add MCP server for the public API
Introduce mcp-server/ — a stdio Model Context Protocol server that
wraps the /api/v1 public API so MCP clients (Claude, Cursor, etc.) can
drive a self-hosted wacrm instance in natural language.

- Thin wrapper over the existing REST API; all auth, scoping, and rate
  limiting stay server-side. No new backend surface.
- Read-only by default: write and broadcast tools are only registered
  when WACRM_ENABLE_WRITES / WACRM_ENABLE_BROADCASTS are set, layered
  on top of the API key's own scopes. send_broadcast also requires an
  explicit confirm and is marked destructive.
- Registry-ready: server.json manifest, package.json mcpName linked to
  it (io.github.arnasdon/wacrm-mcp), and glama.json for discovery.
- Docs: docs/mcp.md + README bullet + mcp-server/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:30:24 +03:00

64 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// Broadcast tool — the highest-risk action.
//
// Registered only when BOTH WACRM_ENABLE_WRITES and
// WACRM_ENABLE_BROADCASTS are set. A single call can message up to
// 1000 people, so on top of the env gate the tool requires an
// explicit `confirm: true` argument — the model must consciously opt
// in, and a client that echoes tool args gives the user a last look.
// Marked destructive so hosts prompt before running it.
// ============================================================
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { WacrmClient } from '../client.js';
import { errorResult, handle, jsonResult } from './shared.js';
export function registerBroadcastTools(server: McpServer, client: WacrmClient): void {
server.registerTool(
'send_broadcast',
{
title: 'Send broadcast',
description:
'Launch a template broadcast to a list of recipients (up to 1000). This sends a real WhatsApp template message to every recipient — a mass, irreversible action. You MUST set confirm=true, and you should show the full recipient list and template to the user for approval before calling. The call returns fast; poll get_broadcast for delivery progress.',
inputSchema: {
name: z.string().describe('A name for this broadcast campaign (for your own reference).'),
template_name: z.string().describe('Meta-approved template name.'),
template_language: z.string().describe('Template language code, e.g. "en_US".'),
recipients: z
.array(
z.object({
to: z.string().describe('Recipient phone number in E.164 format.'),
params: z
.array(z.string())
.optional()
.describe('Positional template body variables for this recipient.'),
}),
)
.min(1)
.max(1000)
.describe('Recipients (1–1000). Invalid numbers are dropped and counted as rejected.'),
confirm: z
.boolean()
.describe('Must be true to actually send. A safety gate against accidental mass sends.'),
},
annotations: {
title: 'Send broadcast',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
handle(async ({ confirm, ...body }) => {
if (confirm !== true) {
return errorResult(
'Refusing to send: confirm must be true. This launches a mass broadcast to ' +
`${body.recipients.length} recipient(s). Confirm the recipient list and template ` +
'with the user, then call again with confirm=true.',
);
}
return jsonResult(await client.sendBroadcast(body));
}),
);
}