@daisyintel/whatsapp-cloud-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.
@@ -0,0 +1,154 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { createHmac } from "node:crypto";
4
+ import { InMemoryWebhookEventStore } from "../src/eventStore.js";
5
+ import { loadConfig } from "../src/config.js";
6
+ import { handleWebhookRequest, normalizeWebhookEvents } from "../src/webhook.js";
7
+ function createMockRequest(options = {
8
+ method: "GET",
9
+ url: "/"
10
+ }) {
11
+ const body = options.body ?? "";
12
+ const chunks = body ? [Buffer.from(body)] : [];
13
+ return {
14
+ method: options.method,
15
+ url: options.url,
16
+ headers: options.headers ?? {},
17
+ async *[Symbol.asyncIterator]() {
18
+ for (const chunk of chunks) {
19
+ yield chunk;
20
+ }
21
+ }
22
+ };
23
+ }
24
+ function createMockResponse() {
25
+ let body = "";
26
+ const headers = new Map();
27
+ return {
28
+ statusCode: 200,
29
+ setHeader(name, value) {
30
+ headers.set(name.toLowerCase(), value);
31
+ },
32
+ end(chunk) {
33
+ body = chunk ?? "";
34
+ },
35
+ getBody() {
36
+ return body;
37
+ },
38
+ getHeader(name) {
39
+ return headers.get(name.toLowerCase());
40
+ }
41
+ };
42
+ }
43
+ test("normalizeWebhookEvents extracts messages and statuses", () => {
44
+ const events = normalizeWebhookEvents({
45
+ entry: [
46
+ {
47
+ id: "waba-1",
48
+ changes: [
49
+ {
50
+ value: {
51
+ metadata: {
52
+ phone_number_id: "phone-1"
53
+ },
54
+ messages: [
55
+ {
56
+ id: "message-1",
57
+ from: "15550000000",
58
+ type: "text"
59
+ }
60
+ ],
61
+ statuses: [
62
+ {
63
+ id: "message-2",
64
+ recipient_id: "15559999999",
65
+ status: "delivered"
66
+ }
67
+ ]
68
+ }
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ });
74
+ assert.equal(events.length, 2);
75
+ assert.equal(events[0]?.kind, "inbound_message");
76
+ assert.equal(events[1]?.kind, "message_status");
77
+ });
78
+ test("webhook verification succeeds with matching verify token", async () => {
79
+ const config = loadConfig({
80
+ WHATSAPP_ACCESS_TOKEN: "token",
81
+ WHATSAPP_VERIFY_TOKEN: "verify"
82
+ });
83
+ const request = createMockRequest({
84
+ method: "GET",
85
+ url: "/webhook?hub.mode=subscribe&hub.verify_token=verify&hub.challenge=challenge"
86
+ });
87
+ const response = createMockResponse();
88
+ await handleWebhookRequest(request, response, config, new InMemoryWebhookEventStore(10));
89
+ assert.equal(response.statusCode, 200);
90
+ assert.equal(response.getBody(), "challenge");
91
+ });
92
+ test("webhook post validates signed payloads", async () => {
93
+ const config = loadConfig({
94
+ WHATSAPP_ACCESS_TOKEN: "token",
95
+ META_APP_SECRET: "secret"
96
+ });
97
+ const payload = JSON.stringify({
98
+ entry: [
99
+ {
100
+ id: "waba-1",
101
+ changes: [
102
+ {
103
+ value: {
104
+ metadata: {
105
+ phone_number_id: "phone-1"
106
+ },
107
+ messages: [
108
+ {
109
+ id: "message-1",
110
+ from: "15550000000",
111
+ type: "text"
112
+ }
113
+ ]
114
+ }
115
+ }
116
+ ]
117
+ }
118
+ ]
119
+ });
120
+ const signature = createHmac("sha256", "secret").update(payload).digest("hex");
121
+ const request = createMockRequest({
122
+ method: "POST",
123
+ url: "/webhook",
124
+ body: payload,
125
+ headers: {
126
+ host: "localhost",
127
+ "x-hub-signature-256": `sha256=${signature}`
128
+ }
129
+ });
130
+ const response = createMockResponse();
131
+ const store = new InMemoryWebhookEventStore(10);
132
+ await handleWebhookRequest(request, response, config, store);
133
+ assert.equal(response.statusCode, 200);
134
+ assert.equal(store.list(10).length, 1);
135
+ });
136
+ test("webhook post can allow unsigned payloads in dev mode", async () => {
137
+ const config = loadConfig({
138
+ WHATSAPP_ACCESS_TOKEN: "token",
139
+ WHATSAPP_ALLOW_UNSIGNED_WEBHOOKS: "true"
140
+ });
141
+ const request = createMockRequest({
142
+ method: "POST",
143
+ url: "/webhook",
144
+ body: JSON.stringify({ entry: [] }),
145
+ headers: {
146
+ host: "localhost"
147
+ }
148
+ });
149
+ const response = createMockResponse();
150
+ await handleWebhookRequest(request, response, config, new InMemoryWebhookEventStore(10));
151
+ assert.equal(response.statusCode, 200);
152
+ assert.equal(JSON.parse(response.getBody()).received_events, 0);
153
+ });
154
+ //# sourceMappingURL=webhook.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.test.js","sourceRoot":"","sources":["../../test/webhook.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ {
2
+ "mcpServers": {
3
+ "whatsapp-cloud-api": {
4
+ "command": "node",
5
+ "args": ["dist/index.js"],
6
+ "env": {
7
+ "WHATSAPP_ACCESS_TOKEN": "replace-me",
8
+ "WHATSAPP_API_VERSION": "v23.0",
9
+ "WHATSAPP_BUSINESS_ID": "",
10
+ "WHATSAPP_WABA_ID": "",
11
+ "WHATSAPP_PHONE_NUMBER_ID": "",
12
+ "WHATSAPP_VERIFY_TOKEN": "replace-me",
13
+ "META_APP_SECRET": "",
14
+ "PORT": "3000",
15
+ "WHATSAPP_ALLOW_UNSIGNED_WEBHOOKS": "false"
16
+ }
17
+ }
18
+ }
19
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@daisyintel/whatsapp-cloud-mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "MCP server for the WhatsApp Cloud API with webhook support.",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "mcp",
9
+ "model-context-protocol",
10
+ "whatsapp",
11
+ "whatsapp-cloud-api",
12
+ "meta"
13
+ ],
14
+ "bin": {
15
+ "whatsapp-cloud-mcp": "dist/src/bin/server.js",
16
+ "whatsapp-cloud-mcp-inspect": "dist/src/bin/inspect.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "mcp.json.example"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "prepack": "npm run build",
26
+ "start": "node dist/src/bin/server.js",
27
+ "dev": "tsx src/index.ts",
28
+ "test": "tsx --test test/**/*.test.ts",
29
+ "inspect": "node dist/src/bin/inspect.js",
30
+ "inspect:dev": "mcp-inspector tsx src/index.ts"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/inspector": "^0.21.1",
37
+ "@modelcontextprotocol/sdk": "^1.5.0",
38
+ "dotenv": "^16.4.7",
39
+ "zod": "^3.24.2"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^24.0.0",
43
+ "tsx": "^4.19.2",
44
+ "typescript": "^5.8.3"
45
+ }
46
+ }