neuron-mcp-server 1.1.0 → 1.1.1

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -146
  3. package/package.json +36 -3
  4. package/.env +0 -13
  5. package/AI_PERSONALITY_DESIGN_RESEARCH.md +0 -1020
  6. package/Dockerfile +0 -18
  7. package/scripts/setup-delivahere-bot.ts +0 -804
  8. package/scripts/test-delivahere-bot.ts +0 -128
  9. package/scripts/update-letschop-prompt.ts +0 -194
  10. package/server.json +0 -26
  11. package/smithery.yaml +0 -19
  12. package/src/client.ts +0 -169
  13. package/src/index.ts +0 -256
  14. package/src/tools/ai.ts +0 -35
  15. package/src/tools/approvals.ts +0 -231
  16. package/src/tools/audit.ts +0 -49
  17. package/src/tools/auth.ts +0 -536
  18. package/src/tools/billing.ts +0 -79
  19. package/src/tools/blog.ts +0 -264
  20. package/src/tools/bot-api-keys.ts +0 -87
  21. package/src/tools/bot-api.ts +0 -193
  22. package/src/tools/bots.ts +0 -320
  23. package/src/tools/broadcasts.ts +0 -224
  24. package/src/tools/builtin-tools.ts +0 -108
  25. package/src/tools/campaigns.ts +0 -429
  26. package/src/tools/channels.ts +0 -520
  27. package/src/tools/contact-lists.ts +0 -320
  28. package/src/tools/contacts.ts +0 -395
  29. package/src/tools/conversations.ts +0 -349
  30. package/src/tools/flows.ts +0 -298
  31. package/src/tools/group-management.ts +0 -265
  32. package/src/tools/knowledge-bases.ts +0 -451
  33. package/src/tools/leads.ts +0 -104
  34. package/src/tools/list-campaigns.ts +0 -190
  35. package/src/tools/list-pool.ts +0 -389
  36. package/src/tools/media.ts +0 -50
  37. package/src/tools/newsletters.ts +0 -271
  38. package/src/tools/organizations.ts +0 -287
  39. package/src/tools/outbound-webhooks.ts +0 -181
  40. package/src/tools/payouts.ts +0 -133
  41. package/src/tools/personas.ts +0 -145
  42. package/src/tools/polls.ts +0 -32
  43. package/src/tools/pool.ts +0 -293
  44. package/src/tools/products.ts +0 -280
  45. package/src/tools/profile-privacy.ts +0 -215
  46. package/src/tools/reflections.ts +0 -124
  47. package/src/tools/scheduled-messages.ts +0 -144
  48. package/src/tools/social-channels.ts +0 -396
  49. package/src/tools/tasks.ts +0 -198
  50. package/src/tools/tools.ts +0 -255
  51. package/src/tools/wallets.ts +0 -107
  52. package/src/tools/webhooks.ts +0 -166
  53. package/src/tools/whatsapp-actions.ts +0 -203
  54. package/tsconfig.json +0 -20
@@ -1,128 +0,0 @@
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
- });
@@ -1,194 +0,0 @@
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 DELETED
@@ -1,26 +0,0 @@
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 DELETED
@@ -1,19 +0,0 @@
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 DELETED
@@ -1,169 +0,0 @@
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
- }