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.
@@ -5,8 +5,32 @@
5
5
 
6
6
  import { SERVER_CONFIG } from '../config/constants.js';
7
7
  import { getDatabasePassword, isPasswordConfigured } from '../config/environment.js';
8
- import { validateDatabasePath, validateQuery } from '../utils/validators.js';
9
- import { executeQueryOnDatabase } from '../services/database-service.js';
8
+ import {
9
+ validateDatabasePath,
10
+ validateQuery,
11
+ validateTableName,
12
+ validateColumnName,
13
+ validatePattern,
14
+ validateNumericParameter,
15
+ resolveDatabasePath
16
+ } from '../utils/validators.js';
17
+ import {
18
+ executeQueryOnDatabase,
19
+ getTableListFromDatabase,
20
+ getTableSchemaFromDatabase,
21
+ getForeignKeysFromDatabase,
22
+ getIndexesFromDatabase,
23
+ getDatabaseInfoFromDatabase,
24
+ getTableInfoFromDatabase,
25
+ testDatabaseConnection,
26
+ explainQueryPlanFromDatabase,
27
+ getTableStatisticsFromDatabase,
28
+ sampleTableDataFromDatabase,
29
+ getColumnStatisticsFromDatabase,
30
+ searchTablesInDatabase,
31
+ searchColumnsInDatabase,
32
+ findRelatedTablesInDatabase
33
+ } from '../services/database-service.js';
10
34
 
11
35
  /**
12
36
  * Handle health check endpoint
@@ -30,12 +54,44 @@ export function handleInfo(req, res) {
30
54
  res.json({
31
55
  name: SERVER_CONFIG.name,
32
56
  version: SERVER_CONFIG.version,
33
- description: 'HTTP wrapper for SQLCipher MCP Server',
57
+ description: 'HTTP wrapper for SQLCipher MCP Server - Full feature parity with MCP server',
34
58
  endpoints: {
59
+ // Server Status
35
60
  health: 'GET /health',
36
- query: 'POST /api/query',
37
61
  info: 'GET /api/info',
62
+
63
+ // Query Execution (backward compatibility)
64
+ query: 'POST /api/query (execute_query)',
65
+
66
+ // Schema Exploration
67
+ list_tables: 'POST /api/tool/list_tables',
68
+ get_table_schema: 'POST /api/tool/get_table_schema',
69
+ list_columns: 'POST /api/tool/list_columns',
70
+ get_foreign_keys: 'POST /api/tool/get_foreign_keys',
71
+ get_indexes: 'POST /api/tool/get_indexes',
72
+ find_related_tables: 'POST /api/tool/find_related_tables',
73
+
74
+ // Database & Table Info
75
+ get_database_info: 'POST /api/tool/get_database_info',
76
+ get_table_info: 'POST /api/tool/get_table_info',
77
+ test_connection: 'POST /api/tool/test_connection',
78
+
79
+ // Query Helpers
80
+ explain_query: 'POST /api/tool/explain_query',
81
+ validate_query_syntax: 'POST /api/tool/validate_query_syntax',
82
+ suggest_query: 'POST /api/tool/suggest_query',
83
+
84
+ // Data Analysis
85
+ get_table_statistics: 'POST /api/tool/get_table_statistics',
86
+ sample_table_data: 'POST /api/tool/sample_table_data',
87
+ get_column_statistics: 'POST /api/tool/get_column_statistics',
88
+
89
+ // Search
90
+ search_tables: 'POST /api/tool/search_tables',
91
+ search_columns: 'POST /api/tool/search_columns',
38
92
  },
93
+ totalTools: 18,
94
+ totalEndpoints: 20,
39
95
  passwordConfigured: isPasswordConfigured(),
40
96
  });
41
97
  }
@@ -87,3 +143,519 @@ export async function handleQuery(req, res) {
87
143
  });
88
144
  }
89
145
  }
146
+
147
+ // ============================================================================
148
+ // Schema Exploration Handlers
149
+ // ============================================================================
150
+
151
+ /**
152
+ * Handle list_tables endpoint
153
+ * @param {Object} req - Express request object
154
+ * @param {Object} res - Express response object
155
+ */
156
+ export async function handleListTables(req, res) {
157
+ try {
158
+ const { database_path } = req.body;
159
+ const dbPath = resolveDatabasePath(database_path);
160
+ const password = getDatabasePassword();
161
+
162
+ const tables = await getTableListFromDatabase(dbPath, password);
163
+
164
+ res.json({
165
+ success: true,
166
+ data: tables,
167
+ message: `Retrieved ${tables.length} table(s) from database.`,
168
+ });
169
+ } catch (error) {
170
+ res.status(400).json({
171
+ success: false,
172
+ error: error.message,
173
+ });
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Handle get_table_schema endpoint
179
+ * @param {Object} req - Express request object
180
+ * @param {Object} res - Express response object
181
+ */
182
+ export async function handleGetTableSchema(req, res) {
183
+ try {
184
+ const { database_path, table_name } = req.body;
185
+ validateTableName(table_name);
186
+
187
+ const dbPath = resolveDatabasePath(database_path);
188
+ const password = getDatabasePassword();
189
+
190
+ const schema = await getTableSchemaFromDatabase(dbPath, password, table_name);
191
+
192
+ res.json({
193
+ success: true,
194
+ data: schema,
195
+ message: `Retrieved schema for table "${table_name}".`,
196
+ });
197
+ } catch (error) {
198
+ res.status(400).json({
199
+ success: false,
200
+ error: error.message,
201
+ });
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Handle list_columns endpoint
207
+ * @param {Object} req - Express request object
208
+ * @param {Object} res - Express response object
209
+ */
210
+ export async function handleListColumns(req, res) {
211
+ try {
212
+ const { database_path, table_name } = req.body;
213
+ validateTableName(table_name);
214
+
215
+ const dbPath = resolveDatabasePath(database_path);
216
+ const password = getDatabasePassword();
217
+
218
+ const schema = await getTableSchemaFromDatabase(dbPath, password, table_name);
219
+ const columns = schema.map(col => col.name);
220
+
221
+ res.json({
222
+ success: true,
223
+ data: { table_name, columns, schema },
224
+ message: `Retrieved ${columns.length} column(s) from table "${table_name}".`,
225
+ });
226
+ } catch (error) {
227
+ res.status(400).json({
228
+ success: false,
229
+ error: error.message,
230
+ });
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Handle get_foreign_keys endpoint
236
+ * @param {Object} req - Express request object
237
+ * @param {Object} res - Express response object
238
+ */
239
+ export async function handleGetForeignKeys(req, res) {
240
+ try {
241
+ const { database_path, table_name } = req.body;
242
+ validateTableName(table_name);
243
+
244
+ const dbPath = resolveDatabasePath(database_path);
245
+ const password = getDatabasePassword();
246
+
247
+ const foreignKeys = await getForeignKeysFromDatabase(dbPath, password, table_name);
248
+
249
+ res.json({
250
+ success: true,
251
+ data: { table_name, foreign_keys: foreignKeys },
252
+ message: `Retrieved ${foreignKeys.length} foreign key(s) for table "${table_name}".`,
253
+ });
254
+ } catch (error) {
255
+ res.status(400).json({
256
+ success: false,
257
+ error: error.message,
258
+ });
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Handle get_indexes endpoint
264
+ * @param {Object} req - Express request object
265
+ * @param {Object} res - Express response object
266
+ */
267
+ export async function handleGetIndexes(req, res) {
268
+ try {
269
+ const { database_path, table_name } = req.body;
270
+ validateTableName(table_name);
271
+
272
+ const dbPath = resolveDatabasePath(database_path);
273
+ const password = getDatabasePassword();
274
+
275
+ const indexes = await getIndexesFromDatabase(dbPath, password, table_name);
276
+
277
+ res.json({
278
+ success: true,
279
+ data: { table_name, indexes },
280
+ message: `Retrieved ${indexes.length} index(es) for table "${table_name}".`,
281
+ });
282
+ } catch (error) {
283
+ res.status(400).json({
284
+ success: false,
285
+ error: error.message,
286
+ });
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Handle find_related_tables endpoint
292
+ * @param {Object} req - Express request object
293
+ * @param {Object} res - Express response object
294
+ */
295
+ export async function handleFindRelatedTables(req, res) {
296
+ try {
297
+ const { database_path, table_name } = req.body;
298
+ validateTableName(table_name);
299
+
300
+ const dbPath = resolveDatabasePath(database_path);
301
+ const password = getDatabasePassword();
302
+
303
+ const relationships = await findRelatedTablesInDatabase(dbPath, password, table_name);
304
+
305
+ res.json({
306
+ success: true,
307
+ data: relationships,
308
+ message: `Found relationships for table "${table_name}".`,
309
+ });
310
+ } catch (error) {
311
+ res.status(400).json({
312
+ success: false,
313
+ error: error.message,
314
+ });
315
+ }
316
+ }
317
+
318
+ // ============================================================================
319
+ // Database & Table Info Handlers
320
+ // ============================================================================
321
+
322
+ /**
323
+ * Handle get_database_info endpoint
324
+ * @param {Object} req - Express request object
325
+ * @param {Object} res - Express response object
326
+ */
327
+ export async function handleGetDatabaseInfo(req, res) {
328
+ try {
329
+ const { database_path } = req.body;
330
+ const dbPath = resolveDatabasePath(database_path);
331
+ const password = getDatabasePassword();
332
+
333
+ const info = await getDatabaseInfoFromDatabase(dbPath, password);
334
+
335
+ res.json({
336
+ success: true,
337
+ data: info,
338
+ message: 'Retrieved database information.',
339
+ });
340
+ } catch (error) {
341
+ res.status(400).json({
342
+ success: false,
343
+ error: error.message,
344
+ });
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Handle get_table_info endpoint
350
+ * @param {Object} req - Express request object
351
+ * @param {Object} res - Express response object
352
+ */
353
+ export async function handleGetTableInfo(req, res) {
354
+ try {
355
+ const { database_path, table_name } = req.body;
356
+ validateTableName(table_name);
357
+
358
+ const dbPath = resolveDatabasePath(database_path);
359
+ const password = getDatabasePassword();
360
+
361
+ const info = await getTableInfoFromDatabase(dbPath, password, table_name);
362
+
363
+ res.json({
364
+ success: true,
365
+ data: info,
366
+ message: `Retrieved information for table "${table_name}".`,
367
+ });
368
+ } catch (error) {
369
+ res.status(400).json({
370
+ success: false,
371
+ error: error.message,
372
+ });
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Handle test_connection endpoint
378
+ * @param {Object} req - Express request object
379
+ * @param {Object} res - Express response object
380
+ */
381
+ export async function handleTestConnection(req, res) {
382
+ try {
383
+ const { database_path } = req.body;
384
+ const dbPath = resolveDatabasePath(database_path);
385
+ const password = getDatabasePassword();
386
+
387
+ const result = await testDatabaseConnection(dbPath, password);
388
+
389
+ res.json({
390
+ success: true,
391
+ data: result,
392
+ message: result.success ? 'Database connection successful.' : 'Database connection failed.',
393
+ });
394
+ } catch (error) {
395
+ res.status(400).json({
396
+ success: false,
397
+ error: error.message,
398
+ });
399
+ }
400
+ }
401
+
402
+ // ============================================================================
403
+ // Query Helper Handlers
404
+ // ============================================================================
405
+
406
+ /**
407
+ * Handle explain_query endpoint
408
+ * @param {Object} req - Express request object
409
+ * @param {Object} res - Express response object
410
+ */
411
+ export async function handleExplainQuery(req, res) {
412
+ try {
413
+ const { database_path, query } = req.body;
414
+ validateQuery(query);
415
+
416
+ const dbPath = resolveDatabasePath(database_path);
417
+ const password = getDatabasePassword();
418
+
419
+ const plan = await explainQueryPlanFromDatabase(dbPath, password, query);
420
+
421
+ res.json({
422
+ success: true,
423
+ data: { query, execution_plan: plan },
424
+ message: 'Query execution plan retrieved.',
425
+ });
426
+ } catch (error) {
427
+ res.status(400).json({
428
+ success: false,
429
+ error: error.message,
430
+ });
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Handle validate_query_syntax endpoint
436
+ * @param {Object} req - Express request object
437
+ * @param {Object} res - Express response object
438
+ */
439
+ export async function handleValidateQuerySyntax(req, res) {
440
+ try {
441
+ const { database_path, query } = req.body;
442
+
443
+ const dbPath = resolveDatabasePath(database_path);
444
+ const password = getDatabasePassword();
445
+
446
+ // Try to explain the query - if it fails, syntax is invalid
447
+ try {
448
+ await explainQueryPlanFromDatabase(dbPath, password, query);
449
+ res.json({
450
+ success: true,
451
+ data: { valid: true, query },
452
+ message: 'Query syntax is valid.',
453
+ });
454
+ } catch (error) {
455
+ res.json({
456
+ success: true,
457
+ data: { valid: false, query, error: error.message },
458
+ message: 'Query syntax is invalid.',
459
+ });
460
+ }
461
+ } catch (error) {
462
+ res.status(400).json({
463
+ success: false,
464
+ error: error.message,
465
+ });
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Handle suggest_query endpoint
471
+ * @param {Object} req - Express request object
472
+ * @param {Object} res - Express response object
473
+ */
474
+ export async function handleSuggestQuery(req, res) {
475
+ try {
476
+ const { database_path, table_name, intent } = req.body;
477
+ validateTableName(table_name);
478
+
479
+ const dbPath = resolveDatabasePath(database_path);
480
+ const password = getDatabasePassword();
481
+
482
+ // Get table schema to build a suggested query
483
+ const schema = await getTableSchemaFromDatabase(dbPath, password, table_name);
484
+ const columns = schema.map(col => col.name).join(', ');
485
+
486
+ let suggestedQuery = `SELECT ${columns} FROM ${table_name}`;
487
+
488
+ if (intent) {
489
+ suggestedQuery += ` -- Intent: ${intent}`;
490
+ }
491
+
492
+ suggestedQuery += ' LIMIT 10;';
493
+
494
+ res.json({
495
+ success: true,
496
+ data: {
497
+ table_name,
498
+ intent: intent || 'General query',
499
+ suggested_query: suggestedQuery,
500
+ columns: schema
501
+ },
502
+ message: `Generated query suggestion for table "${table_name}".`,
503
+ });
504
+ } catch (error) {
505
+ res.status(400).json({
506
+ success: false,
507
+ error: error.message,
508
+ });
509
+ }
510
+ }
511
+
512
+ // ============================================================================
513
+ // Data Analysis Handlers
514
+ // ============================================================================
515
+
516
+ /**
517
+ * Handle get_table_statistics endpoint
518
+ * @param {Object} req - Express request object
519
+ * @param {Object} res - Express response object
520
+ */
521
+ export async function handleGetTableStatistics(req, res) {
522
+ try {
523
+ const { database_path, table_name } = req.body;
524
+ validateTableName(table_name);
525
+
526
+ const dbPath = resolveDatabasePath(database_path);
527
+ const password = getDatabasePassword();
528
+
529
+ const stats = await getTableStatisticsFromDatabase(dbPath, password, table_name);
530
+
531
+ res.json({
532
+ success: true,
533
+ data: stats,
534
+ message: `Retrieved statistics for table "${table_name}".`,
535
+ });
536
+ } catch (error) {
537
+ res.status(400).json({
538
+ success: false,
539
+ error: error.message,
540
+ });
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Handle sample_table_data endpoint
546
+ * @param {Object} req - Express request object
547
+ * @param {Object} res - Express response object
548
+ */
549
+ export async function handleSampleTableData(req, res) {
550
+ try {
551
+ const { database_path, table_name, limit = 10, offset = 0 } = req.body;
552
+ validateTableName(table_name);
553
+ validateNumericParameter(limit, 'limit', 1, 1000);
554
+ validateNumericParameter(offset, 'offset', 0, 1000000);
555
+
556
+ const dbPath = resolveDatabasePath(database_path);
557
+ const password = getDatabasePassword();
558
+
559
+ const sample = await sampleTableDataFromDatabase(dbPath, password, table_name, limit, offset);
560
+
561
+ res.json({
562
+ success: true,
563
+ data: sample,
564
+ message: `Retrieved ${sample.rows.length} sample row(s) from table "${table_name}".`,
565
+ });
566
+ } catch (error) {
567
+ res.status(400).json({
568
+ success: false,
569
+ error: error.message,
570
+ });
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Handle get_column_statistics endpoint
576
+ * @param {Object} req - Express request object
577
+ * @param {Object} res - Express response object
578
+ */
579
+ export async function handleGetColumnStatistics(req, res) {
580
+ try {
581
+ const { database_path, table_name, column_name } = req.body;
582
+ validateTableName(table_name);
583
+ validateColumnName(column_name);
584
+
585
+ const dbPath = resolveDatabasePath(database_path);
586
+ const password = getDatabasePassword();
587
+
588
+ const stats = await getColumnStatisticsFromDatabase(dbPath, password, table_name, column_name);
589
+
590
+ res.json({
591
+ success: true,
592
+ data: stats,
593
+ message: `Retrieved statistics for column "${column_name}" in table "${table_name}".`,
594
+ });
595
+ } catch (error) {
596
+ res.status(400).json({
597
+ success: false,
598
+ error: error.message,
599
+ });
600
+ }
601
+ }
602
+
603
+ // ============================================================================
604
+ // Search Handlers
605
+ // ============================================================================
606
+
607
+ /**
608
+ * Handle search_tables endpoint
609
+ * @param {Object} req - Express request object
610
+ * @param {Object} res - Express response object
611
+ */
612
+ export async function handleSearchTables(req, res) {
613
+ try {
614
+ const { database_path, pattern } = req.body;
615
+ validatePattern(pattern);
616
+
617
+ const dbPath = resolveDatabasePath(database_path);
618
+ const password = getDatabasePassword();
619
+
620
+ const results = await searchTablesInDatabase(dbPath, password, pattern);
621
+
622
+ res.json({
623
+ success: true,
624
+ data: { pattern, matches: results },
625
+ message: `Found ${results.length} table(s) matching pattern "${pattern}".`,
626
+ });
627
+ } catch (error) {
628
+ res.status(400).json({
629
+ success: false,
630
+ error: error.message,
631
+ });
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Handle search_columns endpoint
637
+ * @param {Object} req - Express request object
638
+ * @param {Object} res - Express response object
639
+ */
640
+ export async function handleSearchColumns(req, res) {
641
+ try {
642
+ const { database_path, pattern } = req.body;
643
+ validatePattern(pattern);
644
+
645
+ const dbPath = resolveDatabasePath(database_path);
646
+ const password = getDatabasePassword();
647
+
648
+ const results = await searchColumnsInDatabase(dbPath, password, pattern);
649
+
650
+ res.json({
651
+ success: true,
652
+ data: { pattern, matches: results },
653
+ message: `Found ${results.length} column(s) matching pattern "${pattern}".`,
654
+ });
655
+ } catch (error) {
656
+ res.status(400).json({
657
+ success: false,
658
+ error: error.message,
659
+ });
660
+ }
661
+ }