@sjawhar/whatsapp-mcp 1.0.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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/dist/__tests__/connection.test.d.ts +2 -0
  4. package/dist/__tests__/connection.test.d.ts.map +1 -0
  5. package/dist/__tests__/connection.test.js +105 -0
  6. package/dist/__tests__/connection.test.js.map +1 -0
  7. package/dist/__tests__/disconnect.test.d.ts +2 -0
  8. package/dist/__tests__/disconnect.test.d.ts.map +1 -0
  9. package/dist/__tests__/disconnect.test.js +166 -0
  10. package/dist/__tests__/disconnect.test.js.map +1 -0
  11. package/dist/__tests__/download-media.test.d.ts +2 -0
  12. package/dist/__tests__/download-media.test.d.ts.map +1 -0
  13. package/dist/__tests__/download-media.test.js +110 -0
  14. package/dist/__tests__/download-media.test.js.map +1 -0
  15. package/dist/__tests__/failures/connection-failures.test.d.ts +2 -0
  16. package/dist/__tests__/failures/connection-failures.test.d.ts.map +1 -0
  17. package/dist/__tests__/failures/connection-failures.test.js +146 -0
  18. package/dist/__tests__/failures/connection-failures.test.js.map +1 -0
  19. package/dist/__tests__/failures/edge-cases.test.d.ts +2 -0
  20. package/dist/__tests__/failures/edge-cases.test.d.ts.map +1 -0
  21. package/dist/__tests__/failures/edge-cases.test.js +121 -0
  22. package/dist/__tests__/failures/edge-cases.test.js.map +1 -0
  23. package/dist/__tests__/failures/resource-failures.test.d.ts +2 -0
  24. package/dist/__tests__/failures/resource-failures.test.d.ts.map +1 -0
  25. package/dist/__tests__/failures/resource-failures.test.js +136 -0
  26. package/dist/__tests__/failures/resource-failures.test.js.map +1 -0
  27. package/dist/__tests__/failures/security-failures.test.d.ts +2 -0
  28. package/dist/__tests__/failures/security-failures.test.d.ts.map +1 -0
  29. package/dist/__tests__/failures/security-failures.test.js +0 -0
  30. package/dist/__tests__/failures/security-failures.test.js.map +1 -0
  31. package/dist/__tests__/helpers/fake-baileys.d.ts +52 -0
  32. package/dist/__tests__/helpers/fake-baileys.d.ts.map +1 -0
  33. package/dist/__tests__/helpers/fake-baileys.js +60 -0
  34. package/dist/__tests__/helpers/fake-baileys.js.map +1 -0
  35. package/dist/__tests__/helpers/mcp-test-client.d.ts +9 -0
  36. package/dist/__tests__/helpers/mcp-test-client.d.ts.map +1 -0
  37. package/dist/__tests__/helpers/mcp-test-client.js +40 -0
  38. package/dist/__tests__/helpers/mcp-test-client.js.map +1 -0
  39. package/dist/__tests__/helpers/test-db.d.ts +4 -0
  40. package/dist/__tests__/helpers/test-db.d.ts.map +1 -0
  41. package/dist/__tests__/helpers/test-db.js +32 -0
  42. package/dist/__tests__/helpers/test-db.js.map +1 -0
  43. package/dist/__tests__/integration/chat-navigation.test.d.ts +2 -0
  44. package/dist/__tests__/integration/chat-navigation.test.d.ts.map +1 -0
  45. package/dist/__tests__/integration/chat-navigation.test.js +171 -0
  46. package/dist/__tests__/integration/chat-navigation.test.js.map +1 -0
  47. package/dist/__tests__/integration/contacts-flow.test.d.ts +2 -0
  48. package/dist/__tests__/integration/contacts-flow.test.d.ts.map +1 -0
  49. package/dist/__tests__/integration/contacts-flow.test.js +144 -0
  50. package/dist/__tests__/integration/contacts-flow.test.js.map +1 -0
  51. package/dist/__tests__/integration/media-flow.test.d.ts +2 -0
  52. package/dist/__tests__/integration/media-flow.test.d.ts.map +1 -0
  53. package/dist/__tests__/integration/media-flow.test.js +225 -0
  54. package/dist/__tests__/integration/media-flow.test.js.map +1 -0
  55. package/dist/__tests__/integration/search-flow.test.d.ts +2 -0
  56. package/dist/__tests__/integration/search-flow.test.d.ts.map +1 -0
  57. package/dist/__tests__/integration/search-flow.test.js +44 -0
  58. package/dist/__tests__/integration/search-flow.test.js.map +1 -0
  59. package/dist/__tests__/integration/send-message.test.d.ts +2 -0
  60. package/dist/__tests__/integration/send-message.test.d.ts.map +1 -0
  61. package/dist/__tests__/integration/send-message.test.js +160 -0
  62. package/dist/__tests__/integration/send-message.test.js.map +1 -0
  63. package/dist/__tests__/lock-file.test.d.ts +2 -0
  64. package/dist/__tests__/lock-file.test.d.ts.map +1 -0
  65. package/dist/__tests__/lock-file.test.js +63 -0
  66. package/dist/__tests__/lock-file.test.js.map +1 -0
  67. package/dist/__tests__/medium-fixes.test.d.ts +2 -0
  68. package/dist/__tests__/medium-fixes.test.d.ts.map +1 -0
  69. package/dist/__tests__/medium-fixes.test.js +141 -0
  70. package/dist/__tests__/medium-fixes.test.js.map +1 -0
  71. package/dist/__tests__/rate-limit.test.d.ts +2 -0
  72. package/dist/__tests__/rate-limit.test.d.ts.map +1 -0
  73. package/dist/__tests__/rate-limit.test.js +193 -0
  74. package/dist/__tests__/rate-limit.test.js.map +1 -0
  75. package/dist/__tests__/send-file.test.d.ts +2 -0
  76. package/dist/__tests__/send-file.test.d.ts.map +1 -0
  77. package/dist/__tests__/send-file.test.js +237 -0
  78. package/dist/__tests__/send-file.test.js.map +1 -0
  79. package/dist/__tests__/smoke.test.d.ts +2 -0
  80. package/dist/__tests__/smoke.test.d.ts.map +1 -0
  81. package/dist/__tests__/smoke.test.js +28 -0
  82. package/dist/__tests__/smoke.test.js.map +1 -0
  83. package/dist/__tests__/transcribe.test.d.ts +2 -0
  84. package/dist/__tests__/transcribe.test.d.ts.map +1 -0
  85. package/dist/__tests__/transcribe.test.js +71 -0
  86. package/dist/__tests__/transcribe.test.js.map +1 -0
  87. package/dist/__tests__/zombie.test.d.ts +2 -0
  88. package/dist/__tests__/zombie.test.d.ts.map +1 -0
  89. package/dist/__tests__/zombie.test.js +145 -0
  90. package/dist/__tests__/zombie.test.js.map +1 -0
  91. package/dist/db.d.ts +53 -0
  92. package/dist/db.d.ts.map +1 -0
  93. package/dist/db.js +509 -0
  94. package/dist/db.js.map +1 -0
  95. package/dist/import-contacts.d.ts +37 -0
  96. package/dist/import-contacts.d.ts.map +1 -0
  97. package/dist/import-contacts.js +242 -0
  98. package/dist/import-contacts.js.map +1 -0
  99. package/dist/index.d.ts +3 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +62 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/lock.d.ts +16 -0
  104. package/dist/lock.d.ts.map +1 -0
  105. package/dist/lock.js +65 -0
  106. package/dist/lock.js.map +1 -0
  107. package/dist/tools.d.ts +6 -0
  108. package/dist/tools.d.ts.map +1 -0
  109. package/dist/tools.js +339 -0
  110. package/dist/tools.js.map +1 -0
  111. package/dist/transcribe.d.ts +8 -0
  112. package/dist/transcribe.d.ts.map +1 -0
  113. package/dist/transcribe.js +63 -0
  114. package/dist/transcribe.js.map +1 -0
  115. package/dist/utils.d.ts +51 -0
  116. package/dist/utils.d.ts.map +1 -0
  117. package/dist/utils.js +156 -0
  118. package/dist/utils.js.map +1 -0
  119. package/dist/whatsapp.d.ts +50 -0
  120. package/dist/whatsapp.d.ts.map +1 -0
  121. package/dist/whatsapp.js +896 -0
  122. package/dist/whatsapp.js.map +1 -0
  123. package/package.json +52 -0
  124. package/patches/@whiskeysockets+baileys+6.7.21.patch +46 -0
  125. package/patches/libsignal+2.0.1.patch +84 -0
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Import phone contacts from a .vcf (vCard) file into the WhatsApp MCP database.
4
+ *
5
+ * Usage (standalone CLI):
6
+ * npx tsx src/import-contacts.ts contacts.vcf
7
+ * npx tsx src/import-contacts.ts contacts.vcf --dry-run
8
+ *
9
+ * Also exports `importContactsFromVcf()` for use by the MCP tool.
10
+ *
11
+ * Export your contacts from iPhone:
12
+ * Settings > Contacts > Accounts > iCloud > Contacts (on)
13
+ * Go to iCloud.com > Contacts > Select All > Export vCard
14
+ *
15
+ * Or use any other method that produces a .vcf file.
16
+ */
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { pathToFileURL } from "node:url";
20
+ import Database from "better-sqlite3";
21
+ const __project_root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
22
+ const DB_PATH = path.join(__project_root, "store", "whatsapp.db");
23
+ const CONTACTS_DIR = path.join(__project_root, "contacts");
24
+ const DEFAULT_VCF = path.join(CONTACTS_DIR, "contacts.vcf");
25
+ function parseVcf(content) {
26
+ const contacts = [];
27
+ const cards = content.split("BEGIN:VCARD");
28
+ for (const card of cards) {
29
+ if (!card.includes("END:VCARD"))
30
+ continue;
31
+ // Extract name: try FN first (formatted name), then N (structured name)
32
+ let name = null;
33
+ const fnMatch = card.match(/^FN[;:](.+)$/m);
34
+ if (fnMatch) {
35
+ name = fnMatch[1].trim();
36
+ }
37
+ if (!name) {
38
+ const nMatch = card.match(/^N[;:](.+)$/m);
39
+ if (nMatch) {
40
+ // N field format: Last;First;Middle;Prefix;Suffix
41
+ const parts = nMatch[1].split(";").map((s) => s.trim()).filter(Boolean);
42
+ if (parts.length >= 2) {
43
+ name = `${parts[1]} ${parts[0]}`; // First Last
44
+ }
45
+ else if (parts.length === 1) {
46
+ name = parts[0];
47
+ }
48
+ }
49
+ }
50
+ if (!name)
51
+ continue;
52
+ // Extract phone numbers — handle multi-line folded values
53
+ const phones = [];
54
+ // Unfold continuation lines (RFC 2425: line starting with space/tab continues previous)
55
+ const unfolded = card.replace(/\r?\n[ \t]/g, "");
56
+ const telMatches = unfolded.matchAll(/^TEL[^:]*:(.+)$/gm);
57
+ for (const match of telMatches) {
58
+ const raw = match[1].trim();
59
+ // Strip non-digit except leading +
60
+ const cleaned = raw.replace(/[^\d+]/g, "");
61
+ if (cleaned.length >= 7) {
62
+ phones.push(cleaned);
63
+ }
64
+ }
65
+ if (phones.length > 0) {
66
+ contacts.push({ name, phones });
67
+ }
68
+ }
69
+ return contacts;
70
+ }
71
+ // ─── Phone Number Normalization ─────────────────────────────────────
72
+ /**
73
+ * Normalize a phone number to a WhatsApp JID.
74
+ * Strips leading +, leading 00, and handles common formats.
75
+ */
76
+ function phoneToJid(phone) {
77
+ let digits = phone.replace(/\D/g, "");
78
+ // Strip leading 00 (international prefix)
79
+ if (digits.startsWith("00")) {
80
+ digits = digits.slice(2);
81
+ }
82
+ // Strip leading 0 only if it looks like a local number with country code context
83
+ // (we can't know the country code without more context, so we leave it)
84
+ return `${digits}@s.whatsapp.net`;
85
+ }
86
+ /**
87
+ * Import contacts from a VCF file into the database.
88
+ * Can be called from the MCP tool or standalone CLI.
89
+ *
90
+ * @param dbInstance - An open better-sqlite3 database instance.
91
+ * When called from the MCP server, pass the shared DB.
92
+ * When called from CLI, opens its own connection.
93
+ * @param vcfPath - Path to the .vcf file. Defaults to contacts/contacts.vcf.
94
+ */
95
+ export function importContactsFromVcf(dbInstance, vcfPath) {
96
+ const absPath = path.resolve(vcfPath || DEFAULT_VCF);
97
+ // Containment check: VCF path must be inside the allowed contacts directory
98
+ const allowedBase = path.resolve(process.env.CONTACTS_DIR || CONTACTS_DIR);
99
+ const allowedPrefix = `${allowedBase}${path.sep}`;
100
+ if (!absPath.startsWith(allowedPrefix)) {
101
+ throw new Error(`VCF path not allowed: must be within ${allowedBase}`);
102
+ }
103
+ if (!fs.existsSync(absPath)) {
104
+ throw new Error(`VCF file not found: ${absPath}`);
105
+ }
106
+ // Parse VCF
107
+ const content = fs.readFileSync(absPath, "utf-8");
108
+ const contacts = parseVcf(content);
109
+ // Build lookup maps for all known JIDs (from chats and contacts tables)
110
+ const knownJids = new Set();
111
+ const chatRows = dbInstance.prepare("SELECT jid FROM chats WHERE jid LIKE '%@s.whatsapp.net'").all();
112
+ const contactRows = dbInstance.prepare("SELECT jid FROM contacts WHERE jid LIKE '%@s.whatsapp.net'").all();
113
+ for (const row of chatRows)
114
+ knownJids.add(row.jid);
115
+ for (const row of contactRows)
116
+ knownJids.add(row.jid);
117
+ // Build a suffix map for fuzzy matching: last 9 digits → JID
118
+ // 9 digits is long enough to avoid collisions but catches local-vs-international mismatches
119
+ const SUFFIX_LEN = 9;
120
+ const suffixMap = new Map();
121
+ for (const jid of knownJids) {
122
+ const digits = jid.replace("@s.whatsapp.net", "");
123
+ if (digits.length >= SUFFIX_LEN) {
124
+ const suffix = digits.slice(-SUFFIX_LEN);
125
+ if (!suffixMap.has(suffix))
126
+ suffixMap.set(suffix, []);
127
+ suffixMap.get(suffix).push(jid);
128
+ }
129
+ }
130
+ // Prepare statements
131
+ const upsertContact = dbInstance.prepare(`
132
+ INSERT INTO contacts (jid, name, notify)
133
+ VALUES (@jid, @name, NULL)
134
+ ON CONFLICT(jid) DO UPDATE SET
135
+ name = COALESCE(@name, contacts.name)
136
+ `);
137
+ const upsertChat = dbInstance.prepare(`
138
+ INSERT INTO chats (jid, name, conversation_ts, unread_count)
139
+ VALUES (@jid, @name, 0, 0)
140
+ ON CONFLICT(jid) DO UPDATE SET
141
+ name = COALESCE(@name, chats.name)
142
+ `);
143
+ // Match and update — exact matches first, then fuzzy suffix matches
144
+ let exactMatches = 0;
145
+ let fuzzyMatches = 0;
146
+ const matchedJids = new Set(); // prevent duplicate updates for same JID
147
+ const runImport = dbInstance.transaction(() => {
148
+ for (const contact of contacts) {
149
+ for (const phone of contact.phones) {
150
+ const jid = phoneToJid(phone);
151
+ // Exact match
152
+ if (knownJids.has(jid) && !matchedJids.has(jid)) {
153
+ exactMatches++;
154
+ matchedJids.add(jid);
155
+ upsertContact.run({ jid, name: contact.name });
156
+ upsertChat.run({ jid, name: contact.name });
157
+ continue;
158
+ }
159
+ // Fuzzy suffix match (last 9 digits)
160
+ const digits = phone.replace(/\D/g, "");
161
+ if (digits.length >= SUFFIX_LEN) {
162
+ const suffix = digits.slice(-SUFFIX_LEN);
163
+ const candidates = suffixMap.get(suffix);
164
+ if (candidates) {
165
+ for (const candidateJid of candidates) {
166
+ if (matchedJids.has(candidateJid))
167
+ continue;
168
+ fuzzyMatches++;
169
+ matchedJids.add(candidateJid);
170
+ upsertContact.run({ jid: candidateJid, name: contact.name });
171
+ upsertChat.run({ jid: candidateJid, name: contact.name });
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ });
178
+ runImport();
179
+ const nameless = dbInstance.prepare(`
180
+ SELECT COUNT(*) AS cnt FROM chats
181
+ WHERE name IS NULL AND jid LIKE '%@s.whatsapp.net' AND conversation_ts > 0
182
+ `).get();
183
+ return {
184
+ success: true,
185
+ vcfPath: absPath,
186
+ totalParsed: contacts.length,
187
+ exactMatches,
188
+ fuzzyMatches,
189
+ totalUpdated: exactMatches + fuzzyMatches,
190
+ namelessChatsRemaining: nameless.cnt,
191
+ };
192
+ }
193
+ // ─── CLI Entry Point ────────────────────────────────────────────────
194
+ function main() {
195
+ const args = process.argv.slice(2);
196
+ const dryRun = args.includes("--dry-run");
197
+ const vcfPath = args.find((a) => !a.startsWith("--"));
198
+ if (!vcfPath) {
199
+ console.error("Usage: npx tsx src/import-contacts.ts <contacts.vcf> [--dry-run]");
200
+ console.error("");
201
+ console.error("Export contacts from iPhone:");
202
+ console.error(" iCloud.com > Contacts > Select All > Export vCard");
203
+ process.exit(1);
204
+ }
205
+ const absPath = path.resolve(vcfPath);
206
+ if (!fs.existsSync(absPath)) {
207
+ console.error(`File not found: ${absPath}`);
208
+ process.exit(1);
209
+ }
210
+ if (!fs.existsSync(DB_PATH)) {
211
+ console.error(`Database not found: ${DB_PATH}`);
212
+ console.error("Run the WhatsApp MCP server first to create the database.");
213
+ process.exit(1);
214
+ }
215
+ if (dryRun) {
216
+ // For dry run, open our own DB and just parse + report
217
+ const db = new Database(DB_PATH);
218
+ db.pragma("journal_mode = WAL");
219
+ const content = fs.readFileSync(absPath, "utf-8");
220
+ const contacts = parseVcf(content);
221
+ console.log(`Parsed ${contacts.length} contacts with phone numbers from VCF`);
222
+ console.log("\n(Dry run — no changes made)");
223
+ db.close();
224
+ return;
225
+ }
226
+ const db = new Database(DB_PATH);
227
+ db.pragma("journal_mode = WAL");
228
+ const result = importContactsFromVcf(db, absPath);
229
+ console.log(`Parsed ${result.totalParsed} contacts with phone numbers from VCF`);
230
+ console.log(`\nMatched ${result.totalUpdated} phone numbers to existing WhatsApp JIDs`);
231
+ console.log(` Exact matches: ${result.exactMatches}`);
232
+ console.log(` Fuzzy matches (last 9 digits): ${result.fuzzyMatches}`);
233
+ console.log(`\nUpdated ${result.totalUpdated} contacts with address book names`);
234
+ console.log(`Chats still without a name: ${result.namelessChatsRemaining}`);
235
+ db.close();
236
+ }
237
+ // Only run CLI when executed directly (not imported)
238
+ const isDirectRun = import.meta.url === pathToFileURL(process.argv[1]).href;
239
+ if (isDirectRun) {
240
+ main();
241
+ }
242
+ //# sourceMappingURL=import-contacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-contacts.js","sourceRoot":"","sources":["../src/import-contacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import path from "path";
5
+ import { initDb, closeDb } from "./db.js";
6
+ import { initWhatsApp, closeWhatsApp, resolveConnectionAsReadOnly } from "./whatsapp.js";
7
+ import { registerTools } from "./tools.js";
8
+ import { acquireWhatsAppLock, releaseWhatsAppLock } from "./lock.js";
9
+ const __project_root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
10
+ const LOCK_FILE = path.join(__project_root, "store", ".whatsapp.lock");
11
+ async function main() {
12
+ console.error("Starting WhatsApp MCP Server...");
13
+ // 1. Initialize SQLite database (synchronous, instant).
14
+ initDb();
15
+ // 2. Create MCP server and connect to stdio transport FIRST
16
+ // so the client doesn't time out waiting for the initialize handshake.
17
+ const server = new McpServer({
18
+ name: "whatsapp",
19
+ version: "1.0.0",
20
+ });
21
+ registerTools(server);
22
+ const transport = new StdioServerTransport();
23
+ await server.connect(transport);
24
+ console.error("MCP server running on stdio");
25
+ // 3. Initialize WhatsApp in the background — but only if no other instance
26
+ // already owns the WhatsApp connection. This prevents status 515/440
27
+ // when Claude Desktop spawns multiple MCP server instances.
28
+ if (acquireWhatsAppLock(LOCK_FILE)) {
29
+ console.error("WhatsApp lock acquired — connecting...");
30
+ initWhatsApp().catch((err) => {
31
+ console.error("Failed to initialize WhatsApp:", err);
32
+ });
33
+ }
34
+ else {
35
+ console.error("Another instance owns the WhatsApp connection — running as read-only from SQLite");
36
+ resolveConnectionAsReadOnly();
37
+ }
38
+ // 4. Graceful shutdown
39
+ const shutdown = async () => {
40
+ console.error("Shutting down...");
41
+ await closeWhatsApp();
42
+ releaseWhatsAppLock(LOCK_FILE);
43
+ closeDb();
44
+ await server.close();
45
+ process.exit(0);
46
+ };
47
+ process.on("SIGINT", shutdown);
48
+ process.on("SIGTERM", shutdown);
49
+ process.on("unhandledRejection", (reason) => {
50
+ console.error("Unhandled rejection:", reason);
51
+ shutdown();
52
+ });
53
+ process.on("uncaughtException", (err) => {
54
+ console.error("Uncaught exception:", err);
55
+ shutdown();
56
+ });
57
+ }
58
+ main().catch((err) => {
59
+ console.error("Fatal error:", err);
60
+ process.exit(1);
61
+ });
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/lock.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Atomically acquire a file lock for the WhatsApp connection.
3
+ * Uses O_CREAT | O_EXCL to prevent race conditions between processes.
4
+ * Only one process should connect to WhatsApp at a time to avoid status 515/440.
5
+ *
6
+ * @param lockFilePath - Path to the lock file
7
+ * @returns true if lock was acquired, false if another live process holds it
8
+ */
9
+ export declare function acquireWhatsAppLock(lockFilePath: string): boolean;
10
+ /**
11
+ * Release the lock file, but only if this process owns it.
12
+ *
13
+ * @param lockFilePath - Path to the lock file
14
+ */
15
+ export declare function releaseWhatsAppLock(lockFilePath: string): void;
16
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAyCjE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAW9D"}
package/dist/lock.js ADDED
@@ -0,0 +1,65 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ /**
4
+ * Atomically acquire a file lock for the WhatsApp connection.
5
+ * Uses O_CREAT | O_EXCL to prevent race conditions between processes.
6
+ * Only one process should connect to WhatsApp at a time to avoid status 515/440.
7
+ *
8
+ * @param lockFilePath - Path to the lock file
9
+ * @returns true if lock was acquired, false if another live process holds it
10
+ */
11
+ export function acquireWhatsAppLock(lockFilePath) {
12
+ fs.mkdirSync(path.dirname(lockFilePath), { recursive: true });
13
+ try {
14
+ // Atomic create — throws EEXIST if file already exists (no race window)
15
+ const fd = fs.openSync(lockFilePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
16
+ fs.writeSync(fd, String(process.pid));
17
+ fs.closeSync(fd);
18
+ return true;
19
+ }
20
+ catch (err) {
21
+ if (!(err instanceof Error) || err.code !== "EEXIST") {
22
+ throw err;
23
+ }
24
+ // File exists — check if the owning process is still alive
25
+ const pid = parseInt(fs.readFileSync(lockFilePath, "utf-8").trim(), 10);
26
+ try {
27
+ process.kill(pid, 0); // throws if process is dead
28
+ return false; // another instance is alive
29
+ }
30
+ catch {
31
+ // Stale lock — remove and retry once
32
+ console.error(`Stale lock file found (PID ${pid} is dead) — clearing and taking over`);
33
+ fs.unlinkSync(lockFilePath);
34
+ try {
35
+ const fd = fs.openSync(lockFilePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
36
+ fs.writeSync(fd, String(process.pid));
37
+ fs.closeSync(fd);
38
+ return true;
39
+ }
40
+ catch {
41
+ // Another process beat us to the retry — they win
42
+ return false;
43
+ }
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ * Release the lock file, but only if this process owns it.
49
+ *
50
+ * @param lockFilePath - Path to the lock file
51
+ */
52
+ export function releaseWhatsAppLock(lockFilePath) {
53
+ try {
54
+ if (fs.existsSync(lockFilePath)) {
55
+ const pid = parseInt(fs.readFileSync(lockFilePath, "utf-8").trim(), 10);
56
+ if (pid === process.pid) {
57
+ fs.unlinkSync(lockFilePath);
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ // Best-effort release — don't crash during shutdown
63
+ }
64
+ }
65
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAoB;IACtD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9D,IAAI,CAAC;QACH,wEAAwE;QACxE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CACpB,YAAY,EACZ,EAAE,CAAC,SAAS,CAAC,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CACnE,CAAC;QACF,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChF,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,2DAA2D;QAC3D,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,4BAA4B;YAClD,OAAO,KAAK,CAAC,CAAC,4BAA4B;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,qCAAqC;YACrC,OAAO,CAAC,KAAK,CAAC,8BAA8B,GAAG,sCAAsC,CAAC,CAAC;YACvF,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YAE5B,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CACpB,YAAY,EACZ,EAAE,CAAC,SAAS,CAAC,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CACnE,CAAC;gBACF,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;gBACtC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBACjB,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,kDAAkD;gBAClD,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAoB;IACtD,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YACxE,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;gBACxB,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Register all WhatsApp MCP tools on the server.
4
+ */
5
+ export declare function registerTools(server: McpServer): void;
6
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAyBpE;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+ZrD"}