@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 +81 -49
- package/dist/access.d.ts +16 -0
- package/dist/access.js +40 -0
- package/dist/access.js.map +1 -0
- package/dist/cli.js +18 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +11 -2
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,72 +1,104 @@
|
|
|
1
1
|
# @clickonsearch/whatsapp-mcp-server
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
does — scan a QR code,
|
|
5
|
-
[
|
|
6
|
-
|
|
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
|
-
> **
|
|
11
|
-
> library
|
|
12
|
-
>
|
|
13
|
-
>
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
+
npx @clickonsearch/whatsapp-mcp-server
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
| `
|
|
58
|
-
| `
|
|
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
|
-
|
|
87
|
+
**Running from source:**
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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";
|
package/dist/access.d.ts
ADDED
|
@@ -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
|
-
|
|
13
|
-
console.error(`
|
|
14
|
-
console.error(`
|
|
15
|
-
console.error(`
|
|
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;
|
|
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);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AAElF,OAAO,EAAsB,YAAY,EAAE,MAAM,eAAe,CAAC;
|
|
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.
|
|
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",
|