@sjawhar/whatsapp-mcp 2.0.0 → 2.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/dist/db.d.ts +28 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +153 -30
- package/dist/db.js.map +1 -1
- package/dist/http-server.d.ts +8 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +198 -0
- package/dist/http-server.js.map +1 -0
- package/dist/import-contacts.d.ts +6 -3
- package/dist/import-contacts.d.ts.map +1 -1
- package/dist/import-contacts.js +28 -14
- package/dist/import-contacts.js.map +1 -1
- package/dist/index.js +192 -26
- package/dist/index.js.map +1 -1
- package/dist/resources.d.ts +16 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +84 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +38 -15
- package/dist/tools.js.map +1 -1
- package/dist/whatsapp.d.ts +13 -1
- package/dist/whatsapp.d.ts.map +1 -1
- package/dist/whatsapp.js +167 -31
- package/dist/whatsapp.js.map +1 -1
- package/package.json +5 -1
- package/dist/__tests__/connection.test.d.ts +0 -2
- package/dist/__tests__/connection.test.d.ts.map +0 -1
- package/dist/__tests__/connection.test.js +0 -105
- package/dist/__tests__/connection.test.js.map +0 -1
- package/dist/__tests__/disconnect.test.d.ts +0 -2
- package/dist/__tests__/disconnect.test.d.ts.map +0 -1
- package/dist/__tests__/disconnect.test.js +0 -166
- package/dist/__tests__/disconnect.test.js.map +0 -1
- package/dist/__tests__/download-media.test.d.ts +0 -2
- package/dist/__tests__/download-media.test.d.ts.map +0 -1
- package/dist/__tests__/download-media.test.js +0 -110
- package/dist/__tests__/download-media.test.js.map +0 -1
- package/dist/__tests__/failures/connection-failures.test.d.ts +0 -2
- package/dist/__tests__/failures/connection-failures.test.d.ts.map +0 -1
- package/dist/__tests__/failures/connection-failures.test.js +0 -146
- package/dist/__tests__/failures/connection-failures.test.js.map +0 -1
- package/dist/__tests__/failures/edge-cases.test.d.ts +0 -2
- package/dist/__tests__/failures/edge-cases.test.d.ts.map +0 -1
- package/dist/__tests__/failures/edge-cases.test.js +0 -121
- package/dist/__tests__/failures/edge-cases.test.js.map +0 -1
- package/dist/__tests__/failures/resource-failures.test.d.ts +0 -2
- package/dist/__tests__/failures/resource-failures.test.d.ts.map +0 -1
- package/dist/__tests__/failures/resource-failures.test.js +0 -136
- package/dist/__tests__/failures/resource-failures.test.js.map +0 -1
- package/dist/__tests__/failures/security-failures.test.d.ts +0 -2
- package/dist/__tests__/failures/security-failures.test.d.ts.map +0 -1
- package/dist/__tests__/failures/security-failures.test.js +0 -0
- package/dist/__tests__/failures/security-failures.test.js.map +0 -1
- package/dist/__tests__/helpers/fake-baileys.d.ts +0 -52
- package/dist/__tests__/helpers/fake-baileys.d.ts.map +0 -1
- package/dist/__tests__/helpers/fake-baileys.js +0 -60
- package/dist/__tests__/helpers/fake-baileys.js.map +0 -1
- package/dist/__tests__/helpers/mcp-test-client.d.ts +0 -9
- package/dist/__tests__/helpers/mcp-test-client.d.ts.map +0 -1
- package/dist/__tests__/helpers/mcp-test-client.js +0 -40
- package/dist/__tests__/helpers/mcp-test-client.js.map +0 -1
- package/dist/__tests__/helpers/test-db.d.ts +0 -4
- package/dist/__tests__/helpers/test-db.d.ts.map +0 -1
- package/dist/__tests__/helpers/test-db.js +0 -32
- package/dist/__tests__/helpers/test-db.js.map +0 -1
- package/dist/__tests__/integration/chat-navigation.test.d.ts +0 -2
- package/dist/__tests__/integration/chat-navigation.test.d.ts.map +0 -1
- package/dist/__tests__/integration/chat-navigation.test.js +0 -171
- package/dist/__tests__/integration/chat-navigation.test.js.map +0 -1
- package/dist/__tests__/integration/contacts-flow.test.d.ts +0 -2
- package/dist/__tests__/integration/contacts-flow.test.d.ts.map +0 -1
- package/dist/__tests__/integration/contacts-flow.test.js +0 -144
- package/dist/__tests__/integration/contacts-flow.test.js.map +0 -1
- package/dist/__tests__/integration/media-flow.test.d.ts +0 -2
- package/dist/__tests__/integration/media-flow.test.d.ts.map +0 -1
- package/dist/__tests__/integration/media-flow.test.js +0 -225
- package/dist/__tests__/integration/media-flow.test.js.map +0 -1
- package/dist/__tests__/integration/search-flow.test.d.ts +0 -2
- package/dist/__tests__/integration/search-flow.test.d.ts.map +0 -1
- package/dist/__tests__/integration/search-flow.test.js +0 -44
- package/dist/__tests__/integration/search-flow.test.js.map +0 -1
- package/dist/__tests__/integration/send-message.test.d.ts +0 -2
- package/dist/__tests__/integration/send-message.test.d.ts.map +0 -1
- package/dist/__tests__/integration/send-message.test.js +0 -160
- package/dist/__tests__/integration/send-message.test.js.map +0 -1
- package/dist/__tests__/lock-file.test.d.ts +0 -2
- package/dist/__tests__/lock-file.test.d.ts.map +0 -1
- package/dist/__tests__/lock-file.test.js +0 -63
- package/dist/__tests__/lock-file.test.js.map +0 -1
- package/dist/__tests__/medium-fixes.test.d.ts +0 -2
- package/dist/__tests__/medium-fixes.test.d.ts.map +0 -1
- package/dist/__tests__/medium-fixes.test.js +0 -141
- package/dist/__tests__/medium-fixes.test.js.map +0 -1
- package/dist/__tests__/rate-limit.test.d.ts +0 -2
- package/dist/__tests__/rate-limit.test.d.ts.map +0 -1
- package/dist/__tests__/rate-limit.test.js +0 -193
- package/dist/__tests__/rate-limit.test.js.map +0 -1
- package/dist/__tests__/send-file.test.d.ts +0 -2
- package/dist/__tests__/send-file.test.d.ts.map +0 -1
- package/dist/__tests__/send-file.test.js +0 -237
- package/dist/__tests__/send-file.test.js.map +0 -1
- package/dist/__tests__/smoke.test.d.ts +0 -2
- package/dist/__tests__/smoke.test.d.ts.map +0 -1
- package/dist/__tests__/smoke.test.js +0 -28
- package/dist/__tests__/smoke.test.js.map +0 -1
- package/dist/__tests__/transcribe.test.d.ts +0 -2
- package/dist/__tests__/transcribe.test.d.ts.map +0 -1
- package/dist/__tests__/transcribe.test.js +0 -71
- package/dist/__tests__/transcribe.test.js.map +0 -1
- package/dist/__tests__/zombie.test.d.ts +0 -2
- package/dist/__tests__/zombie.test.d.ts.map +0 -1
- package/dist/__tests__/zombie.test.js +0 -145
- package/dist/__tests__/zombie.test.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-contacts.d.ts","sourceRoot":"","sources":["../src/import-contacts.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAuFtC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED
|
|
1
|
+
{"version":3,"file":"import-contacts.d.ts","sourceRoot":"","sources":["../src/import-contacts.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAuFtC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAuHxH"}
|
package/dist/import-contacts.js
CHANGED
|
@@ -83,27 +83,41 @@ function phoneToJid(phone) {
|
|
|
83
83
|
return `${digits}@s.whatsapp.net`;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Import contacts from a VCF file into the database.
|
|
86
|
+
* Import contacts from a VCF file or string into the database.
|
|
87
87
|
* Can be called from the MCP tool or standalone CLI.
|
|
88
88
|
*
|
|
89
89
|
* @param dbInstance - An open better-sqlite3 database instance.
|
|
90
90
|
* When called from the MCP server, pass the shared DB.
|
|
91
91
|
* When called from CLI, opens its own connection.
|
|
92
|
-
* @param
|
|
92
|
+
* @param vcfContent - Optional VCF content as a string. If provided, parses this directly.
|
|
93
|
+
* If not provided, reads from vcfPath (defaults to contacts/contacts.vcf).
|
|
94
|
+
* @param vcfPath - Path to the .vcf file. Only used if vcfContent is not provided.
|
|
95
|
+
* Defaults to contacts/contacts.vcf.
|
|
93
96
|
*/
|
|
94
|
-
export function importContactsFromVcf(dbInstance, vcfPath) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
export function importContactsFromVcf(dbInstance, vcfContent, vcfPath) {
|
|
98
|
+
let content;
|
|
99
|
+
let absPath;
|
|
100
|
+
if (vcfContent) {
|
|
101
|
+
// Use provided VCF content directly
|
|
102
|
+
content = vcfContent;
|
|
103
|
+
absPath = "[provided-content]";
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
else {
|
|
106
|
+
// Read from file
|
|
107
|
+
const filePath = vcfPath || DEFAULT_VCF;
|
|
108
|
+
absPath = path.resolve(filePath);
|
|
109
|
+
// Containment check: VCF path must be inside the allowed contacts directory
|
|
110
|
+
const allowedBase = path.resolve(process.env.CONTACTS_DIR || CONTACTS_DIR);
|
|
111
|
+
const allowedPrefix = `${allowedBase}${path.sep}`;
|
|
112
|
+
if (!absPath.startsWith(allowedPrefix)) {
|
|
113
|
+
throw new Error(`VCF path not allowed: must be within ${allowedBase}`);
|
|
114
|
+
}
|
|
115
|
+
if (!fs.existsSync(absPath)) {
|
|
116
|
+
throw new Error(`VCF file not found: ${absPath}`);
|
|
117
|
+
}
|
|
118
|
+
// Parse VCF
|
|
119
|
+
content = fs.readFileSync(absPath, "utf-8");
|
|
104
120
|
}
|
|
105
|
-
// Parse VCF
|
|
106
|
-
const content = fs.readFileSync(absPath, "utf-8");
|
|
107
121
|
const contacts = parseVcf(content);
|
|
108
122
|
// Build lookup maps for all known JIDs (from chats and contacts tables)
|
|
109
123
|
const knownJids = new Set();
|
|
@@ -224,7 +238,7 @@ function main() {
|
|
|
224
238
|
}
|
|
225
239
|
const db = new Database(DB_PATH);
|
|
226
240
|
db.pragma("journal_mode = WAL");
|
|
227
|
-
const result = importContactsFromVcf(db, absPath);
|
|
241
|
+
const result = importContactsFromVcf(db, undefined, absPath);
|
|
228
242
|
console.log(`Parsed ${result.totalParsed} contacts with phone numbers from VCF`);
|
|
229
243
|
console.log(`\nMatched ${result.totalUpdated} phone numbers to existing WhatsApp JIDs`);
|
|
230
244
|
console.log(` Exact matches: ${result.exactMatches}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-contacts.js","sourceRoot":"","sources":["../src/import-contacts.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"import-contacts.js","sourceRoot":"","sources":["../src/import-contacts.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,48 +1,214 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import fs from "fs";
|
|
4
6
|
import { initDb, closeDb } from "./db.js";
|
|
5
|
-
import { initWhatsApp, closeWhatsApp, resolveConnectionAsReadOnly } from "./whatsapp.js";
|
|
7
|
+
import { initWhatsApp, closeWhatsApp, resolveConnectionAsReadOnly, setMessageNotificationHandler, } from "./whatsapp.js";
|
|
6
8
|
import { registerTools } from "./tools.js";
|
|
7
9
|
import { acquireWhatsAppLock, releaseWhatsAppLock } from "./lock.js";
|
|
8
10
|
import { LOCK_FILE, DATA_DIR } from "./paths.js";
|
|
11
|
+
import { createHttpServer } from "./http-server.js";
|
|
12
|
+
import { NEW_MESSAGES_RESOURCE_URI, createResourceSubscriptionStore, registerResourceSubscriptionHandlers, registerResources, } from "./resources.js";
|
|
9
13
|
console.error(`Data directory: ${DATA_DIR}`);
|
|
14
|
+
function parseCliOptions(argv) {
|
|
15
|
+
let http = false;
|
|
16
|
+
let port = 3456;
|
|
17
|
+
let host = "0.0.0.0";
|
|
18
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
19
|
+
const arg = argv[i];
|
|
20
|
+
if (arg === "--http") {
|
|
21
|
+
http = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg === "--port") {
|
|
25
|
+
const value = argv[i + 1];
|
|
26
|
+
if (!value) {
|
|
27
|
+
throw new Error("--port requires a value");
|
|
28
|
+
}
|
|
29
|
+
const parsedPort = Number.parseInt(value, 10);
|
|
30
|
+
if (Number.isNaN(parsedPort) || parsedPort < 0 || parsedPort > 65_535) {
|
|
31
|
+
throw new Error(`Invalid --port value: ${value}`);
|
|
32
|
+
}
|
|
33
|
+
port = parsedPort;
|
|
34
|
+
i += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (arg.startsWith("--port=")) {
|
|
38
|
+
const value = arg.slice("--port=".length);
|
|
39
|
+
const parsedPort = Number.parseInt(value, 10);
|
|
40
|
+
if (Number.isNaN(parsedPort) || parsedPort < 0 || parsedPort > 65_535) {
|
|
41
|
+
throw new Error(`Invalid --port value: ${value}`);
|
|
42
|
+
}
|
|
43
|
+
port = parsedPort;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (arg === "--host") {
|
|
47
|
+
const value = argv[i + 1];
|
|
48
|
+
if (!value) {
|
|
49
|
+
throw new Error("--host requires a value");
|
|
50
|
+
}
|
|
51
|
+
host = value;
|
|
52
|
+
i += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg.startsWith("--host=")) {
|
|
56
|
+
host = arg.slice("--host=".length);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { http, port, host };
|
|
60
|
+
}
|
|
10
61
|
async function main() {
|
|
11
62
|
console.error("Starting WhatsApp MCP Server...");
|
|
63
|
+
const options = parseCliOptions(process.argv.slice(2));
|
|
64
|
+
let stdioServer;
|
|
65
|
+
let httpController;
|
|
66
|
+
let stdioSessionId;
|
|
67
|
+
let parentWatchdog;
|
|
68
|
+
let lockRetryInterval;
|
|
69
|
+
let ownsWhatsAppLock = false;
|
|
70
|
+
let shuttingDown = false;
|
|
71
|
+
const resourceSubscriptions = createResourceSubscriptionStore();
|
|
72
|
+
let debouncedNotificationTimer;
|
|
73
|
+
setMessageNotificationHandler(() => {
|
|
74
|
+
if (debouncedNotificationTimer) {
|
|
75
|
+
clearTimeout(debouncedNotificationTimer);
|
|
76
|
+
}
|
|
77
|
+
debouncedNotificationTimer = setTimeout(() => {
|
|
78
|
+
debouncedNotificationTimer = undefined;
|
|
79
|
+
void resourceSubscriptions.notifyResourceUpdated(NEW_MESSAGES_RESOURCE_URI);
|
|
80
|
+
}, 500);
|
|
81
|
+
});
|
|
12
82
|
// 1. Initialize SQLite database (synchronous, instant).
|
|
13
83
|
initDb();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
84
|
+
const shutdown = async () => {
|
|
85
|
+
if (shuttingDown) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
shuttingDown = true;
|
|
89
|
+
console.error("Shutting down...");
|
|
90
|
+
if (parentWatchdog) {
|
|
91
|
+
clearInterval(parentWatchdog);
|
|
92
|
+
parentWatchdog = undefined;
|
|
93
|
+
}
|
|
94
|
+
if (lockRetryInterval) {
|
|
95
|
+
clearInterval(lockRetryInterval);
|
|
96
|
+
lockRetryInterval = undefined;
|
|
97
|
+
}
|
|
98
|
+
if (debouncedNotificationTimer) {
|
|
99
|
+
clearTimeout(debouncedNotificationTimer);
|
|
100
|
+
debouncedNotificationTimer = undefined;
|
|
101
|
+
}
|
|
102
|
+
setMessageNotificationHandler(undefined);
|
|
103
|
+
await closeWhatsApp();
|
|
104
|
+
if (ownsWhatsAppLock) {
|
|
105
|
+
releaseWhatsAppLock(LOCK_FILE);
|
|
106
|
+
}
|
|
107
|
+
closeDb();
|
|
108
|
+
if (httpController) {
|
|
109
|
+
await httpController.closeHttpServer();
|
|
110
|
+
}
|
|
111
|
+
if (stdioServer) {
|
|
112
|
+
if (stdioSessionId) {
|
|
113
|
+
resourceSubscriptions.removeSession(stdioSessionId);
|
|
114
|
+
stdioSessionId = undefined;
|
|
115
|
+
}
|
|
116
|
+
await stdioServer.close();
|
|
117
|
+
}
|
|
118
|
+
process.exit(0);
|
|
119
|
+
};
|
|
120
|
+
if (options.http) {
|
|
121
|
+
const apiKey = process.env.MCP_API_KEY;
|
|
122
|
+
if (!apiKey) {
|
|
123
|
+
throw new Error("MCP_API_KEY is required when using --http mode");
|
|
124
|
+
}
|
|
125
|
+
httpController = await createHttpServer(options.port, options.host, apiKey, resourceSubscriptions);
|
|
126
|
+
console.error(`MCP server running on HTTP ${options.host}:${httpController.port}`);
|
|
29
127
|
initWhatsApp().catch((err) => {
|
|
30
128
|
console.error("Failed to initialize WhatsApp:", err);
|
|
31
129
|
});
|
|
32
130
|
}
|
|
33
131
|
else {
|
|
34
|
-
|
|
35
|
-
|
|
132
|
+
// 2. Create MCP server and connect to stdio transport FIRST
|
|
133
|
+
// so the client doesn't time out waiting for the initialize handshake.
|
|
134
|
+
stdioServer = new McpServer({
|
|
135
|
+
name: "whatsapp",
|
|
136
|
+
version: "1.0.0",
|
|
137
|
+
}, {
|
|
138
|
+
capabilities: {
|
|
139
|
+
resources: {
|
|
140
|
+
subscribe: true,
|
|
141
|
+
listChanged: true,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const activeStdioServer = stdioServer;
|
|
146
|
+
registerTools(activeStdioServer);
|
|
147
|
+
registerResources(activeStdioServer);
|
|
148
|
+
stdioSessionId = `stdio-${randomUUID()}`;
|
|
149
|
+
resourceSubscriptions.registerSession(stdioSessionId, (uri) => activeStdioServer.server.sendResourceUpdated({ uri }));
|
|
150
|
+
registerResourceSubscriptionHandlers(activeStdioServer, stdioSessionId, resourceSubscriptions);
|
|
151
|
+
const transport = new StdioServerTransport();
|
|
152
|
+
await activeStdioServer.connect(transport);
|
|
153
|
+
console.error("MCP server running on stdio");
|
|
154
|
+
// Detect parent death via two mechanisms:
|
|
155
|
+
// 1. stdin EOF — works when process is spawned directly (not via npm wrapper chain)
|
|
156
|
+
process.stdin.on("end", () => {
|
|
157
|
+
console.error("stdin closed (parent disconnected) — shutting down");
|
|
158
|
+
shutdown();
|
|
159
|
+
});
|
|
160
|
+
// 2. Parent PID polling — catches cases where npm/sh wrapper absorbs the signal
|
|
161
|
+
// but the actual parent (OpenCode) is gone. On Linux, orphaned processes get
|
|
162
|
+
// reparented to PID 1 (init/systemd).
|
|
163
|
+
// Note: process.ppid is cached at startup and doesn't update, so we read
|
|
164
|
+
// the live value from /proc/self/stat.
|
|
165
|
+
const originalPpid = process.ppid;
|
|
166
|
+
const getLivePpid = () => {
|
|
167
|
+
try {
|
|
168
|
+
const stat = fs.readFileSync("/proc/self/stat", "utf8");
|
|
169
|
+
return parseInt(stat.split(" ")[3], 10);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return process.ppid; // Fallback for non-Linux
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
parentWatchdog = setInterval(() => {
|
|
176
|
+
const currentPpid = getLivePpid();
|
|
177
|
+
if (currentPpid !== originalPpid) {
|
|
178
|
+
console.error(`Parent changed (${originalPpid} → ${currentPpid}) — shutting down`);
|
|
179
|
+
shutdown();
|
|
180
|
+
}
|
|
181
|
+
}, 5_000);
|
|
182
|
+
// 3. Initialize WhatsApp in the background — but only if no other instance
|
|
183
|
+
// already owns the WhatsApp connection. This prevents status 515/440
|
|
184
|
+
// when the host spawns multiple MCP server instances.
|
|
185
|
+
if (acquireWhatsAppLock(LOCK_FILE)) {
|
|
186
|
+
ownsWhatsAppLock = true;
|
|
187
|
+
console.error("WhatsApp lock acquired — connecting...");
|
|
188
|
+
initWhatsApp().catch((err) => {
|
|
189
|
+
console.error("Failed to initialize WhatsApp:", err);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.error("Another instance owns the WhatsApp connection — running as read-only from SQLite");
|
|
194
|
+
resolveConnectionAsReadOnly();
|
|
195
|
+
// Periodically check if the lock holder died — if so, take over
|
|
196
|
+
lockRetryInterval = setInterval(() => {
|
|
197
|
+
if (acquireWhatsAppLock(LOCK_FILE)) {
|
|
198
|
+
ownsWhatsAppLock = true;
|
|
199
|
+
console.error("Lock holder died — upgrading to read-write mode");
|
|
200
|
+
if (lockRetryInterval) {
|
|
201
|
+
clearInterval(lockRetryInterval);
|
|
202
|
+
lockRetryInterval = undefined;
|
|
203
|
+
}
|
|
204
|
+
initWhatsApp().catch((err) => {
|
|
205
|
+
console.error("Failed to initialize WhatsApp after lock takeover:", err);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}, 10_000); // Check every 10 seconds
|
|
209
|
+
}
|
|
36
210
|
}
|
|
37
211
|
// 4. Graceful shutdown
|
|
38
|
-
const shutdown = async () => {
|
|
39
|
-
console.error("Shutting down...");
|
|
40
|
-
await closeWhatsApp();
|
|
41
|
-
releaseWhatsAppLock(LOCK_FILE);
|
|
42
|
-
closeDb();
|
|
43
|
-
await server.close();
|
|
44
|
-
process.exit(0);
|
|
45
|
-
};
|
|
46
212
|
process.on("SIGINT", shutdown);
|
|
47
213
|
process.on("SIGTERM", shutdown);
|
|
48
214
|
process.on("unhandledRejection", (reason) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare const NEW_MESSAGES_RESOURCE_URI = "whatsapp://messages/new";
|
|
3
|
+
type ResourceUpdateSender = (uri: string) => Promise<void> | void;
|
|
4
|
+
export type ResourceSubscriptionStore = {
|
|
5
|
+
registerSession: (sessionId: string, sendResourceUpdated: ResourceUpdateSender) => void;
|
|
6
|
+
removeSession: (sessionId: string) => void;
|
|
7
|
+
subscribe: (sessionId: string, uri: string) => void;
|
|
8
|
+
unsubscribe: (sessionId: string, uri: string) => void;
|
|
9
|
+
notifyResourceUpdated: (uri: string) => Promise<void>;
|
|
10
|
+
getSubscriberCount: (uri: string) => number;
|
|
11
|
+
};
|
|
12
|
+
export declare function createResourceSubscriptionStore(): ResourceSubscriptionStore;
|
|
13
|
+
export declare function registerResources(server: McpServer): void;
|
|
14
|
+
export declare function registerResourceSubscriptionHandlers(server: McpServer, sessionId: string, store: ResourceSubscriptionStore): void;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AAEnE,KAAK,oBAAoB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAElE,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACxF,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,qBAAqB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7C,CAAC;AAEF,wBAAgB,+BAA+B,IAAI,yBAAyB,CAgE3E;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiBzD;AAED,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,yBAAyB,GAC/B,IAAI,CAUN"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SubscribeRequestSchema, UnsubscribeRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { getRecentMessages } from "./db.js";
|
|
3
|
+
export const NEW_MESSAGES_RESOURCE_URI = "whatsapp://messages/new";
|
|
4
|
+
export function createResourceSubscriptionStore() {
|
|
5
|
+
const subscriptions = new Map();
|
|
6
|
+
const sessionSenders = new Map();
|
|
7
|
+
const subscribe = (sessionId, uri) => {
|
|
8
|
+
const sessionIds = subscriptions.get(uri) ?? new Set();
|
|
9
|
+
sessionIds.add(sessionId);
|
|
10
|
+
subscriptions.set(uri, sessionIds);
|
|
11
|
+
};
|
|
12
|
+
const unsubscribe = (sessionId, uri) => {
|
|
13
|
+
const sessionIds = subscriptions.get(uri);
|
|
14
|
+
if (!sessionIds) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
sessionIds.delete(sessionId);
|
|
18
|
+
if (sessionIds.size === 0) {
|
|
19
|
+
subscriptions.delete(uri);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const removeSession = (sessionId) => {
|
|
23
|
+
sessionSenders.delete(sessionId);
|
|
24
|
+
for (const [uri, sessionIds] of subscriptions) {
|
|
25
|
+
sessionIds.delete(sessionId);
|
|
26
|
+
if (sessionIds.size === 0) {
|
|
27
|
+
subscriptions.delete(uri);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
registerSession(sessionId, sendResourceUpdated) {
|
|
33
|
+
sessionSenders.set(sessionId, sendResourceUpdated);
|
|
34
|
+
},
|
|
35
|
+
removeSession,
|
|
36
|
+
subscribe,
|
|
37
|
+
unsubscribe,
|
|
38
|
+
async notifyResourceUpdated(uri) {
|
|
39
|
+
const sessionIds = subscriptions.get(uri);
|
|
40
|
+
if (!sessionIds || sessionIds.size === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
for (const sessionId of [...sessionIds]) {
|
|
44
|
+
const sender = sessionSenders.get(sessionId);
|
|
45
|
+
if (!sender) {
|
|
46
|
+
sessionIds.delete(sessionId);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
await sender(uri);
|
|
50
|
+
}
|
|
51
|
+
if (sessionIds.size === 0) {
|
|
52
|
+
subscriptions.delete(uri);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
getSubscriberCount(uri) {
|
|
56
|
+
return subscriptions.get(uri)?.size ?? 0;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function registerResources(server) {
|
|
61
|
+
server.resource("new_messages", NEW_MESSAGES_RESOURCE_URI, async (uri) => {
|
|
62
|
+
const messages = getRecentMessages(10);
|
|
63
|
+
return {
|
|
64
|
+
contents: [
|
|
65
|
+
{
|
|
66
|
+
uri: uri.href,
|
|
67
|
+
mimeType: "application/json",
|
|
68
|
+
text: JSON.stringify(messages),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function registerResourceSubscriptionHandlers(server, sessionId, store) {
|
|
75
|
+
server.server.setRequestHandler(SubscribeRequestSchema, async (request) => {
|
|
76
|
+
store.subscribe(sessionId, request.params.uri);
|
|
77
|
+
return {};
|
|
78
|
+
});
|
|
79
|
+
server.server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
|
|
80
|
+
store.unsubscribe(sessionId, request.params.uri);
|
|
81
|
+
return {};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4BpE;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAidrD"}
|
package/dist/tools.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { getChats, getChat, getMessages, searchMessages, searchContacts, getMessageContext, getRecipientInfo, sendTextMessage, sendFileMessage, downloadMessageMedia, transcribeVoiceNote, deleteMessage, deleteChat, updateContact, getMyInfo, resolveUnknownContacts, } from "./whatsapp.js";
|
|
5
|
-
import { getDb } from "./db.js";
|
|
4
|
+
import { getChats, getChat, getMessages, searchMessages, searchContacts, getMessageContext, getRecipientInfo, sendTextMessage, sendFileMessage, downloadMessageMedia, transcribeVoiceNote, deleteMessage, deleteChat, updateContact, getMyInfo, resolveUnknownContacts, getReadOnlyErrorMessage, } from "./whatsapp.js";
|
|
5
|
+
import { getDb, getUnreadChats } from "./db.js";
|
|
6
6
|
import { importContactsFromVcf } from "./import-contacts.js";
|
|
7
7
|
import { validateFilePath } from "./utils.js";
|
|
8
|
+
import { LOCK_FILE } from "./paths.js";
|
|
8
9
|
/**
|
|
9
10
|
* Register all WhatsApp MCP tools on the server.
|
|
10
11
|
*/
|
|
11
12
|
export function registerTools(server) {
|
|
12
13
|
// ─── Reading Tools ──────────────────────────────────────────
|
|
13
|
-
server.tool("list_chats", "List all WhatsApp chats sorted by last activity. Optionally filter by name.", {
|
|
14
|
+
server.tool("list_chats", "List all WhatsApp chats sorted by last activity. Optionally filter by name. Contacts with multiple WhatsApp identities are automatically merged into a single entry.", {
|
|
14
15
|
nameFilter: z.string().optional().describe("Filter chats by name (case-insensitive substring match)"),
|
|
15
16
|
limit: z.number().min(1).max(100).default(20).describe("Max number of chats to return"),
|
|
16
17
|
}, async ({ nameFilter, limit }) => {
|
|
@@ -42,7 +43,7 @@ export function registerTools(server) {
|
|
|
42
43
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
43
44
|
}
|
|
44
45
|
});
|
|
45
|
-
server.tool("list_messages", "Get messages from a specific chat. Returns most recent messages first.", {
|
|
46
|
+
server.tool("list_messages", "Get messages from a specific chat. Returns most recent messages first. Accepts any contact identifier. Messages from all of a contact's identities are returned transparently.", {
|
|
46
47
|
jid: z.string().describe("Chat JID or phone number"),
|
|
47
48
|
limit: z.number().min(1).max(100).default(50).describe("Number of messages to return"),
|
|
48
49
|
}, async ({ jid, limit }) => {
|
|
@@ -56,7 +57,7 @@ export function registerTools(server) {
|
|
|
56
57
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
57
58
|
}
|
|
58
59
|
});
|
|
59
|
-
server.tool("search_messages", "Substring search across messages. Can search in a specific chat or across all chats.", {
|
|
60
|
+
server.tool("search_messages", "Substring search across messages. Can search in a specific chat or across all chats. Search results automatically unify contacts with multiple identities into a single canonical entry.", {
|
|
60
61
|
query: z.string().describe("Text to search for (case-insensitive)"),
|
|
61
62
|
jid: z.string().optional().describe("Optional: limit search to a specific chat JID"),
|
|
62
63
|
}, async ({ query, jid }) => {
|
|
@@ -80,7 +81,7 @@ export function registerTools(server) {
|
|
|
80
81
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
81
82
|
}
|
|
82
83
|
});
|
|
83
|
-
server.tool("search_contacts", "Find contacts by name or phone number.", {
|
|
84
|
+
server.tool("search_contacts", "Find contacts by name or phone number. Contacts with multiple WhatsApp identities are automatically deduplicated into a single entry.", {
|
|
84
85
|
query: z.string().describe("Name or phone number to search for"),
|
|
85
86
|
}, async ({ query }) => {
|
|
86
87
|
try {
|
|
@@ -123,7 +124,7 @@ export function registerTools(server) {
|
|
|
123
124
|
return {
|
|
124
125
|
content: [{
|
|
125
126
|
type: "text",
|
|
126
|
-
text:
|
|
127
|
+
text: getReadOnlyErrorMessage(LOCK_FILE),
|
|
127
128
|
}],
|
|
128
129
|
isError: true,
|
|
129
130
|
};
|
|
@@ -151,11 +152,14 @@ export function registerTools(server) {
|
|
|
151
152
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
152
153
|
}
|
|
153
154
|
});
|
|
154
|
-
server.tool("sync_contacts", "Import phone contacts from
|
|
155
|
+
server.tool("sync_contacts", "Import phone contacts from a VCF file or string into the database. " +
|
|
155
156
|
"Matches phone numbers from the VCF to existing WhatsApp JIDs and updates their display names. " +
|
|
156
|
-
"Use this after a fresh QR code scan to populate contact names from your address book."
|
|
157
|
+
"Use this after a fresh QR code scan to populate contact names from your address book. " +
|
|
158
|
+
"Optionally pass vcf_content to sync from a string instead of the default file.", {
|
|
159
|
+
vcf_content: z.string().optional().describe("Optional VCF content as a string. If provided, syncs from this content instead of reading from file."),
|
|
160
|
+
}, async ({ vcf_content }) => {
|
|
157
161
|
try {
|
|
158
|
-
const result = importContactsFromVcf(getDb());
|
|
162
|
+
const result = importContactsFromVcf(getDb(), vcf_content);
|
|
159
163
|
return {
|
|
160
164
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
161
165
|
};
|
|
@@ -164,14 +168,32 @@ export function registerTools(server) {
|
|
|
164
168
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
165
169
|
}
|
|
166
170
|
});
|
|
171
|
+
server.tool("get_unread_messages", "Get all chats with unread messages and their recent messages in one call. Returns each unread chat with its name, unread count, and the last N messages. Contacts with multiple WhatsApp identities are automatically unified into a single view. Use this for a quick \"what did I miss\" summary.", {
|
|
172
|
+
messagesPerChat: z.number().min(1).max(20).default(5).describe("Number of recent messages to include per chat"),
|
|
173
|
+
}, async ({ messagesPerChat }) => {
|
|
174
|
+
try {
|
|
175
|
+
const unreads = getUnreadChats(messagesPerChat);
|
|
176
|
+
return {
|
|
177
|
+
content: [{
|
|
178
|
+
type: "text",
|
|
179
|
+
text: JSON.stringify({
|
|
180
|
+
totalChatsWithUnread: unreads.length,
|
|
181
|
+
chats: unreads,
|
|
182
|
+
}, null, 2),
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
188
|
+
}
|
|
189
|
+
});
|
|
167
190
|
// ─── Writing Tools ──────────────────────────────────────────
|
|
168
|
-
server.tool("send_message", "Send a text message to a WhatsApp contact or group. "
|
|
169
|
-
"First call without confirmed=true returns a preview for user approval. " +
|
|
170
|
-
"Call again with confirmed=true to actually send.", {
|
|
191
|
+
server.tool("send_message", "Send a text message to a WhatsApp contact or group. Optionally reply to a specific message by providing its ID. First call without confirmed=true returns a preview for user approval. Call again with confirmed=true to actually send. Messages are automatically routed to the contact's most recently active thread.", {
|
|
171
192
|
jid: z.string().describe("Recipient JID (phone@s.whatsapp.net), group JID, or phone number"),
|
|
172
193
|
text: z.string().describe("Message text to send"),
|
|
194
|
+
quotedMessageId: z.string().optional().describe("Message ID to reply to / quote. Get this from list_messages or search_messages."),
|
|
173
195
|
confirmed: z.boolean().default(false).describe("Set to true to confirm and send the message. When false, returns a preview for user approval."),
|
|
174
|
-
}, async ({ jid, text, confirmed }) => {
|
|
196
|
+
}, async ({ jid, text, quotedMessageId, confirmed }) => {
|
|
175
197
|
try {
|
|
176
198
|
const recipient = getRecipientInfo(jid);
|
|
177
199
|
if (!confirmed) {
|
|
@@ -184,12 +206,13 @@ export function registerTools(server) {
|
|
|
184
206
|
phone: recipient.phone,
|
|
185
207
|
jid: recipient.jid,
|
|
186
208
|
message: text,
|
|
209
|
+
quotedMessageId: quotedMessageId || null,
|
|
187
210
|
instruction: "Show the user who this message will be sent to, their number, and the message content. Ask them to confirm before calling send_message again with confirmed=true.",
|
|
188
211
|
}, null, 2),
|
|
189
212
|
}],
|
|
190
213
|
};
|
|
191
214
|
}
|
|
192
|
-
const result = await sendTextMessage(jid, text);
|
|
215
|
+
const result = await sendTextMessage(jid, text, quotedMessageId);
|
|
193
216
|
return {
|
|
194
217
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
195
218
|
};
|