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
@@ -0,0 +1,264 @@
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
+ /**
6
+ * Block content vocabulary. The backend (backend/src/lib/blog-blocks.ts) is the
7
+ * authoritative validator — it rejects malformed blocks with a 400 — so here we
8
+ * keep a permissive shape and describe the vocabulary richly for the model.
9
+ */
10
+ const BLOCKS_SCHEMA = z.array(z.record(z.any()));
11
+
12
+ const BLOCKS_DESCRIPTION = [
13
+ "Structured layout blocks, rendered top-to-bottom as styled components. Each block is an object with a `type` and its fields:",
14
+ "- hero: { type:'hero', title, kicker?, subtitle?, imageUrl?, align?:'left'|'center', ctaLabel?, ctaHref? }",
15
+ "- richText: { type:'richText', markdown } (full GFM markdown)",
16
+ "- callout: { type:'callout', variant:'info'|'tip'|'warning'|'success'|'danger', title?, markdown }",
17
+ "- columns: { type:'columns', ratio?:'1-1'|'1-2'|'2-1', columns:[ [block,...], [block,...] ] } (exactly 2 columns of leaf blocks; no nested columns)",
18
+ "- codeCard: { type:'codeCard', language?, filename?, code }",
19
+ "- imageGrid: { type:'imageGrid', columns?:1|2|3|4, images:[{ url, alt?, caption? }] }",
20
+ "- quote: { type:'quote', quote, attribution? }",
21
+ "- embed: { type:'embed', provider:'youtube'|'loom'|'vimeo'|'generic', url, title? } (paste the normal share URL)",
22
+ "- cta: { type:'cta', headline, body?, buttonLabel, buttonHref, secondaryLabel?, secondaryHref?, accent?:'#hex' }",
23
+ "- divider: { type:'divider' }",
24
+ "- html: { type:'html', html, sandboxed?, height? } (custom HTML. Default is sanitized + indexable, no scripts. Set sandboxed:true to run scripts inside an isolated iframe — NOT indexed by search.)",
25
+ "Build magazine-style posts by composing these, e.g. a hero, then richText, then a columns block, a callout, a codeCard, and a closing cta.",
26
+ ].join("\n");
27
+
28
+ export function registerBlogTools(server: McpServer): void {
29
+ // Create blog post
30
+ server.registerTool(
31
+ "neuron_create_blog_post",
32
+ {
33
+ title: "Create Blog Post",
34
+ description: "Create a new blog post. Body can be authored two ways: (1) `content` as markdown (GFM) + inline raw HTML, or (2) `blocks` — a structured array of typed layout blocks rendered as styled components (heroes, columns, callouts, code cards, embeds, CTAs, sanitized custom HTML). Prefer `blocks` for rich, magazine-style layouts; use `content` for simple prose. If both are given, `blocks` renders and `content` is kept as a fallback.",
35
+ inputSchema: z.object({
36
+ title: z.string().describe("Title of the blog post (max 500 characters)"),
37
+ content: z.string().optional().describe("Optional markdown body (GFM + inline raw HTML). Used when `blocks` is not provided, and kept as a fallback when it is. Rich options: ![alt](url), <video src controls>, <iframe> embeds (16:9), <details><summary>, <mark>, <kbd>."),
38
+ blocks: BLOCKS_SCHEMA.optional().describe(BLOCKS_DESCRIPTION),
39
+ excerpt: z.string().optional().describe("Short summary or preview text for the blog post"),
40
+ authorType: z
41
+ .enum(["user", "organization"])
42
+ .describe("Whether the post is authored by an individual user or the organization"),
43
+ coverImageUrl: z.string().optional().describe("URL of the cover image displayed at the top of the post"),
44
+ tags: z
45
+ .array(z.string())
46
+ .optional()
47
+ .describe("List of tag names or slugs to categorize the post"),
48
+ status: z
49
+ .enum(["draft", "published"])
50
+ .default("draft")
51
+ .describe("Initial publication status of the post (default: 'draft')"),
52
+ seoTitle: z
53
+ .string()
54
+ .optional()
55
+ .describe("Custom SEO title override for search engine results"),
56
+ seoDescription: z
57
+ .string()
58
+ .optional()
59
+ .describe("Meta description for search engine result snippets"),
60
+ seoKeywords: z
61
+ .array(z.string())
62
+ .optional()
63
+ .describe("List of SEO keywords for search engine optimization"),
64
+ }),
65
+ annotations: {
66
+ readOnlyHint: false,
67
+ destructiveHint: false,
68
+ idempotentHint: false,
69
+ openWorldHint: false,
70
+ },
71
+ },
72
+ async (params) => {
73
+ try {
74
+ const result = await api("POST", "blog/posts", {
75
+ title: params.title,
76
+ content: params.content,
77
+ blocks: params.blocks,
78
+ excerpt: params.excerpt,
79
+ authorType: params.authorType,
80
+ coverImageUrl: params.coverImageUrl,
81
+ tags: params.tags,
82
+ status: params.status,
83
+ seoTitle: params.seoTitle,
84
+ seoDescription: params.seoDescription,
85
+ seoKeywords: params.seoKeywords,
86
+ });
87
+ return jsonResult(result);
88
+ } catch (error) {
89
+ return errorResult(error);
90
+ }
91
+ }
92
+ );
93
+
94
+ // List blog posts
95
+ server.registerTool(
96
+ "neuron_list_blog_posts",
97
+ {
98
+ title: "List Blog Posts",
99
+ description: "Retrieve a paginated list of blog posts with optional filtering by status, author type, author ID, or tag.",
100
+ inputSchema: z.object({
101
+ status: z
102
+ .enum(["draft", "published", "hidden"])
103
+ .optional()
104
+ .describe("Filter posts by publication status"),
105
+ authorType: z
106
+ .enum(["user", "organization"])
107
+ .optional()
108
+ .describe("Filter posts by author type"),
109
+ authorId: z
110
+ .string()
111
+ .optional()
112
+ .describe("Filter posts by author identifier (UUID)"),
113
+ tag: z.string().optional().describe("Filter posts by tag slug (e.g., 'announcements')"),
114
+ page: z.number().default(1).describe("Page number for pagination (default: 1)"),
115
+ limit: z.number().default(20).describe("Number of results per page (default: 20)"),
116
+ }),
117
+ annotations: {
118
+ readOnlyHint: true,
119
+ destructiveHint: false,
120
+ idempotentHint: true,
121
+ openWorldHint: false,
122
+ },
123
+ },
124
+ async (params) => {
125
+ try {
126
+ const result = await api("GET", "blog/posts", undefined, {
127
+ status: params.status,
128
+ authorType: params.authorType,
129
+ authorId: params.authorId,
130
+ tag: params.tag,
131
+ page: params.page,
132
+ limit: params.limit,
133
+ });
134
+ return jsonResult(result);
135
+ } catch (error) {
136
+ return errorResult(error);
137
+ }
138
+ }
139
+ );
140
+
141
+ // Get blog post
142
+ server.registerTool(
143
+ "neuron_get_blog_post",
144
+ {
145
+ title: "Get Blog Post",
146
+ description: "Retrieve a single blog post by its URL-friendly slug, including full content and metadata.",
147
+ inputSchema: z.object({
148
+ slug: z.string().describe("URL-friendly slug of the blog post (e.g., 'my-first-post')"),
149
+ }),
150
+ annotations: {
151
+ readOnlyHint: true,
152
+ destructiveHint: false,
153
+ idempotentHint: true,
154
+ openWorldHint: false,
155
+ },
156
+ },
157
+ async (params) => {
158
+ try {
159
+ const result = await api("GET", `blog/posts/${params.slug}`);
160
+ return jsonResult(result);
161
+ } catch (error) {
162
+ return errorResult(error);
163
+ }
164
+ }
165
+ );
166
+
167
+ // Update blog post
168
+ server.registerTool(
169
+ "neuron_update_blog_post",
170
+ {
171
+ title: "Update Blog Post",
172
+ description: "Update an existing blog post's content, metadata, status, or SEO fields. Only provided fields are changed.",
173
+ inputSchema: z.object({
174
+ id: z.string().describe("Unique identifier (UUID) of the blog post to update"),
175
+ title: z.string().optional().describe("New title for the blog post"),
176
+ content: z.string().optional().describe("New markdown body (GFM + inline raw HTML). Supports ![alt](url), <video>, <iframe> embeds, <details>/<summary>, <mark>, <kbd>."),
177
+ blocks: BLOCKS_SCHEMA.nullable().optional().describe(`${BLOCKS_DESCRIPTION}\n\nPass null to clear blocks and revert to the markdown \`content\`. Omit to leave existing blocks unchanged.`),
178
+ excerpt: z.string().optional().describe("New short summary or preview text"),
179
+ tags: z
180
+ .array(z.string())
181
+ .optional()
182
+ .describe("Replace the post's tags with this list of tag names (created if missing). Empty array clears all tags; omit to leave tags unchanged."),
183
+ coverImageUrl: z.string().optional().describe("New URL for the cover image"),
184
+ status: z
185
+ .enum(["draft", "published", "hidden"])
186
+ .optional()
187
+ .describe("New publication status for the post"),
188
+ seoTitle: z.string().optional().describe("New custom SEO title override"),
189
+ seoDescription: z.string().optional().describe("New meta description for search engines"),
190
+ seoKeywords: z
191
+ .array(z.string())
192
+ .optional()
193
+ .describe("New list of SEO keywords"),
194
+ }),
195
+ annotations: {
196
+ readOnlyHint: false,
197
+ destructiveHint: false,
198
+ idempotentHint: true,
199
+ openWorldHint: false,
200
+ },
201
+ },
202
+ async (params) => {
203
+ try {
204
+ const { id, ...body } = params;
205
+ const result = await api("PUT", `blog/posts/${id}`, body);
206
+ return jsonResult(result);
207
+ } catch (error) {
208
+ return errorResult(error);
209
+ }
210
+ }
211
+ );
212
+
213
+ // Delete blog post
214
+ server.registerTool(
215
+ "neuron_delete_blog_post",
216
+ {
217
+ title: "Delete Blog Post",
218
+ description: "Permanently delete a blog post by its ID. This action cannot be undone.",
219
+ inputSchema: z.object({
220
+ id: z.string().describe("Unique identifier (UUID) of the blog post to delete"),
221
+ }),
222
+ annotations: {
223
+ readOnlyHint: false,
224
+ destructiveHint: true,
225
+ idempotentHint: true,
226
+ openWorldHint: false,
227
+ },
228
+ },
229
+ async (params) => {
230
+ try {
231
+ await api("DELETE", `blog/posts/${params.id}`);
232
+ return { content: [{ type: "text" as const, text: "Post deleted successfully" }] };
233
+ } catch (error) {
234
+ return errorResult(error);
235
+ }
236
+ }
237
+ );
238
+
239
+ // Publish post
240
+ server.registerTool(
241
+ "neuron_publish_blog_post",
242
+ {
243
+ title: "Publish Blog Post",
244
+ description: "Publish a blog post, making it publicly visible. The post must be in 'draft' status.",
245
+ inputSchema: z.object({
246
+ id: z.string().describe("Unique identifier (UUID) of the blog post to publish"),
247
+ }),
248
+ annotations: {
249
+ readOnlyHint: false,
250
+ destructiveHint: false,
251
+ idempotentHint: false,
252
+ openWorldHint: false,
253
+ },
254
+ },
255
+ async (params) => {
256
+ try {
257
+ const result = await api("POST", `blog/posts/${params.id}/publish`);
258
+ return jsonResult(result);
259
+ } catch (error) {
260
+ return errorResult(error);
261
+ }
262
+ }
263
+ );
264
+ }
@@ -0,0 +1,87 @@
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 registerBotApiKeyTools(server: McpServer): void {
6
+ // Create API Key
7
+ server.registerTool(
8
+ "neuron_create_api_key",
9
+ {
10
+ title: "Create API Key",
11
+ description: "Create a new API key for programmatic access to a specific bot. Store the key securely as it cannot be retrieved later.",
12
+ annotations: {
13
+ readOnlyHint: false,
14
+ destructiveHint: false,
15
+ idempotentHint: false,
16
+ openWorldHint: false,
17
+ },
18
+ inputSchema: z.object({
19
+ botId: z.string().describe("Unique identifier (UUID) of the bot to create the API key for"),
20
+ name: z.string().describe("Descriptive name for the API key to identify its purpose (e.g., 'Production CRM Integration')"),
21
+ expiresAt: z.string().optional().describe("ISO 8601 date-time when the key should expire (e.g., '2026-12-31T23:59:59Z'). Key never expires if omitted."),
22
+ }),
23
+ },
24
+ async (params) => {
25
+ try {
26
+ const { botId, ...keyData } = params;
27
+ const result = await api("POST", `bots/${botId}/api-keys`, keyData);
28
+ return jsonResult(result);
29
+ } catch (error) {
30
+ return errorResult(error);
31
+ }
32
+ }
33
+ );
34
+
35
+ // List API Keys
36
+ server.registerTool(
37
+ "neuron_list_api_keys",
38
+ {
39
+ title: "List API Keys",
40
+ description: "Retrieve all API keys associated with a specific bot, including their names, creation dates, expiration status, and revocation status.",
41
+ annotations: {
42
+ readOnlyHint: true,
43
+ destructiveHint: false,
44
+ idempotentHint: true,
45
+ openWorldHint: false,
46
+ },
47
+ inputSchema: z.object({
48
+ botId: z.string().describe("Unique identifier (UUID) of the bot whose API keys to retrieve"),
49
+ }),
50
+ },
51
+ async (params) => {
52
+ try {
53
+ const result = await api("GET", `bots/${params.botId}/api-keys`);
54
+ return jsonResult(result);
55
+ } catch (error) {
56
+ return errorResult(error);
57
+ }
58
+ }
59
+ );
60
+
61
+ // Revoke API Key
62
+ server.registerTool(
63
+ "neuron_revoke_api_key",
64
+ {
65
+ title: "Revoke API Key",
66
+ description: "Permanently revoke an API key to immediately invalidate it and prevent further programmatic access. This action cannot be undone.",
67
+ annotations: {
68
+ readOnlyHint: false,
69
+ destructiveHint: true,
70
+ idempotentHint: true,
71
+ openWorldHint: false,
72
+ },
73
+ inputSchema: z.object({
74
+ botId: z.string().describe("Unique identifier (UUID) of the bot that owns the API key"),
75
+ keyId: z.string().describe("Unique identifier (UUID) of the API key to revoke"),
76
+ }),
77
+ },
78
+ async (params) => {
79
+ try {
80
+ const result = await api("DELETE", `bots/${params.botId}/api-keys/${params.keyId}`);
81
+ return jsonResult(result);
82
+ } catch (error) {
83
+ return errorResult(error);
84
+ }
85
+ }
86
+ );
87
+ }
@@ -0,0 +1,193 @@
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 registerBotApiTools(server: McpServer): void {
6
+ // Bot API Chat
7
+ server.registerTool(
8
+ "neuron_bot_api_chat",
9
+ {
10
+ title: "Bot API Chat",
11
+ description: "Send a message to a bot via the API and receive a streaming or complete AI-generated response. Requires a valid API key with 'nrn_' prefix.",
12
+ annotations: {
13
+ readOnlyHint: false,
14
+ destructiveHint: false,
15
+ idempotentHint: false,
16
+ openWorldHint: false,
17
+ },
18
+ inputSchema: z.object({
19
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
20
+ message: z.string().describe("The message text to send to the bot"),
21
+ contactPhone: z.string().describe("Phone number of the contact sending the message (E.164 format, e.g., '+2348012345678')"),
22
+ contactName: z.string().optional().describe("Display name of the contact for conversation context"),
23
+ }),
24
+ },
25
+ async (params) => {
26
+ try {
27
+ const { apiKey, ...chatData } = params;
28
+ const result = await api(
29
+ "POST",
30
+ "bot-api/chat",
31
+ chatData,
32
+ undefined,
33
+ { Authorization: `Bearer ${apiKey}` }
34
+ );
35
+ return jsonResult(result);
36
+ } catch (error) {
37
+ return errorResult(error);
38
+ }
39
+ }
40
+ );
41
+
42
+ // Bot API Send
43
+ server.registerTool(
44
+ "neuron_bot_api_send",
45
+ {
46
+ title: "Bot API Send",
47
+ description: "Send an outbound message through the bot API to a specified phone number. Supports text and media messages. Requires a valid API key.",
48
+ annotations: {
49
+ readOnlyHint: false,
50
+ destructiveHint: false,
51
+ idempotentHint: false,
52
+ openWorldHint: false,
53
+ },
54
+ inputSchema: z.object({
55
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
56
+ to: z.string().describe("Recipient's phone number (E.164 format, e.g., '+2348012345678')"),
57
+ message: z.string().describe("The message text to send to the recipient"),
58
+ messageType: z.string().optional().describe("Type of message to send: 'text', 'image', 'video', 'audio', or 'document' (default: 'text')"),
59
+ mediaUrl: z.string().optional().describe("Public URL of the media file to attach (required for non-text message types)"),
60
+ channelId: z.string().optional().describe("Unique identifier (UUID) of a specific WhatsApp channel to send from. Uses the bot's primary channel if omitted."),
61
+ sendAt: z.string().optional().describe("ISO 8601 date-time for scheduled delivery (e.g., '2025-12-31T10:00:00Z'). Message sends immediately if omitted."),
62
+ }),
63
+ },
64
+ async (params) => {
65
+ try {
66
+ const { apiKey, ...sendData } = params;
67
+ const result = await api(
68
+ "POST",
69
+ "bot-api/send",
70
+ sendData,
71
+ undefined,
72
+ { Authorization: `Bearer ${apiKey}` }
73
+ );
74
+ return jsonResult(result);
75
+ } catch (error) {
76
+ return errorResult(error);
77
+ }
78
+ }
79
+ );
80
+
81
+ // Bot API List Conversations
82
+ server.registerTool(
83
+ "neuron_bot_api_list_conversations",
84
+ {
85
+ title: "Bot API List Conversations",
86
+ description: "Retrieve a paginated list of conversations managed by the bot. Supports filtering by conversation status. Requires a valid API key.",
87
+ annotations: {
88
+ readOnlyHint: true,
89
+ destructiveHint: false,
90
+ idempotentHint: true,
91
+ openWorldHint: false,
92
+ },
93
+ inputSchema: z.object({
94
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
95
+ status: z.string().optional().describe("Filter by conversation status: 'active', 'closed', or 'pending'"),
96
+ page: z.number().positive().optional().describe("Page number for pagination (default: 1)"),
97
+ limit: z.number().positive().optional().describe("Number of conversations per page (default: 20)"),
98
+ }),
99
+ },
100
+ async (params) => {
101
+ try {
102
+ const { apiKey, status, page, limit } = params;
103
+ const queryParams: Record<string, string> = {};
104
+ if (status) queryParams.status = status;
105
+ if (page) queryParams.page = page.toString();
106
+ if (limit) queryParams.limit = limit.toString();
107
+
108
+ const result = await api(
109
+ "GET",
110
+ "bot-api/conversations",
111
+ undefined,
112
+ queryParams,
113
+ { Authorization: `Bearer ${apiKey}` }
114
+ );
115
+ return jsonResult(result);
116
+ } catch (error) {
117
+ return errorResult(error);
118
+ }
119
+ }
120
+ );
121
+
122
+ // Bot API Get Messages
123
+ server.registerTool(
124
+ "neuron_bot_api_get_messages",
125
+ {
126
+ title: "Bot API Get Messages",
127
+ description: "Retrieve message history for a specific conversation with cursor-based pagination. Requires a valid API key.",
128
+ annotations: {
129
+ readOnlyHint: true,
130
+ destructiveHint: false,
131
+ idempotentHint: true,
132
+ openWorldHint: false,
133
+ },
134
+ inputSchema: z.object({
135
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
136
+ conversationId: z.string().describe("Unique identifier (UUID) of the conversation to retrieve messages from"),
137
+ before: z.string().optional().describe("Cursor for pagination — retrieve messages created before this message ID or ISO 8601 timestamp"),
138
+ limit: z.number().positive().optional().describe("Maximum number of messages to return (default: 50)"),
139
+ }),
140
+ },
141
+ async (params) => {
142
+ try {
143
+ const { apiKey, conversationId, before, limit } = params;
144
+ const queryParams: Record<string, string> = {};
145
+ if (before) queryParams.before = before;
146
+ if (limit) queryParams.limit = limit.toString();
147
+
148
+ const result = await api(
149
+ "GET",
150
+ `bot-api/conversations/${conversationId}/messages`,
151
+ undefined,
152
+ queryParams,
153
+ { Authorization: `Bearer ${apiKey}` }
154
+ );
155
+ return jsonResult(result);
156
+ } catch (error) {
157
+ return errorResult(error);
158
+ }
159
+ }
160
+ );
161
+
162
+ // Bot API Get Bot
163
+ server.registerTool(
164
+ "neuron_bot_api_get_bot",
165
+ {
166
+ title: "Bot API Get Bot",
167
+ description: "Retrieve configuration and status information about the bot associated with the provided API key.",
168
+ annotations: {
169
+ readOnlyHint: true,
170
+ destructiveHint: false,
171
+ idempotentHint: true,
172
+ openWorldHint: false,
173
+ },
174
+ inputSchema: z.object({
175
+ apiKey: z.string().describe("Bot API key with 'nrn_' prefix for authentication"),
176
+ }),
177
+ },
178
+ async (params) => {
179
+ try {
180
+ const result = await api(
181
+ "GET",
182
+ "bot-api/bot",
183
+ undefined,
184
+ undefined,
185
+ { Authorization: `Bearer ${params.apiKey}` }
186
+ );
187
+ return jsonResult(result);
188
+ } catch (error) {
189
+ return errorResult(error);
190
+ }
191
+ }
192
+ );
193
+ }