@lizzythelizard/whatsapp-mcp 0.1.3

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 (47) hide show
  1. package/.github/dependabot.yml +20 -0
  2. package/.github/workflows/auto-merge.yml +19 -0
  3. package/.github/workflows/ci.yml +75 -0
  4. package/.vscode/extensions.json +7 -0
  5. package/.vscode/settings.json +12 -0
  6. package/AGENTS.md +42 -0
  7. package/Dockerfile +25 -0
  8. package/LICENSE +21 -0
  9. package/README.md +62 -0
  10. package/dist/auth.d.ts +6 -0
  11. package/dist/auth.d.ts.map +1 -0
  12. package/dist/auth.js +49 -0
  13. package/dist/auth.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +18 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/store.d.ts +20 -0
  19. package/dist/store.d.ts.map +1 -0
  20. package/dist/store.js +138 -0
  21. package/dist/store.js.map +1 -0
  22. package/dist/sync.d.ts +18 -0
  23. package/dist/sync.d.ts.map +1 -0
  24. package/dist/sync.js +115 -0
  25. package/dist/sync.js.map +1 -0
  26. package/dist/syncManager.d.ts +29 -0
  27. package/dist/syncManager.d.ts.map +1 -0
  28. package/dist/syncManager.js +79 -0
  29. package/dist/syncManager.js.map +1 -0
  30. package/dist/tools.d.ts +2 -0
  31. package/dist/tools.d.ts.map +1 -0
  32. package/dist/tools.js +65 -0
  33. package/dist/tools.js.map +1 -0
  34. package/eslint.config.mjs +32 -0
  35. package/package.json +45 -0
  36. package/src/auth.test.ts +138 -0
  37. package/src/auth.ts +58 -0
  38. package/src/index.ts +45 -0
  39. package/src/store.test.ts +353 -0
  40. package/src/store.ts +182 -0
  41. package/src/sync.test.ts +304 -0
  42. package/src/sync.ts +170 -0
  43. package/src/tools.test.ts +254 -0
  44. package/src/tools.ts +132 -0
  45. package/tsconfig.json +19 -0
  46. package/tsconfig.test.json +7 -0
  47. package/vitest.config.ts +7 -0
package/dist/sync.js ADDED
@@ -0,0 +1,115 @@
1
+ import makeWASocket from '@whiskeysockets/baileys';
2
+ export function createSyncHandler(store) {
3
+ let state = { type: 'connecting' };
4
+ let sock;
5
+ const browser = ['Gutschi.site', 'Desktop', '1.0.0'];
6
+ function onError(error) {
7
+ const arg = error instanceof Error ? error : new Error(String(error));
8
+ console.error(`Closing WhatsApp sync due to error ${arg}`);
9
+ close(arg);
10
+ }
11
+ function close(error) {
12
+ if (sock) {
13
+ const sockCpy = sock;
14
+ sock = undefined;
15
+ sockCpy.end(error).catch(onClosed);
16
+ }
17
+ state = { type: 'closed', error };
18
+ }
19
+ function onClosed(error) {
20
+ if (!error) {
21
+ console.log(`WhatsApp sync connection closed without error`);
22
+ state = { type: 'closed' };
23
+ return;
24
+ }
25
+ if (isInvalidAuthError(error)) {
26
+ console.warn(`WhatsApp sync requires re-authentication due to invalid auth state`);
27
+ store.reset();
28
+ start();
29
+ return;
30
+ }
31
+ if (isRequiredReconnectError(error)) {
32
+ console.log(`WhatsApp sync connection closed due to required reconnect`);
33
+ store.reset();
34
+ startAgainAfterLogin();
35
+ return;
36
+ }
37
+ state = { type: 'closed', error };
38
+ }
39
+ function start() {
40
+ sock = makeWASocket({ auth: store.getAuth(), browser, logger: baileysLogger, markOnlineOnConnect: false, syncFullHistory: true, emitOwnEvents: true });
41
+ sock.ev.on('connection.update', (update) => {
42
+ connectionUpdate(update, onClosed, qr => state = { type: 'needAuth', qr }, () => state = { type: 'ready' });
43
+ });
44
+ store.bind(sock.ev);
45
+ }
46
+ function startAgainAfterLogin() {
47
+ sock = makeWASocket({ auth: store.getAuth(), browser, logger: baileysLogger, markOnlineOnConnect: false, syncFullHistory: true, emitOwnEvents: true });
48
+ sock.ev.on('connection.update', (update) => { connectionUpdateAfterLogin(update, onClosed, onError); });
49
+ //wait untill no new messages are received for 2 seconds, then set state to ready
50
+ let timeout = setTimeout(() => state = { type: 'ready' }, 2000);
51
+ sock.ev.process(() => {
52
+ clearTimeout(timeout);
53
+ timeout = setTimeout(() => state = { type: 'ready' }, 2000);
54
+ });
55
+ store.bind(sock.ev);
56
+ }
57
+ start();
58
+ return {
59
+ getStatus: () => state,
60
+ close: close,
61
+ };
62
+ }
63
+ const baileysLogger = {
64
+ level: 'error',
65
+ child: () => baileysLogger,
66
+ trace: () => { },
67
+ debug: () => { },
68
+ info: () => { },
69
+ warn: () => { },
70
+ error: () => { },
71
+ };
72
+ function connectionUpdate(update, onClose, onQr, onReady) {
73
+ if (update.qr)
74
+ onQr?.(update.qr);
75
+ else if (update.connection === 'open')
76
+ onReady?.();
77
+ else if (update.connection !== 'close') { /* do nothing, wait for next update */ }
78
+ else
79
+ onClose(update.lastDisconnect?.error);
80
+ }
81
+ function isInvalidAuthError(error) {
82
+ if (!(error instanceof Error))
83
+ return false;
84
+ if (!('output' in error))
85
+ return false;
86
+ if (typeof error.output !== 'object' || error.output === null)
87
+ return false;
88
+ if (!('statusCode' in error.output))
89
+ return false;
90
+ if (error.output.statusCode !== 401)
91
+ return false;
92
+ return true;
93
+ }
94
+ function isRequiredReconnectError(error) {
95
+ if (!(error instanceof Error))
96
+ return false;
97
+ if (!('output' in error))
98
+ return false;
99
+ if (typeof error.output !== 'object' || error.output === null)
100
+ return false;
101
+ if (!('statusCode' in error.output))
102
+ return false;
103
+ if (error.output.statusCode !== 515)
104
+ return false;
105
+ return true;
106
+ }
107
+ function connectionUpdateAfterLogin(update, onClose, onErr) {
108
+ if (update.qr)
109
+ onErr(new Error('Received QR code update during WhatsApp sync after login'));
110
+ else if (update.connection === 'open') { /* do nothing, wait for next update */ }
111
+ else if (update.connection !== 'close') { /* do nothing, wait for next update */ }
112
+ else
113
+ onClose(update.lastDisconnect?.error);
114
+ }
115
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,YAAuD,MAAM,yBAAyB,CAAA;AAU7F,MAAM,UAAU,iBAAiB,CAAC,KAAqC;IACrE,IAAI,KAAK,GAAe,EAAE,IAAI,EAAE,YAAY,EAAE,CAAA;IAC9C,IAAI,IAAiD,CAAA;IACrD,MAAM,OAAO,GAAG,CAAC,cAAc,EAAE,SAAS,EAAE,OAAO,CAAyB,CAAA;IAE5E,SAAS,OAAO,CAAC,KAAc;QAC7B,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QACrE,OAAO,CAAC,KAAK,CAAC,sCAAsC,GAAG,EAAE,CAAC,CAAA;QAC1D,KAAK,CAAC,GAAG,CAAC,CAAA;IACZ,CAAC;IAED,SAAS,KAAK,CAAC,KAAa;QAC1B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,IAAI,CAAA;YACpB,IAAI,GAAG,SAAS,CAAA;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QACpC,CAAC;QACD,KAAK,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IACnC,CAAC;IAED,SAAS,QAAQ,CAAC,KAAa;QAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAA;YAC5D,KAAK,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;YAC1B,OAAM;QACR,CAAC;QACD,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAA;YAClF,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,KAAK,EAAE,CAAA;YACP,OAAM;QACR,CAAC;QACD,IAAI,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAA;YACxE,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,oBAAoB,EAAE,CAAA;YACtB,OAAM;QACR,CAAC;QACD,KAAK,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IACnC,CAAC;IAED,SAAS,KAAK;QACZ,IAAI,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,mBAAmB,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACtJ,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,MAAM,EAAE,EAAE;YAAG,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAC7E,EAAE,CAAC,EAAE,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EACtC,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QAAC,CAAC,CACnC,CAAA;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACrB,CAAC;IAED,SAAS,oBAAoB;QAC3B,IAAI,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,mBAAmB,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACtJ,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,MAAM,EAAE,EAAE,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QACtG,iFAAiF;QACjF,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,CAAC,CAAA;QAC/D,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YACnB,YAAY,CAAC,OAAO,CAAC,CAAA;YACrB,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,CAAC,CAAA;QAC7D,CAAC,CAAC,CAAA;QACF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACrB,CAAC;IAED,KAAK,EAAE,CAAA;IAEP,OAAO;QACL,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK;QACtB,KAAK,EAAE,KAAK;KACb,CAAA;AACH,CAAC;AAED,MAAM,aAAa,GAAG;IACpB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa;IAC1B,KAAK,EAAE,GAAG,EAAE,GAAe,CAAC;IAC5B,KAAK,EAAE,GAAG,EAAE,GAAe,CAAC;IAC5B,IAAI,EAAE,GAAG,EAAE,GAAe,CAAC;IAC3B,IAAI,EAAE,GAAG,EAAE,GAAe,CAAC;IAC3B,KAAK,EAAE,GAAG,EAAE,GAAe,CAAC;CAC7B,CAAA;AAED,SAAS,gBAAgB,CAAC,MAAgC,EAAE,OAAgC,EAAE,IAA2B,EAAE,OAAoB;IAC7I,IAAI,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;SAC3B,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM;QAAE,OAAO,EAAE,EAAE,CAAA;SAC7C,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC,sCAAsC,CAAC,CAAC;;QAC7E,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;AAC5C,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC3E,IAAI,CAAC,CAAC,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACjD,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IACjD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAc;IAC9C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC3E,IAAI,CAAC,CAAC,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACjD,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IACjD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,0BAA0B,CAAC,MAAgC,EAAE,OAAgC,EAAE,KAA6B;IACnI,IAAI,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC,CAAA;SACtF,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,EAAE,CAAC,CAAC,sCAAsC,CAAC,CAAC;SAC5E,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC,sCAAsC,CAAC,CAAC;;QAC7E,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;AAC5C,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { UserSession } from '@/app/shared/auth/auth';
2
+ import { SyncHandler } from './sync';
3
+ import { Mutex } from 'async-mutex';
4
+ export type RunningSyncStatus = {
5
+ state: 'connecting';
6
+ } | {
7
+ state: 'qr';
8
+ data: string;
9
+ } | {
10
+ state: 'initialsync';
11
+ } | {
12
+ state: 'ready';
13
+ } | {
14
+ state: 'failed';
15
+ data: string;
16
+ };
17
+ interface RunningSync {
18
+ status: RunningSyncStatus;
19
+ timeout: NodeJS.Timeout;
20
+ handler: SyncHandler;
21
+ }
22
+ declare global {
23
+ var runningSyncs: Map<string, RunningSync> | undefined;
24
+ var mutex: Mutex | undefined;
25
+ }
26
+ export declare function getRunningSyncData(user: UserSession): RunningSyncStatus;
27
+ export declare function triggerWhatsappSync(user: UserSession): Promise<void>;
28
+ export {};
29
+ //# sourceMappingURL=syncManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncManager.d.ts","sourceRoot":"","sources":["../src/syncManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAIpD,OAAO,EAAa,WAAW,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,YAAY,CAAA;CAAE,GACnD;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GACxB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC,UAAU,WAAW;IACnB,MAAM,EAAE,iBAAiB,CAAA;IACzB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAA;IACvB,OAAO,EAAE,WAAW,CAAA;CACrB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,SAAS,CAAA;IACtD,IAAI,KAAK,EAAE,KAAK,GAAG,SAAS,CAAA;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,iBAAiB,CAEvE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB1E"}
@@ -0,0 +1,79 @@
1
+ 'use server';
2
+ import { logger } from '@/app/shared/logger';
3
+ import { transactional } from '@/app/shared/_external/db/access';
4
+ import { logEvent } from '@/app/shared/_data/Event';
5
+ import { startSync } from './sync';
6
+ import { Mutex } from 'async-mutex';
7
+ export function getRunningSyncData(user) {
8
+ return getSync(user).status;
9
+ }
10
+ export async function triggerWhatsappSync(user) {
11
+ globalThis.mutex ??= new Mutex();
12
+ await globalThis.mutex.runExclusive(async () => {
13
+ globalThis.runningSyncs ??= new Map();
14
+ const existing = globalThis.runningSyncs.get(user.email);
15
+ if (existing) {
16
+ clearTimeout(existing.timeout);
17
+ existing.timeout = setTimeout(() => { existing.handler.close(); }, 5 * 1000);
18
+ return;
19
+ }
20
+ logger.debug(`Start whatsapp sync for ${user.email}`);
21
+ const handler = await startSync(user.email);
22
+ handler.onQrCode((qr) => { qrCallback(user, qr); });
23
+ handler.onAuth(() => { authCallback(user); });
24
+ handler.onReady(() => { readyCallback(user); });
25
+ handler.onFinished((error) => { finishedCallback(user, error); });
26
+ handler.start();
27
+ const runningSync = {
28
+ status: { state: 'connecting' },
29
+ timeout: setTimeout(() => { closeSync(user); }, 5 * 1000),
30
+ handler,
31
+ };
32
+ globalThis.runningSyncs.set(user.email, runningSync);
33
+ });
34
+ }
35
+ function qrCallback(user, qr) {
36
+ const message = `WhatsApp authentication requires QR code for user ${user.email}`;
37
+ logger.info(message);
38
+ void transactional(async (c) => { await logEvent(c, 'INFO', message); });
39
+ const runningSync = getSync(user);
40
+ runningSync.status = { state: 'qr', data: qr };
41
+ }
42
+ function authCallback(user) {
43
+ const message = `WhatsApp authentication successful for user ${user.email}`;
44
+ logger.info(message);
45
+ void transactional(async (c) => { await logEvent(c, 'INFO', message); });
46
+ const runningSync = getSync(user);
47
+ runningSync.status = { state: 'initialsync' };
48
+ }
49
+ function readyCallback(user) {
50
+ logger.debug(`WhatsApp is ready for user ${user.email}`);
51
+ const runningSync = getSync(user);
52
+ runningSync.status = { state: 'ready' };
53
+ }
54
+ function finishedCallback(user, error) {
55
+ if (!error) {
56
+ logger.debug('Closing WhatsApp sync handler after successful completion');
57
+ globalThis.runningSyncs?.delete(user.email);
58
+ return;
59
+ }
60
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
61
+ const message = `WhatsApp sync failed for user ${user.email}: ${errorMessage}`;
62
+ logger.error(message, error);
63
+ void transactional(async (c) => { await logEvent(c, 'ERROR', message); });
64
+ const runningSync = getSync(user);
65
+ runningSync.status = { state: 'failed', data: message };
66
+ }
67
+ function closeSync(user) {
68
+ logger.info('WhatsApp sync timeout reached, closing sync handler');
69
+ const runningSync = getSync(user);
70
+ globalThis.runningSyncs?.delete(user.email);
71
+ runningSync.handler.close();
72
+ }
73
+ function getSync(user) {
74
+ const runningSync = globalThis.runningSyncs?.get(user.email);
75
+ if (!runningSync)
76
+ throw new Error(`No running WhatsApp sync found for user ${user.email}`);
77
+ return runningSync;
78
+ }
79
+ //# sourceMappingURL=syncManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncManager.js","sourceRoot":"","sources":["../src/syncManager.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,SAAS,EAAe,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAmBnC,MAAM,UAAU,kBAAkB,CAAC,IAAiB;IAClD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAiB;IACzD,UAAU,CAAC,KAAK,KAAK,IAAI,KAAK,EAAE,CAAA;IAChC,MAAM,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE;QAC7C,UAAU,CAAC,YAAY,KAAK,IAAI,GAAG,EAAE,CAAA;QACrC,MAAM,QAAQ,GAAG,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxD,IAAI,QAAQ,EAAE,CAAC;YACb,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;YAC9B,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;YAC3E,OAAM;QACR,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,2BAA2B,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QACrD,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAClD,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAC5C,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAC9C,OAAO,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAChE,OAAO,CAAC,KAAK,EAAE,CAAA;QACf,MAAM,WAAW,GAAgB;YAC/B,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE;YAC/B,OAAO,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;YACxD,OAAO;SACR,CAAA;QACD,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAAiB,EAAE,EAAU;IAC/C,MAAM,OAAO,GAAG,qDAAqD,IAAI,CAAC,KAAK,EAAE,CAAA;IACjF,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACpB,KAAK,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,WAAW,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;AAChD,CAAC;AAED,SAAS,YAAY,CAAC,IAAiB;IACrC,MAAM,OAAO,GAAG,+CAA+C,IAAI,CAAC,KAAK,EAAE,CAAA;IAC3E,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACpB,KAAK,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,WAAW,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;AAC/C,CAAC;AAED,SAAS,aAAa,CAAC,IAAiB;IACtC,MAAM,CAAC,KAAK,CAAC,8BAA8B,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;IACxD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,WAAW,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AACzC,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAiB,EAAE,KAAe;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAA;QACzE,UAAU,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,OAAM;IACR,CAAC;IACD,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAA;IAC7E,MAAM,OAAO,GAAG,iCAAiC,IAAI,CAAC,KAAK,KAAK,YAAY,EAAE,CAAA;IAC9E,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAC5B,KAAK,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IACxE,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,WAAW,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AACzD,CAAC;AAED,SAAS,SAAS,CAAC,IAAiB;IAClC,MAAM,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAA;IAClE,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,UAAU,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3C,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;AAC7B,CAAC;AAED,SAAS,OAAO,CAAC,IAAiB;IAChC,MAAM,WAAW,GAAG,UAAU,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5D,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;IAC1F,OAAO,WAAW,CAAA;AACpB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":""}
package/dist/tools.js ADDED
@@ -0,0 +1,65 @@
1
+ export {};
2
+ /*
3
+ const SendMessageSchema = z.object({
4
+ to: z.string().min(1, "Recipient is required"),
5
+ message: z.string().min(1, "Message body is required"),
6
+ });
7
+
8
+ const ListChatsSchema = z.object({
9
+ limit: z.number().int().positive().optional().default(20),
10
+ });
11
+
12
+ const ReadMessagesSchema = z.object({
13
+ chatId: z.string().min(1, "Chat ID is required"),
14
+ limit: z.number().int().positive().optional().default(50),
15
+ });
16
+
17
+ server.registerTool(
18
+ "send_message",
19
+ {
20
+ description: "Send a WhatsApp message to a recipient",
21
+ inputSchema: SendMessageSchema,
22
+ },
23
+ async ({ to, message }) => ({
24
+ content: [
25
+ {
26
+ type: "text" as const,
27
+ text: `Message would be sent to ${to}: "${message}" (not yet implemented)`,
28
+ },
29
+ ],
30
+ }),
31
+ );
32
+
33
+ server.registerTool(
34
+ "list_chats",
35
+ {
36
+ description: "List recent WhatsApp chats",
37
+ inputSchema: ListChatsSchema,
38
+ },
39
+ async ({ limit }) => ({
40
+ content: [
41
+ {
42
+ type: "text" as const,
43
+ text: `Listing up to ${limit} chats (not yet implemented)`,
44
+ },
45
+ ],
46
+ }),
47
+ );
48
+
49
+ server.registerTool(
50
+ "read_messages",
51
+ {
52
+ description: "Read messages from a WhatsApp chat",
53
+ inputSchema: ReadMessagesSchema,
54
+ },
55
+ async ({ chatId, limit }) => ({
56
+ content: [
57
+ {
58
+ type: "text" as const,
59
+ text: `Reading up to ${limit} messages from ${chatId} (not yet implemented)`,
60
+ },
61
+ ],
62
+ }),
63
+ );
64
+ */
65
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8DE"}
@@ -0,0 +1,32 @@
1
+ import eslint from '@eslint/js'
2
+ import tseslint from 'typescript-eslint'
3
+ import { defineConfig, globalIgnores } from 'eslint/config'
4
+ import stylistic from '@stylistic/eslint-plugin'
5
+
6
+ export default defineConfig([
7
+ eslint.configs.recommended,
8
+ tseslint.configs.recommendedTypeChecked,
9
+ tseslint.configs.strictTypeChecked,
10
+ tseslint.configs.stylisticTypeChecked,
11
+ stylistic.configs.recommended,
12
+ globalIgnores(['node_modules/**', 'dist/**']),
13
+ {
14
+ languageOptions: {
15
+ parserOptions: {
16
+ tsconfigRootDir: import.meta.dirname,
17
+ projectService: {
18
+ allowDefaultProject: ['src/*.test.ts'],
19
+ },
20
+ },
21
+ },
22
+ rules: {
23
+ '@typescript-eslint/no-extraneous-class': ['off'],
24
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
25
+ '@stylistic/max-statements-per-line': ['error', { max: 2 }],
26
+ },
27
+ },
28
+ {
29
+ files: ['**/*.js', '**/*.cjs', '**/*.mjs'],
30
+ extends: [tseslint.configs.disableTypeChecked],
31
+ },
32
+ ])
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@lizzythelizard/whatsapp-mcp",
3
+ "version": "0.1.3",
4
+ "description": "MCP server for WhatsApp integration",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "bin": {
11
+ "whatsapp-mcp": "dist/index.js"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "tsx watch src/index.ts",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "lint": "eslint src/",
20
+ "lint:fix": "eslint src/ --fix",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "whatsapp",
26
+ "model-context-protocol"
27
+ ],
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.29.0",
31
+ "@stylistic/eslint-plugin": "^5.10.0",
32
+ "@whiskeysockets/baileys": "^7.0.0-rc13",
33
+ "zod": "^4.4.3"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^10.0.1",
37
+ "@types/node": "^26.0.1",
38
+ "@typescript-eslint/eslint-plugin": "^8.62.0",
39
+ "eslint": "^10.5.0",
40
+ "tsx": "^4.22.4",
41
+ "typescript": "^6.0.3",
42
+ "typescript-eslint": "^8.62.0",
43
+ "vitest": "^4.1.9"
44
+ }
45
+ }
@@ -0,0 +1,138 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createExportableAuth } from './auth.js'
3
+
4
+ describe('createExportableAuth', () => {
5
+ it('returns fresh auth with initAuthCreds when called without argument', () => {
6
+ const auth = createExportableAuth()
7
+ expect(auth.creds).toBeDefined()
8
+ expect(auth.keys).toBeDefined()
9
+ expect(typeof auth.toAuthState).toBe('function')
10
+ })
11
+
12
+ it('restores creds and keys from a JSON string', () => {
13
+ const original = createExportableAuth()
14
+ const json = original.toAuthState()
15
+ const restored = createExportableAuth(json)
16
+ expect(restored.creds.registrationId).toBe(original.creds.registrationId)
17
+ expect(restored.creds.noiseKey.public).toEqual(original.creds.noiseKey.public)
18
+ })
19
+
20
+ it('roundtrips through toAuthState and back', async () => {
21
+ const auth = createExportableAuth()
22
+ await auth.keys.set({ 'sender-key': { 'test@test:1': new Uint8Array([1, 2, 3]) } })
23
+ const json = auth.toAuthState()
24
+ const imported = createExportableAuth(json)
25
+ const retrieved = await imported.keys.get('sender-key', ['test@test:1'])
26
+ expect(retrieved['test@test:1']).toBeDefined()
27
+ expect(new Uint8Array(retrieved['test@test:1'])).toEqual(new Uint8Array([1, 2, 3]))
28
+ })
29
+
30
+ it('preserves Uint8Array values through export and import', async () => {
31
+ const auth = createExportableAuth()
32
+ const original = new Uint8Array([255, 254, 253])
33
+ await auth.keys.set({ 'sender-key': { 'test-id': original } })
34
+ const json = auth.toAuthState()
35
+ const imported = createExportableAuth(json)
36
+ const retrieved = await imported.keys.get('sender-key', ['test-id'])
37
+ expect(new Uint8Array(retrieved['test-id'])).toEqual(original)
38
+ })
39
+
40
+ it('handles undefined input and returns fresh auth', () => {
41
+ const auth = createExportableAuth(undefined)
42
+ expect(auth.creds).toBeDefined()
43
+ expect(auth.creds.registrationId).toBeGreaterThan(0)
44
+ })
45
+
46
+ it('throws on invalid JSON string', () => {
47
+ expect(() => createExportableAuth('not valid json')).toThrow()
48
+ })
49
+
50
+ it('handles empty JSON object (no creds)', () => {
51
+ const auth = createExportableAuth('{}')
52
+ expect(typeof auth.toAuthState).toBe('function')
53
+ expect(auth.toAuthState()).toBe('{}')
54
+ })
55
+ })
56
+
57
+ describe('keys store (set/get)', () => {
58
+ it('stores and retrieves session values', async () => {
59
+ const auth = createExportableAuth()
60
+ const value = new Uint8Array([10, 20, 30])
61
+ await auth.keys.set({ session: { 'session-id': value } })
62
+ const result = await auth.keys.get('session', ['session-id'])
63
+ expect(result['session-id']).toBeDefined()
64
+ expect(new Uint8Array(result['session-id'])).toEqual(value)
65
+ })
66
+
67
+ it('stores and retrieves string values (lid-mapping)', async () => {
68
+ const auth = createExportableAuth()
69
+ await auth.keys.set({ 'lid-mapping': { test: 'mapped-value' } })
70
+ const result = await auth.keys.get('lid-mapping', ['test'])
71
+ expect(result.test).toBe('mapped-value')
72
+ })
73
+
74
+ it('stores and retrieves string array values (device-list)', async () => {
75
+ const auth = createExportableAuth()
76
+ await auth.keys.set({ 'device-list': { test: ['dev1', 'dev2'] } })
77
+ const result = await auth.keys.get('device-list', ['test'])
78
+ expect(result.test).toEqual(['dev1', 'dev2'])
79
+ })
80
+
81
+ it('returns empty object for missing keys', async () => {
82
+ const auth = createExportableAuth()
83
+ const result = await auth.keys.get('session', ['nonexistent'])
84
+ expect(result).toEqual({})
85
+ })
86
+
87
+ it('overwrites existing values on successive sets', async () => {
88
+ const auth = createExportableAuth()
89
+ await auth.keys.set({ session: { s1: new Uint8Array([1]) } })
90
+ await auth.keys.set({ session: { s1: new Uint8Array([2]) } })
91
+ const result = await auth.keys.get('session', ['s1'])
92
+ expect(new Uint8Array(result.s1)).toEqual(new Uint8Array([2]))
93
+ })
94
+
95
+ it('deletes key when value is set to null', async () => {
96
+ const auth = createExportableAuth()
97
+ await auth.keys.set({ session: { s1: new Uint8Array([1]) } })
98
+ await auth.keys.set({ session: { s1: null } })
99
+ const result = await auth.keys.get('session', ['s1'])
100
+ expect(result).toEqual({})
101
+ })
102
+
103
+ it('retrieves multiple ids in a single get call', async () => {
104
+ const auth = createExportableAuth()
105
+ await auth.keys.set({ 'lid-mapping': { id1: 'val1', id2: 'val2' } })
106
+ const result = await auth.keys.get('lid-mapping', ['id1', 'id2'])
107
+ expect(result.id1).toBe('val1')
108
+ expect(result.id2).toBe('val2')
109
+ })
110
+
111
+ it('handles app-state-sync-key with correct protobuf conversion', async () => {
112
+ const auth = createExportableAuth()
113
+ const keyData = {
114
+ keyData: new Uint8Array([1, 2, 3]),
115
+ timestamp: 1234567890,
116
+ }
117
+ await auth.keys.set({ 'app-state-sync-key': { key1: keyData } })
118
+ const result = await auth.keys.get('app-state-sync-key', ['key1'])
119
+ expect(result.key1).toBeDefined()
120
+ expect(result.key1.keyData).toBeDefined()
121
+ expect(Number(result.key1.timestamp)).toBe(1234567890)
122
+ })
123
+
124
+ it('stores and retrieves sender-key-memory (boolean map)', async () => {
125
+ const auth = createExportableAuth()
126
+ await auth.keys.set({ 'sender-key-memory': { jid1: { user1: true } } })
127
+ const result = await auth.keys.get('sender-key-memory', ['jid1'])
128
+ expect(result.jid1.user1).toBe(true)
129
+ })
130
+
131
+ it('stores and retrieves tctoken', async () => {
132
+ const auth = createExportableAuth()
133
+ await auth.keys.set({ tctoken: { default: { token: Buffer.from('token-data'), timestamp: '123' } } })
134
+ const result = await auth.keys.get('tctoken', ['default'])
135
+ expect(result.default.token).toBeDefined()
136
+ expect(result.default.timestamp).toBe('123')
137
+ })
138
+ })
package/src/auth.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { AuthenticationCreds, BufferJSON, initAuthCreds, SignalDataTypeMap, proto, SignalDataSet, AuthenticationState } from '@whiskeysockets/baileys'
2
+
3
+ export interface ExportableAuthState extends AuthenticationState {
4
+ toAuthState: () => string
5
+ }
6
+
7
+ interface AuthStateInput { creds: AuthenticationCreds, keys: Record<string, string> }
8
+
9
+ export function createExportableAuth(input?: string): ExportableAuthState {
10
+ const { creds, keys } = fromString<AuthStateInput>(input) ?? { creds: initAuthCreds(), keys: {} }
11
+ return {
12
+ creds,
13
+ keys: { get: (t, ids) => getKey(t, ids, keys), set: (data) => { setKey(data, keys) } },
14
+ toAuthState: () => toString({ creds, keys }),
15
+ }
16
+ }
17
+
18
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
19
+ function fromString<T>(str: string | undefined): T | undefined {
20
+ if (!str) return undefined
21
+ return JSON.parse(str, BufferJSON.reviver) as T
22
+ }
23
+
24
+ function toString(data: unknown): string {
25
+ return JSON.stringify(data, BufferJSON.replacer)
26
+ }
27
+
28
+ function getKey<T extends keyof SignalDataTypeMap>(type: T, ids: string[], keys: Record<string, string>): Record<string, SignalDataTypeMap[T]> {
29
+ return ids.reduce<Record<string, SignalDataTypeMap[T]>>((acc, id) => {
30
+ const value = fromString<SignalDataTypeMap[T]>(keys[`${type}-${id}`])
31
+ if (!value) return acc
32
+ if (type === 'app-state-sync-key') {
33
+ const typedValue = value as SignalDataTypeMap['app-state-sync-key']
34
+ const converted = proto.Message.AppStateSyncKeyData.fromObject(typedValue)
35
+ const typeResult = converted as unknown as SignalDataTypeMap[T]
36
+ acc[id] = typeResult
37
+ }
38
+ else {
39
+ acc[id] = value
40
+ }
41
+ return acc
42
+ }, {})
43
+ }
44
+
45
+ function setKey(data: SignalDataSet, keys: Record<string, string>): void {
46
+ for (const category in data) {
47
+ const dataCategory = data[category as keyof SignalDataTypeMap]
48
+ for (const id in dataCategory) {
49
+ const value = dataCategory[id]
50
+ if (!value)
51
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
52
+ delete keys[`${category}-${id}`]
53
+ else {
54
+ keys[`${category}-${id}`] = JSON.stringify(value, BufferJSON.replacer)
55
+ }
56
+ }
57
+ }
58
+ }
package/src/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
+ import pkg from '../package.json' with { type: 'json' }
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
6
+ import { createStore, DataStore } from './store.js'
7
+ import { createHandler } from './sync.js'
8
+ import { promises as fsp } from 'fs'
9
+ import { registerWhatsAppTools } from './tools.js'
10
+
11
+ const dataDir = process.env.DATA_DIR ?? './data'
12
+
13
+ async function readDataFromFile(): Promise<DataStore | undefined> {
14
+ const canAccess = await fsp.access(dataDir).then(() => true).catch(() => false)
15
+ if (!canAccess) return undefined
16
+ const chats = await fsp.readFile(`${dataDir}/chats.json`, 'utf-8')
17
+ const messages = await fsp.readFile(`${dataDir}/messages.json`, 'utf-8')
18
+ const contacts = await fsp.readFile(`${dataDir}/contacts.json`, 'utf-8')
19
+ const auth = await fsp.readFile(`${dataDir}/auth.json`, 'utf-8')
20
+ return { chats, messages, contacts, auth }
21
+ }
22
+
23
+ async function writeDataToFile(data: DataStore): Promise<void> {
24
+ await fsp.mkdir(dataDir, { recursive: true })
25
+ await fsp.writeFile(`${dataDir}/chats.json`, data.chats, 'utf-8')
26
+ await fsp.writeFile(`${dataDir}/messages.json`, data.messages, 'utf-8')
27
+ await fsp.writeFile(`${dataDir}/contacts.json`, data.contacts, 'utf-8')
28
+ await fsp.writeFile(`${dataDir}/auth.json`, data.auth, 'utf-8')
29
+ }
30
+
31
+ async function main() {
32
+ const server = new McpServer({ name: pkg.name, version: pkg.version })
33
+ const transport = new StdioServerTransport()
34
+ const inputData = await readDataFromFile()
35
+ const whatsappStore = createStore(writeDataToFile, inputData)
36
+ const whatsappSync = createHandler(whatsappStore)
37
+ registerWhatsAppTools(server, whatsappStore, whatsappSync)
38
+ await server.connect(transport)
39
+ console.info('whatsapp-mcp server running on stdio')
40
+ }
41
+
42
+ main().catch((error: unknown) => {
43
+ console.error('Server error:', error)
44
+ process.exit(1)
45
+ })