db-mcp 1.1.1 → 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.
Files changed (84) hide show
  1. package/.gitleaks.toml +9 -0
  2. package/.trivyignore +8 -0
  3. package/README.md +193 -120
  4. package/dist/{chunk-TVIZ3XJH.js → chunk-5Y42NPBP.js} +4168 -2411
  5. package/dist/{chunk-AOUL5SHS.js → chunk-645ZEFLA.js} +70 -20
  6. package/dist/chunk-OKOVZ5QE.js +28 -0
  7. package/dist/chunk-SFJQCNG7.js +131 -0
  8. package/dist/{chunk-FW7UCRLN.js → chunk-VIDSICEL.js} +3 -0
  9. package/dist/chunk-WBER5YY4.js +2053 -0
  10. package/dist/{chunk-4IA3DB5C.js → chunk-X3MUUOWM.js} +19 -2
  11. package/dist/{chunk-Z2GFQU3G.js → chunk-Z7C2TM4L.js} +114 -21
  12. package/dist/cli.js +79 -5
  13. package/dist/{http-VSB7DBJR.js → http-6KF4ULDI.js} +199 -77
  14. package/dist/index.d.ts +231 -8
  15. package/dist/index.js +6 -5
  16. package/dist/{sqlite-26V3Y4MK.js → sqlite-U5KSYQXK.js} +62 -138
  17. package/dist/{sqlite-native-5O7FZJGB.js → sqlite-native-JXMCFQBA.js} +575 -113
  18. package/dist/worker-script.js +34 -10
  19. package/logs/.gitkeep +1 -0
  20. package/mcp-config-example.json +83 -0
  21. package/package.json +9 -8
  22. package/playwright.config.ts +1 -1
  23. package/scripts/update-badges.ts +99 -0
  24. package/server.json +7 -5
  25. package/test-server/README.md +20 -23
  26. package/test-server/code-map.md +45 -33
  27. package/test-server/reset-database.ps1 +59 -17
  28. package/test-server/scripts/README.md +27 -0
  29. package/test-server/{test-help-resources.mjs → scripts/test-help-resources.mjs} +12 -5
  30. package/test-server/scripts/test-prompts.mjs +251 -0
  31. package/test-server/{test-tool-annotations.mjs → scripts/test-tool-annotations.mjs} +9 -4
  32. package/test-server/test-advanced/README.md +70 -0
  33. package/test-server/test-advanced/test-codemode-advanced-admin.md +174 -0
  34. package/test-server/test-advanced/test-codemode-advanced-core.md +193 -0
  35. package/test-server/test-advanced/test-codemode-advanced-geo.md +157 -0
  36. package/test-server/test-advanced/test-codemode-advanced-introspection.md +171 -0
  37. package/test-server/test-advanced/test-codemode-advanced-json.md +169 -0
  38. package/test-server/test-advanced/test-codemode-advanced-migration.md +160 -0
  39. package/test-server/test-advanced/test-codemode-advanced-stats.md +185 -0
  40. package/test-server/test-advanced/test-codemode-advanced-text.md +183 -0
  41. package/test-server/test-advanced/test-codemode-advanced-transactions.md +136 -0
  42. package/test-server/test-advanced/test-codemode-advanced-vector.md +141 -0
  43. package/test-server/test-codemode/README.md +121 -0
  44. package/test-server/test-codemode/test-codemode-admin.md +223 -0
  45. package/test-server/test-codemode/test-codemode-core.md +286 -0
  46. package/test-server/test-codemode/test-codemode-geo.md +177 -0
  47. package/test-server/test-codemode/test-codemode-introspection.md +190 -0
  48. package/test-server/test-codemode/test-codemode-json.md +237 -0
  49. package/test-server/test-codemode/test-codemode-migration.md +278 -0
  50. package/test-server/test-codemode/test-codemode-sandbox.md +413 -0
  51. package/test-server/test-codemode/test-codemode-stats.md +232 -0
  52. package/test-server/test-codemode/test-codemode-text.md +237 -0
  53. package/test-server/test-codemode/test-codemode-transactions.md +236 -0
  54. package/test-server/test-codemode/test-codemode-vector.md +244 -0
  55. package/test-server/test-codemode/test-codemode-wasm-degradation.md +394 -0
  56. package/test-server/test-database.sql +37 -1
  57. package/test-server/test-resources.md +43 -16
  58. package/test-server/test-tool-groups/README.md +100 -0
  59. package/test-server/test-tool-groups/test-admin-core.md +165 -0
  60. package/test-server/test-tool-groups/test-admin-extensions.md +133 -0
  61. package/test-server/{test-tools.md → test-tool-groups/test-core-data.md} +103 -17
  62. package/test-server/test-tool-groups/test-core-schema.md +240 -0
  63. package/test-server/test-tool-groups/test-geo-haversine.md +130 -0
  64. package/test-server/test-tool-groups/test-geo-spatialite.md +110 -0
  65. package/test-server/test-tool-groups/test-introspection-diagnostics.md +123 -0
  66. package/test-server/test-tool-groups/test-introspection-schema.md +133 -0
  67. package/test-server/test-tool-groups/test-json-read.md +219 -0
  68. package/test-server/test-tool-groups/test-json-write.md +157 -0
  69. package/test-server/test-tool-groups/test-migration.md +193 -0
  70. package/test-server/test-tool-groups/test-stats-advanced.md +118 -0
  71. package/test-server/test-tool-groups/test-stats-basic.md +156 -0
  72. package/test-server/test-tool-groups/test-text-advanced.md +169 -0
  73. package/test-server/test-tool-groups/test-text-basic.md +177 -0
  74. package/test-server/test-tool-groups/test-transactions.md +179 -0
  75. package/test-server/test-tool-groups/test-vector-read.md +130 -0
  76. package/test-server/test-tool-groups/test-vector-write.md +115 -0
  77. package/test-server/tool-reference.md +79 -58
  78. package/tsconfig.build.json +6 -0
  79. package/dist/chunk-RHVEZ42P.js +0 -873
  80. package/test-server/test-agent-experience.md +0 -243
  81. package/test-server/test-group-tools.md +0 -861
  82. package/test-server/test-tools-advanced-1.md +0 -517
  83. package/test-server/test-tools-advanced-2.md +0 -487
  84. package/test-server/test-tools-codemode.md +0 -629
@@ -1,41 +1,50 @@
1
- import { TransactionBeginOutputSchema, TransactionCommitOutputSchema, TransactionRollbackOutputSchema, TransactionSavepointOutputSchema, TransactionReleaseOutputSchema, TransactionRollbackToOutputSchema, TransactionExecuteOutputSchema, WindowRowNumberOutputSchema, validateWhereClause, WindowRankOutputSchema, WindowLagLeadOutputSchema, WindowRunningTotalOutputSchema, resolveAliases, WindowMovingAvgOutputSchema, WindowNtileOutputSchema, autoEnableWal, detectAndSetJsonbSupport, SchemaManager, applyCommonPragmas, isDDL, getResourceDefinitions, getPromptDefinitions, SpatialiteLoadOutputSchema, SpatialiteCreateTableOutputSchema, SpatialiteQueryOutputSchema, SpatialiteAnalyzeOutputSchema, SpatialiteIndexOutputSchema, SpatialiteTransformOutputSchema, SpatialiteImportOutputSchema, normalizeSqliteParams, getCoreTools, getJsonOperationTools, getJsonHelperTools, getTextTools, getStatsTools, getVirtualTools, getVectorTools, getGeoTools, getAdminTools, getIntrospectionTools, getMigrationTools, getCodeModeTools, FtsCreateOutputSchema, sanitizeIdentifier, FtsSearchOutputSchema, validateTableExists, FtsRebuildOutputSchema } from './chunk-TVIZ3XJH.js';
2
- import { DatabaseAdapter } from './chunk-Z2GFQU3G.js';
3
- import { write, readOnly, VERSION, admin, getToolGroupIcon, idempotent } from './chunk-FW7UCRLN.js';
4
- import { formatHandlerError, ValidationError, ConfigurationError, ConnectionError, ResourceNotFoundError, QueryError } from './chunk-4IA3DB5C.js';
5
- import { logger, ERROR_CODES, DbMcpError } from './chunk-AOUL5SHS.js';
1
+ import { ErrorResponseFields, RowRecordSchema, validateWhereClause, resolveAliases, autoEnableWal, detectAndSetJsonbSupport, SchemaManager, applyCommonPragmas, isDDL, getResourceDefinitions, getPromptDefinitions, normalizeSqliteParams, translateSqliteError, getCoreTools, getJsonOperationTools, getJsonHelperTools, getTextTools, getStatsTools, getVirtualTools, getVectorTools, getGeoTools, getAdminTools, getIntrospectionTools, getMigrationTools, getCodeModeTools, sanitizeIdentifier, validateTableExists, FtsHeadlineSchema } from './chunk-5Y42NPBP.js';
2
+ import { DatabaseAdapter } from './chunk-Z7C2TM4L.js';
3
+ import { write, readOnly, VERSION, admin, getToolGroupIcon, idempotent } from './chunk-VIDSICEL.js';
4
+ import './chunk-OKOVZ5QE.js';
5
+ import { formatHandlerError, ValidationError, ConfigurationError, ConnectionError, ResourceNotFoundError, ExtensionNotAvailableError } from './chunk-X3MUUOWM.js';
6
+ import { logger, ERROR_CODES, DbMcpError } from './chunk-645ZEFLA.js';
6
7
  import Database from 'better-sqlite3';
7
8
  import { z } from 'zod';
8
9
  import path from 'path';
9
10
  import fs from 'fs';
10
11
  import { fileURLToPath } from 'url';
11
12
 
12
- function buildFts5UnavailableError(tableName) {
13
- return {
14
- success: false,
15
- message: "FTS5 module unavailable",
16
- tableName: tableName ?? "",
17
- error: "FTS5 module unavailable",
18
- hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
19
- };
20
- }
21
- function isFts5UnavailableError(error) {
22
- return error instanceof Error && error.message.toLowerCase().includes("no such module: fts5");
23
- }
24
- function isFts5Available(adapter) {
25
- return adapter.isNativeBackend();
26
- }
27
- function buildFts5SearchUnavailableError() {
28
- return {
29
- success: false,
30
- rowCount: 0,
31
- results: [],
32
- error: "FTS5 module unavailable",
33
- hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
34
- };
35
- }
13
+ var FtsCreateOutputSchema = z.object({
14
+ success: z.boolean(),
15
+ message: z.string().optional(),
16
+ tableName: z.string().optional(),
17
+ triggersCreated: z.array(z.string()).optional()
18
+ }).extend(ErrorResponseFields.shape);
19
+ var FtsResultSchema = z.object({
20
+ rank: z.number().nullable().optional(),
21
+ bm25: z.number().nullable().optional()
22
+ }).loose();
23
+ var FtsSearchOutputSchema = z.object({
24
+ success: z.boolean(),
25
+ rowCount: z.number().optional(),
26
+ results: z.array(FtsResultSchema).optional()
27
+ }).extend(ErrorResponseFields.shape);
28
+ var FtsRebuildOutputSchema = z.object({
29
+ success: z.boolean(),
30
+ message: z.string().optional(),
31
+ tableName: z.string().optional()
32
+ }).extend(ErrorResponseFields.shape);
33
+ var FtsHeadlineOutputSchema = z.object({
34
+ success: z.boolean(),
35
+ rowCount: z.number().optional(),
36
+ results: z.array(
37
+ z.object({
38
+ headline: z.string().optional(),
39
+ snippet: z.string().optional(),
40
+ rank: z.number().nullable().optional()
41
+ }).loose()
42
+ ).optional()
43
+ }).extend(ErrorResponseFields.shape);
36
44
  var coerceEnumValues = (allowed) => (val) => typeof val === "string" && allowed.includes(val) ? val : void 0;
37
45
  var FtsCreateSchema = z.object({
38
- tableName: z.string().describe("Name of the FTS table to create"),
46
+ tableName: z.string().optional().describe("Name of the FTS table to create"),
47
+ ftsTable: z.string().optional().describe("Name of the FTS table to create (alias)"),
39
48
  sourceTable: z.string().describe("Source table to index"),
40
49
  columns: z.array(z.string()).describe("Columns to include in the index"),
41
50
  contentTable: z.string().optional().describe("Content table for external content FTS"),
@@ -43,9 +52,7 @@ var FtsCreateSchema = z.object({
43
52
  coerceEnumValues(["unicode61", "ascii", "porter"]),
44
53
  z.enum(["unicode61", "ascii", "porter"]).optional().default("unicode61")
45
54
  ),
46
- createTriggers: z.boolean().optional().default(true).describe(
47
- "Create INSERT/UPDATE/DELETE triggers for auto-sync (only for external content tables)"
48
- )
55
+ createTriggers: z.boolean().optional().default(true).describe("Create triggers")
49
56
  });
50
57
  var FtsSearchSchema = z.object({
51
58
  table: z.string().describe("FTS table name"),
@@ -68,12 +75,39 @@ var FtsMatchInfoSchema = z.object({
68
75
  z.enum(["bm25", "rank"]).optional().default("bm25")
69
76
  )
70
77
  });
78
+
79
+ // src/adapters/sqlite/tools/fts.ts
80
+ function buildFts5UnavailableError(tableName) {
81
+ return {
82
+ success: false,
83
+ message: "FTS5 module unavailable",
84
+ tableName: tableName ?? "",
85
+ error: "FTS5 module unavailable",
86
+ hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
87
+ };
88
+ }
89
+ function isFts5UnavailableError(error) {
90
+ return error instanceof Error && error.message.toLowerCase().includes("no such module: fts5");
91
+ }
92
+ function isFts5Available(adapter) {
93
+ return adapter.isNativeBackend();
94
+ }
95
+ function buildFts5SearchUnavailableError() {
96
+ return {
97
+ success: false,
98
+ rowCount: 0,
99
+ results: [],
100
+ error: "FTS5 module unavailable",
101
+ hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
102
+ };
103
+ }
71
104
  function getFtsTools(adapter) {
72
105
  return [
73
106
  createFtsCreateTool(adapter),
74
107
  createFtsSearchTool(adapter),
75
108
  createFtsRebuildTool(adapter),
76
- createFtsMatchInfoTool(adapter)
109
+ createFtsMatchInfoTool(adapter),
110
+ createFtsHeadlineTool(adapter)
77
111
  ];
78
112
  }
79
113
  function createFtsCreateTool(adapter) {
@@ -88,7 +122,13 @@ function createFtsCreateTool(adapter) {
88
122
  handler: async (params, _context) => {
89
123
  try {
90
124
  const input = FtsCreateSchema.parse(params);
91
- sanitizeIdentifier(input.tableName);
125
+ const targetTableName = input.tableName || input.ftsTable;
126
+ if (!targetTableName) {
127
+ throw new ValidationError(
128
+ "Must provide either tableName or ftsTable"
129
+ );
130
+ }
131
+ sanitizeIdentifier(targetTableName);
92
132
  sanitizeIdentifier(input.sourceTable);
93
133
  for (const col of input.columns) {
94
134
  sanitizeIdentifier(col);
@@ -98,31 +138,31 @@ function createFtsCreateTool(adapter) {
98
138
  const effectiveContentTable = input.contentTable ?? input.sourceTable;
99
139
  sanitizeIdentifier(effectiveContentTable);
100
140
  options += `, content="${effectiveContentTable}"`;
101
- const sql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${input.tableName}" USING fts5(${columnList}, ${options})`;
141
+ const sql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${targetTableName}" USING fts5(${columnList}, ${options})`;
102
142
  await adapter.executeQuery(sql);
103
143
  let triggersCreated = [];
104
144
  if (input.createTriggers) {
105
145
  triggersCreated = await createSyncTriggers(
106
146
  adapter,
107
- input.tableName,
147
+ targetTableName,
108
148
  effectiveContentTable,
109
149
  input.columns
110
150
  );
111
151
  }
112
152
  await adapter.executeQuery(
113
- `INSERT INTO "${input.tableName}"("${input.tableName}") VALUES('rebuild')`
153
+ `INSERT INTO "${targetTableName}"("${targetTableName}") VALUES('rebuild')`
114
154
  );
115
- const message = triggersCreated.length ? `FTS5 table '${input.tableName}' created with ${triggersCreated.length} sync triggers` : `FTS5 table '${input.tableName}' created`;
155
+ const message = triggersCreated.length ? `FTS5 table '${targetTableName}' created with ${triggersCreated.length} sync triggers` : `FTS5 table '${targetTableName}' created`;
116
156
  return {
117
157
  success: true,
118
158
  message,
119
- tableName: input.tableName,
159
+ tableName: targetTableName,
120
160
  triggersCreated: triggersCreated.length ? triggersCreated : void 0
121
161
  };
122
162
  } catch (error) {
123
163
  if (isFts5UnavailableError(error)) {
124
164
  return buildFts5UnavailableError(
125
- params?.tableName
165
+ params?.tableName || params?.ftsTable
126
166
  );
127
167
  }
128
168
  return formatHandlerError(error);
@@ -292,6 +332,223 @@ function createFtsMatchInfoTool(adapter) {
292
332
  }
293
333
  };
294
334
  }
335
+ function createFtsHeadlineTool(adapter) {
336
+ return {
337
+ name: "sqlite_fts_headline",
338
+ description: "Generate highlighted snippets from FTS5 full-text search results. Returns headline (highlight) and snippet (context window) for each match.",
339
+ group: "text",
340
+ inputSchema: FtsHeadlineSchema,
341
+ outputSchema: FtsHeadlineOutputSchema,
342
+ requiredScopes: ["read"],
343
+ annotations: readOnly("FTS Headline"),
344
+ handler: async (params, _context) => {
345
+ try {
346
+ const input = FtsHeadlineSchema.parse(params);
347
+ if (!isFts5Available(adapter)) {
348
+ return buildFts5SearchUnavailableError();
349
+ }
350
+ sanitizeIdentifier(input.table);
351
+ await validateTableExists(adapter, input.table);
352
+ const queryEscaped = input.query.replace(/'/g, "''");
353
+ let columnIndex = 0;
354
+ if (input.column) {
355
+ const colResult = await adapter.executeReadQuery(
356
+ `PRAGMA table_info("${input.table}")`
357
+ );
358
+ const colRows = colResult.rows ?? [];
359
+ const found = colRows.findIndex(
360
+ (r) => String(r["name"]) === input.column
361
+ );
362
+ if (found >= 0) {
363
+ columnIndex = found;
364
+ }
365
+ }
366
+ const startSel = input.startSel.replace(/'/g, "''");
367
+ const stopSel = input.stopSel.replace(/'/g, "''");
368
+ const highlightExpr = `highlight("${input.table}", ${String(columnIndex)}, '${startSel}', '${stopSel}')`;
369
+ const snippetExpr = `snippet("${input.table}", ${String(columnIndex)}, '${startSel}', '${stopSel}', '...', ${String(input.snippetWords)})`;
370
+ if (input.query === "*" || input.query.trim() === "") {
371
+ const sql2 = `SELECT *, NULL as headline, NULL as snippet, NULL as rank FROM "${input.table}" ORDER BY rowid LIMIT ${String(input.limit)}`;
372
+ const result2 = await adapter.executeReadQuery(sql2);
373
+ return {
374
+ success: true,
375
+ rowCount: result2.rows?.length ?? 0,
376
+ results: result2.rows
377
+ };
378
+ }
379
+ const sql = `SELECT ${highlightExpr} as headline, ${snippetExpr} as snippet, rank FROM "${input.table}" WHERE "${input.table}" MATCH '${queryEscaped}' ORDER BY rank LIMIT ${String(input.limit)}`;
380
+ const result = await adapter.executeReadQuery(sql);
381
+ return {
382
+ success: true,
383
+ rowCount: result.rows?.length ?? 0,
384
+ results: result.rows
385
+ };
386
+ } catch (error) {
387
+ if (isFts5UnavailableError(error)) {
388
+ return buildFts5SearchUnavailableError();
389
+ }
390
+ return formatHandlerError(error);
391
+ }
392
+ }
393
+ };
394
+ }
395
+ var TransactionBeginOutputSchema = z.object({
396
+ success: z.boolean(),
397
+ message: z.string().optional(),
398
+ mode: z.string().optional()
399
+ }).extend(ErrorResponseFields.shape);
400
+ var TransactionCommitOutputSchema = z.object({
401
+ success: z.boolean(),
402
+ message: z.string().optional()
403
+ }).extend(ErrorResponseFields.shape);
404
+ var TransactionRollbackOutputSchema = z.object({
405
+ success: z.boolean(),
406
+ message: z.string().optional()
407
+ }).extend(ErrorResponseFields.shape);
408
+ var TransactionSavepointOutputSchema = z.object({
409
+ success: z.boolean(),
410
+ message: z.string().optional(),
411
+ name: z.string().optional(),
412
+ savepoint: z.string().optional()
413
+ }).extend(ErrorResponseFields.shape);
414
+ var TransactionReleaseOutputSchema = z.object({
415
+ success: z.boolean(),
416
+ message: z.string().optional(),
417
+ name: z.string().optional(),
418
+ savepoint: z.string().optional()
419
+ }).extend(ErrorResponseFields.shape);
420
+ var TransactionRollbackToOutputSchema = z.object({
421
+ success: z.boolean(),
422
+ message: z.string().optional(),
423
+ name: z.string().optional(),
424
+ savepoint: z.string().optional()
425
+ }).extend(ErrorResponseFields.shape);
426
+ var TransactionExecuteOutputSchema = z.object({
427
+ success: z.boolean(),
428
+ message: z.string().optional(),
429
+ statementsExecuted: z.number().optional(),
430
+ results: z.array(z.unknown()).optional()
431
+ }).extend(ErrorResponseFields.shape);
432
+ var TransactionStatusOutputSchema = z.object({
433
+ success: z.boolean(),
434
+ status: z.enum(["active", "none"]).optional().describe(
435
+ 'Transaction state: "active" (transaction in progress) or "none" (no active transaction)'
436
+ ),
437
+ active: z.boolean().optional().describe("Whether a transaction is currently in progress"),
438
+ message: z.string().optional()
439
+ }).extend(ErrorResponseFields.shape);
440
+ var RowNumberResultSchema = z.object({
441
+ row_number: z.number()
442
+ }).loose();
443
+ var WindowRowNumberOutputSchema = z.object({
444
+ success: z.boolean(),
445
+ rowCount: z.number().optional(),
446
+ rows: z.array(RowNumberResultSchema).optional(),
447
+ hint: z.string().optional()
448
+ }).extend(ErrorResponseFields.shape);
449
+ var RankResultSchema = z.object({
450
+ rank: z.number().optional(),
451
+ dense_rank: z.number().optional(),
452
+ percent_rank: z.number().optional()
453
+ }).loose();
454
+ var WindowRankOutputSchema = z.object({
455
+ success: z.boolean(),
456
+ rankType: z.string().optional(),
457
+ rowCount: z.number().optional(),
458
+ rows: z.array(RankResultSchema).optional(),
459
+ hint: z.string().optional()
460
+ }).extend(ErrorResponseFields.shape);
461
+ var LagLeadResultSchema = z.object({
462
+ lag_value: z.unknown().optional(),
463
+ lead_value: z.unknown().optional()
464
+ }).loose();
465
+ var WindowLagLeadOutputSchema = z.object({
466
+ success: z.boolean(),
467
+ direction: z.string().optional(),
468
+ offset: z.number().optional(),
469
+ rowCount: z.number().optional(),
470
+ rows: z.array(LagLeadResultSchema).optional(),
471
+ hint: z.string().optional()
472
+ }).extend(ErrorResponseFields.shape);
473
+ var RunningTotalResultSchema = z.object({
474
+ running_total: z.number()
475
+ }).loose();
476
+ var WindowRunningTotalOutputSchema = z.object({
477
+ success: z.boolean(),
478
+ valueColumn: z.string().optional(),
479
+ rowCount: z.number().optional(),
480
+ rows: z.array(RunningTotalResultSchema).optional(),
481
+ hint: z.string().optional()
482
+ }).extend(ErrorResponseFields.shape);
483
+ var MovingAvgResultSchema = z.object({
484
+ moving_avg: z.number()
485
+ }).loose();
486
+ var WindowMovingAvgOutputSchema = z.object({
487
+ success: z.boolean(),
488
+ valueColumn: z.string().optional(),
489
+ windowSize: z.number().optional(),
490
+ rowCount: z.number().optional(),
491
+ rows: z.array(MovingAvgResultSchema).optional(),
492
+ hint: z.string().optional()
493
+ }).extend(ErrorResponseFields.shape);
494
+ var NtileResultSchema = z.object({
495
+ ntile: z.number()
496
+ }).loose();
497
+ var WindowNtileOutputSchema = z.object({
498
+ success: z.boolean(),
499
+ buckets: z.number().optional(),
500
+ rowCount: z.number().optional(),
501
+ rows: z.array(NtileResultSchema).optional(),
502
+ hint: z.string().optional()
503
+ }).extend(ErrorResponseFields.shape);
504
+ var SpatialiteLoadOutputSchema = z.object({
505
+ success: z.boolean(),
506
+ message: z.string().optional(),
507
+ alreadyLoaded: z.boolean().optional(),
508
+ extensionPath: z.string().optional(),
509
+ searchedPaths: z.array(z.string()).optional()
510
+ }).extend(ErrorResponseFields.shape);
511
+ var SpatialiteCreateTableOutputSchema = z.object({
512
+ success: z.boolean(),
513
+ message: z.string().optional(),
514
+ tableName: z.string().optional(),
515
+ alreadyExists: z.boolean().optional(),
516
+ geometryColumn: z.string().optional(),
517
+ geometryType: z.string().optional(),
518
+ srid: z.number().optional()
519
+ }).extend(ErrorResponseFields.shape);
520
+ var SpatialiteQueryOutputSchema = z.object({
521
+ success: z.boolean(),
522
+ rowCount: z.number().optional(),
523
+ rows: z.array(RowRecordSchema).optional()
524
+ }).extend(ErrorResponseFields.shape);
525
+ var SpatialiteIndexOutputSchema = z.object({
526
+ success: z.boolean(),
527
+ message: z.string().optional(),
528
+ action: z.string().optional(),
529
+ alreadyExists: z.boolean().optional(),
530
+ alreadyDropped: z.boolean().optional(),
531
+ indexed: z.boolean().optional(),
532
+ valid: z.boolean().nullable().optional()
533
+ }).extend(ErrorResponseFields.shape);
534
+ var SpatialiteAnalyzeOutputSchema = z.object({
535
+ success: z.boolean(),
536
+ analysisType: z.string().optional(),
537
+ rowCount: z.number().optional(),
538
+ results: z.array(RowRecordSchema).optional()
539
+ }).extend(ErrorResponseFields.shape);
540
+ var SpatialiteTransformOutputSchema = z.object({
541
+ success: z.boolean(),
542
+ operation: z.string().optional(),
543
+ result: z.string().optional()
544
+ }).extend(ErrorResponseFields.shape);
545
+ var SpatialiteImportOutputSchema = z.object({
546
+ success: z.boolean(),
547
+ message: z.string().optional(),
548
+ rowsAffected: z.number().optional()
549
+ }).extend(ErrorResponseFields.shape);
550
+
551
+ // src/adapters/sqlite-native/tools/transactions.ts
295
552
  var VALID_MODES = ["deferred", "immediate", "exclusive"];
296
553
  var BeginTransactionSchema = z.object({
297
554
  mode: z.preprocess(
@@ -315,6 +572,7 @@ var ExecuteInTransactionSchema = z.object({
315
572
  function getTransactionTools(adapter) {
316
573
  return [
317
574
  createBeginTransactionTool(adapter),
575
+ createTransactionStatusTool(adapter),
318
576
  createCommitTransactionTool(adapter),
319
577
  createRollbackTransactionTool(adapter),
320
578
  createSavepointTool(adapter),
@@ -327,7 +585,7 @@ function createBeginTransactionTool(adapter) {
327
585
  return {
328
586
  name: "sqlite_transaction_begin",
329
587
  description: "Begin a new transaction. Use immediate or exclusive mode for write-heavy operations.",
330
- group: "admin",
588
+ group: "transactions",
331
589
  inputSchema: BeginTransactionSchema,
332
590
  outputSchema: TransactionBeginOutputSchema,
333
591
  annotations: write("Begin Transaction"),
@@ -348,11 +606,36 @@ function createBeginTransactionTool(adapter) {
348
606
  }
349
607
  };
350
608
  }
609
+ function createTransactionStatusTool(adapter) {
610
+ return {
611
+ name: "sqlite_transaction_status",
612
+ description: "Check whether a transaction is currently active. Returns status and a boolean flag. Read-only \u2014 does not alter transaction state.",
613
+ group: "transactions",
614
+ inputSchema: z.object({}),
615
+ outputSchema: TransactionStatusOutputSchema,
616
+ annotations: readOnly("Transaction Status"),
617
+ requiredScopes: ["read"],
618
+ handler: (_params, _context) => {
619
+ try {
620
+ const db = adapter.getDatabase();
621
+ const active = db.inTransaction;
622
+ return Promise.resolve({
623
+ success: true,
624
+ status: active ? "active" : "none",
625
+ active,
626
+ message: active ? "A transaction is currently active." : "No transaction is active."
627
+ });
628
+ } catch (error) {
629
+ return Promise.resolve(formatHandlerError(error));
630
+ }
631
+ }
632
+ };
633
+ }
351
634
  function createCommitTransactionTool(adapter) {
352
635
  return {
353
636
  name: "sqlite_transaction_commit",
354
637
  description: "Commit the current transaction, making all changes permanent.",
355
- group: "admin",
638
+ group: "transactions",
356
639
  inputSchema: z.object({}),
357
640
  outputSchema: TransactionCommitOutputSchema,
358
641
  annotations: write("Commit Transaction"),
@@ -374,7 +657,7 @@ function createRollbackTransactionTool(adapter) {
374
657
  return {
375
658
  name: "sqlite_transaction_rollback",
376
659
  description: "Rollback the current transaction, discarding all changes.",
377
- group: "admin",
660
+ group: "transactions",
378
661
  outputSchema: TransactionRollbackOutputSchema,
379
662
  inputSchema: z.object({}),
380
663
  annotations: write("Rollback Transaction"),
@@ -396,7 +679,7 @@ function createSavepointTool(adapter) {
396
679
  return {
397
680
  name: "sqlite_transaction_savepoint",
398
681
  description: "Create a savepoint within the current transaction for partial rollback.",
399
- group: "admin",
682
+ group: "transactions",
400
683
  outputSchema: TransactionSavepointOutputSchema,
401
684
  inputSchema: SavepointSchema,
402
685
  annotations: write("Create Savepoint"),
@@ -429,7 +712,7 @@ function createReleaseSavepointTool(adapter) {
429
712
  return {
430
713
  name: "sqlite_transaction_release",
431
714
  description: "Release a savepoint, keeping the changes made since it was created.",
432
- group: "admin",
715
+ group: "transactions",
433
716
  inputSchema: SavepointSchema,
434
717
  outputSchema: TransactionReleaseOutputSchema,
435
718
  annotations: write("Release Savepoint"),
@@ -462,7 +745,7 @@ function createRollbackToSavepointTool(adapter) {
462
745
  return {
463
746
  name: "sqlite_transaction_rollback_to",
464
747
  description: "Rollback to a savepoint, discarding changes made after it was created.",
465
- group: "admin",
748
+ group: "transactions",
466
749
  inputSchema: SavepointSchema,
467
750
  outputSchema: TransactionRollbackToOutputSchema,
468
751
  annotations: write("Rollback to Savepoint"),
@@ -495,7 +778,7 @@ function createExecuteInTransactionTool(adapter) {
495
778
  return {
496
779
  name: "sqlite_transaction_execute",
497
780
  description: "Execute multiple SQL statements in a single transaction. Automatically commits on success or rolls back on error.",
498
- group: "admin",
781
+ group: "transactions",
499
782
  outputSchema: TransactionExecuteOutputSchema,
500
783
  inputSchema: ExecuteInTransactionSchema,
501
784
  annotations: write("Execute in Transaction"),
@@ -504,18 +787,28 @@ function createExecuteInTransactionTool(adapter) {
504
787
  let input;
505
788
  try {
506
789
  input = ExecuteInTransactionSchema.parse(params);
790
+ if (input.statements.length === 0) {
791
+ throw new ValidationError("Must provide at least one SQL statement");
792
+ }
507
793
  } catch (error) {
794
+ const errObj = formatHandlerError(error);
508
795
  return {
509
- ...formatHandlerError(error),
510
- message: "",
796
+ ...errObj,
797
+ message: errObj.error || "Transaction execution failed",
511
798
  statementsExecuted: 0,
512
799
  results: []
513
800
  };
514
801
  }
515
802
  const results = [];
516
803
  let success = true;
804
+ let transactionStarted = false;
805
+ const db = adapter.getDatabase();
806
+ const originallyInTransaction = db.inTransaction;
517
807
  try {
518
- adapter.beginTransaction();
808
+ if (!originallyInTransaction) {
809
+ adapter.beginTransaction();
810
+ transactionStarted = true;
811
+ }
519
812
  for (const statement of input.statements) {
520
813
  try {
521
814
  const isSelect = statement.trim().toUpperCase().startsWith("SELECT");
@@ -549,25 +842,38 @@ function createExecuteInTransactionTool(adapter) {
549
842
  success = false;
550
843
  }
551
844
  }
552
- adapter.commitTransaction();
845
+ if (transactionStarted) {
846
+ adapter.commitTransaction();
847
+ }
553
848
  return {
554
849
  success,
850
+ error: success ? void 0 : "Transaction completed with errors",
555
851
  message: success ? "Transaction completed successfully" : "Transaction completed with errors",
556
852
  statementsExecuted: input.statements.length,
557
853
  results
558
854
  };
559
855
  } catch (error) {
560
856
  let rollbackFailure;
561
- try {
562
- adapter.rollbackTransaction();
563
- } catch (rbError) {
564
- rollbackFailure = rbError instanceof Error ? rbError.message : String(rbError);
857
+ if (transactionStarted) {
858
+ try {
859
+ adapter.rollbackTransaction();
860
+ } catch (rbError) {
861
+ rollbackFailure = rbError instanceof Error ? rbError.message : String(rbError);
862
+ }
863
+ } else if (originallyInTransaction && input.rollbackOnError) {
864
+ try {
865
+ adapter.rollbackTransaction();
866
+ } catch (rbError) {
867
+ rollbackFailure = rbError instanceof Error ? rbError.message : String(rbError);
868
+ }
565
869
  }
566
870
  const message = error instanceof Error ? error.message : String(error);
567
871
  const formatted = formatHandlerError(error);
568
872
  let rollbackMessage = `Transaction rolled back: ${message}`;
569
873
  if (rollbackFailure) {
570
874
  rollbackMessage += ` (rollback error: ${rollbackFailure})`;
875
+ } else if (!transactionStarted && !originallyInTransaction) {
876
+ rollbackMessage = `Transaction failed to start: ${message}`;
571
877
  }
572
878
  return {
573
879
  ...formatted,
@@ -580,7 +886,13 @@ function createExecuteInTransactionTool(adapter) {
580
886
  }
581
887
  };
582
888
  }
583
- var coerceNumber = (val) => typeof val === "string" ? isNaN(Number(val)) ? void 0 : Number(val) : val;
889
+ var coerceNumber = (val) => {
890
+ if (typeof val === "string") {
891
+ const num = Number(val);
892
+ return isNaN(num) ? void 0 : num;
893
+ }
894
+ return val;
895
+ };
584
896
  var coerceEnumValues2 = (allowed) => (val) => typeof val === "string" && allowed.includes(val) ? val : void 0;
585
897
  var VALID_DIRECTIONS = ["lag", "lead"];
586
898
  var RowNumberSchema = z.object({
@@ -591,7 +903,7 @@ var RowNumberSchema = z.object({
591
903
  whereClause: z.string().optional().describe("Optional WHERE clause"),
592
904
  limit: z.preprocess(
593
905
  coerceNumber,
594
- z.number().optional().default(100).describe("Maximum rows to return")
906
+ z.number().optional().default(20).describe("Maximum rows to return")
595
907
  )
596
908
  });
597
909
  var RankSchema = z.object({
@@ -606,7 +918,7 @@ var RankSchema = z.object({
606
918
  whereClause: z.string().optional().describe("Optional WHERE clause"),
607
919
  limit: z.preprocess(
608
920
  coerceNumber,
609
- z.number().optional().default(100).describe("Maximum rows to return")
921
+ z.number().optional().default(20).describe("Maximum rows to return")
610
922
  )
611
923
  });
612
924
  var LagLeadSchema = z.object({
@@ -624,7 +936,7 @@ var LagLeadSchema = z.object({
624
936
  whereClause: z.string().optional().describe("Optional WHERE clause"),
625
937
  limit: z.preprocess(
626
938
  coerceNumber,
627
- z.number().optional().default(100).describe("Maximum rows to return")
939
+ z.number().optional().default(20).describe("Maximum rows to return")
628
940
  )
629
941
  });
630
942
  var RunningTotalSchema = z.object({
@@ -636,7 +948,7 @@ var RunningTotalSchema = z.object({
636
948
  whereClause: z.string().optional().describe("Optional WHERE clause"),
637
949
  limit: z.preprocess(
638
950
  coerceNumber,
639
- z.number().optional().default(100).describe("Maximum rows to return")
951
+ z.number().optional().default(20).describe("Maximum rows to return")
640
952
  )
641
953
  });
642
954
  var MovingAverageSchema = z.object({
@@ -652,7 +964,7 @@ var MovingAverageSchema = z.object({
652
964
  whereClause: z.string().optional().describe("Optional WHERE clause"),
653
965
  limit: z.preprocess(
654
966
  coerceNumber,
655
- z.number().optional().default(100).describe("Maximum rows to return")
967
+ z.number().optional().default(20).describe("Maximum rows to return")
656
968
  )
657
969
  });
658
970
  var NtileSchema = z.object({
@@ -667,7 +979,7 @@ var NtileSchema = z.object({
667
979
  whereClause: z.string().optional().describe("Optional WHERE clause"),
668
980
  limit: z.preprocess(
669
981
  coerceNumber,
670
- z.number().optional().default(100).describe("Maximum rows to return")
982
+ z.number().optional().default(20).describe("Maximum rows to return")
671
983
  )
672
984
  });
673
985
  async function validateTableExists2(adapter, table) {
@@ -728,11 +1040,68 @@ async function validateOrderByColumns(adapter, table, orderBy) {
728
1040
  await validateColumnInTable(adapter, table, colName);
729
1041
  }
730
1042
  }
731
- function formatColumns(selectColumns) {
732
- if (selectColumns === void 0 || selectColumns.length === 0) {
733
- return "*";
1043
+ async function resolveSelectColumns(adapter, table, selectColumns, rankCol) {
1044
+ if (selectColumns && selectColumns.length > 0) {
1045
+ return {
1046
+ columnList: selectColumns.map((c) => `"${c}"`).join(", ")
1047
+ };
1048
+ }
1049
+ const tableInfo = await adapter.executeReadQuery(
1050
+ `PRAGMA table_info("${table}")`
1051
+ );
1052
+ const TEXT_TYPES = /* @__PURE__ */ new Set([
1053
+ "text",
1054
+ "blob",
1055
+ "clob",
1056
+ "varchar",
1057
+ "nvarchar",
1058
+ "char"
1059
+ ]);
1060
+ const LONG_CONTENT_PATTERNS = [
1061
+ "description",
1062
+ "body",
1063
+ "bio",
1064
+ "content",
1065
+ "notes",
1066
+ "summary",
1067
+ "comment",
1068
+ "details",
1069
+ "html",
1070
+ "markdown",
1071
+ "text",
1072
+ "message",
1073
+ "payload",
1074
+ "raw",
1075
+ "data",
1076
+ "log",
1077
+ "blob"
1078
+ ];
1079
+ const excluded = [];
1080
+ const included = [];
1081
+ for (const c of tableInfo.rows ?? []) {
1082
+ const colName = c["name"];
1083
+ const colType = (c["type"] ?? "").toLowerCase();
1084
+ const nameLower = colName.toLowerCase();
1085
+ const isText = [...TEXT_TYPES].some(
1086
+ (t) => colType === t || colType.startsWith(t)
1087
+ );
1088
+ const isRankCol = rankCol ? nameLower === rankCol.toLowerCase() : false;
1089
+ const isLongContent = LONG_CONTENT_PATTERNS.some(
1090
+ (p) => nameLower === p || nameLower.endsWith(`_${p}`) || nameLower.startsWith(`${p}_`)
1091
+ );
1092
+ if (isText && !isRankCol && isLongContent) {
1093
+ excluded.push(colName);
1094
+ } else {
1095
+ included.push(colName);
1096
+ }
1097
+ }
1098
+ if (excluded.length > 0 && included.length > 0) {
1099
+ return {
1100
+ columnList: included.map((c) => `"${c}"`).join(", "),
1101
+ hint: `Excluded ${excluded.length} long-content column(s) (${excluded.join(", ")}) to reduce payload. Use selectColumns to override.`
1102
+ };
734
1103
  }
735
- return selectColumns.map((c) => `"${c}"`).join(", ");
1104
+ return { columnList: "*" };
736
1105
  }
737
1106
  function getWindowTools(adapter) {
738
1107
  return [
@@ -758,7 +1127,11 @@ function createRowNumberTool(adapter) {
758
1127
  const input = RowNumberSchema.parse(params);
759
1128
  await validateTableExists2(adapter, input.table);
760
1129
  await validateOrderByColumns(adapter, input.table, input.orderBy);
761
- const columns = formatColumns(input.selectColumns);
1130
+ const { columnList: columns, hint } = await resolveSelectColumns(
1131
+ adapter,
1132
+ input.table,
1133
+ input.selectColumns
1134
+ );
762
1135
  const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
763
1136
  let sql = `
764
1137
  SELECT ${columns},
@@ -771,11 +1144,13 @@ function createRowNumberTool(adapter) {
771
1144
  }
772
1145
  sql += ` LIMIT ${input.limit}`;
773
1146
  const result = await adapter.executeReadQuery(sql);
774
- return {
1147
+ const response = {
775
1148
  success: true,
776
1149
  rowCount: result.rows?.length ?? 0,
777
1150
  rows: result.rows
778
1151
  };
1152
+ if (hint) response["hint"] = hint;
1153
+ return response;
779
1154
  } catch (error) {
780
1155
  return formatHandlerError(error);
781
1156
  }
@@ -796,7 +1171,11 @@ function createRankTool(adapter) {
796
1171
  const input = RankSchema.parse(params);
797
1172
  await validateTableExists2(adapter, input.table);
798
1173
  await validateOrderByColumns(adapter, input.table, input.orderBy);
799
- const columns = formatColumns(input.selectColumns);
1174
+ const { columnList: columns, hint } = await resolveSelectColumns(
1175
+ adapter,
1176
+ input.table,
1177
+ input.selectColumns
1178
+ );
800
1179
  const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
801
1180
  const rankFunc = input.rankType.toUpperCase();
802
1181
  let sql = `
@@ -810,12 +1189,14 @@ function createRankTool(adapter) {
810
1189
  }
811
1190
  sql += ` LIMIT ${input.limit}`;
812
1191
  const result = await adapter.executeReadQuery(sql);
813
- return {
1192
+ const response = {
814
1193
  success: true,
815
1194
  rankType: input.rankType,
816
1195
  rowCount: result.rows?.length ?? 0,
817
1196
  rows: result.rows
818
1197
  };
1198
+ if (hint) response["hint"] = hint;
1199
+ return response;
819
1200
  } catch (error) {
820
1201
  return formatHandlerError(error);
821
1202
  }
@@ -849,7 +1230,12 @@ function createLagLeadTool(adapter) {
849
1230
  await validateTableExists2(adapter, input.table);
850
1231
  await validateColumnInTable(adapter, input.table, input.column);
851
1232
  await validateOrderByColumns(adapter, input.table, input.orderBy);
852
- const columns = formatColumns(input.selectColumns);
1233
+ const { columnList: columns, hint } = await resolveSelectColumns(
1234
+ adapter,
1235
+ input.table,
1236
+ input.selectColumns,
1237
+ input.column
1238
+ );
853
1239
  const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
854
1240
  const func = normalizedDirection.toUpperCase();
855
1241
  const defaultVal = input.defaultValue !== void 0 ? `, ${input.defaultValue}` : "";
@@ -864,13 +1250,15 @@ function createLagLeadTool(adapter) {
864
1250
  }
865
1251
  sql += ` LIMIT ${input.limit}`;
866
1252
  const result = await adapter.executeReadQuery(sql);
867
- return {
1253
+ const response = {
868
1254
  success: true,
869
1255
  direction: normalizedDirection,
870
1256
  offset: input.offset,
871
1257
  rowCount: result.rows?.length ?? 0,
872
1258
  rows: result.rows
873
1259
  };
1260
+ if (hint) response["hint"] = hint;
1261
+ return response;
874
1262
  } catch (error) {
875
1263
  return formatHandlerError(error);
876
1264
  }
@@ -894,7 +1282,12 @@ function createRunningTotalTool(adapter) {
894
1282
  await validateTableExists2(adapter, input.table);
895
1283
  await validateColumnInTable(adapter, input.table, input.column);
896
1284
  await validateOrderByColumns(adapter, input.table, input.orderBy);
897
- const columns = formatColumns(input.selectColumns);
1285
+ const { columnList: columns, hint } = await resolveSelectColumns(
1286
+ adapter,
1287
+ input.table,
1288
+ input.selectColumns,
1289
+ input.column
1290
+ );
898
1291
  const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
899
1292
  let sql = `
900
1293
  SELECT ${columns},
@@ -907,12 +1300,14 @@ function createRunningTotalTool(adapter) {
907
1300
  }
908
1301
  sql += ` LIMIT ${input.limit}`;
909
1302
  const result = await adapter.executeReadQuery(sql);
910
- return {
1303
+ const response = {
911
1304
  success: true,
912
1305
  valueColumn: input.column,
913
1306
  rowCount: result.rows?.length ?? 0,
914
1307
  rows: result.rows
915
1308
  };
1309
+ if (hint) response["hint"] = hint;
1310
+ return response;
916
1311
  } catch (error) {
917
1312
  return formatHandlerError(error);
918
1313
  }
@@ -945,7 +1340,12 @@ function createMovingAverageTool(adapter) {
945
1340
  await validateTableExists2(adapter, input.table);
946
1341
  await validateColumnInTable(adapter, input.table, input.column);
947
1342
  await validateOrderByColumns(adapter, input.table, input.orderBy);
948
- const columns = formatColumns(input.selectColumns);
1343
+ const { columnList: columns, hint } = await resolveSelectColumns(
1344
+ adapter,
1345
+ input.table,
1346
+ input.selectColumns,
1347
+ input.column
1348
+ );
949
1349
  const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
950
1350
  const preceding = input.windowSize - 1;
951
1351
  let sql = `
@@ -959,13 +1359,15 @@ function createMovingAverageTool(adapter) {
959
1359
  }
960
1360
  sql += ` LIMIT ${input.limit}`;
961
1361
  const result = await adapter.executeReadQuery(sql);
962
- return {
1362
+ const response = {
963
1363
  success: true,
964
1364
  valueColumn: input.column,
965
1365
  windowSize: input.windowSize,
966
1366
  rowCount: result.rows?.length ?? 0,
967
1367
  rows: result.rows
968
1368
  };
1369
+ if (hint) response["hint"] = hint;
1370
+ return response;
969
1371
  } catch (error) {
970
1372
  return formatHandlerError(error);
971
1373
  }
@@ -995,7 +1397,11 @@ function createNtileTool(adapter) {
995
1397
  }
996
1398
  await validateTableExists2(adapter, input.table);
997
1399
  await validateOrderByColumns(adapter, input.table, input.orderBy);
998
- const columns = formatColumns(input.selectColumns);
1400
+ const { columnList: columns, hint } = await resolveSelectColumns(
1401
+ adapter,
1402
+ input.table,
1403
+ input.selectColumns
1404
+ );
999
1405
  const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
1000
1406
  let sql = `
1001
1407
  SELECT ${columns},
@@ -1008,12 +1414,14 @@ function createNtileTool(adapter) {
1008
1414
  }
1009
1415
  sql += ` LIMIT ${input.limit}`;
1010
1416
  const result = await adapter.executeReadQuery(sql);
1011
- return {
1417
+ const response = {
1012
1418
  success: true,
1013
1419
  buckets: input.buckets,
1014
1420
  rowCount: result.rows?.length ?? 0,
1015
1421
  rows: result.rows
1016
1422
  };
1423
+ if (hint) response["hint"] = hint;
1424
+ return response;
1017
1425
  } catch (error) {
1018
1426
  return formatHandlerError(error);
1019
1427
  }
@@ -1211,11 +1619,9 @@ function ensureSpatialite(adapter) {
1211
1619
  if (!isSpatialiteLoaded(adapter)) {
1212
1620
  const result = tryLoadSpatialite(adapter);
1213
1621
  if (!result.success) {
1214
- throw new DbMcpError(
1215
- result.error ?? "Failed to load SpatiaLite",
1216
- "SPATIALITE_LOAD_FAILED",
1217
- "connection" /* CONNECTION */
1218
- );
1622
+ throw new ExtensionNotAvailableError("SpatiaLite", {
1623
+ suggestion: "Install mod_spatialite and set SPATIALITE_PATH environment variable."
1624
+ });
1219
1625
  }
1220
1626
  }
1221
1627
  }
@@ -1422,9 +1828,10 @@ function createSpatialIndexTool(adapter) {
1422
1828
  }
1423
1829
  const indexExists = async () => {
1424
1830
  const idxCheck = await adapter.executeReadQuery(
1425
- `SELECT name FROM sqlite_master WHERE type='table' AND name='idx_${input.tableName}_${input.geometryColumn}'`
1831
+ `SELECT spatial_index_enabled FROM geometry_columns WHERE f_table_name = '${input.tableName}' AND f_geometry_column = '${input.geometryColumn}'`
1426
1832
  );
1427
- return (idxCheck.rows?.length ?? 0) > 0;
1833
+ const val = idxCheck.rows?.[0]?.["spatial_index_enabled"];
1834
+ return val === 1 || val === "1";
1428
1835
  };
1429
1836
  switch (input.action) {
1430
1837
  case "create": {
@@ -1436,9 +1843,18 @@ function createSpatialIndexTool(adapter) {
1436
1843
  action: "create"
1437
1844
  };
1438
1845
  }
1439
- await adapter.executeReadQuery(
1440
- `SELECT CreateSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
1846
+ const createRes = await adapter.executeReadQuery(
1847
+ `SELECT CreateSpatialIndex('${input.tableName}', '${input.geometryColumn}') as res`
1441
1848
  );
1849
+ if (createRes.rows?.[0]?.["res"] === 0) {
1850
+ return {
1851
+ success: false,
1852
+ error: `Failed to create spatial index on ${input.tableName}.${input.geometryColumn}`,
1853
+ code: "SPATIAL_INDEX_CREATE_FAILED",
1854
+ category: "internal",
1855
+ recoverable: false
1856
+ };
1857
+ }
1442
1858
  return {
1443
1859
  success: true,
1444
1860
  message: `Spatial index created on ${input.tableName}.${input.geometryColumn}`,
@@ -1454,9 +1870,18 @@ function createSpatialIndexTool(adapter) {
1454
1870
  action: "drop"
1455
1871
  };
1456
1872
  }
1457
- await adapter.executeReadQuery(
1458
- `SELECT DisableSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
1873
+ const dropRes = await adapter.executeReadQuery(
1874
+ `SELECT DisableSpatialIndex('${input.tableName}', '${input.geometryColumn}') as res`
1459
1875
  );
1876
+ if (dropRes.rows?.[0]?.["res"] === 0) {
1877
+ return {
1878
+ success: false,
1879
+ error: `Failed to drop spatial index on ${input.tableName}.${input.geometryColumn}`,
1880
+ code: "SPATIAL_INDEX_DROP_FAILED",
1881
+ category: "internal",
1882
+ recoverable: false
1883
+ };
1884
+ }
1460
1885
  return {
1461
1886
  success: true,
1462
1887
  message: `Spatial index dropped from ${input.tableName}.${input.geometryColumn}`,
@@ -1786,6 +2211,19 @@ function createSpatialImportTool(adapter) {
1786
2211
  if (input.format === "geojson") {
1787
2212
  try {
1788
2213
  JSON.parse(input.data);
2214
+ const geojsonCheck = await adapter.executeReadQuery(
2215
+ `SELECT GeomFromGeoJSON('${input.data}') as geom`
2216
+ );
2217
+ const parsedGeom2 = geojsonCheck.rows?.[0]?.["geom"];
2218
+ if (parsedGeom2 === null || parsedGeom2 === void 0) {
2219
+ return {
2220
+ success: false,
2221
+ error: `Invalid GeoJSON geometry: could not be parsed by SpatiaLite (must be a valid Geometry object)`,
2222
+ code: "VALIDATION_ERROR",
2223
+ category: "validation",
2224
+ recoverable: false
2225
+ };
2226
+ }
1789
2227
  const columns2 = ["geom"];
1790
2228
  const values2 = [
1791
2229
  `SetSRID(GeomFromGeoJSON('${input.data}'), ${input.srid})`
@@ -2043,18 +2481,7 @@ function nativeExecuteRead(db, sql, params, log2) {
2043
2481
  executionTimeMs: Date.now() - start
2044
2482
  });
2045
2483
  } catch (error) {
2046
- const message = error instanceof Error ? error.message : String(error);
2047
- log2.error(`Query failed: ${message}`, {
2048
- code: ERROR_CODES.DB.QUERY_FAILED.full
2049
- });
2050
- throw new QueryError(
2051
- `Query execution failed: ${message}`,
2052
- "DB_QUERY_FAILED",
2053
- {
2054
- sql,
2055
- cause: error instanceof Error ? error : void 0
2056
- }
2057
- );
2484
+ translateSqliteError(error, sql, "Query execution", log2);
2058
2485
  }
2059
2486
  }
2060
2487
  function nativeExecuteWrite(db, sql, params, log2) {
@@ -2062,21 +2489,27 @@ function nativeExecuteWrite(db, sql, params, log2) {
2062
2489
  try {
2063
2490
  const stmt = db.prepare(sql);
2064
2491
  const normalizedParams = normalizeSqliteParams(params);
2492
+ if (stmt.reader) {
2493
+ const rows = normalizedParams ? stmt.all(...normalizedParams) : stmt.all();
2494
+ return Promise.resolve({
2495
+ rows,
2496
+ rowsAffected: rows.length,
2497
+ // .all() does not return changes, but we know it's rows.length for RETURNING
2498
+ executionTimeMs: Date.now() - start
2499
+ });
2500
+ }
2065
2501
  const info = normalizedParams ? stmt.run(...normalizedParams) : stmt.run();
2066
- return Promise.resolve({
2502
+ const result = {
2067
2503
  rows: [],
2068
2504
  rowsAffected: info.changes,
2069
2505
  executionTimeMs: Date.now() - start
2070
- });
2506
+ };
2507
+ if (info.lastInsertRowid !== void 0) {
2508
+ result.lastInsertId = Number(info.lastInsertRowid);
2509
+ }
2510
+ return Promise.resolve(result);
2071
2511
  } catch (error) {
2072
- const message = error instanceof Error ? error.message : String(error);
2073
- log2.error(`Write query failed: ${message}`, {
2074
- code: ERROR_CODES.DB.QUERY_FAILED.full
2075
- });
2076
- throw new QueryError(`Write query failed: ${message}`, "DB_WRITE_FAILED", {
2077
- sql,
2078
- cause: error instanceof Error ? error : void 0
2079
- });
2512
+ translateSqliteError(error, sql, "Write query", log2);
2080
2513
  }
2081
2514
  }
2082
2515
 
@@ -2099,7 +2532,8 @@ var NativeSqliteAdapter = class extends DatabaseAdapter {
2099
2532
  */
2100
2533
  getConfiguredPath() {
2101
2534
  const config = this.config;
2102
- return config?.filePath ?? config?.connectionString ?? ":memory:";
2535
+ const path2 = config?.filePath ?? config?.connectionString ?? ":memory:";
2536
+ return path2;
2103
2537
  }
2104
2538
  db = null;
2105
2539
  schemaManager = null;
@@ -2137,6 +2571,17 @@ var NativeSqliteAdapter = class extends DatabaseAdapter {
2137
2571
  const versionResult = db.prepare("SELECT sqlite_version()").get();
2138
2572
  return versionResult?.["sqlite_version()"] ?? "0.0.0";
2139
2573
  }, log);
2574
+ if (sqliteConfig.initializationSql && Array.isArray(sqliteConfig.initializationSql)) {
2575
+ for (const sql of sqliteConfig.initializationSql) {
2576
+ db.exec(sql);
2577
+ }
2578
+ log.info(
2579
+ `Executed ${sqliteConfig.initializationSql.length} initialization SQL statements`,
2580
+ {
2581
+ code: "SQLITE_INIT_SQL"
2582
+ }
2583
+ );
2584
+ }
2140
2585
  this.schemaManager = new SchemaManager(this);
2141
2586
  this.connected = true;
2142
2587
  } catch (error) {
@@ -2328,6 +2773,23 @@ var NativeSqliteAdapter = class extends DatabaseAdapter {
2328
2773
  listSchemas() {
2329
2774
  return Promise.resolve(["main"]);
2330
2775
  }
2776
+ /**
2777
+ * Get indexes, optionally for a specific table (cached via SchemaManager)
2778
+ */
2779
+ async getIndexes(table) {
2780
+ this.ensureConnected();
2781
+ if (this.schemaManager) {
2782
+ if (table) {
2783
+ return this.schemaManager.getTableIndexes(table);
2784
+ }
2785
+ return this.schemaManager.getAllIndexes();
2786
+ }
2787
+ const all = await this.getAllIndexes();
2788
+ if (table) {
2789
+ return all.filter((i) => i.tableName === table);
2790
+ }
2791
+ return all;
2792
+ }
2331
2793
  /**
2332
2794
  * Get all indexes in the database (cached via SchemaManager)
2333
2795
  * Required by sqlite_indexes resource