@clickonsearch/whatsapp-mcp-server 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 +80 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +22 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +93 -0
- package/dist/server.js.map +1 -0
- package/dist/store.d.ts +35 -0
- package/dist/store.js +41 -0
- package/dist/store.js.map +1 -0
- package/dist/whatsapp.d.ts +45 -0
- package/dist/whatsapp.js +182 -0
- package/dist/whatsapp.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @clickonsearch/whatsapp-mcp-server
|
|
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.
|
|
9
|
+
|
|
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.
|
|
14
|
+
|
|
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
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Run
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
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:
|
|
44
|
+
|
|
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.
|
|
51
|
+
|
|
52
|
+
## Config
|
|
53
|
+
|
|
54
|
+
| Env var | Default | Notes |
|
|
55
|
+
| -------------------- | ------------------ | ---------------------------------------------- |
|
|
56
|
+
| `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 |
|
|
59
|
+
|
|
60
|
+
## Tools
|
|
61
|
+
|
|
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 |
|
|
68
|
+
|
|
69
|
+
## Using it as a library
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { WhatsAppConnection } from "./src/whatsapp.js";
|
|
73
|
+
import { createServer } from "./src/server.js";
|
|
74
|
+
|
|
75
|
+
const whatsapp = new WhatsAppConnection("./whatsapp-auth");
|
|
76
|
+
await whatsapp.start();
|
|
77
|
+
|
|
78
|
+
const app = createServer(whatsapp);
|
|
79
|
+
app.listen(4100);
|
|
80
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { WhatsAppConnection } from "./whatsapp.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const authFolder = process.env.WHATSAPP_AUTH_DIR ?? "./whatsapp-auth";
|
|
7
|
+
const port = Number(process.env.PORT ?? 4100);
|
|
8
|
+
const whatsapp = new WhatsAppConnection(authFolder);
|
|
9
|
+
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`);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
main().catch((error) => {
|
|
19
|
+
console.error(error);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import { WhatsAppConnection } from "./whatsapp.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds the Express app: `POST /mcp` is the MCP endpoint (stateless —
|
|
5
|
+
* a fresh McpServer per request, matching the SDK's own stateless example),
|
|
6
|
+
* `GET /events` is a plain SSE stream of incoming WhatsApp messages (not
|
|
7
|
+
* part of MCP itself — MCP is request/response, so a consumer that wants
|
|
8
|
+
* to react to new messages needs a separate push channel), and `GET /health`
|
|
9
|
+
* reports connection status.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createServer(whatsapp: WhatsAppConnection): Express;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
5
|
+
import { normalizeJid } from "./whatsapp.js";
|
|
6
|
+
function buildMcpServer(whatsapp) {
|
|
7
|
+
const server = new McpServer({ name: "whatsapp-mcp-server", version: "0.1.0" }, { capabilities: {} });
|
|
8
|
+
server.registerTool("send_message", {
|
|
9
|
+
description: "Send a WhatsApp text message to a contact or group",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
to: z.string().describe("Phone number (e.g. 15551234567) or a raw WhatsApp JID"),
|
|
12
|
+
text: z.string().describe("Message text to send"),
|
|
13
|
+
},
|
|
14
|
+
}, async ({ to, text }) => {
|
|
15
|
+
await whatsapp.sendMessage(to, text);
|
|
16
|
+
return { content: [{ type: "text", text: `Sent to ${to}` }] };
|
|
17
|
+
});
|
|
18
|
+
server.registerTool("list_chats", { description: "List known WhatsApp chats, including recent history synced on connect", inputSchema: {} }, async () => {
|
|
19
|
+
const chats = whatsapp.store.listChats();
|
|
20
|
+
return { content: [{ type: "text", text: JSON.stringify(chats, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
server.registerTool("get_recent_messages", {
|
|
23
|
+
description: "Get recent messages from a WhatsApp chat",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
chat: z.string().describe("Chat JID or phone number"),
|
|
26
|
+
limit: z.number().int().min(1).max(200).default(20).describe("Max messages to return"),
|
|
27
|
+
},
|
|
28
|
+
}, async ({ chat, limit }) => {
|
|
29
|
+
const messages = whatsapp.store.getRecentMessages(normalizeJid(chat), limit);
|
|
30
|
+
return { content: [{ type: "text", text: JSON.stringify(messages, null, 2) }] };
|
|
31
|
+
});
|
|
32
|
+
server.registerTool("search_contacts", {
|
|
33
|
+
description: "Search known WhatsApp contacts by name or number",
|
|
34
|
+
inputSchema: { query: z.string().describe("Name or number substring to search for") },
|
|
35
|
+
}, async ({ query }) => {
|
|
36
|
+
const results = whatsapp.store.searchContacts(query);
|
|
37
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
38
|
+
});
|
|
39
|
+
return server;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Builds the Express app: `POST /mcp` is the MCP endpoint (stateless —
|
|
43
|
+
* a fresh McpServer per request, matching the SDK's own stateless example),
|
|
44
|
+
* `GET /events` is a plain SSE stream of incoming WhatsApp messages (not
|
|
45
|
+
* part of MCP itself — MCP is request/response, so a consumer that wants
|
|
46
|
+
* to react to new messages needs a separate push channel), and `GET /health`
|
|
47
|
+
* reports connection status.
|
|
48
|
+
*/
|
|
49
|
+
export function createServer(whatsapp) {
|
|
50
|
+
const app = createMcpExpressApp();
|
|
51
|
+
app.post("/mcp", async (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const server = buildMcpServer(whatsapp);
|
|
54
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
55
|
+
await server.connect(transport);
|
|
56
|
+
await transport.handleRequest(req, res, req.body);
|
|
57
|
+
res.on("close", () => {
|
|
58
|
+
void transport.close();
|
|
59
|
+
void server.close();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (!res.headersSent) {
|
|
64
|
+
res.status(500).json({
|
|
65
|
+
jsonrpc: "2.0",
|
|
66
|
+
error: { code: -32603, message: error instanceof Error ? error.message : String(error) },
|
|
67
|
+
id: null,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
app.get("/health", (_req, res) => {
|
|
73
|
+
res.json({ ok: true, connected: whatsapp.isReady, self: whatsapp.selfId ?? null });
|
|
74
|
+
});
|
|
75
|
+
app.get("/events", (req, res) => {
|
|
76
|
+
res.writeHead(200, {
|
|
77
|
+
"Content-Type": "text/event-stream",
|
|
78
|
+
"Cache-Control": "no-cache, no-transform",
|
|
79
|
+
Connection: "keep-alive",
|
|
80
|
+
"X-Accel-Buffering": "no",
|
|
81
|
+
});
|
|
82
|
+
res.write(": connected\n\n");
|
|
83
|
+
const onMessage = (message) => {
|
|
84
|
+
res.write(`data: ${JSON.stringify(message)}\n\n`);
|
|
85
|
+
};
|
|
86
|
+
whatsapp.on("message", onMessage);
|
|
87
|
+
req.on("close", () => {
|
|
88
|
+
whatsapp.off("message", onMessage);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
return app;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface StoredMessage {
|
|
2
|
+
chatId: string;
|
|
3
|
+
fromMe: boolean;
|
|
4
|
+
/** True when `chatId` is the user's own "Message yourself" chat. */
|
|
5
|
+
isSelfChat: boolean;
|
|
6
|
+
senderName?: string;
|
|
7
|
+
text: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
export interface StoredContact {
|
|
11
|
+
id: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* In-memory only, not persisted across restarts. Populated both from
|
|
16
|
+
* Baileys' one-time history backfill on connect (see whatsapp.ts's
|
|
17
|
+
* "messaging-history.set" handler) and from live messages afterward, so
|
|
18
|
+
* this has enough recall for the agent to answer "what did they say" /
|
|
19
|
+
* "who is X" without needing its own database.
|
|
20
|
+
*/
|
|
21
|
+
export declare class WhatsAppStore {
|
|
22
|
+
private messagesByChat;
|
|
23
|
+
private contacts;
|
|
24
|
+
private readonly maxMessagesPerChat;
|
|
25
|
+
recordMessage(message: StoredMessage): void;
|
|
26
|
+
recordContact(id: string, name?: string): void;
|
|
27
|
+
listChats(): {
|
|
28
|
+
chatId: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
lastMessageAt?: number;
|
|
31
|
+
messageCount: number;
|
|
32
|
+
}[];
|
|
33
|
+
getRecentMessages(chatId: string, limit: number): StoredMessage[];
|
|
34
|
+
searchContacts(query: string): StoredContact[];
|
|
35
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory only, not persisted across restarts. Populated both from
|
|
3
|
+
* Baileys' one-time history backfill on connect (see whatsapp.ts's
|
|
4
|
+
* "messaging-history.set" handler) and from live messages afterward, so
|
|
5
|
+
* this has enough recall for the agent to answer "what did they say" /
|
|
6
|
+
* "who is X" without needing its own database.
|
|
7
|
+
*/
|
|
8
|
+
export class WhatsAppStore {
|
|
9
|
+
messagesByChat = new Map();
|
|
10
|
+
contacts = new Map();
|
|
11
|
+
maxMessagesPerChat = 200;
|
|
12
|
+
recordMessage(message) {
|
|
13
|
+
const list = this.messagesByChat.get(message.chatId) ?? [];
|
|
14
|
+
list.push(message);
|
|
15
|
+
if (list.length > this.maxMessagesPerChat)
|
|
16
|
+
list.shift();
|
|
17
|
+
this.messagesByChat.set(message.chatId, list);
|
|
18
|
+
}
|
|
19
|
+
recordContact(id, name) {
|
|
20
|
+
if (!name)
|
|
21
|
+
return;
|
|
22
|
+
this.contacts.set(id, { id, name });
|
|
23
|
+
}
|
|
24
|
+
listChats() {
|
|
25
|
+
return [...this.messagesByChat.entries()].map(([chatId, messages]) => ({
|
|
26
|
+
chatId,
|
|
27
|
+
name: this.contacts.get(chatId)?.name,
|
|
28
|
+
lastMessageAt: messages.at(-1)?.timestamp,
|
|
29
|
+
messageCount: messages.length,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
getRecentMessages(chatId, limit) {
|
|
33
|
+
const list = this.messagesByChat.get(chatId) ?? [];
|
|
34
|
+
return list.slice(-limit);
|
|
35
|
+
}
|
|
36
|
+
searchContacts(query) {
|
|
37
|
+
const q = query.toLowerCase();
|
|
38
|
+
return [...this.contacts.values()].filter((c) => c.id.toLowerCase().includes(q) || c.name?.toLowerCase().includes(q));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IAChB,cAAc,GAAG,IAAI,GAAG,EAA2B,CAAC;IACpD,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IACnC,kBAAkB,GAAG,GAAG,CAAC;IAE1C,aAAa,CAAC,OAAsB;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,kBAAkB;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACxD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,aAAa,CAAC,EAAU,EAAE,IAAa;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,SAAS;QACP,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;YACrE,MAAM;YACN,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI;YACrC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS;YACzC,YAAY,EAAE,QAAQ,CAAC,MAAM;SAC9B,CAAC,CAAC,CAAC;IACN,CAAC;IAED,iBAAiB,CAAC,MAAc,EAAE,KAAa;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,cAAc,CAAC,KAAa;QAC1B,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC3E,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { WhatsAppStore } from "./store.js";
|
|
3
|
+
/** Accepts a raw JID ("1555...@s.whatsapp.net") or a bare phone number and returns a JID. */
|
|
4
|
+
export declare function normalizeJid(input: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Owns one Baileys ("linked device") session for a personal WhatsApp
|
|
7
|
+
* account: QR pairing, session persistence, and buffering recent
|
|
8
|
+
* chats/messages/contacts so the MCP tools below have something to read.
|
|
9
|
+
*/
|
|
10
|
+
export declare class WhatsAppConnection extends EventEmitter {
|
|
11
|
+
readonly store: WhatsAppStore;
|
|
12
|
+
private socket?;
|
|
13
|
+
private selfJid?;
|
|
14
|
+
/**
|
|
15
|
+
* WhatsApp is rolling out privacy-preserving "LID" identities
|
|
16
|
+
* (`@lid` JIDs) alongside the classic phone-number JID
|
|
17
|
+
* (`@s.whatsapp.net`). On accounts where this has rolled out, your own
|
|
18
|
+
* "Message yourself" chat's `remoteJid` is the `@lid` form even though
|
|
19
|
+
* `socket.user.id` still reports the phone-number form — the two will
|
|
20
|
+
* never string-match, so both are tracked here and `isSelfChat` checks
|
|
21
|
+
* against either.
|
|
22
|
+
*/
|
|
23
|
+
private selfLid?;
|
|
24
|
+
private readonly authFolder;
|
|
25
|
+
private readonly logger;
|
|
26
|
+
/**
|
|
27
|
+
* IDs of messages this connection sent via `sendMessage`. WhatsApp's
|
|
28
|
+
* multi-device sync echoes every sent message back through
|
|
29
|
+
* `messages.upsert` — including to a self-chat — with `fromMe: true`,
|
|
30
|
+
* indistinguishable from a message the human actually typed unless we
|
|
31
|
+
* track our own sends. Without this, any consumer that reacts to
|
|
32
|
+
* `fromMe` self-chat messages (like whatsapp-agent's instruction
|
|
33
|
+
* listener) will re-trigger on its own replies forever.
|
|
34
|
+
*/
|
|
35
|
+
private readonly ownMessageIds;
|
|
36
|
+
constructor(authFolder: string);
|
|
37
|
+
get isReady(): boolean;
|
|
38
|
+
get selfId(): string | undefined;
|
|
39
|
+
/** True when `chatId` is your own "Message yourself" chat, in either JID form. */
|
|
40
|
+
isSelfChat(chatId: string): boolean;
|
|
41
|
+
start(): Promise<void>;
|
|
42
|
+
/** Extracts, dedupes against our own sends, and stores one incoming message. Returns it if it was actually stored. */
|
|
43
|
+
private recordMessage;
|
|
44
|
+
sendMessage(to: string, text: string): Promise<void>;
|
|
45
|
+
}
|
package/dist/whatsapp.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import makeWASocket, { useMultiFileAuthState, fetchLatestBaileysVersion, jidNormalizedUser, DisconnectReason, } from "@whiskeysockets/baileys";
|
|
3
|
+
import pino from "pino";
|
|
4
|
+
import qrcode from "qrcode-terminal";
|
|
5
|
+
import { WhatsAppStore } from "./store.js";
|
|
6
|
+
/** Accepts a raw JID ("1555...@s.whatsapp.net") or a bare phone number and returns a JID. */
|
|
7
|
+
export function normalizeJid(input) {
|
|
8
|
+
const trimmed = input.trim();
|
|
9
|
+
if (trimmed.includes("@"))
|
|
10
|
+
return trimmed;
|
|
11
|
+
const digits = trimmed.replace(/[^\d]/g, "");
|
|
12
|
+
if (!digits) {
|
|
13
|
+
throw new Error(`"${input}" is not a valid phone number or WhatsApp JID`);
|
|
14
|
+
}
|
|
15
|
+
return `${digits}@s.whatsapp.net`;
|
|
16
|
+
}
|
|
17
|
+
function extractText(message) {
|
|
18
|
+
const content = message.message;
|
|
19
|
+
if (!content)
|
|
20
|
+
return undefined;
|
|
21
|
+
return (content.conversation ??
|
|
22
|
+
content.extendedTextMessage?.text ??
|
|
23
|
+
content.imageMessage?.caption ??
|
|
24
|
+
content.videoMessage?.caption ??
|
|
25
|
+
undefined);
|
|
26
|
+
}
|
|
27
|
+
function extractTimestamp(message) {
|
|
28
|
+
const raw = Number(message.messageTimestamp);
|
|
29
|
+
return (Number.isFinite(raw) && raw > 0 ? raw : Date.now() / 1000) * 1000;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Owns one Baileys ("linked device") session for a personal WhatsApp
|
|
33
|
+
* account: QR pairing, session persistence, and buffering recent
|
|
34
|
+
* chats/messages/contacts so the MCP tools below have something to read.
|
|
35
|
+
*/
|
|
36
|
+
export class WhatsAppConnection extends EventEmitter {
|
|
37
|
+
store = new WhatsAppStore();
|
|
38
|
+
socket;
|
|
39
|
+
selfJid;
|
|
40
|
+
/**
|
|
41
|
+
* WhatsApp is rolling out privacy-preserving "LID" identities
|
|
42
|
+
* (`@lid` JIDs) alongside the classic phone-number JID
|
|
43
|
+
* (`@s.whatsapp.net`). On accounts where this has rolled out, your own
|
|
44
|
+
* "Message yourself" chat's `remoteJid` is the `@lid` form even though
|
|
45
|
+
* `socket.user.id` still reports the phone-number form — the two will
|
|
46
|
+
* never string-match, so both are tracked here and `isSelfChat` checks
|
|
47
|
+
* against either.
|
|
48
|
+
*/
|
|
49
|
+
selfLid;
|
|
50
|
+
authFolder;
|
|
51
|
+
logger = pino({ level: process.env.BAILEYS_LOG_LEVEL ?? "silent" });
|
|
52
|
+
/**
|
|
53
|
+
* IDs of messages this connection sent via `sendMessage`. WhatsApp's
|
|
54
|
+
* multi-device sync echoes every sent message back through
|
|
55
|
+
* `messages.upsert` — including to a self-chat — with `fromMe: true`,
|
|
56
|
+
* indistinguishable from a message the human actually typed unless we
|
|
57
|
+
* track our own sends. Without this, any consumer that reacts to
|
|
58
|
+
* `fromMe` self-chat messages (like whatsapp-agent's instruction
|
|
59
|
+
* listener) will re-trigger on its own replies forever.
|
|
60
|
+
*/
|
|
61
|
+
ownMessageIds = new Set();
|
|
62
|
+
constructor(authFolder) {
|
|
63
|
+
super();
|
|
64
|
+
this.authFolder = authFolder;
|
|
65
|
+
}
|
|
66
|
+
get isReady() {
|
|
67
|
+
return Boolean(this.socket && this.selfJid);
|
|
68
|
+
}
|
|
69
|
+
get selfId() {
|
|
70
|
+
return this.selfJid;
|
|
71
|
+
}
|
|
72
|
+
/** True when `chatId` is your own "Message yourself" chat, in either JID form. */
|
|
73
|
+
isSelfChat(chatId) {
|
|
74
|
+
if (!this.selfJid && !this.selfLid)
|
|
75
|
+
return false;
|
|
76
|
+
const normalized = jidNormalizedUser(chatId);
|
|
77
|
+
return normalized === this.selfJid || normalized === this.selfLid;
|
|
78
|
+
}
|
|
79
|
+
async start() {
|
|
80
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.authFolder);
|
|
81
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
82
|
+
const socket = makeWASocket({
|
|
83
|
+
auth: state,
|
|
84
|
+
version,
|
|
85
|
+
logger: this.logger,
|
|
86
|
+
printQRInTerminal: false,
|
|
87
|
+
});
|
|
88
|
+
this.socket = socket;
|
|
89
|
+
socket.ev.on("creds.update", saveCreds);
|
|
90
|
+
socket.ev.on("connection.update", (update) => {
|
|
91
|
+
const { connection, qr, lastDisconnect } = update;
|
|
92
|
+
if (qr) {
|
|
93
|
+
console.error("\nScan this QR code in WhatsApp: Settings > Linked devices > Link a device\n");
|
|
94
|
+
qrcode.generate(qr, { small: true });
|
|
95
|
+
}
|
|
96
|
+
if (connection === "open") {
|
|
97
|
+
this.selfJid = socket.user ? jidNormalizedUser(socket.user.id) : undefined;
|
|
98
|
+
this.selfLid = socket.user?.lid ? jidNormalizedUser(socket.user.lid) : undefined;
|
|
99
|
+
console.error(`WhatsApp connected as ${this.selfJid}${this.selfLid ? ` (lid: ${this.selfLid})` : ""}`);
|
|
100
|
+
if (this.selfJid)
|
|
101
|
+
this.emit("ready", this.selfJid);
|
|
102
|
+
}
|
|
103
|
+
if (connection === "close") {
|
|
104
|
+
this.selfJid = undefined;
|
|
105
|
+
this.selfLid = undefined;
|
|
106
|
+
const statusCode = lastDisconnect?.error?.output
|
|
107
|
+
?.statusCode;
|
|
108
|
+
const loggedOut = statusCode === DisconnectReason.loggedOut;
|
|
109
|
+
console.error(`WhatsApp connection closed${loggedOut ? " (logged out — delete the auth folder to relink)" : ", reconnecting..."}`);
|
|
110
|
+
if (loggedOut) {
|
|
111
|
+
this.emit("logged-out");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
void this.start();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
socket.ev.on("contacts.upsert", (contacts) => {
|
|
119
|
+
for (const contact of contacts) {
|
|
120
|
+
this.store.recordContact(contact.id, contact.name ?? contact.notify);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Live messages: record AND emit, so whatsapp-agent's listener reacts to them.
|
|
124
|
+
socket.ev.on("messages.upsert", ({ messages, type }) => {
|
|
125
|
+
if (type !== "notify" && type !== "append")
|
|
126
|
+
return;
|
|
127
|
+
for (const message of messages) {
|
|
128
|
+
const stored = this.recordMessage(message);
|
|
129
|
+
if (stored)
|
|
130
|
+
this.emit("message", stored);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// One-time backfill Baileys sends right after connecting: without this,
|
|
134
|
+
// the store starts empty on every restart and list_chats/
|
|
135
|
+
// get_recent_messages only ever see activity that happens to arrive
|
|
136
|
+
// while the server is running. Recorded but never emitted — these are
|
|
137
|
+
// old messages, not new instructions for whatsapp-agent to act on.
|
|
138
|
+
socket.ev.on("messaging-history.set", ({ messages, contacts }) => {
|
|
139
|
+
for (const contact of contacts) {
|
|
140
|
+
this.store.recordContact(contact.id, contact.name ?? contact.notify);
|
|
141
|
+
}
|
|
142
|
+
for (const message of messages) {
|
|
143
|
+
this.recordMessage(message);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** Extracts, dedupes against our own sends, and stores one incoming message. Returns it if it was actually stored. */
|
|
148
|
+
recordMessage(message) {
|
|
149
|
+
const chatId = message.key.remoteJid;
|
|
150
|
+
const text = extractText(message);
|
|
151
|
+
if (!chatId || !text)
|
|
152
|
+
return undefined;
|
|
153
|
+
if (message.key.id && this.ownMessageIds.has(message.key.id)) {
|
|
154
|
+
this.ownMessageIds.delete(message.key.id);
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
const stored = {
|
|
158
|
+
chatId,
|
|
159
|
+
fromMe: Boolean(message.key.fromMe),
|
|
160
|
+
isSelfChat: this.isSelfChat(chatId),
|
|
161
|
+
senderName: message.pushName ?? undefined,
|
|
162
|
+
text,
|
|
163
|
+
timestamp: extractTimestamp(message),
|
|
164
|
+
};
|
|
165
|
+
this.store.recordMessage(stored);
|
|
166
|
+
return stored;
|
|
167
|
+
}
|
|
168
|
+
async sendMessage(to, text) {
|
|
169
|
+
if (!this.socket) {
|
|
170
|
+
throw new Error("WhatsApp is not connected yet");
|
|
171
|
+
}
|
|
172
|
+
const sent = await this.socket.sendMessage(normalizeJid(to), { text });
|
|
173
|
+
const id = sent?.key?.id;
|
|
174
|
+
if (id) {
|
|
175
|
+
this.ownMessageIds.add(id);
|
|
176
|
+
// The echo normally arrives within a second or two; this is just a
|
|
177
|
+
// bound so an echo that never comes doesn't leak memory forever.
|
|
178
|
+
setTimeout(() => this.ownMessageIds.delete(id), 60_000).unref();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=whatsapp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp.js","sourceRoot":"","sources":["../src/whatsapp.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clickonsearch/whatsapp-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/ClickOnSearch/ai-apps.git",
|
|
10
|
+
"directory": "mcp-servers/whatsapp-mcp-server"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"whatsapp-mcp-server": "dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"start": "node dist/cli.js",
|
|
32
|
+
"dev": "tsx src/cli.ts"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
36
|
+
"@whiskeysockets/baileys": "^7.0.0-rc14",
|
|
37
|
+
"dotenv": "^16.4.5",
|
|
38
|
+
"express": "^4.21.1",
|
|
39
|
+
"pino": "^10.3.1",
|
|
40
|
+
"qrcode-terminal": "^0.12.0",
|
|
41
|
+
"zod": "^3.25.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/express": "^4.17.21",
|
|
45
|
+
"@types/node": "^22.7.5",
|
|
46
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
47
|
+
"tsx": "^4.19.1",
|
|
48
|
+
"typescript": "^5.6.3"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
}
|
|
53
|
+
}
|