botmsg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,984 @@
1
+ # botmsg
2
+
3
+ > Universal MCP Server for multi-platform bot messaging — one interface, seven platforms.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/botmsg.svg)](https://www.npmjs.com/package/botmsg)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+
9
+ Send messages to **DingTalk (钉钉)**, **WeCom (企业微信)**, **Feishu (飞书)**, **Lark**, **Slack**, **Discord**, and **Telegram** through a unified MCP (Model Context Protocol) interface. Configure bots via environment variables or YAML/JSON config files, then let AI assistants send notifications, alerts, and reports across all your team channels.
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [How It Works](#how-it-works)
17
+ - [Installation](#installation)
18
+ - [MCP Client Configuration](#mcp-client-configuration)
19
+ - [Claude Desktop](#claude-desktop)
20
+ - [VS Code (Copilot)](#vs-code-copilot)
21
+ - [Cursor](#cursor)
22
+ - [Windsurf](#windsurf)
23
+ - [Cline](#cline)
24
+ - [Generic / stdio](#generic--stdio)
25
+ - [Configuration](#configuration)
26
+ - [Mode A: Environment Variables](#mode-a-environment-variables)
27
+ - [Mode B: External Config File (YAML/JSON)](#mode-b-external-config-file-yamljson)
28
+ - [Bot Naming Convention](#bot-naming-convention)
29
+ - [Global Settings](#global-settings)
30
+ - [Platform Setup Guides](#platform-setup-guides)
31
+ - [DingTalk (钉钉)](#dingtalk-钉钉)
32
+ - [WeCom (企业微信)](#wecom-企业微信)
33
+ - [Feishu (飞书)](#feishu-飞书)
34
+ - [Lark (International)](#lark-international)
35
+ - [Slack](#slack)
36
+ - [Discord](#discord)
37
+ - [Telegram](#telegram)
38
+ - [MCP Tools Reference](#mcp-tools-reference)
39
+ - [send_message](#send_message)
40
+ - [send_file](#send_file)
41
+ - [send_broadcast](#send_broadcast)
42
+ - [send_batch](#send_batch)
43
+ - [list_bots](#list_bots)
44
+ - [message_status](#message_status)
45
+ - [render_template](#render_template)
46
+ - [Message Types & Platform Mapping](#message-types--platform-mapping)
47
+ - [Template Engine](#template-engine)
48
+ - [Built-in Helpers](#built-in-helpers)
49
+ - [Conditional Logic](#conditional-logic)
50
+ - [Partials](#partials)
51
+ - [Rate Limiting & Retry](#rate-limiting--retry)
52
+ - [Audit Logging](#audit-logging)
53
+ - [Dry Run Mode](#dry-run-mode)
54
+ - [Error Handling](#error-handling)
55
+ - [Development](#development)
56
+ - [Troubleshooting](#troubleshooting)
57
+ - [License](#license)
58
+
59
+ ---
60
+
61
+ ## Features
62
+
63
+ - **7 platforms, 1 interface** — Unified message API across DingTalk, WeCom, Feishu, Lark, Slack, Discord, and Telegram
64
+ - **7 message types** — `text`, `markdown`, `link`, `image`, `card`, `news`, `file` with automatic cross-platform adaptation
65
+ - **Template engine** — Handlebars templates with 12+ custom helpers (`{{date}}`, `{{uppercase}}`, `{{if_eq}}`, `{{now}}`, etc.)
66
+ - **Broadcast** — Send one message to multiple bots across different platforms simultaneously
67
+ - **Batch send** — Send to multiple targets (e.g., Telegram groups) through a single bot
68
+ - **Rate limiting** — Per-platform Bottleneck limiters matching each API's rate limits, with exponential backoff retry
69
+ - **Audit logging** — In-memory ring buffer + optional JSONL file persistence for full message tracking
70
+ - **Dual configuration** — Environment variables for quick setup, YAML/JSON files for complex deployments
71
+ - **Dry run mode** — Preview message payloads without actually sending
72
+ - **Zero-config binary** — Runs via `npx botmsg`, no global installation needed
73
+
74
+ ## How It Works
75
+
76
+ ```
77
+ ┌──────────────┐ MCP (stdio) ┌──────────┐ HTTP/HTTPS ┌─────────────────┐
78
+ │ AI Assistant │ ◄────────────────► │ botmsg │ ────────────────► │ Bot Platforms │
79
+ │ (Claude, │ JSON-RPC tools │ MCP │ Webhook APIs │ DingTalk, WeCom │
80
+ │ VS Code, │ │ Server │ │ Feishu, Slack.. │
81
+ │ Cursor...) │ │ │ │ │
82
+ └──────────────┘ └──────────┘ └─────────────────┘
83
+
84
+
85
+ ┌──────────┐
86
+ │ Audit │
87
+ │ Log │
88
+ └──────────┘
89
+ ```
90
+
91
+ 1. Your AI assistant (Claude, VS Code Copilot, Cursor, etc.) discovers `botmsg` via MCP protocol
92
+ 2. It calls tools like `send_message`, `send_broadcast`, etc. with structured arguments
93
+ 3. `botmsg` applies template rendering, rate limiting, and routes to the correct platform adapter
94
+ 4. The platform adapter translates the unified message format into the platform-specific API payload
95
+ 5. Audit logger records every send attempt with status, latency, and error details
96
+
97
+ ---
98
+
99
+ ## Installation
100
+
101
+ No installation needed — just use `npx`:
102
+
103
+ ```bash
104
+ npx -y botmsg
105
+ ```
106
+
107
+ Or install globally:
108
+
109
+ ```bash
110
+ npm install -g botmsg
111
+ ```
112
+
113
+ **Requirements:** Node.js >= 18.0.0
114
+
115
+ ---
116
+
117
+ ## MCP Client Configuration
118
+
119
+ ### Claude Desktop
120
+
121
+ Edit your Claude Desktop config file:
122
+
123
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
124
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "botmsg": {
130
+ "command": "npx",
131
+ "args": ["-y", "botmsg"],
132
+ "env": {
133
+ "BOTMSG_DINGTALK_OPS_WEBHOOK": "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN",
134
+ "BOTMSG_WECOM_DEV_WEBHOOK": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
135
+ "BOTMSG_FEISHU_ALERT_WEBHOOK": "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_HOOK",
136
+ "BOTMSG_SLACK_GENERAL_WEBHOOK": "https://hooks.slack.com/services/T00/B00/xxx",
137
+ "BOTMSG_DISCORD_GAMING_WEBHOOK": "https://discord.com/api/webhooks/123456/TOKEN",
138
+ "BOTMSG_TELEGRAM_BOT_TOKEN": "123456:ABC-DEF...",
139
+ "BOTMSG_TELEGRAM_BOT_CHAT_ID": "-1001234567890"
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ > **Windows Note:** If `npx` is not found, wrap with `cmd`:
147
+ > ```json
148
+ > {
149
+ > "mcpServers": {
150
+ > "botmsg": {
151
+ > "command": "cmd",
152
+ > "args": ["/c", "npx", "-y", "botmsg"],
153
+ > "env": { "..." : "..." }
154
+ > }
155
+ > }
156
+ > }
157
+ > ```
158
+
159
+ ### VS Code (Copilot)
160
+
161
+ Create `.vscode/mcp.json` in your project root:
162
+
163
+ ```json
164
+ {
165
+ "servers": {
166
+ "botmsg": {
167
+ "command": "npx",
168
+ "args": ["-y", "botmsg"],
169
+ "env": {
170
+ "BOTMSG_DINGTALK_OPS_WEBHOOK": "${env:DINGTALK_WEBHOOK}",
171
+ "BOTMSG_SLACK_GENERAL_WEBHOOK": "${env:SLACK_WEBHOOK}"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### Cursor
179
+
180
+ Open Settings > MCP > Add new MCP server, or edit `~/.cursor/mcp.json`:
181
+
182
+ ```json
183
+ {
184
+ "mcpServers": {
185
+ "botmsg": {
186
+ "command": "npx",
187
+ "args": ["-y", "botmsg"],
188
+ "env": {
189
+ "BOTMSG_DINGTALK_OPS_WEBHOOK": "https://oapi.dingtalk.com/robot/send?access_token=xxx"
190
+ }
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ ### Windsurf
197
+
198
+ Add to your Windsurf MCP config:
199
+
200
+ ```json
201
+ {
202
+ "mcpServers": {
203
+ "botmsg": {
204
+ "command": "npx",
205
+ "args": ["-y", "botmsg"],
206
+ "env": {
207
+ "BOTMSG_CONFIG": "/path/to/bots.yaml"
208
+ }
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ ### Cline
215
+
216
+ Add to Cline MCP settings:
217
+
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "botmsg": {
222
+ "command": "npx",
223
+ "args": ["-y", "botmsg"],
224
+ "env": {
225
+ "BOTMSG_TELEGRAM_BOT_TOKEN": "123456:ABC...",
226
+ "BOTMSG_TELEGRAM_BOT_CHAT_ID": "-1001234"
227
+ }
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Generic / stdio
234
+
235
+ `botmsg` speaks MCP over stdio. Any MCP-compatible client can launch it:
236
+
237
+ ```bash
238
+ node node_modules/botmsg/dist/index.js
239
+ ```
240
+
241
+ Or directly:
242
+
243
+ ```bash
244
+ npx -y botmsg
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Configuration
250
+
251
+ ### Mode A: Environment Variables
252
+
253
+ The simplest way. Pattern: `BOTMSG_{PLATFORM}_{INSTANCE}_{FIELD}`
254
+
255
+ Each bot is identified by a **name** composed of `{platform}-{instance}`. For example, `dingtalk-ops` creates a DingTalk bot named "ops".
256
+
257
+ #### DingTalk (钉钉)
258
+
259
+ ```bash
260
+ BOTMSG_DINGTALK_OPS_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN
261
+ BOTMSG_DINGTALK_OPS_SECRET=SEC... # Optional: for HMAC-SHA256
262
+ BOTMSG_DINGTALK_OPS_SECURITY=sign # keyword (default) | ip | sign
263
+ ```
264
+
265
+ #### WeCom (企业微信)
266
+
267
+ ```bash
268
+ BOTMSG_WECOM_DEV_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY
269
+ ```
270
+
271
+ #### Feishu (飞书)
272
+
273
+ ```bash
274
+ BOTMSG_FEISHU_ALERT_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_HOOK
275
+ BOTMSG_FEISHU_ALERT_SECRET=YOUR_SECRET # Optional: HMAC-SHA256
276
+ ```
277
+
278
+ #### Lark (International Feishu)
279
+
280
+ ```bash
281
+ BOTMSG_LARK_GLOBAL_WEBHOOK=https://open.larksuite.com/open-apis/bot/v2/hook/YOUR_HOOK
282
+ BOTMSG_LARK_GLOBAL_SECRET=YOUR_SECRET # Optional
283
+ ```
284
+
285
+ #### Slack
286
+
287
+ ```bash
288
+ BOTMSG_SLACK_GENERAL_WEBHOOK=https://hooks.slack.com/services/T00000/B00000/xxxxx
289
+ ```
290
+
291
+ #### Discord
292
+
293
+ ```bash
294
+ BOTMSG_DISCORD_GAMING_WEBHOOK=https://discord.com/api/webhooks/WEBHOOK_ID/TOKEN
295
+ BOTMSG_DISCORD_GAMING_USERNAME=MyBot # Optional: override username
296
+ BOTMSG_DISCORD_GAMING_AVATAR_URL=https://... # Optional: override avatar
297
+ ```
298
+
299
+ #### Telegram
300
+
301
+ ```bash
302
+ BOTMSG_TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
303
+ BOTMSG_TELEGRAM_BOT_CHAT_ID=-1001234567890 # Group/channel ID
304
+ BOTMSG_TELEGRAM_BOT_PARSE_MODE=MarkdownV2 # MarkdownV2 | HTML | (empty)
305
+ ```
306
+
307
+ #### Multiple Instances
308
+
309
+ You can configure multiple bots per platform by using different instance names:
310
+
311
+ ```bash
312
+ BOTMSG_DINGTALK_OPS_WEBHOOK=https://oapi.dingtalk.com/...?access_token=TOKEN_A
313
+ BOTMSG_DINGTALK_ALERTS_WEBHOOK=https://oapi.dingtalk.com/...?access_token=TOKEN_B
314
+ BOTMSG_SLACK_ENGINEERING_WEBHOOK=https://hooks.slack.com/services/...
315
+ BOTMSG_SLACK_MARKETING_WEBHOOK=https://hooks.slack.com/services/...
316
+ ```
317
+
318
+ This creates 4 bots: `dingtalk-ops`, `dingtalk-alerts`, `slack-engineering`, `slack-marketing`.
319
+
320
+ ### Mode B: External Config File (YAML/JSON)
321
+
322
+ For complex deployments, point `BOTMSG_CONFIG` to a config file:
323
+
324
+ ```json
325
+ {
326
+ "mcpServers": {
327
+ "botmsg": {
328
+ "command": "npx",
329
+ "args": ["-y", "botmsg"],
330
+ "env": {
331
+ "BOTMSG_CONFIG": "/path/to/bots.yaml"
332
+ }
333
+ }
334
+ }
335
+ }
336
+ ```
337
+
338
+ #### YAML Format (`bots.yaml`)
339
+
340
+ ```yaml
341
+ bots:
342
+ dingtalk-ops:
343
+ platform: dingtalk
344
+ webhook: "https://oapi.dingtalk.com/robot/send?access_token=xxx"
345
+ secret: "SEC..."
346
+ security: sign
347
+
348
+ wecom-dev:
349
+ platform: wecom
350
+ webhook: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
351
+
352
+ feishu-alert:
353
+ platform: feishu
354
+ webhook: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
355
+ secret: "xxx"
356
+
357
+ lark-global:
358
+ platform: lark
359
+ webhook: "https://open.larksuite.com/open-apis/bot/v2/hook/xxx"
360
+
361
+ slack-general:
362
+ platform: slack
363
+ webhook: "https://hooks.slack.com/services/T.../B.../xxx"
364
+
365
+ discord-gaming:
366
+ platform: discord
367
+ webhook: "https://discord.com/api/webhooks/xxx/yyy"
368
+ username: "GameBot"
369
+
370
+ telegram-bot:
371
+ platform: telegram
372
+ token: "123456:ABC..."
373
+ chat_id: "-1001234567"
374
+ parse_mode: "MarkdownV2"
375
+
376
+ settings:
377
+ log_level: info
378
+ audit_log: "/tmp/botmsg-audit.jsonl"
379
+ max_retries: 3
380
+ dry_run: false
381
+ ```
382
+
383
+ #### JSON Format (`bots.json`)
384
+
385
+ ```json
386
+ {
387
+ "bots": {
388
+ "dingtalk-ops": {
389
+ "platform": "dingtalk",
390
+ "webhook": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
391
+ "secret": "SEC...",
392
+ "security": "sign"
393
+ },
394
+ "telegram-bot": {
395
+ "platform": "telegram",
396
+ "token": "123456:ABC...",
397
+ "chat_id": "-1001234567"
398
+ }
399
+ },
400
+ "settings": {
401
+ "log_level": "info",
402
+ "max_retries": 3
403
+ }
404
+ }
405
+ ```
406
+
407
+ #### Mixed Mode
408
+
409
+ File config takes priority, env vars supplement:
410
+
411
+ ```bash
412
+ BOTMSG_CONFIG=/path/to/bots.yaml
413
+ BOTMSG_SLACK_EXTRA_WEBHOOK=https://hooks.slack.com/... # Added on top of file config
414
+ ```
415
+
416
+ ### Bot Naming Convention
417
+
418
+ Bot names are auto-generated as `{platform}-{instance}`:
419
+
420
+ | Env Variable | Bot Name |
421
+ |---|---|
422
+ | `BOTMSG_DINGTALK_OPS_WEBHOOK` | `dingtalk-ops` |
423
+ | `BOTMSG_WECOM_DEV_WEBHOOK` | `wecom-dev` |
424
+ | `BOTMSG_TELEGRAM_BOT_TOKEN` | `telegram-bot` |
425
+ | `BOTMSG_SLACK_GENERAL_WEBHOOK` | `slack-general` |
426
+
427
+ Use these names when calling tools: `{ "bot": "dingtalk-ops", ... }`
428
+
429
+ ### Global Settings
430
+
431
+ | Variable | Default | Description |
432
+ |---|---|---|
433
+ | `BOTMSG_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` |
434
+ | `BOTMSG_AUDIT_LOG` | _(none)_ | Path to JSONL audit log file for persistent message tracking |
435
+ | `BOTMSG_MAX_RETRIES` | `3` | Maximum retry attempts on transient failures (0-10) |
436
+ | `BOTMSG_DRY_RUN` | `false` | When `true`, build payloads but don't send (useful for testing) |
437
+
438
+ ---
439
+
440
+ ## Platform Setup Guides
441
+
442
+ ### DingTalk (钉钉)
443
+
444
+ 1. Open the target group chat > Group Settings > Smart Group Assistant > Add Robot
445
+ 2. Select "Custom (via Webhook)" robot type
446
+ 3. Set security mode (recommended: **Sign** for HMAC-SHA256)
447
+ 4. Copy the webhook URL (contains `access_token`)
448
+ 5. If using sign mode, copy the `SEC...` secret
449
+
450
+ **API Docs:** https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages
451
+
452
+ ### WeCom (企业微信)
453
+
454
+ 1. Open the target group chat > Group Settings > Group Bots > Add Bot
455
+ 2. Select "Webhook Bot"
456
+ 3. Copy the webhook URL (contains `key=...`)
457
+
458
+ **API Docs:** https://developer.work.weixin.qq.com/document/path/99110
459
+
460
+ ### Feishu (飞书)
461
+
462
+ 1. Open the target group chat > Settings > Bots > Add Bot
463
+ 2. Select "Custom Bot"
464
+ 3. Copy the webhook URL (contains `/hook/...`)
465
+ 4. Optionally enable signature verification and copy the secret
466
+
467
+ **API Docs:** https://open.feishu.cn/document/uktmuktmuktm/uctm5yjl3eto24ynxkjn
468
+
469
+ ### Lark (International)
470
+
471
+ Same process as Feishu, but on the Lark (international) platform. The webhook URL will use `open.larksuite.com` instead of `open.feishu.cn`.
472
+
473
+ **API Docs:** https://open.larksuite.com/document/client-docs/bot-v3/add-custom-bot
474
+
475
+ ### Slack
476
+
477
+ 1. Go to https://api.slack.com/apps > Create New App
478
+ 2. Select "From scratch", give it a name and workspace
479
+ 3. Enable "Incoming Webhooks" in the sidebar
480
+ 4. Click "Add New Webhook to Workspace" and select a channel
481
+ 5. Copy the webhook URL (`https://hooks.slack.com/services/...`)
482
+
483
+ **API Docs:** https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks
484
+
485
+ ### Discord
486
+
487
+ 1. Open your Discord server > Server Settings > Integrations > Webhooks
488
+ 2. Click "New Webhook", name it, select a channel
489
+ 3. Copy the webhook URL (`https://discord.com/api/webhooks/...`)
490
+
491
+ **API Docs:** https://discord.com/developers/docs/resources/webhook
492
+
493
+ ### Telegram
494
+
495
+ 1. Open Telegram, search for `@BotFather`
496
+ 2. Send `/newbot`, follow the prompts to create a bot
497
+ 3. Copy the bot token (`123456:ABC-DEF...`)
498
+ 4. Add the bot to your target group/chat
499
+ 5. Get the chat ID (for groups, it starts with `-100`; you can use `@userinfobot` or the `getUpdates` API)
500
+
501
+ **API Docs:** https://core.telegram.org/bots/api
502
+
503
+ ---
504
+
505
+ ## MCP Tools Reference
506
+
507
+ ### `send_message`
508
+
509
+ Send a message to a configured bot. This is the primary tool.
510
+
511
+ **Parameters:**
512
+
513
+ | Parameter | Type | Required | Description |
514
+ |---|---|---|---|
515
+ | `bot` | string | Yes | Bot instance name (e.g., `dingtalk-ops`) |
516
+ | `type` | string | Yes | Message type: `text`, `markdown`, `link`, `image`, `card`, `news`, `file` |
517
+ | `content` | string | Yes | Message body. Supports Handlebars templates: `{{variable}}` |
518
+ | `title` | string | No | Title for markdown/link/card messages |
519
+ | `variables` | object | No | Template variables as key-value pairs |
520
+ | `at` | object | No | @mention config: `{ users: [...], mobiles: [...], all: bool }` |
521
+ | `link_url` | string | No | URL for link-type messages |
522
+ | `pic_url` | string | No | Thumbnail/picture URL |
523
+ | `buttons` | array | No | Buttons for card messages: `[{ title, url }]` |
524
+ | `single_button_text` | string | No | Single button text (DingTalk actionCard) |
525
+ | `single_button_url` | string | No | Single button URL |
526
+ | `news_items` | array | No | News items: `[{ title, description?, url, pic_url? }]` |
527
+ | `image_url` | string | No | Image URL for image-type messages |
528
+ | `image_base64` | string | No | Base64-encoded image (WeCom only) |
529
+
530
+ **Example — Text with @mention:**
531
+
532
+ ```json
533
+ {
534
+ "bot": "dingtalk-ops",
535
+ "type": "text",
536
+ "content": "Server CPU exceeded 90%",
537
+ "at": { "mobiles": ["13800138000"], "all": false }
538
+ }
539
+ ```
540
+
541
+ **Example — Markdown with template variables:**
542
+
543
+ ```json
544
+ {
545
+ "bot": "slack-engineering",
546
+ "type": "markdown",
547
+ "content": "## Deploy Report\n*Service:* {{service}}\n*Status:* {{status}}\n*Duration:* {{duration}}",
548
+ "variables": { "service": "user-api", "status": "Success", "duration": "3m 24s" },
549
+ "title": "Deploy Notification"
550
+ }
551
+ ```
552
+
553
+ **Example — Link card:**
554
+
555
+ ```json
556
+ {
557
+ "bot": "wecom-dev",
558
+ "type": "link",
559
+ "content": "PR #142 merged to main branch",
560
+ "title": "PR Merged",
561
+ "link_url": "https://github.com/org/repo/pull/142",
562
+ "pic_url": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
563
+ }
564
+ ```
565
+
566
+ **Example — Interactive card with buttons:**
567
+
568
+ ```json
569
+ {
570
+ "bot": "feishu-alert",
571
+ "type": "card",
572
+ "content": "A deployment requires your approval.",
573
+ "title": "Deploy Approval",
574
+ "buttons": [
575
+ { "title": "Approve", "url": "https://example.com/approve/123" },
576
+ { "title": "Reject", "url": "https://example.com/reject/123" }
577
+ ]
578
+ }
579
+ ```
580
+
581
+ **Example — News feed:**
582
+
583
+ ```json
584
+ {
585
+ "bot": "dingtalk-ops",
586
+ "type": "news",
587
+ "content": "",
588
+ "news_items": [
589
+ { "title": "Weekly Report: Q2 Summary", "url": "https://example.com/reports/q2", "pic_url": "https://example.com/img/q2.png" },
590
+ { "title": "New Feature: Bot Broadcast", "url": "https://example.com/blog/broadcast" },
591
+ { "title": "Incident Postmortem: June 20", "url": "https://example.com/postmortem/0620" }
592
+ ]
593
+ }
594
+ ```
595
+
596
+ ### `send_file`
597
+
598
+ Send a file or image through a bot. Support varies by platform.
599
+
600
+ | Parameter | Type | Required | Description |
601
+ |---|---|---|---|
602
+ | `bot` | string | Yes | Bot instance name |
603
+ | `file_path` | string | Yes | Absolute path to the file |
604
+ | `caption` | string | No | Caption for the file |
605
+
606
+ **Platform support:**
607
+
608
+ | Platform | File Support |
609
+ |---|---|
610
+ | WeCom | Full support via media_id upload (max 20MB) |
611
+ | Discord | Full support via multipart upload (max 25MB) |
612
+ | Telegram | Full support via sendDocument (max 50MB) |
613
+ | DingTalk | Not supported (degrades to link) |
614
+ | Feishu/Lark | Not supported via webhook |
615
+ | Slack | Not supported via webhook |
616
+
617
+ ```json
618
+ {
619
+ "bot": "wecom-dev",
620
+ "file_path": "/home/user/reports/monthly.pdf",
621
+ "caption": "Monthly Report - June 2025"
622
+ }
623
+ ```
624
+
625
+ ### `send_broadcast`
626
+
627
+ Send the same message to multiple bots simultaneously. Ideal for cross-platform alerts.
628
+
629
+ | Parameter | Type | Required | Description |
630
+ |---|---|---|---|
631
+ | `bots` | string[] | Yes | List of bot names to broadcast to |
632
+ | `type` | string | Yes | Message type |
633
+ | `content` | string | Yes | Message content (supports templates) |
634
+ | `title` | string | No | Message title |
635
+ | `variables` | object | No | Template variables |
636
+ | _(all send_message params)_ | | | Same parameters as send_message |
637
+
638
+ **Example — Emergency alert to all platforms:**
639
+
640
+ ```json
641
+ {
642
+ "bots": ["dingtalk-ops", "wecom-dev", "feishu-alert", "slack-engineering"],
643
+ "type": "markdown",
644
+ "content": "## P0 Incident\n**Service:** payment-gateway\n**Impact:** All payments failing\n**Started:** {{time}}\n\n[Status Page](https://status.example.com)",
645
+ "variables": { "time": "2025-06-24 16:30:00" },
646
+ "title": "P0 Alert"
647
+ }
648
+ ```
649
+
650
+ **Response:**
651
+
652
+ ```json
653
+ {
654
+ "total": 4,
655
+ "success": 3,
656
+ "failed": 1,
657
+ "results": [
658
+ { "bot": "dingtalk-ops", "success": true, "platform": "dingtalk", "latency_ms": 245 },
659
+ { "bot": "wecom-dev", "success": true, "platform": "wecom", "latency_ms": 312 },
660
+ { "bot": "feishu-alert", "success": true, "platform": "feishu", "latency_ms": 189 },
661
+ { "bot": "slack-engineering", "success": false, "error": "Slack error [403]: action_prohibited" }
662
+ ]
663
+ }
664
+ ```
665
+
666
+ ### `send_batch`
667
+
668
+ Send the same message to multiple targets through one bot. Primarily useful for Telegram (multiple chat IDs).
669
+
670
+ | Parameter | Type | Required | Description |
671
+ |---|---|---|---|
672
+ | `bot` | string | Yes | Bot instance name |
673
+ | `targets` | string[] | Yes | Target IDs (e.g., Telegram chat_ids) |
674
+ | `type` | string | Yes | Message type |
675
+ | `content` | string | Yes | Message content |
676
+ | _(all send_message params)_ | | | Same parameters as send_message |
677
+
678
+ ```json
679
+ {
680
+ "bot": "telegram-bot",
681
+ "targets": ["-1001111111", "-1002222222", "-1003333333"],
682
+ "type": "text",
683
+ "content": "Daily standup reminder: 10:00 AM"
684
+ }
685
+ ```
686
+
687
+ ### `list_bots`
688
+
689
+ List all configured bot instances with their platform, status, and queue info.
690
+
691
+ **Parameters:** None
692
+
693
+ **Response:**
694
+
695
+ ```json
696
+ {
697
+ "total": 3,
698
+ "bots": [
699
+ { "name": "dingtalk-ops", "platform": "dingtalk", "enabled": true, "queue": { "queued": 0, "running": 0 } },
700
+ { "name": "wecom-dev", "platform": "wecom", "enabled": true, "queue": { "queued": 0, "running": 0 } },
701
+ { "name": "telegram-bot", "platform": "telegram", "enabled": true, "queue": { "queued": 0, "running": 0 } }
702
+ ],
703
+ "platforms": ["dingtalk", "wecom", "telegram"]
704
+ }
705
+ ```
706
+
707
+ ### `message_status`
708
+
709
+ Query the audit log for message delivery status.
710
+
711
+ | Parameter | Type | Required | Description |
712
+ |---|---|---|---|
713
+ | `message_id` | string | No | Specific message ID to look up |
714
+ | `audit_id` | string | No | Audit log entry ID |
715
+ | `bot` | string | No | Filter by bot name |
716
+ | `platform` | string | No | Filter by platform |
717
+ | `status` | string | No | Filter: `success` or `failed` |
718
+ | `since` | string | No | ISO timestamp — return entries after this time |
719
+ | `limit` | number | No | Max entries to return (default: 20) |
720
+
721
+ ```json
722
+ {
723
+ "bot": "dingtalk-ops",
724
+ "status": "failed",
725
+ "since": "2025-06-24T00:00:00Z",
726
+ "limit": 10
727
+ }
728
+ ```
729
+
730
+ ### `render_template`
731
+
732
+ Preview a Handlebars template rendering without sending. Useful for testing templates.
733
+
734
+ | Parameter | Type | Required | Description |
735
+ |---|---|---|---|
736
+ | `template` | string | Yes | Handlebars template string |
737
+ | `variables` | object | No | Variables to substitute |
738
+
739
+ ```json
740
+ {
741
+ "template": "Service {{service}}: {{#if_eq status \"ok\"}}✅ Healthy{{else}}❌ Down{{/if_eq}}\nCPU: {{cpu}}% {{#if_gt cpu 90}}⚠️ CRITICAL{{/if_gt}}\nTime: {{now}}",
742
+ "variables": { "service": "api-gateway", "status": "ok", "cpu": "95" }
743
+ }
744
+ ```
745
+
746
+ **Response:**
747
+
748
+ ```json
749
+ {
750
+ "template": "Service {{service}}: ...",
751
+ "variables_detected": ["service", "status", "cpu", "now"],
752
+ "variables_provided": ["service", "status", "cpu"],
753
+ "rendered": "Service api-gateway: ✅ Healthy\nCPU: 95% ⚠️ CRITICAL\nTime: 2025-06-24 16:30:00",
754
+ "has_expressions": true
755
+ }
756
+ ```
757
+
758
+ ---
759
+
760
+ ## Message Types & Platform Mapping
761
+
762
+ Each message type is automatically translated to the platform's native format:
763
+
764
+ | Type | DingTalk | WeCom | Feishu/Lark | Slack | Discord | Telegram |
765
+ |---|---|---|---|---|---|---|
766
+ | `text` | text msgtype | text msgtype | text msg_type | plain text | content field | sendMessage |
767
+ | `markdown` | markdown msgtype | markdown msgtype | interactive card (markdown element) | Block Kit (mrkdwn section) | embed description | MarkdownV2 / HTML |
768
+ | `link` | link msgtype | news (1 article) | post (rich text with link) | section + button action | embed with URL | text with inline link |
769
+ | `image` | markdown `![](url)` fallback | image (base64+md5) | image (image_key) | image block | embed image | sendPhoto |
770
+ | `card` | actionCard (buttons) | template_card | interactive card (buttons) | Block Kit (actions) | embed + link buttons | inline_keyboard |
771
+ | `news` | feedCard | news msgtype (articles) | post (multi-paragraph) | multiple sections + dividers | multiple embeds | concatenated message |
772
+ | `file` | link fallback ↓ | file (media_id) | text fallback ↓ | text fallback ↓ | multipart upload | sendDocument |
773
+
774
+ > ↓ = graceful degradation when the platform doesn't support this type natively.
775
+
776
+ ---
777
+
778
+ ## Template Engine
779
+
780
+ `botmsg` uses [Handlebars](https://handlebarsjs.com/) as its template engine with `noEscape` mode (no HTML entity escaping, since messages are plain text).
781
+
782
+ ### Built-in Helpers
783
+
784
+ | Helper | Syntax | Example | Output |
785
+ |---|---|---|---|
786
+ | **date** | `{{date value}}` | `{{date "2025-01-15T14:30:00Z"}}` | `2025-01-15 14:30:00` |
787
+ | date (ISO) | `{{date value "iso"}}` | `{{date "2025-01-15T14:30:00Z" "iso"}}` | `2025-01-15T14:30:00.000Z` |
788
+ | date (locale) | `{{date value "locale"}}` | `{{date "2025-01-15T14:30:00Z" "locale"}}` | `2025/1/15 14:30:00` |
789
+ | **uppercase** | `{{uppercase value}}` | `{{uppercase "hello"}}` | `HELLO` |
790
+ | **lowercase** | `{{lowercase value}}` | `{{lowercase "HELLO"}}` | `hello` |
791
+ | **capitalize** | `{{capitalize value}}` | `{{capitalize "hello world"}}` | `Hello world` |
792
+ | **truncate** | `{{truncate value len}}` | `{{truncate "Long text here" 8}}` | `Long tex...` |
793
+ | **json** | `{{json value}}` | `{{json data}}` | `{"key":"value"}` |
794
+ | **now** | `{{now}}` | `{{now}}` | `2025-06-24 16:30:00` |
795
+ | now (ISO) | `{{now "iso"}}` | `{{now "iso"}}` | `2025-06-24T16:30:00.000Z` |
796
+ | now (timestamp) | `{{now "timestamp"}}` | `{{now "timestamp"}}` | `1750783800` |
797
+ | **join** | `{{join arr sep}}` | `{{join items ", "}}` | `a, b, c` |
798
+
799
+ ### Conditional Logic
800
+
801
+ ```handlebars
802
+ {{#if_eq status "ok"}}
803
+ ✅ Service is healthy
804
+ {{else}}
805
+ ❌ Service is DOWN: {{error}}
806
+ {{/if_eq}}
807
+
808
+ {{#if_gt cpu 90}}
809
+ ⚠️ CPU critical: {{cpu}}%
810
+ {{else}}
811
+ CPU normal: {{cpu}}%
812
+ {{/if_gt}}
813
+ ```
814
+
815
+ ### Partials
816
+
817
+ Register reusable template fragments (via code, not MCP tools):
818
+
819
+ ```handlebars
820
+ {{> header}}
821
+ Service: {{service}}
822
+ Status: {{status}}
823
+ ```
824
+
825
+ ---
826
+
827
+ ## Rate Limiting & Retry
828
+
829
+ Each bot has an independent rate limiter using [Bottleneck](https://github.com/SGrondin/bottleneck), configured to stay safely below each platform's API limits:
830
+
831
+ | Platform | API Limit | botmsg Default | Strategy |
832
+ |---|---|---|---|
833
+ | DingTalk | 20 msg/min | 18 msg/min, 3s spacing | Token bucket |
834
+ | WeCom | 20 msg/min | 18 msg/min, 3s spacing | Token bucket |
835
+ | Feishu | 100 msg/min, 5/sec | 90 msg/min, 250ms spacing | Token bucket |
836
+ | Lark | 100 msg/min, 5/sec | 90 msg/min, 250ms spacing | Token bucket |
837
+ | Slack | ~1 msg/sec | 55 msg/min, 1.1s spacing | Fixed interval |
838
+ | Discord | 5 msg/2sec | 4 msg/2sec, 500ms spacing | Token bucket |
839
+ | Telegram | 30 msg/sec | 25 msg/sec, 40ms spacing | Token bucket |
840
+
841
+ **Retry behavior:** On HTTP 429 or 5xx errors, messages are retried with exponential backoff: 1s → 2s → 4s → 8s → 16s (configurable max via `BOTMSG_MAX_RETRIES`).
842
+
843
+ **High water mark:** If the queue exceeds the platform-specific limit (50-200 pending messages), oldest messages are dropped to prevent memory exhaustion.
844
+
845
+ ---
846
+
847
+ ## Audit Logging
848
+
849
+ Every message send attempt is recorded:
850
+
851
+ ```json
852
+ {
853
+ "id": "msg_m1abc_2d3efg",
854
+ "messageId": "12345",
855
+ "bot": "dingtalk-ops",
856
+ "platform": "dingtalk",
857
+ "type": "markdown",
858
+ "contentHash": "## Deploy Alert\nService **user-ap...",
859
+ "status": "success",
860
+ "timestamp": "2025-06-24T16:30:00.000Z",
861
+ "latencyMs": 245,
862
+ "error": null
863
+ }
864
+ ```
865
+
866
+ **Storage:**
867
+
868
+ - **In-memory:** Ring buffer holding the last 1,000 entries (always active)
869
+ - **File persistence:** Set `BOTMSG_AUDIT_LOG=/path/to/audit.jsonl` for JSONL file output
870
+ - **Query:** Use the `message_status` tool to search by bot, platform, status, time range
871
+
872
+ ---
873
+
874
+ ## Dry Run Mode
875
+
876
+ Set `BOTMSG_DRY_RUN=true` to preview message payloads without actually sending. Perfect for testing configurations and templates.
877
+
878
+ ```json
879
+ {
880
+ "mcpServers": {
881
+ "botmsg": {
882
+ "command": "npx",
883
+ "args": ["-y", "botmsg"],
884
+ "env": {
885
+ "BOTMSG_DINGTALK_OPS_WEBHOOK": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
886
+ "BOTMSG_DRY_RUN": "true"
887
+ }
888
+ }
889
+ }
890
+ }
891
+ ```
892
+
893
+ In dry run mode, `send_message` returns the rendered content and the full platform-specific payload:
894
+
895
+ ```json
896
+ {
897
+ "dry_run": true,
898
+ "bot": "dingtalk-ops",
899
+ "platform": "dingtalk",
900
+ "rendered_content": "Hello Team, deploy completed at 14:30",
901
+ "payload": {
902
+ "msgtype": "text",
903
+ "text": { "content": "Hello Team, deploy completed at 14:30" }
904
+ }
905
+ }
906
+ ```
907
+
908
+ ---
909
+
910
+ ## Error Handling
911
+
912
+ `botmsg` returns structured errors to the MCP client:
913
+
914
+ ```json
915
+ {
916
+ "content": [{ "type": "text", "text": "Error: [PLATFORM_ERROR] [dingtalk] DingTalk error [310000]: keywords not in content" }],
917
+ "isError": true
918
+ }
919
+ ```
920
+
921
+ **Common error codes:**
922
+
923
+ | Error | Cause | Solution |
924
+ |---|---|---|
925
+ | `BOT_NOT_FOUND` | Bot name not in config | Check `list_bots` for available names |
926
+ | `VALIDATION_ERROR` | Invalid parameters | Check tool parameter types and required fields |
927
+ | `PLATFORM_ERROR` | Platform API returned error | Check webhook URL, tokens, and security settings |
928
+ | `RATE_LIMIT_ERROR` | Exceeded rate limit | Messages auto-retry; reduce send frequency |
929
+ | `CONFIG_ERROR` | Config file parse error | Validate YAML/JSON syntax |
930
+
931
+ ---
932
+
933
+ ## Development
934
+
935
+ ```bash
936
+ # Clone and install
937
+ git clone https://github.com/user/botmsg.git
938
+ cd botmsg
939
+ npm install
940
+
941
+ # Build (tsup + esbuild)
942
+ npm run build
943
+
944
+ # Run tests (vitest)
945
+ npm test
946
+
947
+ # Development mode (tsx hot-reload)
948
+ npm run dev
949
+
950
+ # Test MCP server locally
951
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js
952
+ ```
953
+
954
+ ---
955
+
956
+ ## Troubleshooting
957
+
958
+ **"npx: command not found"**
959
+ Ensure Node.js >= 18 is installed: `node --version`
960
+
961
+ **"Bot not found" errors**
962
+ Run the `list_bots` tool to see all configured bot names. Names follow the pattern `{platform}-{instance}`.
963
+
964
+ **Messages not delivered**
965
+ 1. Check webhook URL is correct and not expired
966
+ 2. For DingTalk: verify security mode matches your robot settings (keyword vs sign)
967
+ 3. For signed webhooks: ensure system clock is accurate (within 1 hour)
968
+ 4. Use `BOTMSG_DRY_RUN=true` to inspect payloads without sending
969
+ 5. Check `message_status` tool for recent failures
970
+
971
+ **Rate limit errors (429)**
972
+ botmsg auto-retries with exponential backoff. If persistent, reduce send frequency or split across multiple bot instances.
973
+
974
+ **Windows: "npx not found" in MCP config**
975
+ Use `cmd` wrapper:
976
+ ```json
977
+ { "command": "cmd", "args": ["/c", "npx", "-y", "botmsg"] }
978
+ ```
979
+
980
+ ---
981
+
982
+ ## License
983
+
984
+ MIT