@messegy/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/README.md +133 -0
- package/api/index.js +3 -0
- package/dist/api-client.d.ts +10 -0
- package/dist/api-client.js +100 -0
- package/dist/api-client.js.map +1 -0
- package/dist/cli-setup.d.ts +1 -0
- package/dist/cli-setup.js +74 -0
- package/dist/cli-setup.js.map +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +88 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/quota-guard.d.ts +19 -0
- package/dist/lib/quota-guard.js +101 -0
- package/dist/lib/quota-guard.js.map +1 -0
- package/dist/resources/index.d.ts +2 -0
- package/dist/resources/index.js +62 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +364 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/analytics.d.ts +2 -0
- package/dist/tools/analytics.js +42 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +113 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/auto-replies.d.ts +2 -0
- package/dist/tools/auto-replies.js +117 -0
- package/dist/tools/auto-replies.js.map +1 -0
- package/dist/tools/broadcasts.d.ts +2 -0
- package/dist/tools/broadcasts.js +58 -0
- package/dist/tools/broadcasts.js.map +1 -0
- package/dist/tools/chats.d.ts +2 -0
- package/dist/tools/chats.js +77 -0
- package/dist/tools/chats.js.map +1 -0
- package/dist/tools/contacts.d.ts +2 -0
- package/dist/tools/contacts.js +79 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/flows.d.ts +2 -0
- package/dist/tools/flows.js +148 -0
- package/dist/tools/flows.js.map +1 -0
- package/dist/tools/messages.d.ts +2 -0
- package/dist/tools/messages.js +149 -0
- package/dist/tools/messages.js.map +1 -0
- package/dist/tools/shopify.d.ts +2 -0
- package/dist/tools/shopify.js +66 -0
- package/dist/tools/shopify.js.map +1 -0
- package/dist/tools/templates.d.ts +2 -0
- package/dist/tools/templates.js +160 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/web-app.d.ts +1 -0
- package/dist/web-app.js +845 -0
- package/dist/web-app.js.map +1 -0
- package/package.json +42 -0
- package/src/api-client.ts +112 -0
- package/src/cli-setup.ts +87 -0
- package/src/config.ts +111 -0
- package/src/index.ts +66 -0
- package/src/lib/quota-guard.ts +138 -0
- package/src/resources/index.ts +70 -0
- package/src/server.ts +408 -0
- package/src/tools/analytics.ts +58 -0
- package/src/tools/auth.ts +136 -0
- package/src/tools/auto-replies.ts +144 -0
- package/src/tools/broadcasts.ts +73 -0
- package/src/tools/chats.ts +100 -0
- package/src/tools/contacts.ts +99 -0
- package/src/tools/flows.ts +192 -0
- package/src/tools/messages.ts +177 -0
- package/src/tools/shopify.ts +80 -0
- package/src/tools/templates.ts +193 -0
- package/src/web-app.ts +844 -0
- package/tsconfig.json +18 -0
- package/vercel.json +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-app.js","sourceRoot":"","sources":["../src/web-app.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAy0BD,CAAC;AACT,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@messegy/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Model Context Protocol (MCP) Server for Messegy WhatsApp Business Cloud Platform",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"messegy-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"server": "node dist/server.js",
|
|
14
|
+
"dev": "tsc --watch"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"messegy",
|
|
20
|
+
"whatsapp",
|
|
21
|
+
"whatsapp-business-api",
|
|
22
|
+
"ai-agent",
|
|
23
|
+
"claude",
|
|
24
|
+
"cursor"
|
|
25
|
+
],
|
|
26
|
+
"author": "Messegy",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
30
|
+
"axios": "^1.7.9",
|
|
31
|
+
"cors": "^2.8.6",
|
|
32
|
+
"dotenv": "^16.4.7",
|
|
33
|
+
"express": "^5.2.1",
|
|
34
|
+
"zod": "^3.24.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/cors": "^2.8.19",
|
|
38
|
+
"@types/express": "^5.0.6",
|
|
39
|
+
"@types/node": "^22.13.4",
|
|
40
|
+
"typescript": "^5.7.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import axios, { AxiosError } from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
export class MessegyApiClient {
|
|
5
|
+
private getHeaders(): Record<string, string> {
|
|
6
|
+
const config = getConfig();
|
|
7
|
+
const headers: Record<string, string> = {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
Accept: 'application/json',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (config.apiKey) {
|
|
13
|
+
headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
14
|
+
headers['x-api-key'] = config.apiKey;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (config.projectKey) {
|
|
18
|
+
headers['x-project-key'] = config.projectKey;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return headers;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private checkAuth() {
|
|
25
|
+
const config = getConfig();
|
|
26
|
+
if (!config.apiKey) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'⚠️ No Messegy account connected! Please provide your Messegy API Key by calling the "messegy_connect_account" tool or by saying: "Connect my Messegy account with API Key <your_key>".'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async get<T = any>(endpoint: string, params?: Record<string, any>, skipAuthCheck = false): Promise<T> {
|
|
34
|
+
if (!skipAuthCheck) this.checkAuth();
|
|
35
|
+
const config = getConfig();
|
|
36
|
+
try {
|
|
37
|
+
const response = await axios.get<T>(`${config.baseUrl}${endpoint}`, {
|
|
38
|
+
params,
|
|
39
|
+
headers: this.getHeaders(),
|
|
40
|
+
timeout: 15000,
|
|
41
|
+
});
|
|
42
|
+
return response.data;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
this.handleError(err, `GET ${endpoint}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async post<T = any>(endpoint: string, data?: Record<string, any>, skipAuthCheck = false): Promise<T> {
|
|
49
|
+
if (!skipAuthCheck) this.checkAuth();
|
|
50
|
+
const config = getConfig();
|
|
51
|
+
try {
|
|
52
|
+
const response = await axios.post<T>(`${config.baseUrl}${endpoint}`, data, {
|
|
53
|
+
headers: this.getHeaders(),
|
|
54
|
+
timeout: 15000,
|
|
55
|
+
});
|
|
56
|
+
return response.data;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
this.handleError(err, `POST ${endpoint}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async put<T = any>(endpoint: string, data?: Record<string, any>): Promise<T> {
|
|
63
|
+
this.checkAuth();
|
|
64
|
+
const config = getConfig();
|
|
65
|
+
try {
|
|
66
|
+
const response = await axios.put<T>(`${config.baseUrl}${endpoint}`, data, {
|
|
67
|
+
headers: this.getHeaders(),
|
|
68
|
+
timeout: 15000,
|
|
69
|
+
});
|
|
70
|
+
return response.data;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
this.handleError(err, `PUT ${endpoint}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async delete<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {
|
|
77
|
+
this.checkAuth();
|
|
78
|
+
const config = getConfig();
|
|
79
|
+
try {
|
|
80
|
+
const response = await axios.delete<T>(`${config.baseUrl}${endpoint}`, {
|
|
81
|
+
params,
|
|
82
|
+
headers: this.getHeaders(),
|
|
83
|
+
timeout: 15000,
|
|
84
|
+
});
|
|
85
|
+
return response.data;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
this.handleError(err, `DELETE ${endpoint}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private handleError(error: unknown, action: string): never {
|
|
92
|
+
if (axios.isAxiosError(error)) {
|
|
93
|
+
const axiosErr = error as AxiosError<any>;
|
|
94
|
+
const status = axiosErr.response?.status;
|
|
95
|
+
const responseData = axiosErr.response?.data;
|
|
96
|
+
const message = responseData?.message || responseData?.error || axiosErr.message;
|
|
97
|
+
|
|
98
|
+
if (status === 401 || status === 403) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`🔒 Authentication Failed (Status ${status}): Invalid or expired Messegy API Key. Please reconnect using "messegy_connect_account" with a valid key.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Messegy API Error during [${action}]: (Status ${status || 'Unknown'}) ${message}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Messegy Network Error during [${action}]: ${(error as Error).message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const api = new MessegyApiClient();
|
package/src/cli-setup.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
|
|
6
|
+
function getClaudeConfigPath(): string {
|
|
7
|
+
const home = os.homedir();
|
|
8
|
+
if (process.platform === 'darwin') {
|
|
9
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
10
|
+
} else if (process.platform === 'win32') {
|
|
11
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
12
|
+
return path.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
13
|
+
} else {
|
|
14
|
+
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function runInteractiveCliSetup() {
|
|
19
|
+
const rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const question = (query: string): Promise<string> =>
|
|
25
|
+
new Promise((resolve) => rl.question(query, resolve));
|
|
26
|
+
|
|
27
|
+
console.log('\n======================================================');
|
|
28
|
+
console.log(' 🚀 Messegy WhatsApp MCP 1-Click Auto-Setup');
|
|
29
|
+
console.log('======================================================\n');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const apiKey = (
|
|
33
|
+
await question('🔑 Enter your Messegy API Key (from Messegy Portal -> Settings): ')
|
|
34
|
+
).trim();
|
|
35
|
+
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
console.log('\n❌ Error: API Key is required. Setup cancelled.\n');
|
|
38
|
+
rl.close();
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const projectKey = (
|
|
43
|
+
await question('📁 Enter Project Key (Optional, press Enter to skip): ')
|
|
44
|
+
).trim();
|
|
45
|
+
|
|
46
|
+
const configPath = getClaudeConfigPath();
|
|
47
|
+
const configDir = path.dirname(configPath);
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(configDir)) {
|
|
50
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let existingConfig: any = {};
|
|
54
|
+
if (fs.existsSync(configPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
57
|
+
existingConfig = JSON.parse(raw);
|
|
58
|
+
} catch {
|
|
59
|
+
existingConfig = {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!existingConfig.mcpServers) {
|
|
64
|
+
existingConfig.mcpServers = {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
existingConfig.mcpServers.messegy = {
|
|
68
|
+
command: 'npx',
|
|
69
|
+
args: ['-y', '@messegy/mcp'],
|
|
70
|
+
env: {
|
|
71
|
+
MESSEGY_API_KEY: apiKey,
|
|
72
|
+
...(projectKey ? { MESSEGY_PROJECT_KEY: projectKey } : {}),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
|
|
77
|
+
|
|
78
|
+
console.log('\n✅ SUCCESS! Messegy MCP Server has been configured automatically.');
|
|
79
|
+
console.log(`📂 Updated Config: ${configPath}`);
|
|
80
|
+
console.log('\n👉 Next Step: Open or Restart Claude Desktop, and start chatting with your WhatsApp business!\n');
|
|
81
|
+
} catch (err: any) {
|
|
82
|
+
console.error('\n❌ Setup failed:', err.message);
|
|
83
|
+
} finally {
|
|
84
|
+
rl.close();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
export interface MessegyConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
projectKey?: string;
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CONFIG_DIR = path.join(os.homedir(), '.messegy');
|
|
15
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
16
|
+
|
|
17
|
+
// In-memory configuration cache
|
|
18
|
+
let activeConfig: MessegyConfig | null = null;
|
|
19
|
+
|
|
20
|
+
function ensureConfigDir() {
|
|
21
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
22
|
+
try {
|
|
23
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadSavedFileConfig(): Partial<MessegyConfig> | null {
|
|
29
|
+
try {
|
|
30
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
31
|
+
const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
32
|
+
return JSON.parse(data);
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error('Error reading saved Messegy config:', err);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getConfig(): MessegyConfig {
|
|
41
|
+
if (activeConfig) {
|
|
42
|
+
return activeConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fileConfig = loadSavedFileConfig();
|
|
46
|
+
|
|
47
|
+
const apiKey =
|
|
48
|
+
process.env.MESSEGY_API_KEY ||
|
|
49
|
+
fileConfig?.apiKey ||
|
|
50
|
+
'';
|
|
51
|
+
|
|
52
|
+
const projectKey =
|
|
53
|
+
process.env.MESSEGY_PROJECT_KEY ||
|
|
54
|
+
fileConfig?.projectKey ||
|
|
55
|
+
undefined;
|
|
56
|
+
|
|
57
|
+
const baseUrl = (
|
|
58
|
+
process.env.MESSEGY_BASE_URL ||
|
|
59
|
+
fileConfig?.baseUrl ||
|
|
60
|
+
'https://backend.messegy.com/api'
|
|
61
|
+
).replace(/\/$/, '');
|
|
62
|
+
|
|
63
|
+
activeConfig = {
|
|
64
|
+
apiKey,
|
|
65
|
+
projectKey,
|
|
66
|
+
baseUrl,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return activeConfig;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Save new credentials interactively and persist to ~/.messegy/config.json
|
|
74
|
+
*/
|
|
75
|
+
export function saveConfig(newConfig: { apiKey: string; projectKey?: string; baseUrl?: string }): MessegyConfig {
|
|
76
|
+
ensureConfigDir();
|
|
77
|
+
|
|
78
|
+
const updated: MessegyConfig = {
|
|
79
|
+
apiKey: newConfig.apiKey.trim(),
|
|
80
|
+
projectKey: newConfig.projectKey?.trim() || undefined,
|
|
81
|
+
baseUrl: (newConfig.baseUrl || 'https://backend.messegy.com/api').replace(/\/$/, ''),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), 'utf-8');
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error('Failed to write ~/.messegy/config.json:', err);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
activeConfig = updated;
|
|
91
|
+
return updated;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Disconnect & remove saved credentials
|
|
96
|
+
*/
|
|
97
|
+
export function clearConfig(): void {
|
|
98
|
+
try {
|
|
99
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
100
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('Failed to delete ~/.messegy/config.json:', err);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
activeConfig = {
|
|
107
|
+
apiKey: '',
|
|
108
|
+
projectKey: undefined,
|
|
109
|
+
baseUrl: 'https://backend.messegy.com/api',
|
|
110
|
+
};
|
|
111
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
|
|
6
|
+
import { runInteractiveCliSetup } from './cli-setup.js';
|
|
7
|
+
import { startHttpMcpServer } from './server.js';
|
|
8
|
+
import { registerAuthTools } from './tools/auth.js';
|
|
9
|
+
import { registerMessageTools } from './tools/messages.js';
|
|
10
|
+
import { registerTemplateTools } from './tools/templates.js';
|
|
11
|
+
import { registerChatTools } from './tools/chats.js';
|
|
12
|
+
import { registerContactTools } from './tools/contacts.js';
|
|
13
|
+
import { registerBroadcastTools } from './tools/broadcasts.js';
|
|
14
|
+
import { registerAnalyticsTools } from './tools/analytics.js';
|
|
15
|
+
import { registerAutoReplyTools } from './tools/auto-replies.js';
|
|
16
|
+
import { registerFlowTools } from './tools/flows.js';
|
|
17
|
+
import { registerShopifyTools } from './tools/shopify.js';
|
|
18
|
+
import { registerResources } from './resources/index.js';
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
if (args.includes('init') || args.includes('--init') || args.includes('setup') || args.includes('--setup')) {
|
|
23
|
+
await runInteractiveCliSetup();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (args.includes('server') || args.includes('--server') || args.includes('--http') || process.env.MESSEGY_MCP_MODE === 'http') {
|
|
28
|
+
const port = process.env.PORT || 3001;
|
|
29
|
+
startHttpMcpServer(port);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Initialize official Messegy MCP Server
|
|
34
|
+
const server = new McpServer({
|
|
35
|
+
name: 'messegy-whatsapp',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Register interactive Auth & Account Switching tools (1st priority)
|
|
40
|
+
registerAuthTools(server);
|
|
41
|
+
|
|
42
|
+
// Register all tool suites
|
|
43
|
+
registerMessageTools(server);
|
|
44
|
+
registerTemplateTools(server);
|
|
45
|
+
registerChatTools(server);
|
|
46
|
+
registerContactTools(server);
|
|
47
|
+
registerBroadcastTools(server);
|
|
48
|
+
registerAutoReplyTools(server);
|
|
49
|
+
registerFlowTools(server);
|
|
50
|
+
registerAnalyticsTools(server);
|
|
51
|
+
registerShopifyTools(server);
|
|
52
|
+
|
|
53
|
+
// Register dynamic LLM context resources
|
|
54
|
+
registerResources(server);
|
|
55
|
+
|
|
56
|
+
// Connect via standard input/output transport
|
|
57
|
+
const transport = new StdioServerTransport();
|
|
58
|
+
await server.connect(transport);
|
|
59
|
+
|
|
60
|
+
console.error('🚀 Messegy WhatsApp MCP Server is running on stdio');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch((error) => {
|
|
64
|
+
console.error('Fatal error in Messegy MCP server:', error);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { api } from '../api-client.js';
|
|
2
|
+
|
|
3
|
+
export interface PlanUsageInfo {
|
|
4
|
+
planName: string;
|
|
5
|
+
status: string;
|
|
6
|
+
messagesRemaining: number;
|
|
7
|
+
dailyTierLimit: number;
|
|
8
|
+
maxAutoReplies: number;
|
|
9
|
+
currentAutoReplies: number;
|
|
10
|
+
maxFlows: number;
|
|
11
|
+
currentFlows: number;
|
|
12
|
+
isTrial: boolean;
|
|
13
|
+
trialDaysLeft?: number;
|
|
14
|
+
expiresAt?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let cachedUsage: { data: PlanUsageInfo; timestamp: number } | null = null;
|
|
18
|
+
const CACHE_TTL_MS = 30000; // 30 seconds cache to keep tools fast
|
|
19
|
+
|
|
20
|
+
export async function fetchCurrentPlanUsage(forceRefresh = false): Promise<PlanUsageInfo> {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (!forceRefresh && cachedUsage && now - cachedUsage.timestamp < CACHE_TTL_MS) {
|
|
23
|
+
return cachedUsage.data;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const [planRes, dashRes] = await Promise.all([
|
|
28
|
+
api.get('/billing/my-plan').catch(() => null),
|
|
29
|
+
api.get('/dashboard').catch(() => null),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const plan = planRes?.data?.plan || planRes?.plan || planRes?.data || {};
|
|
33
|
+
const dash = dashRes?.data || dashRes || {};
|
|
34
|
+
const meta = dash?.meta || {};
|
|
35
|
+
const stats = dash?.stats || {};
|
|
36
|
+
|
|
37
|
+
const planName = plan?.name || meta?.plan_name || 'Starter Plan';
|
|
38
|
+
const isTrial = Boolean(dash?.trial_days_left && dash.trial_days_left > 0);
|
|
39
|
+
|
|
40
|
+
const usage: PlanUsageInfo = {
|
|
41
|
+
planName,
|
|
42
|
+
status: plan?.status || 'active',
|
|
43
|
+
messagesRemaining: typeof stats?.messages_remaining === 'number' ? stats.messages_remaining : (plan?.message_limit ?? 1000),
|
|
44
|
+
dailyTierLimit: parseTierLimit(meta?.throughput || meta?.tier),
|
|
45
|
+
maxAutoReplies: plan?.max_auto_replies ?? (planName.toLowerCase().includes('enterprise') ? 9999 : planName.toLowerCase().includes('pro') ? 50 : 5),
|
|
46
|
+
currentAutoReplies: typeof stats?.active_auto_replies === 'number' ? stats.active_auto_replies : 0,
|
|
47
|
+
maxFlows: plan?.max_flows ?? (planName.toLowerCase().includes('enterprise') ? 9999 : planName.toLowerCase().includes('pro') ? 20 : 2),
|
|
48
|
+
currentFlows: typeof stats?.active_flows === 'number' ? stats.active_flows : 0,
|
|
49
|
+
isTrial,
|
|
50
|
+
trialDaysLeft: dash?.trial_days_left,
|
|
51
|
+
expiresAt: plan?.expires_at || plan?.renewal_date,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
cachedUsage = { data: usage, timestamp: now };
|
|
55
|
+
return usage;
|
|
56
|
+
} catch (err: any) {
|
|
57
|
+
// Fallback safe limits if billing API is unreachable
|
|
58
|
+
return {
|
|
59
|
+
planName: 'Standard Plan',
|
|
60
|
+
status: 'active',
|
|
61
|
+
messagesRemaining: 500,
|
|
62
|
+
dailyTierLimit: 1000,
|
|
63
|
+
maxAutoReplies: 10,
|
|
64
|
+
currentAutoReplies: 0,
|
|
65
|
+
maxFlows: 5,
|
|
66
|
+
currentFlows: 0,
|
|
67
|
+
isTrial: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseTierLimit(throughputOrTier: any): number {
|
|
73
|
+
if (typeof throughputOrTier === 'number') return throughputOrTier;
|
|
74
|
+
const str = String(throughputOrTier || '').toLowerCase();
|
|
75
|
+
if (str.includes('100k') || str.includes('tier 3')) return 100000;
|
|
76
|
+
if (str.includes('10k') || str.includes('tier 2')) return 10000;
|
|
77
|
+
if (str.includes('1k') || str.includes('tier 1')) return 1000;
|
|
78
|
+
return 1000; // default Meta tier 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validates requested action against plan quotas before executing.
|
|
83
|
+
* Throws friendly user error if limit exceeded.
|
|
84
|
+
*/
|
|
85
|
+
export async function enforcePlanQuota(
|
|
86
|
+
action: 'send_message' | 'send_broadcast' | 'create_auto_reply' | 'create_flow',
|
|
87
|
+
quantity = 1
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
const usage = await fetchCurrentPlanUsage();
|
|
90
|
+
|
|
91
|
+
// Check if account status is active
|
|
92
|
+
if (usage.status.toLowerCase() === 'expired' || usage.status.toLowerCase() === 'suspended') {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`🚫 Action Blocked: Your Messegy subscription [${usage.planName}] is currently ${usage.status.toUpperCase()}. Please renew your plan on the Messegy Billing portal.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 1. Message sending check
|
|
99
|
+
if (action === 'send_message') {
|
|
100
|
+
if (usage.messagesRemaining <= 0) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`🚫 Message Limit Reached: You have 0 messages remaining on your [${usage.planName}]. Please top up message credits on Messegy Dashboard.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. Broadcast bulk recipient check
|
|
108
|
+
if (action === 'send_broadcast') {
|
|
109
|
+
if (quantity > usage.messagesRemaining) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`🚫 Broadcast Quota Exceeded: You are trying to send ${quantity} messages, but your current plan [${usage.planName}] only has ${usage.messagesRemaining} credits remaining. Please reduce recipient count or purchase additional credits.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (quantity > usage.dailyTierLimit) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`⚠️ Meta Daily Tier Limit Warning: Your current WhatsApp number tier allows max ${usage.dailyTierLimit.toLocaleString()} messages/day. Batch size ${quantity} exceeds this limit.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 3. Auto-reply rule limit check
|
|
122
|
+
if (action === 'create_auto_reply') {
|
|
123
|
+
if (usage.currentAutoReplies >= usage.maxAutoReplies) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`🚫 Auto-Reply Rule Limit Reached: Your [${usage.planName}] allows a maximum of ${usage.maxAutoReplies} automated rules (${usage.currentAutoReplies}/${usage.maxAutoReplies} used). Please upgrade your plan or delete unused rules.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 4. WhatsApp Flow creation limit check
|
|
131
|
+
if (action === 'create_flow') {
|
|
132
|
+
if (usage.currentFlows >= usage.maxFlows) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`🚫 WhatsApp Flow Limit Reached: Your [${usage.planName}] allows a maximum of ${usage.maxFlows} active flows (${usage.currentFlows}/${usage.maxFlows} used). Please upgrade your plan on Messegy Dashboard to create more flows.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { api } from '../api-client.js';
|
|
3
|
+
|
|
4
|
+
export function registerResources(server: McpServer) {
|
|
5
|
+
/**
|
|
6
|
+
* Resource 1: Approved WhatsApp Templates Context
|
|
7
|
+
*/
|
|
8
|
+
server.resource(
|
|
9
|
+
'messegy-templates',
|
|
10
|
+
'messegy://templates',
|
|
11
|
+
async (uri) => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await api.get('/v1/templates', { status: 'APPROVED' });
|
|
14
|
+
const templates = res.data || res.templates || res;
|
|
15
|
+
return {
|
|
16
|
+
contents: [
|
|
17
|
+
{
|
|
18
|
+
uri: uri.href,
|
|
19
|
+
mimeType: 'application/json',
|
|
20
|
+
text: JSON.stringify(templates, null, 2),
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
} catch (err: any) {
|
|
25
|
+
return {
|
|
26
|
+
contents: [
|
|
27
|
+
{
|
|
28
|
+
uri: uri.href,
|
|
29
|
+
mimeType: 'text/plain',
|
|
30
|
+
text: `Failed to fetch active templates: ${err.message}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resource 2: WhatsApp Account & Phone Health
|
|
40
|
+
*/
|
|
41
|
+
server.resource(
|
|
42
|
+
'messegy-account',
|
|
43
|
+
'messegy://account-status',
|
|
44
|
+
async (uri) => {
|
|
45
|
+
try {
|
|
46
|
+
const res = await api.get('/dashboard');
|
|
47
|
+
const data = res.data || res;
|
|
48
|
+
return {
|
|
49
|
+
contents: [
|
|
50
|
+
{
|
|
51
|
+
uri: uri.href,
|
|
52
|
+
mimeType: 'application/json',
|
|
53
|
+
text: JSON.stringify(data, null, 2),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
} catch (err: any) {
|
|
58
|
+
return {
|
|
59
|
+
contents: [
|
|
60
|
+
{
|
|
61
|
+
uri: uri.href,
|
|
62
|
+
mimeType: 'text/plain',
|
|
63
|
+
text: `Failed to fetch account status: ${err.message}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|