neuron-mcp-server 1.0.0 → 1.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/.env +13 -0
- package/AI_PERSONALITY_DESIGN_RESEARCH.md +1020 -0
- package/Dockerfile +18 -0
- package/README.md +146 -131
- package/dist/client.d.ts +6 -0
- package/dist/client.js +50 -12
- package/dist/client.js.map +1 -1
- package/dist/index.js +51 -7
- package/dist/index.js.map +1 -1
- package/dist/tools/ai.js +9 -3
- package/dist/tools/ai.js.map +1 -1
- package/dist/tools/approvals.d.ts +2 -0
- package/dist/tools/approvals.js +181 -0
- package/dist/tools/approvals.js.map +1 -0
- package/dist/tools/audit.js +7 -4
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/auth.js +119 -19
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/billing.js +22 -6
- package/dist/tools/billing.js.map +1 -1
- package/dist/tools/blog.js +98 -34
- package/dist/tools/blog.js.map +1 -1
- package/dist/tools/bot-api-keys.js +27 -12
- package/dist/tools/bot-api-keys.js.map +1 -1
- package/dist/tools/bot-api.js +53 -27
- package/dist/tools/bot-api.js.map +1 -1
- package/dist/tools/bots.js +116 -68
- package/dist/tools/bots.js.map +1 -1
- package/dist/tools/broadcasts.js +78 -38
- package/dist/tools/broadcasts.js.map +1 -1
- package/dist/tools/builtin-tools.js +19 -12
- package/dist/tools/builtin-tools.js.map +1 -1
- package/dist/tools/campaigns.js +149 -61
- package/dist/tools/campaigns.js.map +1 -1
- package/dist/tools/channels.js +163 -53
- package/dist/tools/channels.js.map +1 -1
- package/dist/tools/contact-lists.js +111 -51
- package/dist/tools/contact-lists.js.map +1 -1
- package/dist/tools/contacts.js +117 -44
- package/dist/tools/contacts.js.map +1 -1
- package/dist/tools/conversations.js +95 -50
- package/dist/tools/conversations.js.map +1 -1
- package/dist/tools/flows.d.ts +2 -0
- package/dist/tools/flows.js +242 -0
- package/dist/tools/flows.js.map +1 -0
- package/dist/tools/group-management.d.ts +2 -0
- package/dist/tools/group-management.js +239 -0
- package/dist/tools/group-management.js.map +1 -0
- package/dist/tools/leads.d.ts +2 -0
- package/dist/tools/leads.js +100 -0
- package/dist/tools/leads.js.map +1 -0
- package/dist/tools/list-campaigns.d.ts +2 -0
- package/dist/tools/list-campaigns.js +152 -0
- package/dist/tools/list-campaigns.js.map +1 -0
- package/dist/tools/list-pool.js +250 -33
- package/dist/tools/list-pool.js.map +1 -1
- package/dist/tools/media.js +12 -7
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/newsletters.js +106 -26
- package/dist/tools/newsletters.js.map +1 -1
- package/dist/tools/organizations.js +76 -35
- package/dist/tools/organizations.js.map +1 -1
- package/dist/tools/outbound-webhooks.js +32 -20
- package/dist/tools/outbound-webhooks.js.map +1 -1
- package/dist/tools/payouts.js +44 -17
- package/dist/tools/payouts.js.map +1 -1
- package/dist/tools/personas.d.ts +2 -0
- package/dist/tools/personas.js +141 -0
- package/dist/tools/personas.js.map +1 -0
- package/dist/tools/polls.d.ts +2 -0
- package/dist/tools/polls.js +30 -0
- package/dist/tools/polls.js.map +1 -0
- package/dist/tools/pool.js +101 -43
- package/dist/tools/pool.js.map +1 -1
- package/dist/tools/products.d.ts +2 -0
- package/dist/tools/products.js +277 -0
- package/dist/tools/products.js.map +1 -0
- package/dist/tools/profile-privacy.d.ts +2 -0
- package/dist/tools/profile-privacy.js +195 -0
- package/dist/tools/profile-privacy.js.map +1 -0
- package/dist/tools/reflections.js +25 -13
- package/dist/tools/reflections.js.map +1 -1
- package/dist/tools/scheduled-messages.d.ts +2 -0
- package/dist/tools/scheduled-messages.js +124 -0
- package/dist/tools/scheduled-messages.js.map +1 -0
- package/dist/tools/social-channels.d.ts +2 -0
- package/dist/tools/social-channels.js +318 -0
- package/dist/tools/social-channels.js.map +1 -0
- package/dist/tools/tasks.d.ts +2 -0
- package/dist/tools/tasks.js +155 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/tools.js +86 -40
- package/dist/tools/tools.js.map +1 -1
- package/dist/tools/wallets.js +34 -12
- package/dist/tools/wallets.js.map +1 -1
- package/dist/tools/webhooks.js +35 -20
- package/dist/tools/webhooks.js.map +1 -1
- package/dist/tools/whatsapp-actions.d.ts +2 -0
- package/dist/tools/whatsapp-actions.js +186 -0
- package/dist/tools/whatsapp-actions.js.map +1 -0
- package/package.json +4 -31
- package/scripts/setup-delivahere-bot.ts +804 -0
- package/scripts/test-delivahere-bot.ts +128 -0
- package/scripts/update-letschop-prompt.ts +194 -0
- package/server.json +26 -0
- package/smithery.yaml +19 -0
- package/src/client.ts +169 -0
- package/src/index.ts +256 -0
- package/src/tools/ai.ts +35 -0
- package/src/tools/approvals.ts +231 -0
- package/src/tools/audit.ts +49 -0
- package/src/tools/auth.ts +536 -0
- package/src/tools/billing.ts +79 -0
- package/src/tools/blog.ts +264 -0
- package/src/tools/bot-api-keys.ts +87 -0
- package/src/tools/bot-api.ts +193 -0
- package/src/tools/bots.ts +320 -0
- package/src/tools/broadcasts.ts +224 -0
- package/src/tools/builtin-tools.ts +108 -0
- package/src/tools/campaigns.ts +429 -0
- package/src/tools/channels.ts +520 -0
- package/src/tools/contact-lists.ts +320 -0
- package/src/tools/contacts.ts +395 -0
- package/src/tools/conversations.ts +349 -0
- package/src/tools/flows.ts +298 -0
- package/src/tools/group-management.ts +265 -0
- package/src/tools/knowledge-bases.ts +451 -0
- package/src/tools/leads.ts +104 -0
- package/src/tools/list-campaigns.ts +190 -0
- package/src/tools/list-pool.ts +389 -0
- package/src/tools/media.ts +50 -0
- package/src/tools/newsletters.ts +271 -0
- package/src/tools/organizations.ts +287 -0
- package/src/tools/outbound-webhooks.ts +181 -0
- package/src/tools/payouts.ts +133 -0
- package/src/tools/personas.ts +145 -0
- package/src/tools/polls.ts +32 -0
- package/src/tools/pool.ts +293 -0
- package/src/tools/products.ts +280 -0
- package/src/tools/profile-privacy.ts +215 -0
- package/src/tools/reflections.ts +124 -0
- package/src/tools/scheduled-messages.ts +144 -0
- package/src/tools/social-channels.ts +396 -0
- package/src/tools/tasks.ts +198 -0
- package/src/tools/tools.ts +255 -0
- package/src/tools/wallets.ts +107 -0
- package/src/tools/webhooks.ts +166 -0
- package/src/tools/whatsapp-actions.ts +203 -0
- package/tsconfig.json +20 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Demo script: Tests the DelivaHere bot via the Bot API.
|
|
4
|
+
*
|
|
5
|
+
* This script demonstrates programmatic interaction with the bot
|
|
6
|
+
* using the API key generated by setup-delivahere-bot.ts.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* NEURON_API_KEY=nrn_... tsx scripts/test-delivahere-bot.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import axios from "axios";
|
|
13
|
+
|
|
14
|
+
const API = process.env.NEURON_API_URL || "http://localhost:4000/api/v1";
|
|
15
|
+
const API_KEY = process.env.NEURON_API_KEY;
|
|
16
|
+
|
|
17
|
+
if (!API_KEY) {
|
|
18
|
+
console.error("Error: NEURON_API_KEY environment variable is required.");
|
|
19
|
+
console.error("Run setup-delivahere-bot.ts first to get an API key.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function botApi(method: string, path: string, data?: unknown, params?: Record<string, string>) {
|
|
24
|
+
const res = await axios({
|
|
25
|
+
method,
|
|
26
|
+
url: `${API}/bot-api/${path}`,
|
|
27
|
+
data,
|
|
28
|
+
params,
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
return res.data;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function separator(title: string) {
|
|
38
|
+
console.log(`\n${"=".repeat(60)}`);
|
|
39
|
+
console.log(` ${title}`);
|
|
40
|
+
console.log(`${"=".repeat(60)}\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const phone = "+2348012345678";
|
|
45
|
+
|
|
46
|
+
separator("Test 1: Get Bot Details");
|
|
47
|
+
const bot = await botApi("GET", "bot");
|
|
48
|
+
console.log(`Bot: ${bot.data.name}`);
|
|
49
|
+
console.log(`Model: ${bot.data.llmModel}`);
|
|
50
|
+
console.log(`Active: ${bot.data.isActive}`);
|
|
51
|
+
|
|
52
|
+
separator("Test 2: Initial Greeting");
|
|
53
|
+
const greeting = await botApi("POST", "chat", {
|
|
54
|
+
message: "Hi, I need to send a package",
|
|
55
|
+
contactPhone: phone,
|
|
56
|
+
contactName: "Demo Customer",
|
|
57
|
+
});
|
|
58
|
+
console.log(`Response (${greeting.data.latencyMs}ms, ${greeting.data.tokensUsed} tokens):`);
|
|
59
|
+
console.log(greeting.data.response);
|
|
60
|
+
const conversationId = greeting.data.conversationId;
|
|
61
|
+
|
|
62
|
+
separator("Test 3: Provide Delivery Details");
|
|
63
|
+
const details = await botApi("POST", "chat", {
|
|
64
|
+
message:
|
|
65
|
+
"I want to send a laptop from 15 Allen Avenue, Ikeja, Lagos to 23 Wuse Zone 5, Abuja. " +
|
|
66
|
+
"It weighs about 3kg, small size. I'd like it picked up tomorrow morning and delivered within 2 days. " +
|
|
67
|
+
"Please handle with care, it's fragile.",
|
|
68
|
+
contactPhone: phone,
|
|
69
|
+
});
|
|
70
|
+
console.log(`Response (${details.data.latencyMs}ms, ${details.data.tokensUsed} tokens):`);
|
|
71
|
+
console.log(details.data.response);
|
|
72
|
+
|
|
73
|
+
separator("Test 4: Ask About Pricing");
|
|
74
|
+
const pricing = await botApi("POST", "chat", {
|
|
75
|
+
message: "How much will this delivery cost approximately?",
|
|
76
|
+
contactPhone: phone,
|
|
77
|
+
});
|
|
78
|
+
console.log(`Response (${pricing.data.latencyMs}ms, ${pricing.data.tokensUsed} tokens):`);
|
|
79
|
+
console.log(pricing.data.response);
|
|
80
|
+
|
|
81
|
+
separator("Test 5: Ask About Tracking");
|
|
82
|
+
const tracking = await botApi("POST", "chat", {
|
|
83
|
+
message: "How can I track my package once it's picked up?",
|
|
84
|
+
contactPhone: phone,
|
|
85
|
+
});
|
|
86
|
+
console.log(`Response (${tracking.data.latencyMs}ms, ${tracking.data.tokensUsed} tokens):`);
|
|
87
|
+
console.log(tracking.data.response);
|
|
88
|
+
|
|
89
|
+
separator("Test 6: List Conversations");
|
|
90
|
+
const convos = await botApi("GET", "conversations");
|
|
91
|
+
console.log(`Total conversations: ${convos.data.total}`);
|
|
92
|
+
console.log(
|
|
93
|
+
`Conversations: ${convos.data.data.map((c: { contactName: string; status: string }) => `${c.contactName} (${c.status})`).join(", ")}`,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
separator("Test 7: Get Conversation Messages");
|
|
97
|
+
const messages = await botApi("GET", `conversations/${conversationId}/messages`);
|
|
98
|
+
console.log(`Messages in conversation: ${messages.data.length}`);
|
|
99
|
+
for (const msg of messages.data.slice(-4)) {
|
|
100
|
+
const role = msg.role.toUpperCase().padEnd(10);
|
|
101
|
+
const content = (msg.content || "").slice(0, 100);
|
|
102
|
+
console.log(` [${role}] ${content}${msg.content?.length > 100 ? "..." : ""}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
separator("Test 8: Queue a WhatsApp Message");
|
|
106
|
+
try {
|
|
107
|
+
const send = await botApi("POST", "send", {
|
|
108
|
+
to: phone,
|
|
109
|
+
message: "Your delivery request has been received! We'll notify you when bids come in.",
|
|
110
|
+
});
|
|
111
|
+
console.log(`Message queued: ${JSON.stringify(send.data)}`);
|
|
112
|
+
} catch (err: unknown) {
|
|
113
|
+
const axErr = err as { response?: { data?: unknown } };
|
|
114
|
+
console.log(
|
|
115
|
+
`Expected: Bot may not have WhatsApp channel assigned.`,
|
|
116
|
+
);
|
|
117
|
+
console.log(`Response: ${JSON.stringify(axErr.response?.data)}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
separator("All Tests Complete");
|
|
121
|
+
console.log("The DelivaHere bot is working correctly via the Bot API!");
|
|
122
|
+
console.log(`Conversation ID: ${conversationId}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main().catch((err) => {
|
|
126
|
+
console.error("Test failed:", err.response?.data || err.message);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Updates the LetsChop bot's system prompt with:
|
|
4
|
+
* 1. Strict group chat engagement rules (respond only when necessary)
|
|
5
|
+
* 2. Spam detection and deletion policy
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* NEURON_EMAIL=admin@example.com NEURON_PASSWORD=secret tsx scripts/update-letschop-prompt.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import axios from "axios";
|
|
12
|
+
|
|
13
|
+
const API = process.env.NEURON_API_URL || "http://localhost:4000/api/v1";
|
|
14
|
+
const EMAIL = process.env.NEURON_EMAIL || "a.rasheedalabi@gmail.com";
|
|
15
|
+
const PASSWORD = process.env.NEURON_PASSWORD || "12345678";
|
|
16
|
+
|
|
17
|
+
let token = "";
|
|
18
|
+
|
|
19
|
+
async function api(method: string, path: string, data?: unknown) {
|
|
20
|
+
const res = await axios({
|
|
21
|
+
method,
|
|
22
|
+
url: `${API}/${path}`,
|
|
23
|
+
data,
|
|
24
|
+
headers: {
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
return res.data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
console.log("=== LetsChop Bot Prompt Update ===\n");
|
|
34
|
+
|
|
35
|
+
// 1. Login
|
|
36
|
+
console.log("1. Logging in...");
|
|
37
|
+
const auth = await api("POST", "auth/login", {
|
|
38
|
+
email: EMAIL,
|
|
39
|
+
password: PASSWORD,
|
|
40
|
+
});
|
|
41
|
+
token = auth.data.accessToken;
|
|
42
|
+
console.log(` Logged in as ${EMAIL}\n`);
|
|
43
|
+
|
|
44
|
+
// 2. Find the LetsChop bot
|
|
45
|
+
console.log("2. Finding LetsChop bot...");
|
|
46
|
+
const botsRes = await api("GET", "bots");
|
|
47
|
+
const bots = botsRes.data?.data ?? botsRes.data ?? [];
|
|
48
|
+
const letsChopBot = bots.find(
|
|
49
|
+
(b: any) => b.name?.toLowerCase().includes("letschop") || b.name?.toLowerCase().includes("lets chop"),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!letsChopBot) {
|
|
53
|
+
console.error(" LetsChop bot not found! Available bots:");
|
|
54
|
+
for (const b of bots) {
|
|
55
|
+
console.error(` - ${b.name} (${b.id})`);
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
console.log(` Found: ${letsChopBot.name} (${letsChopBot.id})\n`);
|
|
60
|
+
|
|
61
|
+
// 3. Update system prompt + metadata with newsletter JID
|
|
62
|
+
console.log("3. Updating system prompt and metadata...");
|
|
63
|
+
await api("PUT", `bots/${letsChopBot.id}`, {
|
|
64
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
65
|
+
assignment: "LetsChop community assistant and group moderator for WhatsApp food ordering groups.",
|
|
66
|
+
responsibilities: [
|
|
67
|
+
"Help users discover meals, place orders, and navigate the LetsChop platform",
|
|
68
|
+
"Detect and delete spam messages (external group links, scams, unsolicited DM requests)",
|
|
69
|
+
"Occasionally enrich group conversations with relevant food tips, meal suggestions, or fun prompts",
|
|
70
|
+
"Answer direct questions about LetsChop services, menus, or ordering process",
|
|
71
|
+
"Post interesting food content, meal highlights, and community updates to the WhatsApp Channel",
|
|
72
|
+
],
|
|
73
|
+
metadata: {
|
|
74
|
+
...(letsChopBot.metadata ?? {}),
|
|
75
|
+
newsletterJid: "120363425082349937@newsletter",
|
|
76
|
+
groupFilter: {
|
|
77
|
+
botNames: ["lisa", "letschop", "lets chop"],
|
|
78
|
+
triggerKeywords: [
|
|
79
|
+
"menu", "order", "deliver", "delivery", "price",
|
|
80
|
+
"how much", "available", "today's meal", "what's cooking",
|
|
81
|
+
"help", "support", "complain", "app", "issues", "download", "question",
|
|
82
|
+
],
|
|
83
|
+
detectSpam: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
console.log(" System prompt and metadata updated.\n");
|
|
88
|
+
|
|
89
|
+
// 4. Enable built-in tools
|
|
90
|
+
console.log("4. Enabling built-in tools...");
|
|
91
|
+
try {
|
|
92
|
+
await api("PUT", `builtin-tools/bot/${letsChopBot.id}/bulk`, {
|
|
93
|
+
settings: [
|
|
94
|
+
{ toolId: "delete_message", enabled: true },
|
|
95
|
+
{ toolId: "post_to_channel", enabled: true },
|
|
96
|
+
{ toolId: "post_status", enabled: true },
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
console.log(" delete_message, post_to_channel, and post_status tools enabled.\n");
|
|
100
|
+
} catch (err: any) {
|
|
101
|
+
console.warn(` Warning: Could not enable tools: ${err.message}\n`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log("=== Update Complete ===");
|
|
105
|
+
console.log(`Bot: ${letsChopBot.name} (${letsChopBot.id})`);
|
|
106
|
+
console.log("Changes:");
|
|
107
|
+
console.log(" - System prompt updated with group engagement rules + spam policy");
|
|
108
|
+
console.log(" - delete_message built-in tool enabled");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// System Prompt
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
const SYSTEM_PROMPT = `You are LetsChop's assistant in a WhatsApp food ordering community. You help users discover meals, place orders, and keep the group running smoothly.
|
|
116
|
+
|
|
117
|
+
## Group Chat Engagement Rules
|
|
118
|
+
|
|
119
|
+
You are in a group chat. Most messages are NOT for you. Follow these rules strictly:
|
|
120
|
+
|
|
121
|
+
### RESPOND to these (and ONLY these):
|
|
122
|
+
- Someone directly mentions you by name, tags you, or asks you a question
|
|
123
|
+
- Someone asks a question about LetsChop — the menu, how to order, delivery, pricing, or anything related to the platform
|
|
124
|
+
- Someone needs help with an order (tracking, issues, complaints)
|
|
125
|
+
- A message is spam (see Spam Policy below) — delete it and warn the sender
|
|
126
|
+
|
|
127
|
+
### STAY SILENT for these (do NOT respond):
|
|
128
|
+
- Members chatting with each other about anything — food, life, banter, jokes
|
|
129
|
+
- Someone sharing their opinion, reaction, or feedback to another member
|
|
130
|
+
- General greetings like "good morning" to the group (unless someone greets you specifically)
|
|
131
|
+
- Messages that are clearly part of an ongoing conversation between other people
|
|
132
|
+
- Photos of food, memes, stickers, voice notes that aren't directed at you
|
|
133
|
+
- Arguments or debates between members (stay out of it)
|
|
134
|
+
|
|
135
|
+
### OCCASIONALLY (once or twice a day at most, not every conversation):
|
|
136
|
+
- Drop a short, natural nudge when the group has been quiet for a while: a meal suggestion, a fun food question, or a gentle reminder about today's menu — keep it brief, casual, and non-pushy
|
|
137
|
+
- Add a genuinely useful comment to an ongoing food conversation ONLY if you have something specific and valuable to contribute (e.g. "that dish is actually on today's menu if anyone wants to try it")
|
|
138
|
+
- React to interesting food posts with an emoji instead of a full message
|
|
139
|
+
|
|
140
|
+
### Hard Rules:
|
|
141
|
+
- When in doubt, DO NOT respond. Silence is always better than an unnecessary message.
|
|
142
|
+
- Never respond to two consecutive messages from different people in a row — if you just responded to something, stay quiet for the next few messages unless directly addressed.
|
|
143
|
+
- Never repeat information you already shared in the same conversation.
|
|
144
|
+
- Keep responses short. One to three sentences max unless someone asked a detailed question.
|
|
145
|
+
- Do not start messages with "Hey everyone!" or similar broadcast-style greetings.
|
|
146
|
+
- Do not explain what you are or what you can do unless someone asks.
|
|
147
|
+
|
|
148
|
+
## Spam & Moderation Policy
|
|
149
|
+
|
|
150
|
+
When a message matches ANY of the spam patterns below, immediately delete it using the delete_message tool and send a brief warning. Do NOT engage with the spam content.
|
|
151
|
+
|
|
152
|
+
### Spam Patterns (DELETE immediately):
|
|
153
|
+
1. **External WhatsApp group links** — Messages containing chat.whatsapp.com/join links or invitations to join other WhatsApp groups
|
|
154
|
+
2. **Unsolicited DM requests** — Users asking others to "DM me", "inbox me", "check your inbox", or similar private message solicitations, UNLESS they have recently shared a LetsChop menu link or are clearly following up on an existing order conversation
|
|
155
|
+
3. **External platform promotion** — Links to competing food platforms, unrelated Telegram/Discord groups, or promotional content not related to LetsChop
|
|
156
|
+
4. **Scam patterns** — Messages promoting crypto/forex schemes, "double your money" offers, fake job postings, MLM pitches, or phishing links
|
|
157
|
+
5. **Offensive content** — Hate speech, slurs, explicit content, or deliberately inflammatory messages unrelated to food ordering
|
|
158
|
+
6. **Chain messages** — "Forward this to 10 people" or similar chain/broadcast spam
|
|
159
|
+
7. **Unsolicited advertising** — Promoting products or services unrelated to food or the LetsChop community
|
|
160
|
+
|
|
161
|
+
### NOT Spam (do NOT delete):
|
|
162
|
+
- Normal food ordering conversations, questions about the menu, delivery inquiries
|
|
163
|
+
- Users sharing their LetsChop meal posts or menu links
|
|
164
|
+
- General friendly conversation between community members about food
|
|
165
|
+
- Feedback or complaints about LetsChop service (even if negative or harsh)
|
|
166
|
+
- Users coordinating group orders
|
|
167
|
+
- Off-topic but harmless conversation between members (this is not your problem, just ignore it)
|
|
168
|
+
|
|
169
|
+
### After Deleting:
|
|
170
|
+
- Send ONE brief warning: "[Name], that message was removed — [brief reason]. Please keep conversations relevant to the LetsChop community."
|
|
171
|
+
- For repeat offenders (same person warned before in recent history), mention it's a repeat but continue deleting.
|
|
172
|
+
- Never delete messages you're unsure about. When in doubt, leave it alone.
|
|
173
|
+
|
|
174
|
+
## WhatsApp Channel
|
|
175
|
+
|
|
176
|
+
You have a WhatsApp Channel where you can post content for subscribers using the post_to_channel tool. Use it to share things like:
|
|
177
|
+
- Meal of the day highlights or chef spotlights
|
|
178
|
+
- Quick food tips, recipes, or fun food facts
|
|
179
|
+
- Community milestones or announcements
|
|
180
|
+
- Interesting food-related content that subscribers would enjoy
|
|
181
|
+
|
|
182
|
+
Post to the channel at most once or twice a day, and only when you have something genuinely worth sharing. Do not repost group conversations to the channel. Keep channel posts concise and engaging.
|
|
183
|
+
|
|
184
|
+
## Communication Style
|
|
185
|
+
- Be casual and warm — you're part of the community, not a corporate bot
|
|
186
|
+
- Use simple, conversational Nigerian English where natural
|
|
187
|
+
- Keep it brief. No walls of text.
|
|
188
|
+
- No emojis in every message — use them sparingly and naturally
|
|
189
|
+
- Never be preachy, condescending, or overly enthusiastic`;
|
|
190
|
+
|
|
191
|
+
main().catch((err) => {
|
|
192
|
+
console.error("Update failed:", err.response?.data || err.message);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
});
|
package/server.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.conquext/neuron",
|
|
4
|
+
"description": "AI-powered WhatsApp chatbot platform with 315 tools for bots, campaigns, and contacts.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/conquext/neuron",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.1.0",
|
|
10
|
+
"remotes": [
|
|
11
|
+
{
|
|
12
|
+
"type": "streamable-http",
|
|
13
|
+
"url": "https://mcp.neuron.ng/mcp"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"packages": [
|
|
17
|
+
{
|
|
18
|
+
"registryType": "npm",
|
|
19
|
+
"identifier": "neuron-mcp-server",
|
|
20
|
+
"version": "1.1.0",
|
|
21
|
+
"transport": {
|
|
22
|
+
"type": "stdio"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
|
|
2
|
+
|
|
3
|
+
startCommand:
|
|
4
|
+
type: stdio
|
|
5
|
+
|
|
6
|
+
configSchema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
apiUrl:
|
|
10
|
+
type: string
|
|
11
|
+
default: "https://api.neuron.ng/api/v1"
|
|
12
|
+
description: Base URL of the Neuron API server.
|
|
13
|
+
authToken:
|
|
14
|
+
type: string
|
|
15
|
+
description: Pre-configured JWT or MCP token for authentication. If omitted, use the neuron_login tool to authenticate interactively.
|
|
16
|
+
|
|
17
|
+
commandFunction:
|
|
18
|
+
|-
|
|
19
|
+
(config) => ({ command: 'node', args: ['dist/index.js'], env: { NEURON_API_URL: config.apiUrl || 'https://api.neuron.ng/api/v1', ...(config.authToken ? { NEURON_AUTH_TOKEN: config.authToken } : {}) } })
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import axios, { AxiosError } from "axios";
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
// Resolve API URL: prefer NEURON_API_URL if it points to a real server (not Docker-internal hostnames)
|
|
7
|
+
const envUrl = process.env.NEURON_API_URL || "";
|
|
8
|
+
const isDockerInternal = envUrl.includes("neuron-api:") || (envUrl.includes("localhost") && !envUrl.includes("https://"));
|
|
9
|
+
const DEFAULT_BASE_URL = isDockerInternal ? "https://api.neuron.ng/api/v1" : (envUrl || "https://api.neuron.ng/api/v1");
|
|
10
|
+
|
|
11
|
+
let baseUrl = DEFAULT_BASE_URL;
|
|
12
|
+
|
|
13
|
+
// Token vault: stores tokens per base URL so switching instances preserves auth
|
|
14
|
+
const tokenVault: Record<string, string> = {};
|
|
15
|
+
|
|
16
|
+
const TOKEN_CACHE_DIR = join(homedir(), ".neuron");
|
|
17
|
+
const TOKEN_CACHE_FILE = join(TOKEN_CACHE_DIR, "mcp-token-cache.json");
|
|
18
|
+
|
|
19
|
+
function readCachedTokens(): Record<string, string> {
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(TOKEN_CACHE_FILE, "utf-8");
|
|
22
|
+
const cache = JSON.parse(raw);
|
|
23
|
+
// Support legacy single-entry format
|
|
24
|
+
if (cache.baseUrl && cache.token && !cache.instances) {
|
|
25
|
+
return { [cache.baseUrl]: cache.token };
|
|
26
|
+
}
|
|
27
|
+
// Multi-instance format
|
|
28
|
+
if (cache.instances && typeof cache.instances === "object") {
|
|
29
|
+
return cache.instances as Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// No cache or unreadable — that's fine
|
|
33
|
+
}
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeCachedTokens(): void {
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(TOKEN_CACHE_DIR, { recursive: true, mode: 0o700 });
|
|
40
|
+
writeFileSync(
|
|
41
|
+
TOKEN_CACHE_FILE,
|
|
42
|
+
JSON.stringify({ instances: tokenVault, activeBaseUrl: baseUrl }),
|
|
43
|
+
{ mode: 0o600 },
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
// Non-critical — best effort
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initialize: load all cached tokens into the vault, then set active token
|
|
51
|
+
const cachedTokens = readCachedTokens();
|
|
52
|
+
for (const [url, token] of Object.entries(cachedTokens)) {
|
|
53
|
+
tokenVault[url] = token;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let authToken = process.env.NEURON_AUTH_TOKEN || tokenVault[baseUrl] || "";
|
|
57
|
+
|
|
58
|
+
export function setAuthToken(token: string): void {
|
|
59
|
+
authToken = token;
|
|
60
|
+
if (token) {
|
|
61
|
+
tokenVault[baseUrl] = token;
|
|
62
|
+
} else {
|
|
63
|
+
delete tokenVault[baseUrl];
|
|
64
|
+
}
|
|
65
|
+
writeCachedTokens();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getAuthToken(): string {
|
|
69
|
+
return authToken;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getBaseUrl(): string {
|
|
73
|
+
return baseUrl;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function setBaseUrl(url: string): void {
|
|
77
|
+
// Save current token for current instance
|
|
78
|
+
if (authToken) {
|
|
79
|
+
tokenVault[baseUrl] = authToken;
|
|
80
|
+
}
|
|
81
|
+
// Switch to new instance
|
|
82
|
+
baseUrl = url;
|
|
83
|
+
// Restore token from vault if we've authenticated with this instance before
|
|
84
|
+
authToken = tokenVault[url] || "";
|
|
85
|
+
writeCachedTokens();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function listInstances(): Array<{ url: string; active: boolean; authenticated: boolean }> {
|
|
89
|
+
const urls = new Set<string>([baseUrl, ...Object.keys(tokenVault)]);
|
|
90
|
+
return Array.from(urls).map((url) => ({
|
|
91
|
+
url,
|
|
92
|
+
active: url === baseUrl,
|
|
93
|
+
authenticated: !!tokenVault[url],
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ApiResponse<T = unknown> {
|
|
98
|
+
success: boolean;
|
|
99
|
+
data?: T;
|
|
100
|
+
error?: { message: string; code: string; details?: unknown };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function api<T = unknown>(
|
|
104
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
|
|
105
|
+
endpoint: string,
|
|
106
|
+
data?: unknown,
|
|
107
|
+
params?: Record<string, string | number | undefined>,
|
|
108
|
+
headers?: Record<string, string>,
|
|
109
|
+
): Promise<ApiResponse<T>> {
|
|
110
|
+
try {
|
|
111
|
+
const cleanParams: Record<string, string | number> = {};
|
|
112
|
+
if (params) {
|
|
113
|
+
for (const [k, v] of Object.entries(params)) {
|
|
114
|
+
if (v !== undefined) cleanParams[k] = v;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const cleanEndpoint = endpoint.replace(/^\/+/, "");
|
|
119
|
+
const response = await axios({
|
|
120
|
+
method,
|
|
121
|
+
url: `${baseUrl}/${cleanEndpoint}`,
|
|
122
|
+
data,
|
|
123
|
+
params: Object.keys(cleanParams).length > 0 ? cleanParams : undefined,
|
|
124
|
+
timeout: 60000,
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
Accept: "application/json",
|
|
128
|
+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
129
|
+
...headers,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return response.data as ApiResponse<T>;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof AxiosError && error.response?.data) {
|
|
135
|
+
return error.response.data as ApiResponse<T>;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function handleError(error: unknown): string {
|
|
142
|
+
if (error instanceof AxiosError) {
|
|
143
|
+
if (error.response) {
|
|
144
|
+
const body = error.response.data as ApiResponse;
|
|
145
|
+
const msg = body?.error?.message || `HTTP ${error.response.status}`;
|
|
146
|
+
return `Error: ${msg}`;
|
|
147
|
+
}
|
|
148
|
+
const url = error.config?.url || baseUrl;
|
|
149
|
+
if (error.code === "ECONNABORTED") return `Error: Request timed out connecting to ${url}`;
|
|
150
|
+
if (error.code === "ECONNREFUSED") {
|
|
151
|
+
return `Error: Connection refused to ${url}. Try: neuron_login({ token: "your-mcp-token" }) with a token from the Neuron dashboard.`;
|
|
152
|
+
}
|
|
153
|
+
if (error.code === "ENOTFOUND") return `Error: DNS lookup failed for ${url}. Check your network connection.`;
|
|
154
|
+
return `Error: Network error (${error.code || "unknown"}): ${error.message} — URL: ${url}`;
|
|
155
|
+
}
|
|
156
|
+
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function jsonResult(data: unknown): { content: Array<{ type: "text"; text: string }> } {
|
|
160
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function textResult(text: string): { content: Array<{ type: "text"; text: string }> } {
|
|
164
|
+
return { content: [{ type: "text" as const, text }] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function errorResult(error: unknown): { content: Array<{ type: "text"; text: string }>; isError: true } {
|
|
168
|
+
return { content: [{ type: "text" as const, text: handleError(error) }], isError: true };
|
|
169
|
+
}
|