databar-mcp-server 1.3.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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/audit.d.ts +15 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +33 -0
- package/dist/audit.js.map +1 -0
- package/dist/cache.d.ts +40 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +78 -0
- package/dist/cache.js.map +1 -0
- package/dist/databar-client.d.ts +47 -0
- package/dist/databar-client.d.ts.map +1 -0
- package/dist/databar-client.js +377 -0
- package/dist/databar-client.js.map +1 -0
- package/dist/guards.d.ts +42 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +108 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +637 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +65 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +226 -0
- package/dist/utils.js.map +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Databar MCP Server
|
|
4
|
+
* Enables AI assistants to interact with Databar.ai's enrichment API
|
|
5
|
+
*/
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import dotenv from 'dotenv';
|
|
10
|
+
import { DatabarClient } from './databar-client.js';
|
|
11
|
+
import { Cache } from './cache.js';
|
|
12
|
+
import { categorizeEnrichment, searchEnrichments, filterByCategory, formatEnrichmentForDisplay, formatWaterfallForDisplay, formatResults, validateParams, formatTableForDisplay, formatColumnForDisplay, formatTableEnrichmentForDisplay, formatCreateRowsResponse, formatPatchRowsResponse, formatUpsertRowsResponse } from './utils.js';
|
|
13
|
+
import { loadSpendingConfig, checkSpendingGuard, unsafeModeWarning, validateBulkArray, validateRowBatchSize, sanitizeResult } from './guards.js';
|
|
14
|
+
import { auditLog } from './audit.js';
|
|
15
|
+
dotenv.config();
|
|
16
|
+
const API_KEY = process.env.DATABAR_API_KEY;
|
|
17
|
+
if (!API_KEY) {
|
|
18
|
+
console.error('Error: DATABAR_API_KEY environment variable is required');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const config = {
|
|
22
|
+
apiKey: API_KEY,
|
|
23
|
+
baseUrl: process.env.DATABAR_BASE_URL || 'https://api.databar.ai/v1',
|
|
24
|
+
cacheTtlHours: parseInt(process.env.CACHE_TTL_HOURS || '24'),
|
|
25
|
+
maxPollAttempts: parseInt(process.env.MAX_POLL_ATTEMPTS || '150'),
|
|
26
|
+
pollIntervalMs: parseInt(process.env.POLL_INTERVAL_MS || '2000'),
|
|
27
|
+
};
|
|
28
|
+
const spendingConfig = loadSpendingConfig();
|
|
29
|
+
const databarClient = new DatabarClient(config);
|
|
30
|
+
const cache = new Cache(config.cacheTtlHours);
|
|
31
|
+
let enrichmentsCache = null;
|
|
32
|
+
let enrichmentsCacheTime = 0;
|
|
33
|
+
const ENRICHMENTS_CACHE_TTL = 5 * 60 * 1000;
|
|
34
|
+
async function getCachedEnrichments() {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
if (enrichmentsCache && (now - enrichmentsCacheTime) < ENRICHMENTS_CACHE_TTL) {
|
|
37
|
+
return enrichmentsCache;
|
|
38
|
+
}
|
|
39
|
+
const enrichments = await databarClient.getAllEnrichments();
|
|
40
|
+
enrichmentsCache = enrichments.map(categorizeEnrichment);
|
|
41
|
+
enrichmentsCacheTime = now;
|
|
42
|
+
return enrichmentsCache;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Helper: check spending guard and return an MCP error response if blocked, else null.
|
|
46
|
+
*/
|
|
47
|
+
async function guardSpending(toolName, estimatedCost, params) {
|
|
48
|
+
const blocked = await checkSpendingGuard(databarClient, estimatedCost, spendingConfig);
|
|
49
|
+
if (blocked) {
|
|
50
|
+
auditLog({ timestamp: new Date().toISOString(), tool: toolName, params, estimatedCost, result: 'blocked', message: blocked });
|
|
51
|
+
return { content: [{ type: 'text', text: blocked }], isError: true };
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Helper: wrap result text with sanitization.
|
|
57
|
+
*/
|
|
58
|
+
function safeResult(text) {
|
|
59
|
+
return sanitizeResult(text, spendingConfig.maxResultLength);
|
|
60
|
+
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Tool Definitions
|
|
63
|
+
// ============================================================================
|
|
64
|
+
const TOOLS = [
|
|
65
|
+
// --- Enrichment tools ---
|
|
66
|
+
{
|
|
67
|
+
name: 'search_enrichments',
|
|
68
|
+
description: 'Search and discover available data enrichments. Use this to find the right enrichment for a specific task (e.g., "linkedin profile", "email finder", "company data"). Returns a list of matching enrichments with their IDs, descriptions, required parameters, and pricing.',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
query: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'Search query to find enrichments (e.g., "linkedin", "email verification", "company data")'
|
|
75
|
+
},
|
|
76
|
+
category: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
enum: ['people', 'company', 'email', 'phone', 'social', 'financial', 'verification', 'other'],
|
|
79
|
+
description: 'Optional: Filter by category'
|
|
80
|
+
},
|
|
81
|
+
limit: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: 'Maximum number of results to return (default: 10)',
|
|
84
|
+
default: 10
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
required: ['query']
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'get_enrichment_details',
|
|
92
|
+
description: 'Get detailed information about a specific enrichment, including all required and optional parameters, response fields, pricing, and data source. Use this before running an enrichment to understand what parameters are needed.',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
enrichment_id: {
|
|
97
|
+
type: 'number',
|
|
98
|
+
description: 'The ID of the enrichment to get details for'
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
required: ['enrichment_id']
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'run_enrichment',
|
|
106
|
+
description: 'Execute a data enrichment with the provided parameters. Automatically handles async execution and polling, returning final results. Results are cached for 24 hours to reduce costs. Subject to spending limits (DATABAR_MAX_COST_PER_REQUEST, DATABAR_MIN_BALANCE).',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
enrichment_id: {
|
|
111
|
+
type: 'number',
|
|
112
|
+
description: 'The ID of the enrichment to run'
|
|
113
|
+
},
|
|
114
|
+
params: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
description: 'Parameters required by the enrichment (e.g., {"email": "test@example.com"})',
|
|
117
|
+
additionalProperties: true
|
|
118
|
+
},
|
|
119
|
+
skip_cache: {
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
description: 'Skip cache and fetch fresh data (default: false)',
|
|
122
|
+
default: false
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: ['enrichment_id', 'params']
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'run_bulk_enrichment',
|
|
130
|
+
description: 'Execute an enrichment on multiple inputs at once. Provide an array of parameter objects. Subject to spending limits.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
enrichment_id: {
|
|
135
|
+
type: 'number',
|
|
136
|
+
description: 'The ID of the enrichment to run'
|
|
137
|
+
},
|
|
138
|
+
params_list: {
|
|
139
|
+
type: 'array',
|
|
140
|
+
items: { type: 'object', additionalProperties: true },
|
|
141
|
+
description: 'Array of parameter objects, one per record'
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
required: ['enrichment_id', 'params_list']
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
// --- Waterfall tools ---
|
|
148
|
+
{
|
|
149
|
+
name: 'search_waterfalls',
|
|
150
|
+
description: 'Search available waterfall enrichments. Waterfalls try multiple data providers in sequence until one succeeds, maximizing data retrieval success rate.',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
query: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
description: 'Search query (e.g., "email finder", "phone lookup")'
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
required: ['query']
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'run_waterfall',
|
|
164
|
+
description: 'Execute a waterfall enrichment that tries multiple providers until one succeeds. Subject to spending limits.',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
waterfall_identifier: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
description: 'The identifier of the waterfall to run (e.g., "email_getter")'
|
|
171
|
+
},
|
|
172
|
+
params: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
description: 'Parameters required by the waterfall',
|
|
175
|
+
additionalProperties: true
|
|
176
|
+
},
|
|
177
|
+
provider_ids: {
|
|
178
|
+
type: 'array',
|
|
179
|
+
items: { type: 'number' },
|
|
180
|
+
description: 'Optional: Specific provider IDs to use (default: uses all in cost-optimized order)'
|
|
181
|
+
},
|
|
182
|
+
email_verifier: {
|
|
183
|
+
type: 'number',
|
|
184
|
+
description: 'Optional: Email verifier enrichment ID to verify results'
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
required: ['waterfall_identifier', 'params']
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'run_bulk_waterfall',
|
|
192
|
+
description: 'Execute a waterfall enrichment on multiple inputs at once. Subject to spending limits.',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
waterfall_identifier: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
description: 'The identifier of the waterfall to run'
|
|
199
|
+
},
|
|
200
|
+
params_list: {
|
|
201
|
+
type: 'array',
|
|
202
|
+
items: { type: 'object', additionalProperties: true },
|
|
203
|
+
description: 'Array of parameter objects, one per record'
|
|
204
|
+
},
|
|
205
|
+
provider_ids: {
|
|
206
|
+
type: 'array',
|
|
207
|
+
items: { type: 'number' },
|
|
208
|
+
description: 'Optional: Specific provider IDs to use'
|
|
209
|
+
},
|
|
210
|
+
email_verifier: {
|
|
211
|
+
type: 'number',
|
|
212
|
+
description: 'Optional: Email verifier enrichment ID'
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
required: ['waterfall_identifier', 'params_list']
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
// --- Table tools ---
|
|
219
|
+
{
|
|
220
|
+
name: 'create_table',
|
|
221
|
+
description: 'Create a new empty table in your Databar workspace.',
|
|
222
|
+
inputSchema: { type: 'object', properties: {} }
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'list_tables',
|
|
226
|
+
description: 'List all tables in your Databar workspace. Returns table UUIDs, names, and timestamps.',
|
|
227
|
+
inputSchema: { type: 'object', properties: {} }
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'get_table_columns',
|
|
231
|
+
description: 'Get all columns defined on a table. Returns column names, types, and identifiers.',
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
table_uuid: { type: 'string', description: 'The UUID of the table' }
|
|
236
|
+
},
|
|
237
|
+
required: ['table_uuid']
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: 'get_table_rows',
|
|
242
|
+
description: 'Get rows from a table with pagination. Returns up to 1000 rows per page by default.',
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
table_uuid: { type: 'string', description: 'The UUID of the table' },
|
|
247
|
+
page: { type: 'number', description: 'Page number (default: 1)', default: 1 },
|
|
248
|
+
per_page: { type: 'number', description: 'Rows per page (default: 1000, max: 1000)', default: 1000 }
|
|
249
|
+
},
|
|
250
|
+
required: ['table_uuid']
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'create_rows',
|
|
255
|
+
description: 'Insert new rows into a table (max 100 per request).',
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: 'object',
|
|
258
|
+
properties: {
|
|
259
|
+
table_id: { type: 'string', description: 'The ID of the table' },
|
|
260
|
+
records: {
|
|
261
|
+
type: 'array',
|
|
262
|
+
items: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: { fields: { type: 'object', additionalProperties: true } },
|
|
265
|
+
required: ['fields']
|
|
266
|
+
},
|
|
267
|
+
description: 'Array of row records (max 100)',
|
|
268
|
+
maxItems: 100
|
|
269
|
+
},
|
|
270
|
+
options: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
allowNewColumns: { type: 'boolean', description: 'Auto-create columns (default: false)' },
|
|
274
|
+
typecast: { type: 'boolean', description: 'Attempt type coercion (default: true)' },
|
|
275
|
+
dedupe: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
properties: {
|
|
278
|
+
enabled: { type: 'boolean' },
|
|
279
|
+
keys: { type: 'array', items: { type: 'string' } }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
required: ['table_id', 'records']
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'patch_rows',
|
|
290
|
+
description: 'Update specific fields on existing rows by row ID (max 100 per request).',
|
|
291
|
+
inputSchema: {
|
|
292
|
+
type: 'object',
|
|
293
|
+
properties: {
|
|
294
|
+
table_id: { type: 'string', description: 'The ID of the table' },
|
|
295
|
+
rows: {
|
|
296
|
+
type: 'array',
|
|
297
|
+
items: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
id: { type: 'string', description: 'Row ID' },
|
|
301
|
+
fields: { type: 'object', additionalProperties: true }
|
|
302
|
+
},
|
|
303
|
+
required: ['id', 'fields']
|
|
304
|
+
},
|
|
305
|
+
description: 'Array of patch operations (max 100)',
|
|
306
|
+
maxItems: 100
|
|
307
|
+
},
|
|
308
|
+
overwrite: { type: 'boolean', description: 'Overwrite non-empty cells (default: true)', default: true }
|
|
309
|
+
},
|
|
310
|
+
required: ['table_id', 'rows']
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: 'upsert_rows',
|
|
315
|
+
description: 'Insert or update rows by matching key (max 100 per request).',
|
|
316
|
+
inputSchema: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
properties: {
|
|
319
|
+
table_id: { type: 'string', description: 'The ID of the table' },
|
|
320
|
+
rows: {
|
|
321
|
+
type: 'array',
|
|
322
|
+
items: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
key: { type: 'object', additionalProperties: true },
|
|
326
|
+
fields: { type: 'object', additionalProperties: true }
|
|
327
|
+
},
|
|
328
|
+
required: ['key', 'fields']
|
|
329
|
+
},
|
|
330
|
+
description: 'Array of upsert operations (max 100)',
|
|
331
|
+
maxItems: 100
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
required: ['table_id', 'rows']
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'get_table_enrichments',
|
|
339
|
+
description: 'List all enrichments configured on a table.',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
table_uuid: { type: 'string', description: 'The UUID of the table' }
|
|
344
|
+
},
|
|
345
|
+
required: ['table_uuid']
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: 'add_table_enrichment',
|
|
350
|
+
description: 'Add an enrichment to a table with column mapping.',
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: 'object',
|
|
353
|
+
properties: {
|
|
354
|
+
table_uuid: { type: 'string', description: 'The UUID of the table' },
|
|
355
|
+
enrichment_id: { type: 'number', description: 'The enrichment ID to add' },
|
|
356
|
+
mapping: { type: 'object', description: 'Parameter-to-column mapping', additionalProperties: true }
|
|
357
|
+
},
|
|
358
|
+
required: ['table_uuid', 'enrichment_id', 'mapping']
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: 'run_table_enrichment',
|
|
363
|
+
description: 'Trigger an enrichment to run on all rows in a table. Subject to spending limits.',
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
table_uuid: { type: 'string', description: 'The UUID of the table' },
|
|
368
|
+
enrichment_id: { type: 'string', description: 'The table enrichment ID to run' }
|
|
369
|
+
},
|
|
370
|
+
required: ['table_uuid', 'enrichment_id']
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
// --- User tools ---
|
|
374
|
+
{
|
|
375
|
+
name: 'get_user_balance',
|
|
376
|
+
description: 'Get the current user\'s credit balance and account information.',
|
|
377
|
+
inputSchema: { type: 'object', properties: {} }
|
|
378
|
+
}
|
|
379
|
+
];
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// Server Setup
|
|
382
|
+
// ============================================================================
|
|
383
|
+
const server = new Server({ name: 'databar-mcp-server', version: '1.3.0' }, { capabilities: { tools: {} } });
|
|
384
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
385
|
+
return { tools: TOOLS };
|
|
386
|
+
});
|
|
387
|
+
// ============================================================================
|
|
388
|
+
// Tool Handlers
|
|
389
|
+
// ============================================================================
|
|
390
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
391
|
+
const { name, arguments: args } = request.params;
|
|
392
|
+
const ts = new Date().toISOString();
|
|
393
|
+
try {
|
|
394
|
+
switch (name) {
|
|
395
|
+
// ----------------------------------------------------------------
|
|
396
|
+
// Enrichment handlers
|
|
397
|
+
// ----------------------------------------------------------------
|
|
398
|
+
case 'search_enrichments': {
|
|
399
|
+
const { query, category, limit = 10 } = args;
|
|
400
|
+
auditLog({ timestamp: ts, tool: name, params: { query, category, limit }, result: 'success' });
|
|
401
|
+
let enrichments = await getCachedEnrichments();
|
|
402
|
+
if (category)
|
|
403
|
+
enrichments = filterByCategory(enrichments, category);
|
|
404
|
+
enrichments = searchEnrichments(enrichments, query).slice(0, limit);
|
|
405
|
+
if (enrichments.length === 0) {
|
|
406
|
+
return { content: [{ type: 'text', text: `No enrichments found matching "${query}".` }] };
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
content: [{ type: 'text', text: safeResult(`Found ${enrichments.length} enrichment(s):\n\n${enrichments.map(formatEnrichmentForDisplay).join('\n\n---\n\n')}`) }]
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
case 'get_enrichment_details': {
|
|
413
|
+
const { enrichment_id } = args;
|
|
414
|
+
auditLog({ timestamp: ts, tool: name, params: { enrichment_id }, result: 'success' });
|
|
415
|
+
const enrichment = await databarClient.getEnrichmentDetails(enrichment_id);
|
|
416
|
+
const categorized = categorizeEnrichment(enrichment);
|
|
417
|
+
const details = {
|
|
418
|
+
id: categorized.id, name: categorized.name, category: categorized.category,
|
|
419
|
+
description: categorized.description, data_source: categorized.data_source,
|
|
420
|
+
price: categorized.price, auth_method: categorized.auth_method,
|
|
421
|
+
parameters: categorized.params?.map(p => ({ name: p.name, required: p.is_required, type: p.type_field, description: p.description })),
|
|
422
|
+
response_fields: categorized.response_fields?.map(f => ({ name: f.name, type: f.type_field }))
|
|
423
|
+
};
|
|
424
|
+
return {
|
|
425
|
+
content: [{ type: 'text', text: safeResult(`Enrichment Details:\n\n${formatEnrichmentForDisplay(categorized)}\n\nFull Details:\n${JSON.stringify(details, null, 2)}`) }]
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
case 'run_enrichment': {
|
|
429
|
+
const { enrichment_id, params, skip_cache = false } = args;
|
|
430
|
+
const enrichment = await databarClient.getEnrichmentDetails(enrichment_id);
|
|
431
|
+
const validation = validateParams(enrichment, params);
|
|
432
|
+
if (!validation.valid) {
|
|
433
|
+
auditLog({ timestamp: ts, tool: name, params: { enrichment_id }, result: 'error', message: 'validation failed' });
|
|
434
|
+
return { content: [{ type: 'text', text: `Parameter validation failed:\n${validation.errors.join('\n')}` }], isError: true };
|
|
435
|
+
}
|
|
436
|
+
if (!skip_cache) {
|
|
437
|
+
const cachedData = cache.get(enrichment_id, params);
|
|
438
|
+
if (cachedData) {
|
|
439
|
+
auditLog({ timestamp: ts, tool: name, params: { enrichment_id }, estimatedCost: 0, result: 'cached' });
|
|
440
|
+
return { content: [{ type: 'text', text: safeResult(`Enrichment completed (cached result)\n\nEnrichment: ${enrichment.name}\nCost: 0 credits (from cache)\n\nResults:\n${formatResults(cachedData)}`) }] };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const guard = await guardSpending(name, enrichment.price, { enrichment_id });
|
|
444
|
+
if (guard)
|
|
445
|
+
return guard;
|
|
446
|
+
const data = await databarClient.runEnrichmentSync(enrichment_id, params);
|
|
447
|
+
cache.set(enrichment_id, params, data);
|
|
448
|
+
auditLog({ timestamp: ts, tool: name, params: { enrichment_id }, estimatedCost: enrichment.price, result: 'success' });
|
|
449
|
+
const warn = unsafeModeWarning(spendingConfig, enrichment.price);
|
|
450
|
+
return { content: [{ type: 'text', text: safeResult(`${warn}Enrichment completed successfully\n\nEnrichment: ${enrichment.name}\nCost: ${enrichment.price} credits\n\nResults:\n${formatResults(data)}`) }] };
|
|
451
|
+
}
|
|
452
|
+
case 'run_bulk_enrichment': {
|
|
453
|
+
const { enrichment_id, params_list } = args;
|
|
454
|
+
const sizeErr = validateBulkArray(params_list, 'params_list');
|
|
455
|
+
if (sizeErr) {
|
|
456
|
+
auditLog({ timestamp: ts, tool: name, params: { enrichment_id, count: params_list?.length }, result: 'error', message: sizeErr });
|
|
457
|
+
return { content: [{ type: 'text', text: sizeErr }], isError: true };
|
|
458
|
+
}
|
|
459
|
+
const enrichment = await databarClient.getEnrichmentDetails(enrichment_id);
|
|
460
|
+
const estimatedCost = enrichment.price * params_list.length;
|
|
461
|
+
const guard = await guardSpending(name, estimatedCost, { enrichment_id, count: params_list.length });
|
|
462
|
+
if (guard)
|
|
463
|
+
return guard;
|
|
464
|
+
const data = await databarClient.runBulkEnrichmentSync(enrichment_id, params_list);
|
|
465
|
+
auditLog({ timestamp: ts, tool: name, params: { enrichment_id, count: params_list.length }, estimatedCost, result: 'success' });
|
|
466
|
+
const warn = unsafeModeWarning(spendingConfig, estimatedCost, params_list.length);
|
|
467
|
+
return { content: [{ type: 'text', text: safeResult(`${warn}Bulk enrichment completed\n\nEnrichment: ${enrichment.name}\nRecords: ${params_list.length}\nEstimated cost: ~${estimatedCost.toFixed(2)} credits\n\nResults:\n${formatResults(data)}`) }] };
|
|
468
|
+
}
|
|
469
|
+
// ----------------------------------------------------------------
|
|
470
|
+
// Waterfall handlers
|
|
471
|
+
// ----------------------------------------------------------------
|
|
472
|
+
case 'search_waterfalls': {
|
|
473
|
+
const { query } = args;
|
|
474
|
+
auditLog({ timestamp: ts, tool: name, params: { query }, result: 'success' });
|
|
475
|
+
const waterfalls = await databarClient.getAllWaterfalls();
|
|
476
|
+
const lowerQuery = query.toLowerCase();
|
|
477
|
+
const filtered = waterfalls.filter(w => w.name.toLowerCase().includes(lowerQuery) ||
|
|
478
|
+
w.description.toLowerCase().includes(lowerQuery) ||
|
|
479
|
+
w.identifier.toLowerCase().includes(lowerQuery));
|
|
480
|
+
if (filtered.length === 0) {
|
|
481
|
+
return { content: [{ type: 'text', text: `No waterfalls found matching "${query}".` }] };
|
|
482
|
+
}
|
|
483
|
+
return { content: [{ type: 'text', text: safeResult(`Found ${filtered.length} waterfall(s):\n\n${filtered.map(formatWaterfallForDisplay).join('\n\n---\n\n')}`) }] };
|
|
484
|
+
}
|
|
485
|
+
case 'run_waterfall': {
|
|
486
|
+
const { waterfall_identifier, params, provider_ids, email_verifier } = args;
|
|
487
|
+
const waterfall = await databarClient.getWaterfallDetails(waterfall_identifier);
|
|
488
|
+
const maxPrice = Math.max(...waterfall.available_enrichments.map(e => parseFloat(e.price)));
|
|
489
|
+
const guard = await guardSpending(name, maxPrice, { waterfall_identifier });
|
|
490
|
+
if (guard)
|
|
491
|
+
return guard;
|
|
492
|
+
const result = await databarClient.runWaterfallSync(waterfall_identifier, params, provider_ids, email_verifier);
|
|
493
|
+
if (!result.data || result.data.length === 0) {
|
|
494
|
+
auditLog({ timestamp: ts, tool: name, params: { waterfall_identifier }, result: 'success', message: 'no data' });
|
|
495
|
+
return { content: [{ type: 'text', text: 'Waterfall completed but no data was found from any provider.' }] };
|
|
496
|
+
}
|
|
497
|
+
const resultData = result.data[0];
|
|
498
|
+
const totalCost = resultData.steps.reduce((sum, step) => sum + parseFloat(step.cost), 0);
|
|
499
|
+
auditLog({ timestamp: ts, tool: name, params: { waterfall_identifier }, estimatedCost: totalCost, result: 'success' });
|
|
500
|
+
const warn = unsafeModeWarning(spendingConfig, totalCost);
|
|
501
|
+
return { content: [{ type: 'text', text: safeResult(`${warn}Waterfall completed\n\nTotal Cost: ${totalCost.toFixed(2)} credits\n\nProviders Tried:\n${resultData.steps.map(s => `- ${s.provider}: ${s.result} (${s.cost} credits)`).join('\n')}\n\nResults:\n${formatResults(resultData.result)}`) }] };
|
|
502
|
+
}
|
|
503
|
+
case 'run_bulk_waterfall': {
|
|
504
|
+
const { waterfall_identifier, params_list, provider_ids, email_verifier } = args;
|
|
505
|
+
const sizeErr = validateBulkArray(params_list, 'params_list');
|
|
506
|
+
if (sizeErr) {
|
|
507
|
+
auditLog({ timestamp: ts, tool: name, params: { waterfall_identifier, count: params_list?.length }, result: 'error', message: sizeErr });
|
|
508
|
+
return { content: [{ type: 'text', text: sizeErr }], isError: true };
|
|
509
|
+
}
|
|
510
|
+
const waterfall = await databarClient.getWaterfallDetails(waterfall_identifier);
|
|
511
|
+
const maxPrice = Math.max(...waterfall.available_enrichments.map(e => parseFloat(e.price)));
|
|
512
|
+
const estimatedCost = maxPrice * params_list.length;
|
|
513
|
+
const guard = await guardSpending(name, estimatedCost, { waterfall_identifier, count: params_list.length });
|
|
514
|
+
if (guard)
|
|
515
|
+
return guard;
|
|
516
|
+
const data = await databarClient.runBulkWaterfallSync(waterfall_identifier, params_list, provider_ids, email_verifier);
|
|
517
|
+
auditLog({ timestamp: ts, tool: name, params: { waterfall_identifier, count: params_list.length }, estimatedCost, result: 'success' });
|
|
518
|
+
const warn = unsafeModeWarning(spendingConfig, estimatedCost, params_list.length);
|
|
519
|
+
return { content: [{ type: 'text', text: safeResult(`${warn}Bulk waterfall completed\n\nRecords: ${params_list.length}\nEstimated max cost: ~${estimatedCost.toFixed(2)} credits\n\nResults:\n${formatResults(data)}`) }] };
|
|
520
|
+
}
|
|
521
|
+
// ----------------------------------------------------------------
|
|
522
|
+
// Table handlers
|
|
523
|
+
// ----------------------------------------------------------------
|
|
524
|
+
case 'create_table': {
|
|
525
|
+
const table = await databarClient.createTable();
|
|
526
|
+
auditLog({ timestamp: ts, tool: name, params: {}, result: 'success' });
|
|
527
|
+
return { content: [{ type: 'text', text: `Table created successfully\n\n${formatTableForDisplay(table)}` }] };
|
|
528
|
+
}
|
|
529
|
+
case 'list_tables': {
|
|
530
|
+
const tables = await databarClient.getAllTables();
|
|
531
|
+
auditLog({ timestamp: ts, tool: name, params: {}, result: 'success' });
|
|
532
|
+
if (tables.length === 0)
|
|
533
|
+
return { content: [{ type: 'text', text: 'No tables found in your workspace.' }] };
|
|
534
|
+
return { content: [{ type: 'text', text: safeResult(`Found ${tables.length} table(s):\n\n${tables.map(formatTableForDisplay).join('\n\n---\n\n')}`) }] };
|
|
535
|
+
}
|
|
536
|
+
case 'get_table_columns': {
|
|
537
|
+
const { table_uuid } = args;
|
|
538
|
+
const columns = await databarClient.getTableColumns(table_uuid);
|
|
539
|
+
auditLog({ timestamp: ts, tool: name, params: { table_uuid }, result: 'success' });
|
|
540
|
+
if (columns.length === 0)
|
|
541
|
+
return { content: [{ type: 'text', text: 'No columns found on this table.' }] };
|
|
542
|
+
return { content: [{ type: 'text', text: safeResult(`Table has ${columns.length} column(s):\n\n${columns.map(formatColumnForDisplay).join('\n')}`) }] };
|
|
543
|
+
}
|
|
544
|
+
case 'get_table_rows': {
|
|
545
|
+
const { table_uuid, page = 1, per_page = 1000 } = args;
|
|
546
|
+
const data = await databarClient.getTableRows(table_uuid, page, per_page);
|
|
547
|
+
auditLog({ timestamp: ts, tool: name, params: { table_uuid, page, per_page }, result: 'success' });
|
|
548
|
+
return { content: [{ type: 'text', text: safeResult(`Table rows (page ${page}):\n\n${formatResults(data)}`) }] };
|
|
549
|
+
}
|
|
550
|
+
case 'create_rows': {
|
|
551
|
+
const { table_id, records, options } = args;
|
|
552
|
+
const sizeErr = validateRowBatchSize(records, 'records');
|
|
553
|
+
if (sizeErr)
|
|
554
|
+
return { content: [{ type: 'text', text: sizeErr }], isError: true };
|
|
555
|
+
const response = await databarClient.createRows(table_id, { records, options });
|
|
556
|
+
auditLog({ timestamp: ts, tool: name, params: { table_id, count: records.length }, result: 'success' });
|
|
557
|
+
return { content: [{ type: 'text', text: `Create rows result:\n\n${formatCreateRowsResponse(response)}` }] };
|
|
558
|
+
}
|
|
559
|
+
case 'patch_rows': {
|
|
560
|
+
const { table_id, rows, overwrite = true } = args;
|
|
561
|
+
const sizeErr = validateRowBatchSize(rows, 'rows');
|
|
562
|
+
if (sizeErr)
|
|
563
|
+
return { content: [{ type: 'text', text: sizeErr }], isError: true };
|
|
564
|
+
const response = await databarClient.patchRows(table_id, { rows, overwrite });
|
|
565
|
+
auditLog({ timestamp: ts, tool: name, params: { table_id, count: rows.length }, result: 'success' });
|
|
566
|
+
return { content: [{ type: 'text', text: `Patch rows result:\n\n${formatPatchRowsResponse(response)}` }] };
|
|
567
|
+
}
|
|
568
|
+
case 'upsert_rows': {
|
|
569
|
+
const { table_id, rows } = args;
|
|
570
|
+
const sizeErr = validateRowBatchSize(rows, 'rows');
|
|
571
|
+
if (sizeErr)
|
|
572
|
+
return { content: [{ type: 'text', text: sizeErr }], isError: true };
|
|
573
|
+
const response = await databarClient.upsertRows(table_id, { rows });
|
|
574
|
+
auditLog({ timestamp: ts, tool: name, params: { table_id, count: rows.length }, result: 'success' });
|
|
575
|
+
return { content: [{ type: 'text', text: `Upsert rows result:\n\n${formatUpsertRowsResponse(response)}` }] };
|
|
576
|
+
}
|
|
577
|
+
case 'get_table_enrichments': {
|
|
578
|
+
const { table_uuid } = args;
|
|
579
|
+
const enrichments = await databarClient.getTableEnrichments(table_uuid);
|
|
580
|
+
auditLog({ timestamp: ts, tool: name, params: { table_uuid }, result: 'success' });
|
|
581
|
+
if (enrichments.length === 0)
|
|
582
|
+
return { content: [{ type: 'text', text: 'No enrichments configured on this table.' }] };
|
|
583
|
+
return { content: [{ type: 'text', text: `Table has ${enrichments.length} enrichment(s):\n\n${enrichments.map(formatTableEnrichmentForDisplay).join('\n')}` }] };
|
|
584
|
+
}
|
|
585
|
+
case 'add_table_enrichment': {
|
|
586
|
+
const { table_uuid, enrichment_id, mapping } = args;
|
|
587
|
+
const result = await databarClient.addTableEnrichment(table_uuid, { enrichment: enrichment_id, mapping });
|
|
588
|
+
auditLog({ timestamp: ts, tool: name, params: { table_uuid, enrichment_id }, result: 'success' });
|
|
589
|
+
return { content: [{ type: 'text', text: `Enrichment added to table successfully\n\n${formatResults(result)}` }] };
|
|
590
|
+
}
|
|
591
|
+
case 'run_table_enrichment': {
|
|
592
|
+
const { table_uuid, enrichment_id } = args;
|
|
593
|
+
// Table enrichments run on all rows — use a conservative cost estimate
|
|
594
|
+
const guard = await guardSpending(name, 0, { table_uuid, enrichment_id });
|
|
595
|
+
if (guard)
|
|
596
|
+
return guard;
|
|
597
|
+
const result = await databarClient.runTableEnrichment(table_uuid, enrichment_id);
|
|
598
|
+
auditLog({ timestamp: ts, tool: name, params: { table_uuid, enrichment_id }, result: 'success' });
|
|
599
|
+
return { content: [{ type: 'text', text: `Table enrichment triggered successfully\n\n${formatResults(result)}` }] };
|
|
600
|
+
}
|
|
601
|
+
// ----------------------------------------------------------------
|
|
602
|
+
// User handlers
|
|
603
|
+
// ----------------------------------------------------------------
|
|
604
|
+
case 'get_user_balance': {
|
|
605
|
+
const user = await databarClient.getUserInfo();
|
|
606
|
+
auditLog({ timestamp: ts, tool: name, params: {}, result: 'success' });
|
|
607
|
+
return {
|
|
608
|
+
content: [{ type: 'text', text: `User Account Information:\n\nName: ${user.first_name || 'N/A'}\nEmail: ${user.email}\nBalance: ${user.balance} credits\nPlan: ${user.plan}` }]
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
default:
|
|
612
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
auditLog({ timestamp: ts, tool: name, params: args, result: 'error', message: error.message });
|
|
617
|
+
return {
|
|
618
|
+
content: [{ type: 'text', text: `Error: ${error.message || 'Unknown error occurred'}` }],
|
|
619
|
+
isError: true
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
// ============================================================================
|
|
624
|
+
// Start Server
|
|
625
|
+
// ============================================================================
|
|
626
|
+
async function main() {
|
|
627
|
+
const transport = new StdioServerTransport();
|
|
628
|
+
await server.connect(transport);
|
|
629
|
+
console.error('Databar MCP Server running on stdio');
|
|
630
|
+
console.error(`Mode: ${spendingConfig.safeMode ? 'safe (balance checked before each run)' : 'unsafe (no balance checks, cost warnings only)'}`);
|
|
631
|
+
console.error(`Spending guard: max_cost_per_request=${spendingConfig.maxCostPerRequest ?? 'unlimited'}, min_balance=${spendingConfig.minBalance}`);
|
|
632
|
+
}
|
|
633
|
+
main().catch((error) => {
|
|
634
|
+
console.error('Fatal error:', error);
|
|
635
|
+
process.exit(1);
|
|
636
|
+
});
|
|
637
|
+
//# sourceMappingURL=index.js.map
|