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,395 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { api, jsonResult, errorResult, getAuthToken, getBaseUrl } from "../client.js";
|
|
4
|
+
|
|
5
|
+
export function registerContactsTools(server: McpServer): void {
|
|
6
|
+
// List contacts
|
|
7
|
+
server.registerTool(
|
|
8
|
+
"neuron_list_contacts",
|
|
9
|
+
{
|
|
10
|
+
title: "List Contacts",
|
|
11
|
+
description: "Retrieve all contacts for the current organization with optional filtering by search term, tag, and pagination.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
search: z.string().optional().describe("Search term to filter contacts by name, phone number, or email address"),
|
|
14
|
+
tag: z.string().optional().describe("Tag name to filter contacts by category"),
|
|
15
|
+
doNotBroadcast: z.boolean().optional().describe("Filter to contacts that are (true) or are not (false) excluded from broadcasts"),
|
|
16
|
+
doNotAutoRespond: z.boolean().optional().describe("Filter to contacts the bot will never (true) / may (false) auto-respond to"),
|
|
17
|
+
page: z.number().int().positive().optional().describe("Page number for pagination (default: 1)"),
|
|
18
|
+
limit: z.number().int().positive().optional().describe("Number of contacts per page (default: 20)"),
|
|
19
|
+
}),
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: true,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
idempotentHint: true,
|
|
24
|
+
openWorldHint: false,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
async (params) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await api("GET", "/contacts", undefined, {
|
|
30
|
+
search: params.search,
|
|
31
|
+
tag: params.tag,
|
|
32
|
+
doNotBroadcast: params.doNotBroadcast === undefined ? undefined : String(params.doNotBroadcast),
|
|
33
|
+
doNotAutoRespond: params.doNotAutoRespond === undefined ? undefined : String(params.doNotAutoRespond),
|
|
34
|
+
page: params.page,
|
|
35
|
+
limit: params.limit,
|
|
36
|
+
});
|
|
37
|
+
return jsonResult(result);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return errorResult(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Search contacts
|
|
45
|
+
server.registerTool(
|
|
46
|
+
"neuron_search_contacts",
|
|
47
|
+
{
|
|
48
|
+
title: "Search Contacts",
|
|
49
|
+
description: "Search contacts by a query string across name, phone number, and email fields. Returns matching contacts ranked by relevance.",
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
q: z.string().describe("Search query string to match against contact name, phone, or email"),
|
|
52
|
+
}),
|
|
53
|
+
annotations: {
|
|
54
|
+
readOnlyHint: true,
|
|
55
|
+
destructiveHint: false,
|
|
56
|
+
idempotentHint: true,
|
|
57
|
+
openWorldHint: false,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
async (params) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await api("GET", "/contacts/search", undefined, { q: params.q });
|
|
63
|
+
return jsonResult(result);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return errorResult(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Get contact
|
|
71
|
+
server.registerTool(
|
|
72
|
+
"neuron_get_contact",
|
|
73
|
+
{
|
|
74
|
+
title: "Get Contact",
|
|
75
|
+
description: "Retrieve detailed information about a specific contact including their tags, notes, and associated lists.",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
id: z.string().describe("Unique identifier (UUID) of the contact"),
|
|
78
|
+
}),
|
|
79
|
+
annotations: {
|
|
80
|
+
readOnlyHint: true,
|
|
81
|
+
destructiveHint: false,
|
|
82
|
+
idempotentHint: true,
|
|
83
|
+
openWorldHint: false,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
async (params) => {
|
|
87
|
+
try {
|
|
88
|
+
const result = await api("GET", `/contacts/${params.id}`);
|
|
89
|
+
return jsonResult(result);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return errorResult(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Create contact
|
|
97
|
+
server.registerTool(
|
|
98
|
+
"neuron_create_contact",
|
|
99
|
+
{
|
|
100
|
+
title: "Create Contact",
|
|
101
|
+
description: "Create a new contact in the organization's address book. If notes are provided, they are saved as a structured note entry with source tracking.",
|
|
102
|
+
inputSchema: z.object({
|
|
103
|
+
name: z.string().describe("Full name of the contact"),
|
|
104
|
+
phone: z.string().describe("Contact phone number in E.164 format (e.g., +2348012345678)"),
|
|
105
|
+
email: z.string().optional().describe("Email address of the contact"),
|
|
106
|
+
tags: z.array(z.string()).optional().describe("Tags to categorize the contact (e.g., ['vendor', 'lagos'])"),
|
|
107
|
+
notes: z.string().optional().describe("Additional notes about the contact, saved as a structured entry"),
|
|
108
|
+
doNotBroadcast: z.boolean().optional().describe("If true, this contact is permanently excluded from all broadcasts"),
|
|
109
|
+
doNotAutoRespond: z.boolean().optional().describe("If true, the bot will never auto-respond to this contact (manual/API and human sends still work)"),
|
|
110
|
+
}),
|
|
111
|
+
annotations: {
|
|
112
|
+
readOnlyHint: false,
|
|
113
|
+
destructiveHint: false,
|
|
114
|
+
idempotentHint: false,
|
|
115
|
+
openWorldHint: false,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
async (params) => {
|
|
119
|
+
try {
|
|
120
|
+
const result = await api("POST", "/contacts", params);
|
|
121
|
+
return jsonResult(result);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return errorResult(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Update contact
|
|
129
|
+
server.registerTool(
|
|
130
|
+
"neuron_update_contact",
|
|
131
|
+
{
|
|
132
|
+
title: "Update Contact",
|
|
133
|
+
description: "Update an existing contact's details. If notes are provided, they are appended as a new structured note entry without overwriting existing notes.",
|
|
134
|
+
inputSchema: z.object({
|
|
135
|
+
id: z.string().describe("Unique identifier (UUID) of the contact to update"),
|
|
136
|
+
name: z.string().optional().describe("Updated full name for the contact"),
|
|
137
|
+
phone: z.string().optional().describe("Updated phone number in E.164 format (e.g., +2348012345678)"),
|
|
138
|
+
email: z.string().optional().describe("Updated email address for the contact"),
|
|
139
|
+
tags: z.array(z.string()).optional().describe("Updated tags for the contact (replaces existing tags)"),
|
|
140
|
+
notes: z.string().optional().describe("New note to append to the contact's existing notes"),
|
|
141
|
+
doNotBroadcast: z.boolean().optional().describe("Set true to exclude this contact from all broadcasts, false to allow"),
|
|
142
|
+
doNotAutoRespond: z.boolean().optional().describe("Set true so the bot never auto-responds to this contact, false to allow (manual/API and human sends are unaffected either way)"),
|
|
143
|
+
}),
|
|
144
|
+
annotations: {
|
|
145
|
+
readOnlyHint: false,
|
|
146
|
+
destructiveHint: false,
|
|
147
|
+
idempotentHint: true,
|
|
148
|
+
openWorldHint: false,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
async (params) => {
|
|
152
|
+
try {
|
|
153
|
+
const { id, ...body } = params;
|
|
154
|
+
const result = await api("PUT", `/contacts/${id}`, body);
|
|
155
|
+
return jsonResult(result);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return errorResult(error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Delete contact
|
|
163
|
+
server.registerTool(
|
|
164
|
+
"neuron_delete_contact",
|
|
165
|
+
{
|
|
166
|
+
title: "Delete Contact",
|
|
167
|
+
description: "Permanently delete a contact and remove them from all contact lists. This action cannot be undone.",
|
|
168
|
+
inputSchema: z.object({
|
|
169
|
+
id: z.string().describe("Unique identifier (UUID) of the contact to delete"),
|
|
170
|
+
}),
|
|
171
|
+
annotations: {
|
|
172
|
+
readOnlyHint: false,
|
|
173
|
+
destructiveHint: true,
|
|
174
|
+
idempotentHint: true,
|
|
175
|
+
openWorldHint: false,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
async (params) => {
|
|
179
|
+
try {
|
|
180
|
+
const result = await api("DELETE", `/contacts/${params.id}`);
|
|
181
|
+
return jsonResult(result);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return errorResult(error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Export contacts
|
|
189
|
+
server.registerTool(
|
|
190
|
+
"neuron_export_contacts",
|
|
191
|
+
{
|
|
192
|
+
title: "Export Contacts",
|
|
193
|
+
description: "Export all contacts as a downloadable dataset for the current organization.",
|
|
194
|
+
inputSchema: z.object({}),
|
|
195
|
+
annotations: {
|
|
196
|
+
readOnlyHint: true,
|
|
197
|
+
destructiveHint: false,
|
|
198
|
+
idempotentHint: true,
|
|
199
|
+
openWorldHint: false,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
async () => {
|
|
203
|
+
try {
|
|
204
|
+
const result = await api("GET", "/contacts/export");
|
|
205
|
+
return jsonResult(result);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return errorResult(error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Import contacts
|
|
213
|
+
server.registerTool(
|
|
214
|
+
"neuron_import_contacts",
|
|
215
|
+
{
|
|
216
|
+
title: "Import Contacts",
|
|
217
|
+
description: "Bulk import multiple contacts into the organization's address book. Accepts an array of contact objects with name, phone, and optional fields.",
|
|
218
|
+
inputSchema: z.object({
|
|
219
|
+
contacts: z.array(z.object({
|
|
220
|
+
name: z.string().describe("Full name of the contact"),
|
|
221
|
+
phone: z.string().describe("Contact phone number in E.164 format (e.g., +2348012345678)"),
|
|
222
|
+
email: z.string().optional().describe("Email address of the contact"),
|
|
223
|
+
tags: z.array(z.string()).optional().describe("Tags to categorize the contact"),
|
|
224
|
+
notes: z.string().optional().describe("Additional notes about the contact"),
|
|
225
|
+
})).describe("Array of contact objects to import into the organization"),
|
|
226
|
+
}),
|
|
227
|
+
annotations: {
|
|
228
|
+
readOnlyHint: false,
|
|
229
|
+
destructiveHint: false,
|
|
230
|
+
idempotentHint: false,
|
|
231
|
+
openWorldHint: false,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
async (params) => {
|
|
235
|
+
try {
|
|
236
|
+
const result = await api("POST", "/contacts/import", { contacts: params.contacts });
|
|
237
|
+
return jsonResult(result);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
return errorResult(error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Populate contacts
|
|
245
|
+
server.registerTool(
|
|
246
|
+
"neuron_populate_contacts",
|
|
247
|
+
{
|
|
248
|
+
title: "Populate Contacts",
|
|
249
|
+
description: "Enrich existing contacts with additional data from connected messaging channels. Syncs profile information from linked platforms.",
|
|
250
|
+
inputSchema: z.object({}),
|
|
251
|
+
annotations: {
|
|
252
|
+
readOnlyHint: false,
|
|
253
|
+
destructiveHint: false,
|
|
254
|
+
idempotentHint: true,
|
|
255
|
+
openWorldHint: false,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
async () => {
|
|
259
|
+
try {
|
|
260
|
+
const result = await api("POST", "/contacts/populate");
|
|
261
|
+
return jsonResult(result);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return errorResult(error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Sync WhatsApp contacts
|
|
269
|
+
server.registerTool(
|
|
270
|
+
"neuron_sync_whatsapp_contacts",
|
|
271
|
+
{
|
|
272
|
+
title: "Sync WhatsApp Contacts",
|
|
273
|
+
description: "Synchronize contacts from all connected WhatsApp channels into the organization's contact database. Creates new contacts for unseen numbers.",
|
|
274
|
+
inputSchema: z.object({}),
|
|
275
|
+
annotations: {
|
|
276
|
+
readOnlyHint: false,
|
|
277
|
+
destructiveHint: false,
|
|
278
|
+
idempotentHint: true,
|
|
279
|
+
openWorldHint: false,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
async () => {
|
|
283
|
+
try {
|
|
284
|
+
const result = await api("POST", "/contacts/sync-whatsapp");
|
|
285
|
+
return jsonResult(result);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return errorResult(error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// Semantic search contacts
|
|
293
|
+
server.registerTool(
|
|
294
|
+
"neuron_semantic_search_contacts",
|
|
295
|
+
{
|
|
296
|
+
title: "Semantic Search Contacts",
|
|
297
|
+
description:
|
|
298
|
+
"Search contacts using natural language AI-powered semantic search. Finds contacts based on the meaning of their notes — skills, services, schedules, preferences, etc. Returns ranked results with relevance scores and AI-generated match reasons.",
|
|
299
|
+
inputSchema: z.object({
|
|
300
|
+
query: z.string().describe("Natural language search query (e.g., 'who can deliver to Lagos on Monday?')"),
|
|
301
|
+
limit: z.number().int().positive().max(20).optional().describe("Maximum number of results to return (default: 10, max: 20)"),
|
|
302
|
+
}),
|
|
303
|
+
annotations: {
|
|
304
|
+
readOnlyHint: true,
|
|
305
|
+
destructiveHint: false,
|
|
306
|
+
idempotentHint: true,
|
|
307
|
+
openWorldHint: false,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
async (params) => {
|
|
311
|
+
try {
|
|
312
|
+
const queryParams: Record<string, string | number> = { q: params.query };
|
|
313
|
+
if (params.limit) queryParams.limit = params.limit;
|
|
314
|
+
const result = await api("GET", "/contacts/semantic-search", undefined, queryParams);
|
|
315
|
+
return jsonResult(result);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return errorResult(error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Audio-to-contact extraction
|
|
323
|
+
server.registerTool(
|
|
324
|
+
"neuron_audio_contact",
|
|
325
|
+
{
|
|
326
|
+
title: "Extract Contact from Audio",
|
|
327
|
+
description:
|
|
328
|
+
"Transcribe an audio recording and extract contact information (name, phone, email, notes, tags). " +
|
|
329
|
+
"Automatically saves the extracted contact to the organization's address book.",
|
|
330
|
+
inputSchema: z.object({
|
|
331
|
+
audioBase64: z.string().describe("Base64-encoded audio data containing spoken contact information"),
|
|
332
|
+
audioFormat: z
|
|
333
|
+
.string()
|
|
334
|
+
.optional()
|
|
335
|
+
.default("webm")
|
|
336
|
+
.describe("Audio format hint for decoding (default: 'webm')"),
|
|
337
|
+
}),
|
|
338
|
+
annotations: {
|
|
339
|
+
readOnlyHint: false,
|
|
340
|
+
destructiveHint: false,
|
|
341
|
+
idempotentHint: false,
|
|
342
|
+
openWorldHint: false,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
async (params) => {
|
|
346
|
+
try {
|
|
347
|
+
// Decode base64 to buffer and create a File-like object for the API
|
|
348
|
+
const buffer = Buffer.from(params.audioBase64, "base64");
|
|
349
|
+
const blob = new Blob([buffer], { type: `audio/${params.audioFormat || "webm"}` });
|
|
350
|
+
|
|
351
|
+
// Create FormData and send to extract-audio endpoint
|
|
352
|
+
const formData = new FormData();
|
|
353
|
+
formData.append("audio", blob, `recording.${params.audioFormat || "webm"}`);
|
|
354
|
+
|
|
355
|
+
// Use raw fetch since api() doesn't handle FormData
|
|
356
|
+
const response = await fetch(`${getBaseUrl()}/contacts/extract-audio`, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: {
|
|
359
|
+
Authorization: `Bearer ${getAuthToken()}`,
|
|
360
|
+
},
|
|
361
|
+
body: formData,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const extractResult: any = await response.json();
|
|
365
|
+
if (!extractResult.success || extractResult.data?.extracted?.error) {
|
|
366
|
+
return errorResult(
|
|
367
|
+
new Error(extractResult.data?.extracted?.error || extractResult.error?.message || "Extraction failed"),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const { extracted, transcript } = extractResult.data;
|
|
372
|
+
|
|
373
|
+
// Auto-save the contact
|
|
374
|
+
const saveResult = await api("POST", "/contacts", {
|
|
375
|
+
name: extracted.name,
|
|
376
|
+
phone: extracted.phone,
|
|
377
|
+
email: extracted.email,
|
|
378
|
+
tags: extracted.tags || [],
|
|
379
|
+
notes: extracted.notes,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return jsonResult({
|
|
383
|
+
success: true,
|
|
384
|
+
data: {
|
|
385
|
+
transcript,
|
|
386
|
+
extracted,
|
|
387
|
+
contact: (saveResult as any).data,
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
} catch (error) {
|
|
391
|
+
return errorResult(error);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
}
|