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.
Files changed (150) hide show
  1. package/.env +13 -0
  2. package/AI_PERSONALITY_DESIGN_RESEARCH.md +1020 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +146 -131
  5. package/dist/client.d.ts +6 -0
  6. package/dist/client.js +50 -12
  7. package/dist/client.js.map +1 -1
  8. package/dist/index.js +51 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/tools/ai.js +9 -3
  11. package/dist/tools/ai.js.map +1 -1
  12. package/dist/tools/approvals.d.ts +2 -0
  13. package/dist/tools/approvals.js +181 -0
  14. package/dist/tools/approvals.js.map +1 -0
  15. package/dist/tools/audit.js +7 -4
  16. package/dist/tools/audit.js.map +1 -1
  17. package/dist/tools/auth.js +119 -19
  18. package/dist/tools/auth.js.map +1 -1
  19. package/dist/tools/billing.js +22 -6
  20. package/dist/tools/billing.js.map +1 -1
  21. package/dist/tools/blog.js +98 -34
  22. package/dist/tools/blog.js.map +1 -1
  23. package/dist/tools/bot-api-keys.js +27 -12
  24. package/dist/tools/bot-api-keys.js.map +1 -1
  25. package/dist/tools/bot-api.js +53 -27
  26. package/dist/tools/bot-api.js.map +1 -1
  27. package/dist/tools/bots.js +116 -68
  28. package/dist/tools/bots.js.map +1 -1
  29. package/dist/tools/broadcasts.js +78 -38
  30. package/dist/tools/broadcasts.js.map +1 -1
  31. package/dist/tools/builtin-tools.js +19 -12
  32. package/dist/tools/builtin-tools.js.map +1 -1
  33. package/dist/tools/campaigns.js +149 -61
  34. package/dist/tools/campaigns.js.map +1 -1
  35. package/dist/tools/channels.js +163 -53
  36. package/dist/tools/channels.js.map +1 -1
  37. package/dist/tools/contact-lists.js +111 -51
  38. package/dist/tools/contact-lists.js.map +1 -1
  39. package/dist/tools/contacts.js +117 -44
  40. package/dist/tools/contacts.js.map +1 -1
  41. package/dist/tools/conversations.js +95 -50
  42. package/dist/tools/conversations.js.map +1 -1
  43. package/dist/tools/flows.d.ts +2 -0
  44. package/dist/tools/flows.js +242 -0
  45. package/dist/tools/flows.js.map +1 -0
  46. package/dist/tools/group-management.d.ts +2 -0
  47. package/dist/tools/group-management.js +239 -0
  48. package/dist/tools/group-management.js.map +1 -0
  49. package/dist/tools/leads.d.ts +2 -0
  50. package/dist/tools/leads.js +100 -0
  51. package/dist/tools/leads.js.map +1 -0
  52. package/dist/tools/list-campaigns.d.ts +2 -0
  53. package/dist/tools/list-campaigns.js +152 -0
  54. package/dist/tools/list-campaigns.js.map +1 -0
  55. package/dist/tools/list-pool.js +250 -33
  56. package/dist/tools/list-pool.js.map +1 -1
  57. package/dist/tools/media.js +12 -7
  58. package/dist/tools/media.js.map +1 -1
  59. package/dist/tools/newsletters.js +106 -26
  60. package/dist/tools/newsletters.js.map +1 -1
  61. package/dist/tools/organizations.js +76 -35
  62. package/dist/tools/organizations.js.map +1 -1
  63. package/dist/tools/outbound-webhooks.js +32 -20
  64. package/dist/tools/outbound-webhooks.js.map +1 -1
  65. package/dist/tools/payouts.js +44 -17
  66. package/dist/tools/payouts.js.map +1 -1
  67. package/dist/tools/personas.d.ts +2 -0
  68. package/dist/tools/personas.js +141 -0
  69. package/dist/tools/personas.js.map +1 -0
  70. package/dist/tools/polls.d.ts +2 -0
  71. package/dist/tools/polls.js +30 -0
  72. package/dist/tools/polls.js.map +1 -0
  73. package/dist/tools/pool.js +101 -43
  74. package/dist/tools/pool.js.map +1 -1
  75. package/dist/tools/products.d.ts +2 -0
  76. package/dist/tools/products.js +277 -0
  77. package/dist/tools/products.js.map +1 -0
  78. package/dist/tools/profile-privacy.d.ts +2 -0
  79. package/dist/tools/profile-privacy.js +195 -0
  80. package/dist/tools/profile-privacy.js.map +1 -0
  81. package/dist/tools/reflections.js +25 -13
  82. package/dist/tools/reflections.js.map +1 -1
  83. package/dist/tools/scheduled-messages.d.ts +2 -0
  84. package/dist/tools/scheduled-messages.js +124 -0
  85. package/dist/tools/scheduled-messages.js.map +1 -0
  86. package/dist/tools/social-channels.d.ts +2 -0
  87. package/dist/tools/social-channels.js +318 -0
  88. package/dist/tools/social-channels.js.map +1 -0
  89. package/dist/tools/tasks.d.ts +2 -0
  90. package/dist/tools/tasks.js +155 -0
  91. package/dist/tools/tasks.js.map +1 -0
  92. package/dist/tools/tools.js +86 -40
  93. package/dist/tools/tools.js.map +1 -1
  94. package/dist/tools/wallets.js +34 -12
  95. package/dist/tools/wallets.js.map +1 -1
  96. package/dist/tools/webhooks.js +35 -20
  97. package/dist/tools/webhooks.js.map +1 -1
  98. package/dist/tools/whatsapp-actions.d.ts +2 -0
  99. package/dist/tools/whatsapp-actions.js +186 -0
  100. package/dist/tools/whatsapp-actions.js.map +1 -0
  101. package/package.json +4 -31
  102. package/scripts/setup-delivahere-bot.ts +804 -0
  103. package/scripts/test-delivahere-bot.ts +128 -0
  104. package/scripts/update-letschop-prompt.ts +194 -0
  105. package/server.json +26 -0
  106. package/smithery.yaml +19 -0
  107. package/src/client.ts +169 -0
  108. package/src/index.ts +256 -0
  109. package/src/tools/ai.ts +35 -0
  110. package/src/tools/approvals.ts +231 -0
  111. package/src/tools/audit.ts +49 -0
  112. package/src/tools/auth.ts +536 -0
  113. package/src/tools/billing.ts +79 -0
  114. package/src/tools/blog.ts +264 -0
  115. package/src/tools/bot-api-keys.ts +87 -0
  116. package/src/tools/bot-api.ts +193 -0
  117. package/src/tools/bots.ts +320 -0
  118. package/src/tools/broadcasts.ts +224 -0
  119. package/src/tools/builtin-tools.ts +108 -0
  120. package/src/tools/campaigns.ts +429 -0
  121. package/src/tools/channels.ts +520 -0
  122. package/src/tools/contact-lists.ts +320 -0
  123. package/src/tools/contacts.ts +395 -0
  124. package/src/tools/conversations.ts +349 -0
  125. package/src/tools/flows.ts +298 -0
  126. package/src/tools/group-management.ts +265 -0
  127. package/src/tools/knowledge-bases.ts +451 -0
  128. package/src/tools/leads.ts +104 -0
  129. package/src/tools/list-campaigns.ts +190 -0
  130. package/src/tools/list-pool.ts +389 -0
  131. package/src/tools/media.ts +50 -0
  132. package/src/tools/newsletters.ts +271 -0
  133. package/src/tools/organizations.ts +287 -0
  134. package/src/tools/outbound-webhooks.ts +181 -0
  135. package/src/tools/payouts.ts +133 -0
  136. package/src/tools/personas.ts +145 -0
  137. package/src/tools/polls.ts +32 -0
  138. package/src/tools/pool.ts +293 -0
  139. package/src/tools/products.ts +280 -0
  140. package/src/tools/profile-privacy.ts +215 -0
  141. package/src/tools/reflections.ts +124 -0
  142. package/src/tools/scheduled-messages.ts +144 -0
  143. package/src/tools/social-channels.ts +396 -0
  144. package/src/tools/tasks.ts +198 -0
  145. package/src/tools/tools.ts +255 -0
  146. package/src/tools/wallets.ts +107 -0
  147. package/src/tools/webhooks.ts +166 -0
  148. package/src/tools/whatsapp-actions.ts +203 -0
  149. package/tsconfig.json +20 -0
  150. package/LICENSE +0 -21
package/src/index.ts ADDED
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Neuron MCP Server
4
+ *
5
+ * Exposes the full Neuron AI chatbot platform as MCP tools.
6
+ * Supports 120+ tools covering bots, conversations, knowledge bases,
7
+ * tools, webhooks, built-in tools, reflections, channels, contacts,
8
+ * broadcasts, billing, media, AI, marketplace, and more.
9
+ *
10
+ * Environment variables:
11
+ * NEURON_API_URL - Base URL (default: https://api.neuron.ng/api/v1)
12
+ * NEURON_AUTH_TOKEN - Pre-set JWT or API key for authentication
13
+ * MCP_TRANSPORT - "http" for Streamable HTTP, "stdio" (default) for stdio
14
+ * PORT - HTTP server port (default: 3001)
15
+ *
16
+ * Transports:
17
+ * stdio - For local Claude Code / IDE integration (default)
18
+ * http - For remote connections (Claude.ai custom connectors)
19
+ */
20
+
21
+ import { config } from "dotenv";
22
+ import { fileURLToPath } from "node:url";
23
+ import { dirname, resolve } from "node:path";
24
+
25
+ // Load .env from mcp-server directory (not CWD which may be the project root)
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ config({ path: resolve(__dirname, "../.env") });
28
+
29
+ import { randomUUID } from "node:crypto";
30
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
31
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
32
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
33
+ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
34
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
35
+
36
+ import { registerAuthTools } from "./tools/auth.js";
37
+ import { registerBotTools } from "./tools/bots.js";
38
+ import { registerBotApiKeyTools } from "./tools/bot-api-keys.js";
39
+ import { registerBotApiTools } from "./tools/bot-api.js";
40
+ import { registerConversationTools } from "./tools/conversations.js";
41
+ import { registerKnowledgeBaseTools } from "./tools/knowledge-bases.js";
42
+ import { registerToolsTools } from "./tools/tools.js";
43
+ import { registerTaskTools } from "./tools/tasks.js";
44
+ import { registerLeadTools } from "./tools/leads.js";
45
+ import { registerPersonaTools } from "./tools/personas.js";
46
+ import { registerWebhooksTools } from "./tools/webhooks.js";
47
+ import { registerReflectionsTools } from "./tools/reflections.js";
48
+ import { registerChannelsTools } from "./tools/channels.js";
49
+ import { registerPoolTools } from "./tools/pool.js";
50
+ import { registerOrganizationTools } from "./tools/organizations.js";
51
+ import { registerAuditTools } from "./tools/audit.js";
52
+ import { registerBuiltinToolsTools } from "./tools/builtin-tools.js";
53
+ import { registerOutboundWebhookTools } from "./tools/outbound-webhooks.js";
54
+ import { registerContactsTools } from "./tools/contacts.js";
55
+ import { registerContactListsTools } from "./tools/contact-lists.js";
56
+ import { registerListCampaignTools } from "./tools/list-campaigns.js";
57
+ import { registerBroadcastsTools } from "./tools/broadcasts.js";
58
+ import { registerBillingTools } from "./tools/billing.js";
59
+ import { registerMediaTools } from "./tools/media.js";
60
+ import { registerAiTools } from "./tools/ai.js";
61
+ import { registerCampaignTools } from "./tools/campaigns.js";
62
+ import { registerWalletTools } from "./tools/wallets.js";
63
+ import { registerPayoutTools } from "./tools/payouts.js";
64
+ import { registerBlogTools } from "./tools/blog.js";
65
+ import { registerNewsletterTools } from "./tools/newsletters.js";
66
+ import { registerListPoolTools } from "./tools/list-pool.js";
67
+ import { registerWhatsAppActionTools } from "./tools/whatsapp-actions.js";
68
+ import { registerGroupManagementTools } from "./tools/group-management.js";
69
+ import { registerPollTools } from "./tools/polls.js";
70
+ import { registerProfilePrivacyTools } from "./tools/profile-privacy.js";
71
+ import { registerSocialChannelsTools } from "./tools/social-channels.js";
72
+ import { registerScheduledMessageTools } from "./tools/scheduled-messages.js";
73
+ import { registerApprovalTools } from "./tools/approvals.js";
74
+ import { registerFlowTools } from "./tools/flows.js";
75
+ import { registerProductsTools } from "./tools/products.js";
76
+
77
+ function createServer(): McpServer {
78
+ const server = new McpServer({
79
+ name: "neuron-mcp-server",
80
+ version: "1.0.0",
81
+ });
82
+
83
+ // Register all tool groups
84
+ registerAuthTools(server);
85
+ registerBotTools(server);
86
+ registerBotApiKeyTools(server);
87
+ registerBotApiTools(server);
88
+ registerConversationTools(server);
89
+ registerKnowledgeBaseTools(server);
90
+ registerToolsTools(server);
91
+ registerTaskTools(server);
92
+ registerLeadTools(server);
93
+ registerPersonaTools(server);
94
+ registerWebhooksTools(server);
95
+ registerReflectionsTools(server);
96
+ registerChannelsTools(server);
97
+ registerPoolTools(server);
98
+ registerOrganizationTools(server);
99
+ registerAuditTools(server);
100
+ registerBuiltinToolsTools(server);
101
+ registerOutboundWebhookTools(server);
102
+ registerContactsTools(server);
103
+ registerContactListsTools(server);
104
+ registerListCampaignTools(server);
105
+ registerBroadcastsTools(server);
106
+ registerBillingTools(server);
107
+ registerMediaTools(server);
108
+ registerAiTools(server);
109
+ registerCampaignTools(server);
110
+ registerWalletTools(server);
111
+ registerPayoutTools(server);
112
+ registerBlogTools(server);
113
+ registerNewsletterTools(server);
114
+ registerListPoolTools(server);
115
+ registerWhatsAppActionTools(server);
116
+ registerGroupManagementTools(server);
117
+ registerPollTools(server);
118
+ registerProfilePrivacyTools(server);
119
+ registerSocialChannelsTools(server);
120
+ registerScheduledMessageTools(server);
121
+ registerApprovalTools(server);
122
+ registerFlowTools(server);
123
+ registerProductsTools(server);
124
+
125
+ return server;
126
+ }
127
+
128
+ async function startStdio() {
129
+ const server = createServer();
130
+ const transport = new StdioServerTransport();
131
+ await server.connect(transport);
132
+ console.error("Neuron MCP server running via stdio");
133
+ }
134
+
135
+ async function startHttp() {
136
+ const PORT = parseInt(process.env.PORT || "3001", 10);
137
+ const app = createMcpExpressApp({ host: "0.0.0.0" });
138
+
139
+ const transports: Record<string, StreamableHTTPServerTransport> = {};
140
+
141
+ function createTransport(): StreamableHTTPServerTransport {
142
+ const transport = new StreamableHTTPServerTransport({
143
+ sessionIdGenerator: () => randomUUID(),
144
+ onsessioninitialized: (sid) => {
145
+ transports[sid] = transport;
146
+ console.error(`Session initialized: ${sid}`);
147
+ },
148
+ });
149
+
150
+ transport.onclose = () => {
151
+ const sid = transport.sessionId;
152
+ if (sid && transports[sid]) {
153
+ delete transports[sid];
154
+ }
155
+ };
156
+
157
+ return transport;
158
+ }
159
+
160
+ app.all("/mcp", async (req, res) => {
161
+ try {
162
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
163
+ let transport: StreamableHTTPServerTransport;
164
+
165
+ if (sessionId && transports[sessionId]) {
166
+ // Known session — reuse existing transport
167
+ transport = transports[sessionId];
168
+ } else if (req.method === "POST" && isInitializeRequest(req.body)) {
169
+ // New or re-initializing client — create a fresh session
170
+ // (handles both no session ID and stale session ID after server restart)
171
+ transport = createTransport();
172
+ const server = createServer();
173
+ await server.connect(transport);
174
+ } else if (sessionId && !transports[sessionId]) {
175
+ // Stale session ID with a non-initialize request — tell client to re-initialize
176
+ res.status(404).json({
177
+ jsonrpc: "2.0",
178
+ error: { code: -32000, message: "Session expired. Please reconnect." },
179
+ id: null,
180
+ });
181
+ return;
182
+ } else {
183
+ res.status(400).json({
184
+ jsonrpc: "2.0",
185
+ error: { code: -32000, message: "Bad Request: No valid session ID provided" },
186
+ id: null,
187
+ });
188
+ return;
189
+ }
190
+
191
+ await transport.handleRequest(req, res, req.body);
192
+ } catch (error) {
193
+ console.error("Error handling MCP request:", error);
194
+ if (!res.headersSent) {
195
+ res.status(500).json({
196
+ jsonrpc: "2.0",
197
+ error: { code: -32603, message: "Internal server error" },
198
+ id: null,
199
+ });
200
+ }
201
+ }
202
+ });
203
+
204
+ const httpServer = app.listen(PORT, "0.0.0.0", () => {
205
+ console.error(`Neuron MCP server listening on http://0.0.0.0:${PORT}/mcp`);
206
+ });
207
+
208
+ let shuttingDown = false;
209
+
210
+ const shutdownMcp = async () => {
211
+ if (shuttingDown) return;
212
+ shuttingDown = true;
213
+ console.error("Shutting down...");
214
+
215
+ const forceTimer = setTimeout(() => {
216
+ console.error("MCP shutdown timed out, forcing exit");
217
+ process.exit(1);
218
+ }, 10_000);
219
+ forceTimer.unref();
220
+
221
+ // Stop accepting new connections and drain in-flight requests
222
+ await new Promise<void>((resolve) => {
223
+ httpServer.close((err) => {
224
+ if (err) console.error("Error closing HTTP server:", err);
225
+ resolve();
226
+ });
227
+ });
228
+
229
+ // Close all MCP session transports
230
+ await Promise.all(
231
+ Object.keys(transports).map((sid) =>
232
+ transports[sid].close().catch(() => {}).then(() => { delete transports[sid]; }),
233
+ ),
234
+ );
235
+
236
+ clearTimeout(forceTimer);
237
+ process.exit(0);
238
+ };
239
+
240
+ process.on("SIGINT", shutdownMcp);
241
+ process.on("SIGTERM", shutdownMcp);
242
+ }
243
+
244
+ const mode = process.env.MCP_TRANSPORT || "stdio";
245
+
246
+ if (mode === "http") {
247
+ startHttp().catch((error) => {
248
+ console.error("Server error:", error);
249
+ process.exit(1);
250
+ });
251
+ } else {
252
+ startStdio().catch((error) => {
253
+ console.error("Server error:", error);
254
+ process.exit(1);
255
+ });
256
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { api, jsonResult, errorResult } from "../client.js";
4
+
5
+ export function registerAiTools(server: McpServer): void {
6
+ // Rewrite text
7
+ server.registerTool(
8
+ "neuron_rewrite_text",
9
+ {
10
+ title: "Rewrite Text",
11
+ description: "Rewrite or rephrase text using AI with specified tone, style, or instructions (e.g., make it more formal, translate, summarize).",
12
+ inputSchema: z.object({
13
+ text: z.string().describe("The original text to rewrite or transform"),
14
+ instruction: z.string().optional().describe("Instructions for how to rewrite the text (e.g., 'make it more professional', 'translate to Spanish', 'summarize in 2 sentences')"),
15
+ }),
16
+ annotations: {
17
+ readOnlyHint: false,
18
+ destructiveHint: false,
19
+ idempotentHint: false,
20
+ openWorldHint: false,
21
+ },
22
+ },
23
+ async (params) => {
24
+ try {
25
+ const result = await api("POST", "/ai/rewrite", {
26
+ text: params.text,
27
+ instruction: params.instruction,
28
+ });
29
+ return jsonResult(result);
30
+ } catch (error) {
31
+ return errorResult(error);
32
+ }
33
+ }
34
+ );
35
+ }
@@ -0,0 +1,231 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { api, jsonResult, errorResult } from "../client.js";
4
+
5
+ const callbackShape = z
6
+ .object({
7
+ response: z
8
+ .object({
9
+ schemes: z
10
+ .array(z.enum(["interactive", "keyword", "code", "llm"]))
11
+ .optional()
12
+ .describe("Ordered fallback for turning a reply into a decision (default: ['keyword'])"),
13
+ code: z.string().optional().describe("One-time confirmation code — enables the 'code' scheme"),
14
+ })
15
+ .optional(),
16
+ delivery: z
17
+ .object({
18
+ scheme: z
19
+ .enum(["webhook_signed", "webhook_simple", "neuron_message", "neuron_tool", "none"])
20
+ .describe("How the decision reaches the caller"),
21
+ config: z
22
+ .record(z.any())
23
+ .optional()
24
+ .describe("Scheme config: {url,secret} or {webhookId}; {targetJid,channelId}; {toolId}; {method}"),
25
+ })
26
+ .optional(),
27
+ await: z.boolean().optional().describe("Hold the request until resolved (long-poll)"),
28
+ awaitTimeoutSec: z.number().int().min(1).max(60).optional(),
29
+ })
30
+ .optional()
31
+ .describe("Callback config. Omit for a poll-only flow (delivery scheme 'none').");
32
+
33
+ export function registerApprovalTools(server: McpServer): void {
34
+ // Request an approval
35
+ server.registerTool(
36
+ "neuron_request_approval",
37
+ {
38
+ title: "Request Approval",
39
+ description:
40
+ "Ask a human to approve or reject an action over WhatsApp and get the decision back. " +
41
+ "The recipient replies approve/reject (optionally with a reason) or quotes the message. " +
42
+ "Use this to gate any action needing human sign-off (refunds, deploys, spend, publishing). " +
43
+ "Returns an approvalId; poll neuron_get_approval / neuron_list_approvals for the outcome, or supply a " +
44
+ "webhook in `callback` to be notified. Requires a bot API key ('nrn_' prefix).",
45
+ annotations: {
46
+ readOnlyHint: false,
47
+ destructiveHint: false,
48
+ idempotentHint: false,
49
+ openWorldHint: true,
50
+ },
51
+ inputSchema: z.object({
52
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
53
+ to: z.string().describe("Recipient phone number (E.164/digits) or WhatsApp group JID"),
54
+ prompt: z.string().describe("The approval question shown to the human"),
55
+ context: z.string().optional().describe("Optional long-form details — stored in a shareable Neuron Note and linked from the WhatsApp message"),
56
+ channelId: z.string().optional().describe("Optional WhatsApp channel override (UUID)"),
57
+ expiresInSeconds: z.number().int().positive().optional().describe("Time to live before auto-expiry (default 86400, 60–604800)"),
58
+ metadata: z.record(z.any()).optional().describe("Opaque data echoed back verbatim in the result/callback"),
59
+ idempotencyKey: z.string().optional().describe("Dedupe key — returns the existing approval on repeat"),
60
+ callback: callbackShape,
61
+ }),
62
+ },
63
+ async (params) => {
64
+ try {
65
+ const { apiKey, callback, ...rest } = params;
66
+ const body = {
67
+ ...rest,
68
+ callback: callback ?? { response: { schemes: ["keyword"] }, delivery: { scheme: "none" } },
69
+ };
70
+ const result = await api("POST", "approvals", body, undefined, { Authorization: `Bearer ${apiKey}` });
71
+ return jsonResult(result);
72
+ } catch (error) {
73
+ return errorResult(error);
74
+ }
75
+ },
76
+ );
77
+
78
+ // Get an approval (optionally long-poll)
79
+ server.registerTool(
80
+ "neuron_get_approval",
81
+ {
82
+ title: "Get Approval",
83
+ description:
84
+ "Fetch a single approval request by id, including its decision once resolved. " +
85
+ "Set waitSeconds (1–60) to long-poll until it resolves. Requires a bot API key.",
86
+ annotations: {
87
+ readOnlyHint: true,
88
+ destructiveHint: false,
89
+ idempotentHint: true,
90
+ openWorldHint: false,
91
+ },
92
+ inputSchema: z.object({
93
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
94
+ approvalId: z.string().describe("The approval request id (UUID)"),
95
+ waitSeconds: z.number().int().min(1).max(60).optional().describe("Long-poll up to N seconds for resolution"),
96
+ }),
97
+ },
98
+ async (params) => {
99
+ try {
100
+ const { apiKey, approvalId, waitSeconds } = params;
101
+ const result = await api(
102
+ "GET",
103
+ `approvals/${approvalId}`,
104
+ undefined,
105
+ waitSeconds ? { wait: waitSeconds } : undefined,
106
+ { Authorization: `Bearer ${apiKey}` },
107
+ );
108
+ return jsonResult(result);
109
+ } catch (error) {
110
+ return errorResult(error);
111
+ }
112
+ },
113
+ );
114
+
115
+ // List approvals
116
+ server.registerTool(
117
+ "neuron_list_approvals",
118
+ {
119
+ title: "List Approvals",
120
+ description:
121
+ "List approval requests for the organization, newest first. Filter by status " +
122
+ "(pending/approved/rejected/expired/cancelled). Requires a bot API key.",
123
+ annotations: {
124
+ readOnlyHint: true,
125
+ destructiveHint: false,
126
+ idempotentHint: true,
127
+ openWorldHint: false,
128
+ },
129
+ inputSchema: z.object({
130
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
131
+ status: z.string().optional().describe("Filter: pending | approved | rejected | expired | cancelled"),
132
+ channelId: z.string().optional().describe("Filter by WhatsApp channel (UUID)"),
133
+ page: z.number().int().positive().optional().describe("Page number (default 1)"),
134
+ limit: z.number().int().positive().optional().describe("Items per page (default 25, max 100)"),
135
+ }),
136
+ },
137
+ async (params) => {
138
+ try {
139
+ const { apiKey, status, channelId, page, limit } = params;
140
+ const result = await api(
141
+ "GET",
142
+ "approvals",
143
+ undefined,
144
+ { status, channelId, page, limit },
145
+ { Authorization: `Bearer ${apiKey}` },
146
+ );
147
+ return jsonResult(result);
148
+ } catch (error) {
149
+ return errorResult(error);
150
+ }
151
+ },
152
+ );
153
+
154
+ // Respond to (approve/reject) a pending approval
155
+ server.registerTool(
156
+ "neuron_respond_approval",
157
+ {
158
+ title: "Respond to Approval",
159
+ description:
160
+ "Approve or reject a pending approval programmatically — the decision surface for an authorized operator " +
161
+ "acting via MCP instead of replying on WhatsApp. Fires the configured callback and posts the outcome to the " +
162
+ "WhatsApp chat. Exactly-once: the first responder (here or on WhatsApp) wins; later responses return 410. " +
163
+ "Requires a bot API key.",
164
+ annotations: {
165
+ readOnlyHint: false,
166
+ destructiveHint: false,
167
+ idempotentHint: false,
168
+ openWorldHint: true,
169
+ },
170
+ inputSchema: z.object({
171
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
172
+ approvalId: z.string().describe("The approval request id (UUID)"),
173
+ decision: z.enum(["approve", "reject"]).describe("The decision to record"),
174
+ reason: z.string().optional().describe("Optional reason (recommended for rejections)"),
175
+ responderName: z.string().optional().describe("Optional display name of the operator making the decision"),
176
+ }),
177
+ },
178
+ async (params) => {
179
+ try {
180
+ const { apiKey, approvalId, decision, reason, responderName } = params;
181
+ const result = await api(
182
+ "POST",
183
+ `approvals/${approvalId}/respond`,
184
+ { decision, reason, responderName },
185
+ undefined,
186
+ { Authorization: `Bearer ${apiKey}` },
187
+ );
188
+ return jsonResult(result);
189
+ } catch (error) {
190
+ return errorResult(error);
191
+ }
192
+ },
193
+ );
194
+
195
+ // Cancel a pending approval
196
+ server.registerTool(
197
+ "neuron_cancel_approval",
198
+ {
199
+ title: "Cancel Approval",
200
+ description:
201
+ "Cancel a still-pending approval (e.g. the upstream action was aborted). " +
202
+ "Fires the approval.cancelled callback. Requires a bot API key.",
203
+ annotations: {
204
+ readOnlyHint: false,
205
+ destructiveHint: true,
206
+ idempotentHint: true,
207
+ openWorldHint: false,
208
+ },
209
+ inputSchema: z.object({
210
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
211
+ approvalId: z.string().describe("The approval request id (UUID) to cancel"),
212
+ reason: z.string().optional().describe("Optional cancellation reason"),
213
+ }),
214
+ },
215
+ async (params) => {
216
+ try {
217
+ const { apiKey, approvalId, reason } = params;
218
+ const result = await api(
219
+ "POST",
220
+ `approvals/${approvalId}/cancel`,
221
+ { reason },
222
+ undefined,
223
+ { Authorization: `Bearer ${apiKey}` },
224
+ );
225
+ return jsonResult(result);
226
+ } catch (error) {
227
+ return errorResult(error);
228
+ }
229
+ },
230
+ );
231
+ }
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { api, jsonResult, errorResult } from "../client.js";
4
+
5
+ export function registerAuditTools(server: McpServer): void {
6
+ // List Audit Logs
7
+ server.registerTool(
8
+ "neuron_list_audit_logs",
9
+ {
10
+ title: "List Audit Logs",
11
+ description: "Retrieve audit logs for the organization, showing a chronological record of actions performed by members. Supports filtering by action type and resource.",
12
+ inputSchema: z.object({
13
+ page: z.number().int().positive().optional().describe("Page number for pagination (default: 1)"),
14
+ limit: z.number().int().positive().optional().describe("Number of log entries per page (default: 20)"),
15
+ action: z.string().optional().describe("Filter by action type (e.g., 'CREATE', 'UPDATE', 'DELETE')"),
16
+ resourceType: z.string().optional().describe("Filter by resource type (e.g., 'USER', 'ORGANIZATION', 'BOT', 'CAMPAIGN')"),
17
+ }),
18
+ annotations: {
19
+ readOnlyHint: true,
20
+ destructiveHint: false,
21
+ idempotentHint: true,
22
+ openWorldHint: false,
23
+ },
24
+ },
25
+ async (params) => {
26
+ try {
27
+ const queryParams: Record<string, string | number> = {};
28
+
29
+ if (params.page !== undefined) {
30
+ queryParams.page = params.page;
31
+ }
32
+ if (params.limit !== undefined) {
33
+ queryParams.limit = params.limit;
34
+ }
35
+ if (params.action !== undefined) {
36
+ queryParams.action = params.action;
37
+ }
38
+ if (params.resourceType !== undefined) {
39
+ queryParams.resourceType = params.resourceType;
40
+ }
41
+
42
+ const result = await api("GET", "/audit", undefined, queryParams);
43
+ return jsonResult(result);
44
+ } catch (error) {
45
+ return errorResult(error);
46
+ }
47
+ }
48
+ );
49
+ }