@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.
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/__tests__/connection.test.d.ts +2 -0
- package/dist/__tests__/connection.test.d.ts.map +1 -0
- package/dist/__tests__/connection.test.js +105 -0
- package/dist/__tests__/connection.test.js.map +1 -0
- package/dist/__tests__/disconnect.test.d.ts +2 -0
- package/dist/__tests__/disconnect.test.d.ts.map +1 -0
- package/dist/__tests__/disconnect.test.js +166 -0
- package/dist/__tests__/disconnect.test.js.map +1 -0
- package/dist/__tests__/download-media.test.d.ts +2 -0
- package/dist/__tests__/download-media.test.d.ts.map +1 -0
- package/dist/__tests__/download-media.test.js +110 -0
- package/dist/__tests__/download-media.test.js.map +1 -0
- package/dist/__tests__/failures/connection-failures.test.d.ts +2 -0
- package/dist/__tests__/failures/connection-failures.test.d.ts.map +1 -0
- package/dist/__tests__/failures/connection-failures.test.js +146 -0
- package/dist/__tests__/failures/connection-failures.test.js.map +1 -0
- package/dist/__tests__/failures/edge-cases.test.d.ts +2 -0
- package/dist/__tests__/failures/edge-cases.test.d.ts.map +1 -0
- package/dist/__tests__/failures/edge-cases.test.js +121 -0
- package/dist/__tests__/failures/edge-cases.test.js.map +1 -0
- package/dist/__tests__/failures/resource-failures.test.d.ts +2 -0
- package/dist/__tests__/failures/resource-failures.test.d.ts.map +1 -0
- package/dist/__tests__/failures/resource-failures.test.js +136 -0
- package/dist/__tests__/failures/resource-failures.test.js.map +1 -0
- package/dist/__tests__/failures/security-failures.test.d.ts +2 -0
- package/dist/__tests__/failures/security-failures.test.d.ts.map +1 -0
- package/dist/__tests__/failures/security-failures.test.js +0 -0
- package/dist/__tests__/failures/security-failures.test.js.map +1 -0
- package/dist/__tests__/helpers/fake-baileys.d.ts +52 -0
- package/dist/__tests__/helpers/fake-baileys.d.ts.map +1 -0
- package/dist/__tests__/helpers/fake-baileys.js +60 -0
- package/dist/__tests__/helpers/fake-baileys.js.map +1 -0
- package/dist/__tests__/helpers/mcp-test-client.d.ts +9 -0
- package/dist/__tests__/helpers/mcp-test-client.d.ts.map +1 -0
- package/dist/__tests__/helpers/mcp-test-client.js +40 -0
- package/dist/__tests__/helpers/mcp-test-client.js.map +1 -0
- package/dist/__tests__/helpers/test-db.d.ts +4 -0
- package/dist/__tests__/helpers/test-db.d.ts.map +1 -0
- package/dist/__tests__/helpers/test-db.js +32 -0
- package/dist/__tests__/helpers/test-db.js.map +1 -0
- package/dist/__tests__/integration/chat-navigation.test.d.ts +2 -0
- package/dist/__tests__/integration/chat-navigation.test.d.ts.map +1 -0
- package/dist/__tests__/integration/chat-navigation.test.js +171 -0
- package/dist/__tests__/integration/chat-navigation.test.js.map +1 -0
- package/dist/__tests__/integration/contacts-flow.test.d.ts +2 -0
- package/dist/__tests__/integration/contacts-flow.test.d.ts.map +1 -0
- package/dist/__tests__/integration/contacts-flow.test.js +144 -0
- package/dist/__tests__/integration/contacts-flow.test.js.map +1 -0
- package/dist/__tests__/integration/media-flow.test.d.ts +2 -0
- package/dist/__tests__/integration/media-flow.test.d.ts.map +1 -0
- package/dist/__tests__/integration/media-flow.test.js +225 -0
- package/dist/__tests__/integration/media-flow.test.js.map +1 -0
- package/dist/__tests__/integration/search-flow.test.d.ts +2 -0
- package/dist/__tests__/integration/search-flow.test.d.ts.map +1 -0
- package/dist/__tests__/integration/search-flow.test.js +44 -0
- package/dist/__tests__/integration/search-flow.test.js.map +1 -0
- package/dist/__tests__/integration/send-message.test.d.ts +2 -0
- package/dist/__tests__/integration/send-message.test.d.ts.map +1 -0
- package/dist/__tests__/integration/send-message.test.js +160 -0
- package/dist/__tests__/integration/send-message.test.js.map +1 -0
- package/dist/__tests__/lock-file.test.d.ts +2 -0
- package/dist/__tests__/lock-file.test.d.ts.map +1 -0
- package/dist/__tests__/lock-file.test.js +63 -0
- package/dist/__tests__/lock-file.test.js.map +1 -0
- package/dist/__tests__/medium-fixes.test.d.ts +2 -0
- package/dist/__tests__/medium-fixes.test.d.ts.map +1 -0
- package/dist/__tests__/medium-fixes.test.js +141 -0
- package/dist/__tests__/medium-fixes.test.js.map +1 -0
- package/dist/__tests__/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit.test.js +193 -0
- package/dist/__tests__/rate-limit.test.js.map +1 -0
- package/dist/__tests__/send-file.test.d.ts +2 -0
- package/dist/__tests__/send-file.test.d.ts.map +1 -0
- package/dist/__tests__/send-file.test.js +237 -0
- package/dist/__tests__/send-file.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +28 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/__tests__/transcribe.test.d.ts +2 -0
- package/dist/__tests__/transcribe.test.d.ts.map +1 -0
- package/dist/__tests__/transcribe.test.js +71 -0
- package/dist/__tests__/transcribe.test.js.map +1 -0
- package/dist/__tests__/zombie.test.d.ts +2 -0
- package/dist/__tests__/zombie.test.d.ts.map +1 -0
- package/dist/__tests__/zombie.test.js +145 -0
- package/dist/__tests__/zombie.test.js.map +1 -0
- package/dist/db.d.ts +53 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +509 -0
- package/dist/db.js.map +1 -0
- package/dist/import-contacts.d.ts +37 -0
- package/dist/import-contacts.d.ts.map +1 -0
- package/dist/import-contacts.js +242 -0
- package/dist/import-contacts.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +16 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +65 -0
- package/dist/lock.js.map +1 -0
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +339 -0
- package/dist/tools.js.map +1 -0
- package/dist/transcribe.d.ts +8 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/transcribe.js +63 -0
- package/dist/transcribe.js.map +1 -0
- package/dist/utils.d.ts +51 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +156 -0
- package/dist/utils.js.map +1 -0
- package/dist/whatsapp.d.ts +50 -0
- package/dist/whatsapp.d.ts.map +1 -0
- package/dist/whatsapp.js +896 -0
- package/dist/whatsapp.js.map +1 -0
- package/package.json +52 -0
- package/patches/@whiskeysockets+baileys+6.7.21.patch +46 -0
- 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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
package/dist/lock.js.map
ADDED
|
@@ -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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -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"}
|