@daisyintel/whatsapp-cloud-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/src/bin/inspect.js +26 -0
- package/dist/src/bin/inspect.js.map +1 -0
- package/dist/src/bin/server.js +3 -0
- package/dist/src/bin/server.js.map +1 -0
- package/dist/src/config.js +37 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/eventStore.js +22 -0
- package/dist/src/eventStore.js.map +1 -0
- package/dist/src/graphClient.js +114 -0
- package/dist/src/graphClient.js.map +1 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/tools.js +1187 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/webhook.js +278 -0
- package/dist/src/webhook.js.map +1 -0
- package/dist/test/config.test.js +50 -0
- package/dist/test/config.test.js.map +1 -0
- package/dist/test/graphClient.test.js +59 -0
- package/dist/test/graphClient.test.js.map +1 -0
- package/dist/test/tools.test.js +71 -0
- package/dist/test/tools.test.js.map +1 -0
- package/dist/test/webhook.test.js +154 -0
- package/dist/test/webhook.test.js.map +1 -0
- package/mcp.json.example +19 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# WhatsApp Cloud API MCP Server
|
|
2
|
+
|
|
3
|
+
TypeScript MCP server that exposes WhatsApp Cloud API admin and messaging tools over `stdio` and runs an HTTP webhook endpoint for verification and inbound event intake.
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
Set these variables before starting the server:
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
$env:WHATSAPP_ACCESS_TOKEN="..."
|
|
11
|
+
$env:WHATSAPP_API_VERSION="v23.0"
|
|
12
|
+
$env:WHATSAPP_BUSINESS_ID="..."
|
|
13
|
+
$env:WHATSAPP_WABA_ID="..."
|
|
14
|
+
$env:WHATSAPP_PHONE_NUMBER_ID="..."
|
|
15
|
+
$env:WHATSAPP_VERIFY_TOKEN="..."
|
|
16
|
+
$env:META_APP_SECRET="..."
|
|
17
|
+
$env:PORT="3000"
|
|
18
|
+
$env:WHATSAPP_ALLOW_UNSIGNED_WEBHOOKS="false"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`WHATSAPP_BUSINESS_ID`, `WHATSAPP_WABA_ID`, and `WHATSAPP_PHONE_NUMBER_ID` are optional if you want to discover them via the MCP tools first.
|
|
22
|
+
|
|
23
|
+
## Package Commands
|
|
24
|
+
|
|
25
|
+
After building or installing the package, these CLI commands are available:
|
|
26
|
+
|
|
27
|
+
- `whatsapp-cloud-mcp` starts the MCP server
|
|
28
|
+
- `whatsapp-cloud-mcp-inspect` starts MCP Inspector against the packaged server automatically
|
|
29
|
+
|
|
30
|
+
If you publish this package, users can run:
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
npx --package @daisy/whatsapp-cloud-mcp whatsapp-cloud-mcp
|
|
34
|
+
npx --package @daisy/whatsapp-cloud-mcp whatsapp-cloud-mcp-inspect
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The most practical local equivalents in this repo are:
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
npm start
|
|
43
|
+
npm run inspect
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Scripts
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
npm test
|
|
52
|
+
npm start
|
|
53
|
+
npm run inspect
|
|
54
|
+
npm run inspect:dev
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## MCP Inspector
|
|
58
|
+
|
|
59
|
+
The packaged inspector command starts the server under MCP Inspector automatically.
|
|
60
|
+
|
|
61
|
+
- `npm run inspect` launches Inspector against the built package server
|
|
62
|
+
- `npm run inspect:dev` launches Inspector directly against `src/index.ts`
|
|
63
|
+
- `whatsapp-cloud-mcp-inspect` does the same as the packaged built-server flow
|
|
64
|
+
|
|
65
|
+
Inspector opens a local UI, usually at `http://localhost:6274`, and proxies to the spawned MCP server. Before starting it, set the same WhatsApp environment variables shown above in your shell.
|
|
66
|
+
|
|
67
|
+
The installed Inspector version requires Node `>=22.7.5`. This machine is already compatible.
|
|
68
|
+
|
|
69
|
+
If you want a reusable client/server config, copy `mcp.json.example` to `mcp.json` and replace the placeholder env values.
|
|
70
|
+
|
|
71
|
+
## Webhook routes
|
|
72
|
+
|
|
73
|
+
- `GET /health`
|
|
74
|
+
- `GET /webhook`
|
|
75
|
+
- `POST /webhook`
|
|
76
|
+
|
|
77
|
+
## MCP transports
|
|
78
|
+
|
|
79
|
+
- `stdio` via the main MCP server process
|
|
80
|
+
- `streamable-http` at `http://127.0.0.1:3000/mcp`
|
|
81
|
+
- `sse` at `http://127.0.0.1:3000/sse` with POST messages to `http://127.0.0.1:3000/messages`
|
|
82
|
+
|
|
83
|
+
## Testing with MCP Inspector
|
|
84
|
+
|
|
85
|
+
To test the current `stdio` transport in Inspector:
|
|
86
|
+
|
|
87
|
+
```powershell
|
|
88
|
+
$env:WHATSAPP_ACCESS_TOKEN="..."
|
|
89
|
+
$env:WHATSAPP_VERIFY_TOKEN="..."
|
|
90
|
+
npm run inspect:dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To test the HTTP transports:
|
|
94
|
+
|
|
95
|
+
1. Start the server with `npm run dev`
|
|
96
|
+
2. Open MCP Inspector
|
|
97
|
+
3. Choose:
|
|
98
|
+
- `streamable-http` with `http://127.0.0.1:3000/mcp`
|
|
99
|
+
- `sse` with `http://127.0.0.1:3000/sse`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const serverBinPath = join(currentDir, "server.js");
|
|
9
|
+
const inspectorPackagePath = require.resolve("@modelcontextprotocol/inspector/package.json");
|
|
10
|
+
const inspectorCliPath = join(dirname(inspectorPackagePath), "cli", "build", "cli.js");
|
|
11
|
+
const child = spawn(process.execPath, [inspectorCliPath, process.execPath, serverBinPath, ...process.argv.slice(2)], {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
env: process.env
|
|
14
|
+
});
|
|
15
|
+
child.on("exit", (code, signal) => {
|
|
16
|
+
if (signal) {
|
|
17
|
+
process.kill(process.pid, signal);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
process.exit(code ?? 0);
|
|
21
|
+
});
|
|
22
|
+
child.on("error", (error) => {
|
|
23
|
+
console.error(error instanceof Error ? error.message : error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../../src/bin/inspect.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3D,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AACpD,MAAM,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC;AAC7F,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AAEvF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,gBAAgB,EAAE,OAAO,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;IACnH,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,OAAO,CAAC,GAAG;CACjB,CAAC,CAAC;AAEH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;IAChC,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;IAC1B,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/bin/server.ts"],"names":[],"mappings":";AACA,OAAO,aAAa,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { config as loadDotEnv } from "dotenv";
|
|
2
|
+
loadDotEnv();
|
|
3
|
+
const DEFAULT_API_VERSION = "v23.0";
|
|
4
|
+
const DEFAULT_PORT = 3000;
|
|
5
|
+
const DEFAULT_MAX_WEBHOOK_EVENTS = 100;
|
|
6
|
+
function parseIntWithDefault(rawValue, fallback, label) {
|
|
7
|
+
if (!rawValue) {
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
11
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
12
|
+
throw new Error(`Invalid ${label}: expected a positive integer.`);
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
function parseBoolean(rawValue) {
|
|
17
|
+
return rawValue === "true" || rawValue === "1";
|
|
18
|
+
}
|
|
19
|
+
export function loadConfig(env = process.env) {
|
|
20
|
+
const accessToken = env.WHATSAPP_ACCESS_TOKEN;
|
|
21
|
+
if (!accessToken) {
|
|
22
|
+
throw new Error("WHATSAPP_ACCESS_TOKEN is required.");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
accessToken,
|
|
26
|
+
apiVersion: env.WHATSAPP_API_VERSION ?? DEFAULT_API_VERSION,
|
|
27
|
+
businessId: env.WHATSAPP_BUSINESS_ID ?? env.BUSINESS_ID,
|
|
28
|
+
wabaId: env.WHATSAPP_WABA_ID ?? env.WAB_ID,
|
|
29
|
+
phoneNumberId: env.WHATSAPP_PHONE_NUMBER_ID ?? env.PHONE_ID,
|
|
30
|
+
verifyToken: env.WHATSAPP_VERIFY_TOKEN,
|
|
31
|
+
appSecret: env.META_APP_SECRET,
|
|
32
|
+
port: parseIntWithDefault(env.PORT, DEFAULT_PORT, "PORT"),
|
|
33
|
+
maxWebhookEvents: parseIntWithDefault(env.WHATSAPP_MAX_WEBHOOK_EVENTS, DEFAULT_MAX_WEBHOOK_EVENTS, "WHATSAPP_MAX_WEBHOOK_EVENTS"),
|
|
34
|
+
allowUnsignedWebhooks: parseBoolean(env.WHATSAPP_ALLOW_UNSIGNED_WEBHOOKS)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG9C,UAAU,EAAE,CAAC;AAEb,MAAM,mBAAmB,GAAG,OAAO,CAAC;AACpC,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAEvC,SAAS,mBAAmB,CAAC,QAA4B,EAAE,QAAgB,EAAE,KAAa;IACxF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,gCAAgC,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CAAC,QAA4B;IAChD,OAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,GAAG,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,WAAW,GAAG,GAAG,CAAC,qBAAqB,CAAC;IAC9C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO;QACL,WAAW;QACX,UAAU,EAAE,GAAG,CAAC,oBAAoB,IAAI,mBAAmB;QAC3D,UAAU,EAAE,GAAG,CAAC,oBAAoB,IAAI,GAAG,CAAC,WAAW;QACvD,MAAM,EAAE,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,MAAM;QAC1C,aAAa,EAAE,GAAG,CAAC,wBAAwB,IAAI,GAAG,CAAC,QAAQ;QAC3D,WAAW,EAAE,GAAG,CAAC,qBAAqB;QACtC,SAAS,EAAE,GAAG,CAAC,eAAe;QAC9B,IAAI,EAAE,mBAAmB,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC;QACzD,gBAAgB,EAAE,mBAAmB,CACnC,GAAG,CAAC,2BAA2B,EAC/B,0BAA0B,EAC1B,6BAA6B,CAC9B;QACD,qBAAqB,EAAE,YAAY,CAAC,GAAG,CAAC,gCAAgC,CAAC;KAC1E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class InMemoryWebhookEventStore {
|
|
2
|
+
maxEvents;
|
|
3
|
+
events = [];
|
|
4
|
+
constructor(maxEvents) {
|
|
5
|
+
this.maxEvents = maxEvents;
|
|
6
|
+
}
|
|
7
|
+
add(events) {
|
|
8
|
+
for (const event of events) {
|
|
9
|
+
this.events.unshift(event);
|
|
10
|
+
}
|
|
11
|
+
if (this.events.length > this.maxEvents) {
|
|
12
|
+
this.events.length = this.maxEvents;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
list(limit = 20) {
|
|
16
|
+
return this.events.slice(0, limit);
|
|
17
|
+
}
|
|
18
|
+
clear() {
|
|
19
|
+
this.events.length = 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=eventStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventStore.js","sourceRoot":"","sources":["../../src/eventStore.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,yBAAyB;IAGP;IAFZ,MAAM,GAA6B,EAAE,CAAC;IAEvD,YAA6B,SAAiB;QAAjB,cAAS,GAAT,SAAS,CAAQ;IAAG,CAAC;IAElD,GAAG,CAAC,MAAgC;QAClC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;QACtC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAK,GAAG,EAAE;QACb,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export class GraphRequestError extends Error {
|
|
2
|
+
endpoint;
|
|
3
|
+
status;
|
|
4
|
+
requestId;
|
|
5
|
+
graphError;
|
|
6
|
+
constructor(message, endpoint, status, requestId, graphError) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.endpoint = endpoint;
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.requestId = requestId;
|
|
11
|
+
this.graphError = graphError;
|
|
12
|
+
this.name = "GraphRequestError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function buildUrl(config, path, query) {
|
|
16
|
+
const sanitizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
17
|
+
const url = new URL(`https://graph.facebook.com/${config.apiVersion}/${sanitizedPath}`);
|
|
18
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
19
|
+
if (value !== undefined) {
|
|
20
|
+
url.searchParams.set(key, String(value));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
async function parseErrorBody(response) {
|
|
26
|
+
let body;
|
|
27
|
+
try {
|
|
28
|
+
body = await response.json();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
body = undefined;
|
|
32
|
+
}
|
|
33
|
+
const root = typeof body === "object" && body !== null ? body : undefined;
|
|
34
|
+
const graphError = root?.error;
|
|
35
|
+
const normalized = typeof graphError === "object" && graphError !== null ? graphError : undefined;
|
|
36
|
+
return {
|
|
37
|
+
message: typeof normalized?.message === "string"
|
|
38
|
+
? normalized.message
|
|
39
|
+
: `Graph API request failed with status ${response.status}.`,
|
|
40
|
+
type: typeof normalized?.type === "string" ? normalized.type : undefined,
|
|
41
|
+
code: typeof normalized?.code === "number" ? normalized.code : undefined,
|
|
42
|
+
errorSubcode: typeof normalized?.error_subcode === "number" ? normalized.error_subcode : undefined,
|
|
43
|
+
fbtraceId: typeof normalized?.fbtrace_id === "string" ? normalized.fbtrace_id : undefined,
|
|
44
|
+
details: body
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export class WhatsAppGraphClient {
|
|
48
|
+
config;
|
|
49
|
+
fetchImpl;
|
|
50
|
+
constructor(config, fetchImpl = fetch) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
this.fetchImpl = fetchImpl;
|
|
53
|
+
}
|
|
54
|
+
async request(options) {
|
|
55
|
+
const url = buildUrl(this.config, options.path, options.query);
|
|
56
|
+
const endpoint = url.toString();
|
|
57
|
+
const headers = new Headers(options.headers ?? {});
|
|
58
|
+
headers.set("Authorization", `Bearer ${this.config.accessToken}`);
|
|
59
|
+
let body;
|
|
60
|
+
if (options.formData) {
|
|
61
|
+
body = options.formData;
|
|
62
|
+
}
|
|
63
|
+
else if (options.body !== undefined) {
|
|
64
|
+
headers.set("Content-Type", "application/json");
|
|
65
|
+
body = JSON.stringify(options.body);
|
|
66
|
+
}
|
|
67
|
+
const response = await this.fetchImpl(endpoint, {
|
|
68
|
+
method: options.method ?? "GET",
|
|
69
|
+
headers,
|
|
70
|
+
body
|
|
71
|
+
});
|
|
72
|
+
const requestId = response.headers.get("x-fb-request-id") ?? undefined;
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const graphError = await parseErrorBody(response);
|
|
75
|
+
throw new GraphRequestError(graphError.message, endpoint, response.status, requestId, graphError);
|
|
76
|
+
}
|
|
77
|
+
if (response.status === 204) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return (await response.json());
|
|
81
|
+
}
|
|
82
|
+
async safeRequest(options) {
|
|
83
|
+
const url = buildUrl(this.config, options.path, options.query);
|
|
84
|
+
const endpoint = url.toString();
|
|
85
|
+
try {
|
|
86
|
+
const data = await this.request(options);
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
endpoint,
|
|
90
|
+
data
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof GraphRequestError) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
endpoint: error.endpoint,
|
|
98
|
+
status: error.status,
|
|
99
|
+
requestId: error.requestId,
|
|
100
|
+
error: error.graphError ?? { message: error.message }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
endpoint,
|
|
106
|
+
error: {
|
|
107
|
+
message: error instanceof Error ? error.message : "Unknown Graph API error.",
|
|
108
|
+
details: error
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=graphClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphClient.js","sourceRoot":"","sources":["../../src/graphClient.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAGxB;IACA;IACA;IACA;IALlB,YACE,OAAe,EACC,QAAgB,EAChB,MAAe,EACf,SAAkB,EAClB,UAA0B;QAE1C,KAAK,CAAC,OAAO,CAAC,CAAC;QALC,aAAQ,GAAR,QAAQ,CAAQ;QAChB,WAAM,GAAN,MAAM,CAAS;QACf,cAAS,GAAT,SAAS,CAAS;QAClB,eAAU,GAAV,UAAU,CAAgB;QAG1C,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,MAAsB,EAAE,IAAY,EAAE,KAAoC;IAC1F,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,8BAA8B,MAAM,CAAC,UAAU,IAAI,aAAa,EAAE,CAAC,CAAC;IAExF,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,QAAkB;IAC9C,IAAI,IAAa,CAAC;IAElB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAE,IAAgC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvG,MAAM,UAAU,GAAG,IAAI,EAAE,KAAK,CAAC;IAC/B,MAAM,UAAU,GACd,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,CAAC,CAAC,CAAE,UAAsC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE9G,OAAO;QACL,OAAO,EACL,OAAO,UAAU,EAAE,OAAO,KAAK,QAAQ;YACrC,CAAC,CAAC,UAAU,CAAC,OAAO;YACpB,CAAC,CAAC,wCAAwC,QAAQ,CAAC,MAAM,GAAG;QAChE,IAAI,EAAE,OAAO,UAAU,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACxE,IAAI,EAAE,OAAO,UAAU,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACxE,YAAY,EAAE,OAAO,UAAU,EAAE,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;QAClG,SAAS,EAAE,OAAO,UAAU,EAAE,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QACzF,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,MAAsB,EACtB,YAA0B,KAAK;QAD/B,WAAM,GAAN,MAAM,CAAgB;QACtB,cAAS,GAAT,SAAS,CAAsB;IAC/C,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAI,OAA4B;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAElE,IAAI,IAA0B,CAAC;QAC/B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC1B,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;YAChD,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE;YAC9C,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;YAC/B,OAAO;YACP,IAAI;SACL,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,SAAS,CAAC;QACvE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;YAClD,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QACpG,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,SAAc,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,WAAW,CAAI,OAA4B;QAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAEhC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAI,OAAO,CAAC,CAAC;YAC5C,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,QAAQ;gBACR,IAAI;aACL,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBACvC,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,KAAK,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;iBACtD,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,QAAQ;gBACR,KAAK,EAAE;oBACL,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B;oBAC5E,OAAO,EAAE,KAAK;iBACf;aACF,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { InMemoryWebhookEventStore } from "./eventStore.js";
|
|
4
|
+
import { WhatsAppGraphClient } from "./graphClient.js";
|
|
5
|
+
import { createWhatsAppMcpServer } from "./tools.js";
|
|
6
|
+
import { startHttpServer } from "./webhook.js";
|
|
7
|
+
async function main() {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const eventStore = new InMemoryWebhookEventStore(config.maxWebhookEvents);
|
|
10
|
+
const client = new WhatsAppGraphClient(config);
|
|
11
|
+
const createMcpServer = () => createWhatsAppMcpServer(client, config, eventStore);
|
|
12
|
+
const httpServer = await startHttpServer(config, eventStore, createMcpServer);
|
|
13
|
+
console.error(`HTTP server listening on port ${config.port}`);
|
|
14
|
+
console.error(`MCP streamable HTTP endpoint: http://127.0.0.1:${config.port}/mcp`);
|
|
15
|
+
console.error(`MCP SSE endpoint: http://127.0.0.1:${config.port}/sse`);
|
|
16
|
+
console.error(`Webhook endpoint: http://127.0.0.1:${config.port}/webhook`);
|
|
17
|
+
const stdioServer = createMcpServer();
|
|
18
|
+
const transport = new StdioServerTransport();
|
|
19
|
+
await stdioServer.connect(transport);
|
|
20
|
+
const shutdown = async () => {
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
httpServer.close((error) => {
|
|
23
|
+
if (error) {
|
|
24
|
+
reject(error);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
resolve();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
process.exit(0);
|
|
31
|
+
};
|
|
32
|
+
process.on("SIGINT", () => void shutdown());
|
|
33
|
+
process.on("SIGTERM", () => void shutdown());
|
|
34
|
+
}
|
|
35
|
+
main().catch((error) => {
|
|
36
|
+
console.error(error instanceof Error ? error.stack : error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,IAAI,yBAAyB,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE/C,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAElF,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAC9E,OAAO,CAAC,KAAK,CAAC,iCAAiC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,KAAK,CAAC,kDAAkD,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC;IACnF,OAAO,CAAC,KAAK,CAAC,sCAAsC,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC;IACvE,OAAO,CAAC,KAAK,CAAC,sCAAsC,MAAM,CAAC,IAAI,UAAU,CAAC,CAAC;IAE3E,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACzB,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;gBACT,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|