sqlcipher-mcp-server 1.0.4 → 2.0.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.
@@ -1,69 +1,558 @@
1
- /**
2
- * MCP Tool Handlers
3
- * Handlers for MCP tool requests
4
- */
5
-
6
- import { TOOL_DEFINITIONS } from '../config/constants.js';
7
- import { getDatabasePassword } from '../config/environment.js';
8
- import { validateArguments, validateQuery, resolveDatabasePath } from '../utils/validators.js';
9
- import { formatQueryResults } from '../utils/formatters.js';
10
- import { createMcpErrorResponse, createMcpSuccessResponse } from '../utils/errors.js';
11
- import { executeQueryOnDatabase } from '../services/database-service.js';
12
-
13
- /**
14
- * Handle list tools request
15
- * @returns {Object} List of available tools
16
- */
17
- export function handleListTools() {
18
- return {
19
- tools: [TOOL_DEFINITIONS.execute_query],
20
- };
21
- }
22
-
23
- /**
24
- * Handle execute_query tool request
25
- * @param {Object} args - Tool arguments
26
- * @param {string} [args.database_path] - Database path (optional if env var set)
27
- * @param {string} args.query - SQL query to execute
28
- * @returns {Promise<Object>} MCP response object
29
- */
30
- export async function handleExecuteQuery(args) {
31
- try {
32
- // Validate arguments
33
- validateArguments(args);
34
-
35
- const { database_path, query } = args;
36
-
37
- // Validate query
38
- validateQuery(query);
39
-
40
- // Resolve database path
41
- const dbPath = resolveDatabasePath(database_path);
42
-
43
- // Get database password
44
- const password = getDatabasePassword();
45
-
46
- // Execute query
47
- try {
48
- const result = await executeQueryOnDatabase(dbPath, password, query);
49
-
50
- // Format results for response
51
- const responseText = formatQueryResults(result);
52
-
53
- return createMcpSuccessResponse(responseText);
54
- } catch (error) {
55
- return createMcpErrorResponse(`Query execution failed: ${error.message}`);
56
- }
57
- } catch (error) {
58
- return createMcpErrorResponse(`Error: ${error.message}`);
59
- }
60
- }
61
-
62
- /**
63
- * Handle unknown tool request
64
- * @param {string} toolName - Name of the unknown tool
65
- * @returns {Object} Error response
66
- */
67
- export function handleUnknownTool(toolName) {
68
- return createMcpErrorResponse(`Unknown tool: ${toolName}`);
69
- }
1
+ /**
2
+ * MCP Tool Handlers
3
+ * Handlers for MCP tool requests
4
+ */
5
+
6
+ import { TOOL_DEFINITIONS } from '../definitions/tools.js';
7
+ import { getDatabasePassword } from '../config/environment.js';
8
+ import {
9
+ validateArguments,
10
+ validateQuery,
11
+ resolveDatabasePath,
12
+ validateTableName,
13
+ validateColumnName,
14
+ validatePattern,
15
+ validateNumericParameter
16
+ } from '../utils/validators.js';
17
+ import {
18
+ formatQueryResults,
19
+ formatTableList,
20
+ formatTableSchema,
21
+ formatForeignKeys,
22
+ formatIndexes,
23
+ formatDatabaseInfo,
24
+ formatTableInfo,
25
+ formatQueryPlan,
26
+ formatTableStatistics,
27
+ formatSampleData,
28
+ formatColumnStatistics,
29
+ formatSearchResults,
30
+ formatRelatedTables
31
+ } from '../utils/formatters.js';
32
+ import { createMcpErrorResponse, createMcpSuccessResponse } from '../utils/errors.js';
33
+ import {
34
+ executeQueryOnDatabase,
35
+ testDatabaseConnection,
36
+ getTableListFromDatabase,
37
+ getTableSchemaFromDatabase,
38
+ getForeignKeysFromDatabase,
39
+ getIndexesFromDatabase,
40
+ getDatabaseInfoFromDatabase,
41
+ getTableInfoFromDatabase,
42
+ explainQueryPlanFromDatabase,
43
+ getTableStatisticsFromDatabase,
44
+ sampleTableDataFromDatabase,
45
+ getColumnStatisticsFromDatabase,
46
+ searchTablesInDatabase,
47
+ searchColumnsInDatabase,
48
+ findRelatedTablesInDatabase
49
+ } from '../services/database-service.js';
50
+
51
+ /**
52
+ * Handle list tools request
53
+ * @returns {Object} List of available tools
54
+ */
55
+ export function handleListTools() {
56
+ return {
57
+ tools: Object.values(TOOL_DEFINITIONS),
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Handle execute_query tool request
63
+ * @param {Object} args - Tool arguments
64
+ * @param {string} [args.database_path] - Database path (optional if env var set)
65
+ * @param {string} args.query - SQL query to execute
66
+ * @returns {Promise<Object>} MCP response object
67
+ */
68
+ export async function handleExecuteQuery(args) {
69
+ try {
70
+ // Validate arguments
71
+ validateArguments(args);
72
+
73
+ const { database_path, query } = args;
74
+
75
+ // Validate query
76
+ validateQuery(query);
77
+
78
+ // Resolve database path
79
+ const dbPath = resolveDatabasePath(database_path);
80
+
81
+ // Get database password
82
+ const password = getDatabasePassword();
83
+
84
+ // Execute query
85
+ try {
86
+ const result = await executeQueryOnDatabase(dbPath, password, query);
87
+
88
+ // Format results for response
89
+ const responseText = formatQueryResults(result);
90
+
91
+ return createMcpSuccessResponse(responseText);
92
+ } catch (error) {
93
+ return createMcpErrorResponse(`Query execution failed: ${error.message}`);
94
+ }
95
+ } catch (error) {
96
+ return createMcpErrorResponse(`Error: ${error.message}`);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Handle list_tables tool request
102
+ * @param {Object} args - Tool arguments
103
+ * @returns {Promise<Object>} MCP response object
104
+ */
105
+ export async function handleListTables(args) {
106
+ try {
107
+ validateArguments(args);
108
+
109
+ const { database_path, table_names } = args;
110
+ const dbPath = resolveDatabasePath(database_path);
111
+ const password = getDatabasePassword();
112
+
113
+ const tables = await getTableListFromDatabase(dbPath, password, table_names);
114
+ const responseText = formatTableList(tables);
115
+
116
+ return createMcpSuccessResponse(responseText);
117
+ } catch (error) {
118
+ return createMcpErrorResponse(`Error: ${error.message}`);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Handle get_table_schema tool request
124
+ * @param {Object} args - Tool arguments
125
+ * @returns {Promise<Object>} MCP response object
126
+ */
127
+ export async function handleGetTableSchema(args) {
128
+ try {
129
+ validateArguments(args);
130
+ validateTableName(args.table_name);
131
+
132
+ const { database_path, table_name } = args;
133
+ const dbPath = resolveDatabasePath(database_path);
134
+ const password = getDatabasePassword();
135
+
136
+ const schema = await getTableSchemaFromDatabase(dbPath, password, table_name);
137
+ const responseText = formatTableSchema(schema);
138
+
139
+ return createMcpSuccessResponse(responseText);
140
+ } catch (error) {
141
+ return createMcpErrorResponse(`Error: ${error.message}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Handle list_columns tool request
147
+ * @param {Object} args - Tool arguments
148
+ * @returns {Promise<Object>} MCP response object
149
+ */
150
+ export async function handleListColumns(args) {
151
+ try {
152
+ validateArguments(args);
153
+ validateTableName(args.table_name);
154
+
155
+ const { database_path, table_name } = args;
156
+ const dbPath = resolveDatabasePath(database_path);
157
+ const password = getDatabasePassword();
158
+
159
+ const schema = await getTableSchemaFromDatabase(dbPath, password, table_name);
160
+ const responseText = formatTableSchema(schema);
161
+
162
+ return createMcpSuccessResponse(responseText);
163
+ } catch (error) {
164
+ return createMcpErrorResponse(`Error: ${error.message}`);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Handle get_foreign_keys tool request
170
+ * @param {Object} args - Tool arguments
171
+ * @returns {Promise<Object>} MCP response object
172
+ */
173
+ export async function handleGetForeignKeys(args) {
174
+ try {
175
+ validateArguments(args);
176
+
177
+ const { database_path, table_name } = args;
178
+ const dbPath = resolveDatabasePath(database_path);
179
+ const password = getDatabasePassword();
180
+
181
+ const foreignKeys = await getForeignKeysFromDatabase(dbPath, password, table_name);
182
+ const responseText = formatForeignKeys(foreignKeys);
183
+
184
+ return createMcpSuccessResponse(responseText);
185
+ } catch (error) {
186
+ return createMcpErrorResponse(`Error: ${error.message}`);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Handle get_indexes tool request
192
+ * @param {Object} args - Tool arguments
193
+ * @returns {Promise<Object>} MCP response object
194
+ */
195
+ export async function handleGetIndexes(args) {
196
+ try {
197
+ validateArguments(args);
198
+
199
+ const { database_path, table_name } = args;
200
+ const dbPath = resolveDatabasePath(database_path);
201
+ const password = getDatabasePassword();
202
+
203
+ const indexes = await getIndexesFromDatabase(dbPath, password, table_name);
204
+ const responseText = formatIndexes(indexes);
205
+
206
+ return createMcpSuccessResponse(responseText);
207
+ } catch (error) {
208
+ return createMcpErrorResponse(`Error: ${error.message}`);
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Handle get_database_info tool request
214
+ * @param {Object} args - Tool arguments
215
+ * @returns {Promise<Object>} MCP response object
216
+ */
217
+ export async function handleGetDatabaseInfo(args) {
218
+ try {
219
+ validateArguments(args);
220
+
221
+ const { database_path } = args;
222
+ const dbPath = resolveDatabasePath(database_path);
223
+ const password = getDatabasePassword();
224
+
225
+ const info = await getDatabaseInfoFromDatabase(dbPath, password);
226
+ const responseText = formatDatabaseInfo(info);
227
+
228
+ return createMcpSuccessResponse(responseText);
229
+ } catch (error) {
230
+ return createMcpErrorResponse(`Error: ${error.message}`);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Handle get_table_info tool request
236
+ * @param {Object} args - Tool arguments
237
+ * @returns {Promise<Object>} MCP response object
238
+ */
239
+ export async function handleGetTableInfo(args) {
240
+ try {
241
+ validateArguments(args);
242
+ validateTableName(args.table_name);
243
+
244
+ const { database_path, table_name } = args;
245
+ const dbPath = resolveDatabasePath(database_path);
246
+ const password = getDatabasePassword();
247
+
248
+ const info = await getTableInfoFromDatabase(dbPath, password, table_name);
249
+ const responseText = formatTableInfo(info);
250
+
251
+ return createMcpSuccessResponse(responseText);
252
+ } catch (error) {
253
+ return createMcpErrorResponse(`Error: ${error.message}`);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Handle test_connection tool request
259
+ * @param {Object} args - Tool arguments
260
+ * @returns {Promise<Object>} MCP response object
261
+ */
262
+ export async function handleTestConnection(args) {
263
+ try {
264
+ validateArguments(args);
265
+
266
+ const { database_path } = args;
267
+ const dbPath = resolveDatabasePath(database_path);
268
+ const password = getDatabasePassword();
269
+
270
+ await testDatabaseConnection(dbPath, password);
271
+ const responseText = 'Database connection successful.';
272
+
273
+ return createMcpSuccessResponse(responseText);
274
+ } catch (error) {
275
+ return createMcpErrorResponse(`Connection test failed: ${error.message}`);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Handle explain_query tool request
281
+ * @param {Object} args - Tool arguments
282
+ * @returns {Promise<Object>} MCP response object
283
+ */
284
+ export async function handleExplainQuery(args) {
285
+ try {
286
+ validateArguments(args);
287
+ validateQuery(args.query);
288
+
289
+ const { database_path, query } = args;
290
+ const dbPath = resolveDatabasePath(database_path);
291
+ const password = getDatabasePassword();
292
+
293
+ const plan = await explainQueryPlanFromDatabase(dbPath, password, query);
294
+ const responseText = formatQueryPlan(plan);
295
+
296
+ return createMcpSuccessResponse(responseText);
297
+ } catch (error) {
298
+ return createMcpErrorResponse(`Error: ${error.message}`);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Handle validate_query_syntax tool request
304
+ * @param {Object} args - Tool arguments
305
+ * @returns {Promise<Object>} MCP response object
306
+ */
307
+ export async function handleValidateQuerySyntax(args) {
308
+ try {
309
+ validateArguments(args);
310
+ validateQuery(args.query);
311
+
312
+ const { database_path, query } = args;
313
+ const dbPath = resolveDatabasePath(database_path);
314
+ const password = getDatabasePassword();
315
+
316
+ // Try to explain the query - if it succeeds, syntax is valid
317
+ await explainQueryPlanFromDatabase(dbPath, password, query);
318
+ const responseText = 'Query syntax is valid.';
319
+
320
+ return createMcpSuccessResponse(responseText);
321
+ } catch (error) {
322
+ return createMcpErrorResponse(`Query syntax validation failed: ${error.message}`);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Handle suggest_query tool request
328
+ * @param {Object} args - Tool arguments
329
+ * @returns {Promise<Object>} MCP response object
330
+ */
331
+ export async function handleSuggestQuery(args) {
332
+ try {
333
+ validateArguments(args);
334
+
335
+ const { database_path, table_name, intent } = args;
336
+ const dbPath = resolveDatabasePath(database_path);
337
+ const password = getDatabasePassword();
338
+
339
+ let suggestions = [];
340
+
341
+ if (table_name) {
342
+ // Get table schema to build suggestions
343
+ const schema = await getTableSchemaFromDatabase(dbPath, password, table_name);
344
+ const columns = schema.columns.map(c => c.name).join(', ');
345
+
346
+ switch (intent) {
347
+ case 'count':
348
+ suggestions.push(`SELECT COUNT(*) FROM "${table_name}"`);
349
+ break;
350
+ case 'sample':
351
+ suggestions.push(`SELECT ${columns} FROM "${table_name}" LIMIT 10`);
352
+ break;
353
+ case 'aggregate':
354
+ const numericCols = schema.columns.filter(c =>
355
+ c.type && (c.type.toUpperCase().includes('INT') ||
356
+ c.type.toUpperCase().includes('REAL') ||
357
+ c.type.toUpperCase().includes('NUMERIC'))
358
+ );
359
+ if (numericCols.length > 0) {
360
+ const col = numericCols[0].name;
361
+ suggestions.push(`SELECT MIN("${col}"), MAX("${col}"), AVG("${col}") FROM "${table_name}"`);
362
+ }
363
+ break;
364
+ case 'search':
365
+ const textCols = schema.columns.filter(c =>
366
+ !c.type || c.type.toUpperCase().includes('TEXT') ||
367
+ c.type.toUpperCase().includes('VARCHAR') ||
368
+ c.type.toUpperCase().includes('CHAR')
369
+ );
370
+ if (textCols.length > 0) {
371
+ const col = textCols[0].name;
372
+ suggestions.push(`SELECT ${columns} FROM "${table_name}" WHERE "${col}" LIKE '%search_term%'`);
373
+ }
374
+ break;
375
+ case 'join':
376
+ if (schema.foreign_keys && schema.foreign_keys.length > 0) {
377
+ const fk = schema.foreign_keys[0];
378
+ suggestions.push(`SELECT * FROM "${table_name}" t1 JOIN "${fk.table}" t2 ON t1."${fk.from}" = t2."${fk.to}"`);
379
+ }
380
+ break;
381
+ default:
382
+ suggestions.push(`SELECT ${columns} FROM "${table_name}"`);
383
+ }
384
+ } else {
385
+ // General suggestions
386
+ suggestions.push('SELECT * FROM table_name LIMIT 10');
387
+ suggestions.push('SELECT COUNT(*) FROM table_name');
388
+ suggestions.push('SELECT column_name FROM table_name WHERE condition');
389
+ }
390
+
391
+ let responseText = 'Query Suggestions:\n\n';
392
+ suggestions.forEach((s, i) => {
393
+ responseText += `${i + 1}. ${s}\n`;
394
+ });
395
+
396
+ responseText += '\n\nJSON representation:\n';
397
+ responseText += JSON.stringify({ suggestions }, null, 2);
398
+
399
+ return createMcpSuccessResponse(responseText);
400
+ } catch (error) {
401
+ return createMcpErrorResponse(`Error: ${error.message}`);
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Handle get_table_statistics tool request
407
+ * @param {Object} args - Tool arguments
408
+ * @returns {Promise<Object>} MCP response object
409
+ */
410
+ export async function handleGetTableStatistics(args) {
411
+ try {
412
+ validateArguments(args);
413
+ validateTableName(args.table_name);
414
+
415
+ const { database_path, table_name, max_sample_size, timeout_ms } = args;
416
+ const dbPath = resolveDatabasePath(database_path);
417
+ const password = getDatabasePassword();
418
+
419
+ const maxSample = validateNumericParameter(max_sample_size, 'max_sample_size', 1, 1000000) || 10000;
420
+
421
+ const stats = await getTableStatisticsFromDatabase(dbPath, password, table_name, maxSample);
422
+ const responseText = formatTableStatistics(stats);
423
+
424
+ return createMcpSuccessResponse(responseText);
425
+ } catch (error) {
426
+ return createMcpErrorResponse(`Error: ${error.message}`);
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Handle sample_table_data tool request
432
+ * @param {Object} args - Tool arguments
433
+ * @returns {Promise<Object>} MCP response object
434
+ */
435
+ export async function handleSampleTableData(args) {
436
+ try {
437
+ validateArguments(args);
438
+ validateTableName(args.table_name);
439
+
440
+ const { database_path, table_name, limit, offset, columns } = args;
441
+ const dbPath = resolveDatabasePath(database_path);
442
+ const password = getDatabasePassword();
443
+
444
+ const limitNum = validateNumericParameter(limit, 'limit', 1, 10000) || 10;
445
+ const offsetNum = validateNumericParameter(offset, 'offset', 0, Number.MAX_SAFE_INTEGER) || 0;
446
+
447
+ const sample = await sampleTableDataFromDatabase(dbPath, password, table_name, limitNum, offsetNum, columns);
448
+ const responseText = formatSampleData(sample);
449
+
450
+ return createMcpSuccessResponse(responseText);
451
+ } catch (error) {
452
+ return createMcpErrorResponse(`Error: ${error.message}`);
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Handle get_column_statistics tool request
458
+ * @param {Object} args - Tool arguments
459
+ * @returns {Promise<Object>} MCP response object
460
+ */
461
+ export async function handleGetColumnStatistics(args) {
462
+ try {
463
+ validateArguments(args);
464
+ validateTableName(args.table_name);
465
+ validateColumnName(args.column_name);
466
+
467
+ const { database_path, table_name, column_name, max_sample_size } = args;
468
+ const dbPath = resolveDatabasePath(database_path);
469
+ const password = getDatabasePassword();
470
+
471
+ const maxSample = validateNumericParameter(max_sample_size, 'max_sample_size', 1, 1000000) || 10000;
472
+
473
+ const stats = await getColumnStatisticsFromDatabase(dbPath, password, table_name, column_name, maxSample);
474
+ const responseText = formatColumnStatistics(stats);
475
+
476
+ return createMcpSuccessResponse(responseText);
477
+ } catch (error) {
478
+ return createMcpErrorResponse(`Error: ${error.message}`);
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Handle search_tables tool request
484
+ * @param {Object} args - Tool arguments
485
+ * @returns {Promise<Object>} MCP response object
486
+ */
487
+ export async function handleSearchTables(args) {
488
+ try {
489
+ validateArguments(args);
490
+ validatePattern(args.pattern);
491
+
492
+ const { database_path, pattern } = args;
493
+ const dbPath = resolveDatabasePath(database_path);
494
+ const password = getDatabasePassword();
495
+
496
+ const results = await searchTablesInDatabase(dbPath, password, pattern);
497
+ const responseText = formatSearchResults(results, 'tables');
498
+
499
+ return createMcpSuccessResponse(responseText);
500
+ } catch (error) {
501
+ return createMcpErrorResponse(`Error: ${error.message}`);
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Handle search_columns tool request
507
+ * @param {Object} args - Tool arguments
508
+ * @returns {Promise<Object>} MCP response object
509
+ */
510
+ export async function handleSearchColumns(args) {
511
+ try {
512
+ validateArguments(args);
513
+ validatePattern(args.pattern);
514
+
515
+ const { database_path, pattern } = args;
516
+ const dbPath = resolveDatabasePath(database_path);
517
+ const password = getDatabasePassword();
518
+
519
+ const results = await searchColumnsInDatabase(dbPath, password, pattern);
520
+ const responseText = formatSearchResults(results, 'columns');
521
+
522
+ return createMcpSuccessResponse(responseText);
523
+ } catch (error) {
524
+ return createMcpErrorResponse(`Error: ${error.message}`);
525
+ }
526
+ }
527
+
528
+ /**
529
+ * Handle find_related_tables tool request
530
+ * @param {Object} args - Tool arguments
531
+ * @returns {Promise<Object>} MCP response object
532
+ */
533
+ export async function handleFindRelatedTables(args) {
534
+ try {
535
+ validateArguments(args);
536
+ validateTableName(args.table_name);
537
+
538
+ const { database_path, table_name } = args;
539
+ const dbPath = resolveDatabasePath(database_path);
540
+ const password = getDatabasePassword();
541
+
542
+ const related = await findRelatedTablesInDatabase(dbPath, password, table_name);
543
+ const responseText = formatRelatedTables(related);
544
+
545
+ return createMcpSuccessResponse(responseText);
546
+ } catch (error) {
547
+ return createMcpErrorResponse(`Error: ${error.message}`);
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Handle unknown tool request
553
+ * @param {string} toolName - Name of the unknown tool
554
+ * @returns {Object} Error response
555
+ */
556
+ export function handleUnknownTool(toolName) {
557
+ return createMcpErrorResponse(`Unknown tool: ${toolName}`);
558
+ }