chatrail-mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ChatRail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # ChatRail MCP server
2
+
3
+ Operate a [ChatRail](https://www.chatrail.dev) workspace from Claude, Cursor, VS Code and any other
4
+ [Model Context Protocol](https://modelcontextprotocol.io) client. Check the health of your WhatsApp
5
+ connections, look up groups, trace why a message failed, manage scheduled messages and replay
6
+ webhook deliveries without leaving your editor.
7
+
8
+ ChatRail is a WhatsApp API for developers: send and receive WhatsApp messages over HTTP from a number
9
+ you already have. See the [MCP documentation](https://www.chatrail.dev/mcp) for the full reference.
10
+
11
+ ## Privacy by design
12
+
13
+ - Message bodies, recipient phone numbers and credentials are never returned by any tool.
14
+ - Tools that change state return a preview first and apply only when called again with
15
+ `confirm: true`.
16
+ - Every call is scoped to one workspace and recorded in its audit log.
17
+
18
+ ## Two ways to connect
19
+
20
+ ### 1. Hosted server with OAuth (recommended)
21
+
22
+ Point any client that supports remote MCP servers at:
23
+
24
+ ```
25
+ https://www.chatrail.dev/v1/mcp
26
+ ```
27
+
28
+ Your client opens a browser window where a workspace owner, admin or developer approves access.
29
+ Nothing to install and no key to copy.
30
+
31
+ **Claude Code**
32
+
33
+ ```bash
34
+ claude mcp add --transport http chatrail https://www.chatrail.dev/v1/mcp
35
+ ```
36
+
37
+ **Cursor** (`~/.cursor/mcp.json`)
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "chatrail": { "url": "https://www.chatrail.dev/v1/mcp" }
43
+ }
44
+ }
45
+ ```
46
+
47
+ **VS Code** (`.vscode/mcp.json`)
48
+
49
+ ```json
50
+ {
51
+ "servers": {
52
+ "chatrail": { "type": "http", "url": "https://www.chatrail.dev/v1/mcp" }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### 2. Local stdio server with an API key
58
+
59
+ For clients that only run local servers, or when you would rather use an API key. This package runs
60
+ on your machine and forwards requests to the hosted server.
61
+
62
+ 1. In the [ChatRail dashboard](https://www.chatrail.dev/dashboard), create a workspace API key with
63
+ the `mcp:read` scope. Add `mcp:write` if the assistant may reschedule, cancel, react or replay.
64
+ 2. Add the server to your client.
65
+
66
+ **Claude Desktop** (`claude_desktop_config.json`)
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "chatrail": {
72
+ "command": "npx",
73
+ "args": ["-y", "github:chatrail/chatrail_mcp"],
74
+ "env": { "CHATRAIL_API_KEY": "cr_live_..." }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ **Docker**
81
+
82
+ ```bash
83
+ docker build -t chatrail-mcp .
84
+ docker run -i --rm -e CHATRAIL_API_KEY=cr_live_... chatrail-mcp
85
+ ```
86
+
87
+ | Variable | Required | Description |
88
+ | --- | --- | --- |
89
+ | `CHATRAIL_API_KEY` | Yes, to call tools | Workspace API key with `mcp:read` (and optionally `mcp:write`). |
90
+ | `CHATRAIL_MCP_URL` | No | Override the endpoint. Defaults to `https://www.chatrail.dev/v1/mcp`. |
91
+
92
+ Without a key the server still starts and lists its tools, so you can inspect it before signing up.
93
+
94
+ ## Tools
95
+
96
+ | Tool | Scope | What it does |
97
+ | --- | --- | --- |
98
+ | `list_connections` | read | Every WhatsApp connection in the workspace with its state and health. |
99
+ | `get_connection` | read | One connection with its state, health and recent state history. |
100
+ | `list_whatsapp_groups` | read | Groups visible to a connected number, with ids and participant counts. |
101
+ | `get_whatsapp_group` | read | Name, description, settings and member counts for one group. |
102
+ | `list_messages` | read | Recent messages with their delivery state. |
103
+ | `get_message` | read | One message and its delivery timeline. |
104
+ | `explain_message_failure` | read | A structured explanation of why a message failed. |
105
+ | `list_scheduled_messages` | read | Messages waiting to be sent, soonest first. |
106
+ | `list_webhook_deliveries` | read | Recent webhook deliveries and their outcomes. |
107
+ | `reschedule_message` | write | Change when a scheduled message is sent. Previews first. |
108
+ | `cancel_scheduled_message` | write | Cancel a message that has not been sent. Previews first. |
109
+ | `react_to_message` | write | Set or remove an emoji reaction on a message. Previews first. |
110
+ | `replay_webhook` | write | Requeue a dead or skipped webhook delivery. Previews first. |
111
+
112
+ ## Plans
113
+
114
+ MCP access is included on the ChatRail **Core + AI** and **Team** plans. See
115
+ [pricing](https://www.chatrail.dev/pricing).
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ npm install
121
+ npm run build
122
+ CHATRAIL_API_KEY=cr_test_... node dist/index.js
123
+ ```
124
+
125
+ `tools.json` is the tool catalog served when no API key is configured. It is exported from the
126
+ hosted server and should be refreshed when the hosted tools change.
127
+
128
+ ## Links
129
+
130
+ - Website: <https://www.chatrail.dev>
131
+ - MCP docs: <https://www.chatrail.dev/mcp>
132
+ - API reference: <https://www.chatrail.dev/api-reference>
133
+ - Authentication: <https://www.chatrail.dev/auth.md>
134
+
135
+ ## License
136
+
137
+ [MIT](LICENSE)
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
8
+ /**
9
+ * A local stdio MCP server for ChatRail.
10
+ *
11
+ * It forwards every request to the hosted ChatRail MCP endpoint, authenticated with a workspace
12
+ * API key. The hosted endpoint is the single source of truth for what the tools do and who may
13
+ * call them; this process holds no business logic and stores nothing.
14
+ *
15
+ * Without an API key it still starts and lists the bundled tool catalog, so clients and
16
+ * directories can inspect it. Calling a tool then explains how to configure a key.
17
+ */
18
+ const VERSION = '0.1.0';
19
+ const DEFAULT_URL = 'https://www.chatrail.dev/v1/mcp';
20
+ const catalog = JSON.parse(readFileSync(new URL('../tools.json', import.meta.url), 'utf8'));
21
+ const apiKey = process.env.CHATRAIL_API_KEY?.trim() || undefined;
22
+ const endpoint = new URL(process.env.CHATRAIL_MCP_URL?.trim() || DEFAULT_URL);
23
+ const MISSING_KEY = 'CHATRAIL_API_KEY is not set. Create a workspace API key with the mcp:read scope ' +
24
+ '(add mcp:write to allow changes) at https://www.chatrail.dev/dashboard and pass it to ' +
25
+ 'this server as the CHATRAIL_API_KEY environment variable.';
26
+ /** Turn the hosted endpoint's HTTP refusals into messages a person can act on. */
27
+ function explain(err) {
28
+ const status = err.code;
29
+ if (status === 401) {
30
+ return new McpError(ErrorCode.InvalidRequest, 'ChatRail rejected the API key. Check that CHATRAIL_API_KEY is a current workspace key.');
31
+ }
32
+ if (status === 403) {
33
+ return new McpError(ErrorCode.InvalidRequest, 'ChatRail refused the request. The API key needs the mcp:read scope (mcp:write for ' +
34
+ 'changes), and MCP is included on the Core + AI and Team plans.');
35
+ }
36
+ return err;
37
+ }
38
+ let upstream;
39
+ /** One connection to the hosted endpoint, opened on first use and reused afterwards. */
40
+ function connectUpstream(key) {
41
+ upstream ??= (async () => {
42
+ const client = new Client({ name: 'chatrail-mcp', version: VERSION });
43
+ const transport = new StreamableHTTPClientTransport(endpoint, {
44
+ requestInit: {
45
+ headers: {
46
+ Authorization: `Bearer ${key}`,
47
+ 'User-Agent': `chatrail-mcp/${VERSION}`,
48
+ },
49
+ },
50
+ });
51
+ await client.connect(transport);
52
+ return client;
53
+ })().catch((err) => {
54
+ // Let the next call try again instead of caching the failure.
55
+ upstream = undefined;
56
+ throw explain(err);
57
+ });
58
+ return upstream;
59
+ }
60
+ const server = new Server({ name: 'chatrail', version: VERSION }, { capabilities: { tools: {} }, instructions: catalog.instructions });
61
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => {
62
+ if (!apiKey)
63
+ return { tools: catalog.tools };
64
+ const client = await connectUpstream(apiKey);
65
+ return client.listTools(request.params).catch((err) => {
66
+ throw explain(err);
67
+ });
68
+ });
69
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
70
+ if (!apiKey) {
71
+ return { content: [{ type: 'text', text: MISSING_KEY }], isError: true };
72
+ }
73
+ const client = await connectUpstream(apiKey);
74
+ return client.callTool(request.params).catch((err) => {
75
+ throw explain(err);
76
+ });
77
+ });
78
+ await server.connect(new StdioServerTransport());
79
+ // The client owns this process: when it closes stdin, exit rather than let the open upstream
80
+ // connection keep us alive.
81
+ process.stdin.on('close', () => process.exit(0));
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "chatrail-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for ChatRail: inspect WhatsApp connections, groups, message delivery, schedules and webhook deliveries from Claude, Cursor and other MCP clients.",
5
+ "mcpName": "dev.chatrail/chatrail",
6
+ "type": "module",
7
+ "bin": {
8
+ "chatrail-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "tools.json"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prepare": "npm run build",
17
+ "start": "node dist/index.js"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "whatsapp",
23
+ "whatsapp-api",
24
+ "chatrail",
25
+ "claude",
26
+ "cursor"
27
+ ],
28
+ "homepage": "https://www.chatrail.dev/mcp",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/chatrail/chatrail_mcp.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/chatrail/chatrail_mcp/issues"
35
+ },
36
+ "license": "MIT",
37
+ "engines": {
38
+ "node": ">=20"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.30.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22.10.0",
48
+ "typescript": "^5.9.0"
49
+ }
50
+ }
package/tools.json ADDED
@@ -0,0 +1,428 @@
1
+ {
2
+ "instructions": "Operational tools for one ChatRail workspace: connections, WhatsApp groups, message delivery, schedules and webhook deliveries. Message bodies, recipient numbers and credentials are never returned. Tools that change state return a preview first and apply only when called again with confirm: true.",
3
+ "tools": [
4
+ {
5
+ "name": "list_connections",
6
+ "title": "List connections",
7
+ "description": "Every WhatsApp connection in the workspace with its current state and health. Phone numbers are not included.",
8
+ "inputSchema": {
9
+ "type": "object",
10
+ "properties": {
11
+ "limit": {
12
+ "default": 20,
13
+ "type": "integer",
14
+ "minimum": 1,
15
+ "maximum": 50
16
+ }
17
+ },
18
+ "$schema": "http://json-schema.org/draft-07/schema#"
19
+ },
20
+ "annotations": {
21
+ "readOnlyHint": true,
22
+ "destructiveHint": false,
23
+ "idempotentHint": true
24
+ },
25
+ "execution": {
26
+ "taskSupport": "forbidden"
27
+ }
28
+ },
29
+ {
30
+ "name": "get_connection",
31
+ "title": "Get a connection",
32
+ "description": "One connection, with its state, health and recent state history.",
33
+ "inputSchema": {
34
+ "type": "object",
35
+ "properties": {
36
+ "connection": {
37
+ "type": "string",
38
+ "minLength": 1,
39
+ "maxLength": 40
40
+ }
41
+ },
42
+ "required": [
43
+ "connection"
44
+ ],
45
+ "$schema": "http://json-schema.org/draft-07/schema#"
46
+ },
47
+ "annotations": {
48
+ "readOnlyHint": true,
49
+ "destructiveHint": false,
50
+ "idempotentHint": true
51
+ },
52
+ "execution": {
53
+ "taskSupport": "forbidden"
54
+ }
55
+ },
56
+ {
57
+ "name": "list_whatsapp_groups",
58
+ "title": "List WhatsApp groups",
59
+ "description": "A bounded page of groups visible to one connected WhatsApp number. Returns group names, stable group ids and participant counts when available; participant identities are never returned.",
60
+ "inputSchema": {
61
+ "type": "object",
62
+ "properties": {
63
+ "connection": {
64
+ "type": "string",
65
+ "minLength": 2,
66
+ "maxLength": 40
67
+ },
68
+ "limit": {
69
+ "default": 20,
70
+ "type": "integer",
71
+ "minimum": 1,
72
+ "maximum": 50
73
+ },
74
+ "offset": {
75
+ "default": 0,
76
+ "type": "integer",
77
+ "minimum": 0,
78
+ "maximum": 100000
79
+ }
80
+ },
81
+ "required": [
82
+ "connection"
83
+ ],
84
+ "$schema": "http://json-schema.org/draft-07/schema#"
85
+ },
86
+ "annotations": {
87
+ "readOnlyHint": true,
88
+ "destructiveHint": false,
89
+ "idempotentHint": true
90
+ },
91
+ "execution": {
92
+ "taskSupport": "forbidden"
93
+ }
94
+ },
95
+ {
96
+ "name": "get_whatsapp_group",
97
+ "title": "Describe a WhatsApp group",
98
+ "description": "Name, description, settings and member counts for one group a connected number is in. Like list_whatsapp_groups, it never returns who the members are.",
99
+ "inputSchema": {
100
+ "type": "object",
101
+ "properties": {
102
+ "connection": {
103
+ "type": "string",
104
+ "minLength": 2,
105
+ "maxLength": 40
106
+ },
107
+ "group_id": {
108
+ "type": "string",
109
+ "minLength": 5,
110
+ "maxLength": 40
111
+ }
112
+ },
113
+ "required": [
114
+ "connection",
115
+ "group_id"
116
+ ],
117
+ "$schema": "http://json-schema.org/draft-07/schema#"
118
+ },
119
+ "annotations": {
120
+ "readOnlyHint": true,
121
+ "destructiveHint": false,
122
+ "idempotentHint": true
123
+ },
124
+ "execution": {
125
+ "taskSupport": "forbidden"
126
+ }
127
+ },
128
+ {
129
+ "name": "list_messages",
130
+ "title": "List messages",
131
+ "description": "Recent messages with their delivery state. Message bodies and recipient numbers are deliberately excluded.",
132
+ "inputSchema": {
133
+ "type": "object",
134
+ "properties": {
135
+ "connection": {
136
+ "type": "string",
137
+ "minLength": 1,
138
+ "maxLength": 40
139
+ },
140
+ "status": {
141
+ "type": "string",
142
+ "minLength": 1,
143
+ "maxLength": 20
144
+ },
145
+ "direction": {
146
+ "type": "string",
147
+ "enum": [
148
+ "inbound",
149
+ "outbound"
150
+ ]
151
+ },
152
+ "limit": {
153
+ "default": 20,
154
+ "type": "integer",
155
+ "minimum": 1,
156
+ "maximum": 50
157
+ },
158
+ "cursor": {
159
+ "type": "string",
160
+ "maxLength": 200
161
+ }
162
+ },
163
+ "$schema": "http://json-schema.org/draft-07/schema#"
164
+ },
165
+ "annotations": {
166
+ "readOnlyHint": true,
167
+ "destructiveHint": false,
168
+ "idempotentHint": true
169
+ },
170
+ "execution": {
171
+ "taskSupport": "forbidden"
172
+ }
173
+ },
174
+ {
175
+ "name": "get_message",
176
+ "title": "Get a message",
177
+ "description": "One message and its delivery timeline. The body is not returned.",
178
+ "inputSchema": {
179
+ "type": "object",
180
+ "properties": {
181
+ "message_id": {
182
+ "type": "string",
183
+ "format": "uuid",
184
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
185
+ }
186
+ },
187
+ "required": [
188
+ "message_id"
189
+ ],
190
+ "$schema": "http://json-schema.org/draft-07/schema#"
191
+ },
192
+ "annotations": {
193
+ "readOnlyHint": true,
194
+ "destructiveHint": false,
195
+ "idempotentHint": true
196
+ },
197
+ "execution": {
198
+ "taskSupport": "forbidden"
199
+ }
200
+ },
201
+ {
202
+ "name": "explain_message_failure",
203
+ "title": "Explain why a message failed",
204
+ "description": "A structured explanation derived from the failure classification recorded when the message failed. Deterministic — it does not summarise logs.",
205
+ "inputSchema": {
206
+ "type": "object",
207
+ "properties": {
208
+ "message_id": {
209
+ "type": "string",
210
+ "format": "uuid",
211
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
212
+ }
213
+ },
214
+ "required": [
215
+ "message_id"
216
+ ],
217
+ "$schema": "http://json-schema.org/draft-07/schema#"
218
+ },
219
+ "annotations": {
220
+ "readOnlyHint": true,
221
+ "destructiveHint": false,
222
+ "idempotentHint": true
223
+ },
224
+ "execution": {
225
+ "taskSupport": "forbidden"
226
+ }
227
+ },
228
+ {
229
+ "name": "list_scheduled_messages",
230
+ "title": "List scheduled messages",
231
+ "description": "Messages waiting to be sent, soonest first.",
232
+ "inputSchema": {
233
+ "type": "object",
234
+ "properties": {
235
+ "limit": {
236
+ "default": 20,
237
+ "type": "integer",
238
+ "minimum": 1,
239
+ "maximum": 50
240
+ },
241
+ "cursor": {
242
+ "type": "string",
243
+ "maxLength": 200
244
+ }
245
+ },
246
+ "$schema": "http://json-schema.org/draft-07/schema#"
247
+ },
248
+ "annotations": {
249
+ "readOnlyHint": true,
250
+ "destructiveHint": false,
251
+ "idempotentHint": true
252
+ },
253
+ "execution": {
254
+ "taskSupport": "forbidden"
255
+ }
256
+ },
257
+ {
258
+ "name": "list_webhook_deliveries",
259
+ "title": "List webhook deliveries",
260
+ "description": "Recent customer webhook deliveries and their outcomes. Payloads and endpoint URLs are not returned.",
261
+ "inputSchema": {
262
+ "type": "object",
263
+ "properties": {
264
+ "status": {
265
+ "type": "string",
266
+ "enum": [
267
+ "pending",
268
+ "delivered",
269
+ "failed",
270
+ "dead",
271
+ "skipped"
272
+ ]
273
+ },
274
+ "limit": {
275
+ "default": 20,
276
+ "type": "integer",
277
+ "minimum": 1,
278
+ "maximum": 50
279
+ }
280
+ },
281
+ "$schema": "http://json-schema.org/draft-07/schema#"
282
+ },
283
+ "annotations": {
284
+ "readOnlyHint": true,
285
+ "destructiveHint": false,
286
+ "idempotentHint": true
287
+ },
288
+ "execution": {
289
+ "taskSupport": "forbidden"
290
+ }
291
+ },
292
+ {
293
+ "name": "reschedule_message",
294
+ "title": "Move a scheduled message",
295
+ "description": "Change when a scheduled message will be sent. Returns a preview of the change; pass confirm: true to apply it.",
296
+ "inputSchema": {
297
+ "type": "object",
298
+ "properties": {
299
+ "message_id": {
300
+ "type": "string",
301
+ "format": "uuid",
302
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
303
+ },
304
+ "send_at": {
305
+ "type": "string",
306
+ "minLength": 1,
307
+ "maxLength": 40
308
+ },
309
+ "confirm": {
310
+ "default": false,
311
+ "type": "boolean"
312
+ }
313
+ },
314
+ "required": [
315
+ "message_id",
316
+ "send_at"
317
+ ],
318
+ "$schema": "http://json-schema.org/draft-07/schema#"
319
+ },
320
+ "annotations": {
321
+ "readOnlyHint": false,
322
+ "destructiveHint": false,
323
+ "idempotentHint": false
324
+ },
325
+ "execution": {
326
+ "taskSupport": "forbidden"
327
+ }
328
+ },
329
+ {
330
+ "name": "cancel_scheduled_message",
331
+ "title": "Cancel a scheduled message",
332
+ "description": "Cancel a message that has not yet been sent. Returns a preview; pass confirm: true to apply it.",
333
+ "inputSchema": {
334
+ "type": "object",
335
+ "properties": {
336
+ "message_id": {
337
+ "type": "string",
338
+ "format": "uuid",
339
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
340
+ },
341
+ "confirm": {
342
+ "default": false,
343
+ "type": "boolean"
344
+ }
345
+ },
346
+ "required": [
347
+ "message_id"
348
+ ],
349
+ "$schema": "http://json-schema.org/draft-07/schema#"
350
+ },
351
+ "annotations": {
352
+ "readOnlyHint": false,
353
+ "destructiveHint": false,
354
+ "idempotentHint": false
355
+ },
356
+ "execution": {
357
+ "taskSupport": "forbidden"
358
+ }
359
+ },
360
+ {
361
+ "name": "react_to_message",
362
+ "title": "React to a message",
363
+ "description": "Set this number's emoji reaction on a message it sent or received, or remove it with an empty emoji. The person in the chat sees it. Returns a preview; pass confirm: true to apply.",
364
+ "inputSchema": {
365
+ "type": "object",
366
+ "properties": {
367
+ "message_id": {
368
+ "type": "string",
369
+ "format": "uuid",
370
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
371
+ },
372
+ "emoji": {
373
+ "type": "string",
374
+ "maxLength": 16
375
+ },
376
+ "confirm": {
377
+ "default": false,
378
+ "type": "boolean"
379
+ }
380
+ },
381
+ "required": [
382
+ "message_id",
383
+ "emoji"
384
+ ],
385
+ "$schema": "http://json-schema.org/draft-07/schema#"
386
+ },
387
+ "annotations": {
388
+ "readOnlyHint": false,
389
+ "destructiveHint": false,
390
+ "idempotentHint": false
391
+ },
392
+ "execution": {
393
+ "taskSupport": "forbidden"
394
+ }
395
+ },
396
+ {
397
+ "name": "replay_webhook",
398
+ "title": "Replay a failed webhook delivery",
399
+ "description": "Return a dead or skipped webhook delivery to the queue. Returns a preview; pass confirm: true to apply it.",
400
+ "inputSchema": {
401
+ "type": "object",
402
+ "properties": {
403
+ "delivery_id": {
404
+ "type": "string",
405
+ "minLength": 1,
406
+ "maxLength": 64
407
+ },
408
+ "confirm": {
409
+ "default": false,
410
+ "type": "boolean"
411
+ }
412
+ },
413
+ "required": [
414
+ "delivery_id"
415
+ ],
416
+ "$schema": "http://json-schema.org/draft-07/schema#"
417
+ },
418
+ "annotations": {
419
+ "readOnlyHint": false,
420
+ "destructiveHint": false,
421
+ "idempotentHint": false
422
+ },
423
+ "execution": {
424
+ "taskSupport": "forbidden"
425
+ }
426
+ }
427
+ ]
428
+ }