@clickonsearch/whatsapp-mcp-server 0.1.0 → 0.2.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 CHANGED
@@ -1,72 +1,104 @@
1
1
  # @clickonsearch/whatsapp-mcp-server
2
2
 
3
- Connects to **your personal WhatsApp account** (the same way WhatsApp Web
4
- does — scan a QR code, keep a linked-device session) via
5
- [Baileys](https://github.com/WhiskeySockets/Baileys), and exposes it as an
6
- MCP server: `send_message`, `list_chats`, `get_recent_messages`,
7
- `search_contacts`, plus a plain SSE stream of incoming messages for anything
8
- that wants to react to them live.
3
+ Links a personal WhatsApp account to this repo's agents, the same way
4
+ WhatsApp Web does — scan a QR code once, it stays connected. Usually you
5
+ won't run this directly; [`whatsapp-agent`](../../agents/whatsapp-agent)'s
6
+ README tells you when to start it.
9
7
 
10
- > **This is not the official WhatsApp Business API.** It's an unofficial
11
- > library automating a personal account, which is outside WhatsApp's ToS for
12
- > bots. Risk is generally low for personal, low-volume use, but there's a
13
- > real (if small) chance of the account being flagged. Go in aware of that.
8
+ > **Heads up:** this isn't the official WhatsApp Business API — it's an
9
+ > unofficial library ([Baileys](https://github.com/WhiskeySockets/Baileys))
10
+ > automating a personal account, which WhatsApp's terms don't really
11
+ > cover. Risk is low for normal personal use, but there's a small chance of
12
+ > the account getting flagged.
14
13
 
15
- ## Why an MCP server, not just a script
16
-
17
- Any of this repo's remote MCP bridges (`openai-remote-mcp-bridge`,
18
- `claude-remote-mcp-bridge`, `deepseek-remote-mcp-bridge`) can already
19
- connect to **any** MCP server over Streamable HTTP — including this one,
20
- running on `localhost`. This server needed zero special-casing in those
21
- bridges; it just had to speak the same protocol GitHub's remote MCP server
22
- does.
23
-
24
- ## Setup
25
-
26
- ```bash
27
- cd mcp-servers/whatsapp-mcp-server
28
- npm install
29
- cp .env.example .env
14
+ ```
15
+ whatsapp-agent (or any MCP client)
16
+ │
17
+ ▼
18
+ ┌─────────────────────────────┐
19
+ │ whatsapp-mcp-server │ Streamable HTTP MCP
20
+ └──────────────┬──────────────┘
21
+ │
22
+ ▼
23
+ ┌─────────────────────────────┐
24
+ │ Baileys │ WhatsApp Web protocol
25
+ └──────────────┬──────────────┘
26
+ │
27
+ ▼
28
+ your WhatsApp account
30
29
  ```
31
30
 
32
31
  ## Run
33
32
 
34
33
  ```bash
35
- npm run dev
34
+ npx @clickonsearch/whatsapp-mcp-server
36
35
  ```
37
36
 
38
- On first run it prints a QR code in the terminal — scan it from your phone:
39
- **WhatsApp → Settings → Linked devices → Link a device**. The session is
40
- then saved to `WHATSAPP_AUTH_DIR` (default `./whatsapp-auth`) so you won't
41
- need to re-scan on restart, unless you log the device out from your phone.
42
-
43
- Once connected:
37
+ The first time, it prints a QR code — scan it with your phone: **WhatsApp →
38
+ Settings → Linked devices → Link a device**. After that it reconnects
39
+ automatically, no need to scan again (unless you unlink the device from
40
+ your phone).
44
41
 
45
- - `POST http://localhost:4100/mcp` — the MCP endpoint (Streamable HTTP,
46
- stateless — same shape as the SDK's own reference server).
47
- - `GET http://localhost:4100/events` — SSE stream of every incoming
48
- message: `{ chatId, fromMe, senderName, text, timestamp }`.
49
- - `GET http://localhost:4100/health` — `{ ok, connected, self }`, where
50
- `self` is your own normalized JID once connected.
42
+ Once connected, it's listening on `http://127.0.0.1:4100` for whatever
43
+ agent you point at it. No environment variables are required to try it —
44
+ everything below has a default.
51
45
 
52
- ## Config
46
+ ## Config reference
53
47
 
54
48
  | Env var | Default | Notes |
55
49
  | -------------------- | ------------------ | ---------------------------------------------- |
56
50
  | `PORT` | `4100` | |
57
- | `WHATSAPP_AUTH_DIR` | `./whatsapp-auth` | Your session — never commit this directory |
58
- | `BAILEYS_LOG_LEVEL` | `silent` | `info`/`debug` if you need to see WA internals |
51
+ | `BIND_HOST` | `127.0.0.1` | Who can connect. The default means only this computer |
52
+ | `WHATSAPP_MCP_TOKEN` | unset | Secret every caller must send. Required whenever `BIND_HOST` isn't `127.0.0.1` |
53
+ | `WHATSAPP_AUTH_DIR` | `./whatsapp-auth` | Your session — never share or commit this folder |
54
+ | `BAILEYS_LOG_LEVEL` | `silent` | Set to `info` or `debug` to see connection details |
55
+
56
+ ### Letting another machine connect
57
+
58
+ By default only programs on the same computer can reach this server. To
59
+ allow others (an agent in a container, or on another machine), pick a
60
+ secret and set both:
61
+
62
+ ```bash
63
+ export WHATSAPP_MCP_TOKEN=$(openssl rand -hex 32)
64
+ BIND_HOST=0.0.0.0 npx @clickonsearch/whatsapp-mcp-server
65
+ ```
66
+
67
+ Give `whatsapp-agent` the same `WHATSAPP_MCP_TOKEN` and it sends it
68
+ automatically. The server refuses to start on any other address without a
69
+ token. Anyone holding the token can read your chats and send messages as
70
+ you, so treat it like a password — and since it travels over plain HTTP,
71
+ only use this on a network you trust or behind TLS (for example a reverse
72
+ proxy).
73
+
74
+ ## What it exposes
75
+
76
+ | Tool | What it does |
77
+ | ---------------------- | --------------------------------------- |
78
+ | `send_message` | Sends a text message to a phone number or chat |
79
+ | `list_chats` | Lists chats seen since connecting (includes WhatsApp's own recent history) |
80
+ | `get_recent_messages` | Gets recent messages from one chat (up to 200, in memory only) |
81
+ | `search_contacts` | Finds a contact by name or number |
82
+
83
+ ---
84
+
85
+ ## For developers
59
86
 
60
- ## Tools
87
+ **Running from source:**
61
88
 
62
- | Tool | Args | Notes |
63
- | ---------------------- | ------------------------------ | ----------------------------------------------- |
64
- | `send_message` | `to`, `text` | `to` accepts a phone number or a raw JID |
65
- | `list_chats` | — | Includes history Baileys backfills on connect, plus anything live since |
66
- | `get_recent_messages` | `chat`, `limit` (default 20) | In-memory only, capped at the last 200/chat — how far back that reaches depends on how much history WhatsApp backfilled on connect |
67
- | `search_contacts` | `query` | Matches by name or number substring |
89
+ ```bash
90
+ cd mcp-servers/whatsapp-mcp-server
91
+ npm install
92
+ cp .env.example .env
93
+ npm run dev
94
+ ```
68
95
 
69
- ## Using it as a library
96
+ This runs as a standard MCP server (Streamable HTTP on `/mcp`), so any of
97
+ this repo's bridges can connect to it with no special-casing — it just had
98
+ to speak the same protocol GitHub's own remote MCP server does. It also
99
+ exposes `GET /events` (a plain SSE stream of incoming messages) and
100
+ `GET /health` (`{ ok, connected, self }`) for anything that wants to react
101
+ to messages live, like `whatsapp-agent` does.
70
102
 
71
103
  ```ts
72
104
  import { WhatsAppConnection } from "./src/whatsapp.js";
@@ -0,0 +1,16 @@
1
+ import type { RequestHandler } from "express";
2
+ export interface ListenConfig {
3
+ host: string;
4
+ /** Bearer token every caller must present. Always set when `host` isn't loopback. */
5
+ authToken?: string;
6
+ }
7
+ export declare function isLoopbackHost(host: string): boolean;
8
+ /**
9
+ * Listens on loopback only unless BIND_HOST says otherwise, and refuses to
10
+ * listen on anything else without a token — the MCP tools can send
11
+ * WhatsApp messages and read chats, so a network-reachable server with no
12
+ * caller authentication is never acceptable.
13
+ */
14
+ export declare function resolveListenConfig(env?: NodeJS.ProcessEnv): ListenConfig;
15
+ /** Rejects any request without `Authorization: Bearer <token>`. Compared in constant time. */
16
+ export declare function requireBearerToken(token: string): RequestHandler;
package/dist/access.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createHash, timingSafeEqual } from "node:crypto";
2
+ const LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
3
+ const MIN_TOKEN_LENGTH = 16;
4
+ export function isLoopbackHost(host) {
5
+ return LOOPBACK_HOSTS.includes(host);
6
+ }
7
+ /**
8
+ * Listens on loopback only unless BIND_HOST says otherwise, and refuses to
9
+ * listen on anything else without a token — the MCP tools can send
10
+ * WhatsApp messages and read chats, so a network-reachable server with no
11
+ * caller authentication is never acceptable.
12
+ */
13
+ export function resolveListenConfig(env = process.env) {
14
+ const host = env.BIND_HOST || "127.0.0.1";
15
+ const authToken = env.WHATSAPP_MCP_TOKEN || undefined;
16
+ if (authToken && authToken.length < MIN_TOKEN_LENGTH) {
17
+ throw new Error(`WHATSAPP_MCP_TOKEN must be at least ${MIN_TOKEN_LENGTH} characters (try: openssl rand -hex 32)`);
18
+ }
19
+ if (!isLoopbackHost(host) && !authToken) {
20
+ throw new Error(`Refusing to listen on ${host} without authentication. Set WHATSAPP_MCP_TOKEN ` +
21
+ `(try: openssl rand -hex 32), or unset BIND_HOST to listen on 127.0.0.1 only.`);
22
+ }
23
+ return { host, authToken };
24
+ }
25
+ function sha256(value) {
26
+ return createHash("sha256").update(value).digest();
27
+ }
28
+ /** Rejects any request without `Authorization: Bearer <token>`. Compared in constant time. */
29
+ export function requireBearerToken(token) {
30
+ const expected = sha256(token);
31
+ return (req, res, next) => {
32
+ const match = /^Bearer\s+(.+)$/i.exec(req.headers.authorization ?? "");
33
+ if (match && timingSafeEqual(sha256(match[1]), expected)) {
34
+ next();
35
+ return;
36
+ }
37
+ res.status(401).set("WWW-Authenticate", "Bearer").json({ error: "unauthorized" });
38
+ };
39
+ }
40
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;AACzD,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAQ5B,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACtE,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,IAAI,WAAW,CAAC;IAC1C,MAAM,SAAS,GAAG,GAAG,CAAC,kBAAkB,IAAI,SAAS,CAAC;IAEtD,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,uCAAuC,gBAAgB,yCAAyC,CAAC,CAAC;IACpH,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,kDAAkD;YAC7E,8EAA8E,CACjF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;AACrD,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE/B,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;QACvE,IAAI,KAAK,IAAI,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;YACzD,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC;AACJ,CAAC"}
package/dist/cli.js CHANGED
@@ -2,17 +2,29 @@
2
2
  import "dotenv/config";
3
3
  import { WhatsAppConnection } from "./whatsapp.js";
4
4
  import { createServer } from "./server.js";
5
+ import { resolveListenConfig } from "./access.js";
5
6
  async function main() {
7
+ let listen;
8
+ try {
9
+ listen = resolveListenConfig();
10
+ }
11
+ catch (error) {
12
+ console.error(error instanceof Error ? error.message : String(error));
13
+ process.exitCode = 1;
14
+ return;
15
+ }
6
16
  const authFolder = process.env.WHATSAPP_AUTH_DIR ?? "./whatsapp-auth";
7
17
  const port = Number(process.env.PORT ?? 4100);
8
18
  const whatsapp = new WhatsAppConnection(authFolder);
9
19
  await whatsapp.start();
10
- const app = createServer(whatsapp);
11
- app.listen(port, () => {
12
- console.error(`whatsapp-mcp-server listening on http://localhost:${port}`);
13
- console.error(` MCP endpoint: POST http://localhost:${port}/mcp`);
14
- console.error(` Events: GET http://localhost:${port}/events`);
15
- console.error(` Health: GET http://localhost:${port}/health`);
20
+ const app = createServer(whatsapp, listen);
21
+ app.listen(port, listen.host, () => {
22
+ const origin = `http://${listen.host.includes(":") ? `[${listen.host}]` : listen.host}:${port}`;
23
+ console.error(`whatsapp-mcp-server listening on ${origin}`);
24
+ console.error(` MCP endpoint: POST ${origin}/mcp`);
25
+ console.error(` Events: GET ${origin}/events`);
26
+ console.error(` Health: GET ${origin}/health`);
27
+ console.error(listen.authToken ? " Auth: bearer token required (WHATSAPP_MCP_TOKEN)" : " Auth: none (loopback only)");
16
28
  });
17
29
  }
18
30
  main().catch((error) => {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,iBAAiB,CAAC;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAE9C,MAAM,QAAQ,GAAG,IAAI,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEvB,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACnC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,OAAO,CAAC,KAAK,CAAC,qDAAqD,IAAI,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,MAAM,CAAC,CAAC;QACnE,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,SAAS,CAAC,CAAC;QACtE,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,SAAS,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAqB,MAAM,aAAa,CAAC;AAErE,KAAK,UAAU,IAAI;IACjB,IAAI,MAAoB,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,mBAAmB,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,iBAAiB,CAAC;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAE9C,MAAM,QAAQ,GAAG,IAAI,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEvB,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACjC,MAAM,MAAM,GAAG,UAAU,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QAChG,OAAO,CAAC,KAAK,CAAC,oCAAoC,MAAM,EAAE,CAAC,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,MAAM,CAAC,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,SAAS,CAAC,CAAC;QACvD,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,SAAS,CAAC,CAAC;QACvD,OAAO,CAAC,KAAK,CACX,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,4DAA4D,CAAC,CAAC,CAAC,sCAAsC,CACzH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export { WhatsAppConnection, normalizeJid } from "./whatsapp.js";
2
2
  export { WhatsAppStore } from "./store.js";
3
3
  export type { StoredMessage, StoredContact } from "./store.js";
4
4
  export { createServer } from "./server.js";
5
+ export { resolveListenConfig, requireBearerToken, isLoopbackHost } from "./access.js";
6
+ export type { ListenConfig } from "./access.js";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { WhatsAppConnection, normalizeJid } from "./whatsapp.js";
2
2
  export { WhatsAppStore } from "./store.js";
3
3
  export { createServer } from "./server.js";
4
+ export { resolveListenConfig, requireBearerToken, isLoopbackHost } from "./access.js";
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Express } from "express";
2
2
  import { WhatsAppConnection } from "./whatsapp.js";
3
+ import { type ListenConfig } from "./access.js";
3
4
  /**
4
5
  * Builds the Express app: `POST /mcp` is the MCP endpoint (stateless —
5
6
  * a fresh McpServer per request, matching the SDK's own stateless example),
@@ -7,5 +8,10 @@ import { WhatsAppConnection } from "./whatsapp.js";
7
8
  * part of MCP itself — MCP is request/response, so a consumer that wants
8
9
  * to react to new messages needs a separate push channel), and `GET /health`
9
10
  * reports connection status.
11
+ *
12
+ * `listen` must match what the caller passes to `app.listen`. The SDK's Host
13
+ * header check only guards browser-origin (DNS rebinding) attacks and is
14
+ * trivially bypassed by a network client sending `Host: localhost`, so it
15
+ * is not authentication: `authToken`, when set, gates every route.
10
16
  */
11
- export declare function createServer(whatsapp: WhatsAppConnection): Express;
17
+ export declare function createServer(whatsapp: WhatsAppConnection, listen?: ListenConfig): Express;
package/dist/server.js CHANGED
@@ -3,6 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
4
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
5
5
  import { normalizeJid } from "./whatsapp.js";
6
+ import { requireBearerToken } from "./access.js";
6
7
  function buildMcpServer(whatsapp) {
7
8
  const server = new McpServer({ name: "whatsapp-mcp-server", version: "0.1.0" }, { capabilities: {} });
8
9
  server.registerTool("send_message", {
@@ -45,9 +46,17 @@ function buildMcpServer(whatsapp) {
45
46
  * part of MCP itself — MCP is request/response, so a consumer that wants
46
47
  * to react to new messages needs a separate push channel), and `GET /health`
47
48
  * reports connection status.
49
+ *
50
+ * `listen` must match what the caller passes to `app.listen`. The SDK's Host
51
+ * header check only guards browser-origin (DNS rebinding) attacks and is
52
+ * trivially bypassed by a network client sending `Host: localhost`, so it
53
+ * is not authentication: `authToken`, when set, gates every route.
48
54
  */
49
- export function createServer(whatsapp) {
50
- const app = createMcpExpressApp();
55
+ export function createServer(whatsapp, listen = { host: "127.0.0.1" }) {
56
+ const app = createMcpExpressApp({ host: listen.host });
57
+ if (listen.authToken) {
58
+ app.use(requireBearerToken(listen.authToken));
59
+ }
51
60
  app.post("/mcp", async (req, res) => {
52
61
  try {
53
62
  const server = buildMcpServer(whatsapp);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clickonsearch/whatsapp-mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that connects to a personal WhatsApp account (via Baileys) and exposes it as tools — send messages, list chats, read recent messages, search contacts — plus an SSE stream of incoming messages.",
5
5
  "type": "module",
6
6
  "license": "MIT",