db-mcp 3.0.2 → 5.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 (183) hide show
  1. package/README.md +112 -75
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/adapters/database-adapter.d.ts +11 -1
  4. package/dist/adapters/database-adapter.d.ts.map +1 -1
  5. package/dist/adapters/registration/resources.d.ts.map +1 -1
  6. package/dist/adapters/registration/tools.d.ts.map +1 -1
  7. package/dist/adapters/sqlite/prompts/analysis.d.ts.map +1 -1
  8. package/dist/adapters/sqlite/query-executor.d.ts.map +1 -1
  9. package/dist/adapters/sqlite/read-write-lock.d.ts +49 -0
  10. package/dist/adapters/sqlite/read-write-lock.d.ts.map +1 -0
  11. package/dist/adapters/sqlite/resources.d.ts +1 -1
  12. package/dist/adapters/sqlite/resources.d.ts.map +1 -1
  13. package/dist/adapters/sqlite/schemas/admin.d.ts +33 -19
  14. package/dist/adapters/sqlite/schemas/admin.d.ts.map +1 -1
  15. package/dist/adapters/sqlite/schemas/core.d.ts +96 -0
  16. package/dist/adapters/sqlite/schemas/core.d.ts.map +1 -1
  17. package/dist/adapters/sqlite/schemas/fts.d.ts +4 -0
  18. package/dist/adapters/sqlite/schemas/fts.d.ts.map +1 -1
  19. package/dist/adapters/sqlite/schemas/introspection.d.ts +25 -15
  20. package/dist/adapters/sqlite/schemas/introspection.d.ts.map +1 -1
  21. package/dist/adapters/sqlite/schemas/migration.d.ts +2 -2
  22. package/dist/adapters/sqlite/schemas/migration.d.ts.map +1 -1
  23. package/dist/adapters/sqlite/schemas/spatialite.d.ts +1 -0
  24. package/dist/adapters/sqlite/schemas/spatialite.d.ts.map +1 -1
  25. package/dist/adapters/sqlite/schemas/text.d.ts +36 -0
  26. package/dist/adapters/sqlite/schemas/text.d.ts.map +1 -1
  27. package/dist/adapters/sqlite/sqlite-adapter.d.ts +6 -0
  28. package/dist/adapters/sqlite/sqlite-adapter.d.ts.map +1 -1
  29. package/dist/adapters/sqlite/tools/admin/backup/create.d.ts.map +1 -1
  30. package/dist/adapters/sqlite/tools/admin/backup/dump.d.ts.map +1 -1
  31. package/dist/adapters/sqlite/tools/admin/backup/restore.d.ts.map +1 -1
  32. package/dist/adapters/sqlite/tools/admin/index.d.ts.map +1 -1
  33. package/dist/adapters/sqlite/tools/admin/pragma.d.ts +0 -4
  34. package/dist/adapters/sqlite/tools/admin/pragma.d.ts.map +1 -1
  35. package/dist/adapters/sqlite/tools/admin/verify.d.ts.map +1 -1
  36. package/dist/adapters/sqlite/tools/codemode.d.ts.map +1 -1
  37. package/dist/adapters/sqlite/tools/core/convenience.d.ts.map +1 -1
  38. package/dist/adapters/sqlite/tools/core/index.d.ts.map +1 -1
  39. package/dist/adapters/sqlite/tools/core/queries.d.ts.map +1 -1
  40. package/dist/adapters/sqlite/tools/core/tables.d.ts.map +1 -1
  41. package/dist/adapters/sqlite/tools/core/versioning.d.ts +19 -0
  42. package/dist/adapters/sqlite/tools/core/versioning.d.ts.map +1 -0
  43. package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -1
  44. package/dist/adapters/sqlite/tools/introspection/diagnostics/indexes.d.ts.map +1 -1
  45. package/dist/adapters/sqlite/tools/introspection/graph/tools.d.ts.map +1 -1
  46. package/dist/adapters/sqlite/tools/json-helpers/read.d.ts.map +1 -1
  47. package/dist/adapters/sqlite/tools/json-operations/crud.d.ts.map +1 -1
  48. package/dist/adapters/sqlite/tools/json-operations/query.d.ts.map +1 -1
  49. package/dist/adapters/sqlite/tools/migration/helpers.d.ts.map +1 -1
  50. package/dist/adapters/sqlite/tools/text/helpers.d.ts +9 -0
  51. package/dist/adapters/sqlite/tools/text/helpers.d.ts.map +1 -1
  52. package/dist/adapters/sqlite/tools/text/index.d.ts.map +1 -1
  53. package/dist/adapters/sqlite/tools/text/search.d.ts +4 -0
  54. package/dist/adapters/sqlite/tools/text/search.d.ts.map +1 -1
  55. package/dist/adapters/sqlite/tools/vector/metadata.d.ts.map +1 -1
  56. package/dist/adapters/sqlite/tools/vector/search.d.ts.map +1 -1
  57. package/dist/adapters/sqlite/tools/vector/storage.d.ts.map +1 -1
  58. package/dist/adapters/sqlite/tools/virtual/vtable/analyze-csv.d.ts.map +1 -1
  59. package/dist/adapters/sqlite/tools/virtual/vtable/csv.d.ts.map +1 -1
  60. package/dist/adapters/sqlite/tools/virtual/vtable/info.d.ts.map +1 -1
  61. package/dist/adapters/sqlite/types.d.ts +5 -1
  62. package/dist/adapters/sqlite/types.d.ts.map +1 -1
  63. package/dist/adapters/sqlite-native/index.d.ts +1 -1
  64. package/dist/adapters/sqlite-native/index.d.ts.map +1 -1
  65. package/dist/adapters/sqlite-native/native-sqlite-adapter.d.ts.map +1 -1
  66. package/dist/adapters/sqlite-native/tools/spatialite/tools.d.ts.map +1 -1
  67. package/dist/adapters/sqlite-native/tools/window/helpers.d.ts +52 -0
  68. package/dist/adapters/sqlite-native/tools/window/helpers.d.ts.map +1 -0
  69. package/dist/adapters/sqlite-native/tools/window/index.d.ts +7 -0
  70. package/dist/adapters/sqlite-native/tools/window/index.d.ts.map +1 -0
  71. package/dist/adapters/sqlite-native/tools/window/lag-lead.d.ts +7 -0
  72. package/dist/adapters/sqlite-native/tools/window/lag-lead.d.ts.map +1 -0
  73. package/dist/adapters/sqlite-native/tools/window/moving-average.d.ts +7 -0
  74. package/dist/adapters/sqlite-native/tools/window/moving-average.d.ts.map +1 -0
  75. package/dist/adapters/sqlite-native/tools/window/ntile.d.ts +7 -0
  76. package/dist/adapters/sqlite-native/tools/window/ntile.d.ts.map +1 -0
  77. package/dist/adapters/sqlite-native/tools/window/rank.d.ts +7 -0
  78. package/dist/adapters/sqlite-native/tools/window/rank.d.ts.map +1 -0
  79. package/dist/adapters/sqlite-native/tools/window/row-number.d.ts +7 -0
  80. package/dist/adapters/sqlite-native/tools/window/row-number.d.ts.map +1 -0
  81. package/dist/adapters/sqlite-native/tools/window/running-total.d.ts +7 -0
  82. package/dist/adapters/sqlite-native/tools/window/running-total.d.ts.map +1 -0
  83. package/dist/adapters/sqlite-native/tools/window/schemas.d.ts +159 -0
  84. package/dist/adapters/sqlite-native/tools/window/schemas.d.ts.map +1 -0
  85. package/dist/adapters/sqlite-native/transaction-methods.d.ts.map +1 -1
  86. package/dist/audit/interceptor.d.ts.map +1 -1
  87. package/dist/audit/logger.d.ts +26 -25
  88. package/dist/audit/logger.d.ts.map +1 -1
  89. package/dist/auth/authorization-server-discovery.d.ts.map +1 -1
  90. package/dist/auth/scopes/mapping.d.ts.map +1 -1
  91. package/dist/auth/token-validator.d.ts.map +1 -1
  92. package/dist/{chunk-W5WQVNVX.js → chunk-27C5EE6U.js} +23 -17
  93. package/dist/chunk-2TYICG7B.js +1183 -0
  94. package/dist/{chunk-37G4DLL6.js → chunk-LLIAIJPO.js} +1146 -539
  95. package/dist/{chunk-NV34JSQM.js → chunk-UE7GVOBV.js} +1757 -1331
  96. package/dist/{chunk-L552U3QS.js → chunk-XLIYO3YA.js} +401 -77
  97. package/dist/cli.js +116 -11
  98. package/dist/codemode/api-constants.d.ts.map +1 -1
  99. package/dist/codemode/api.d.ts.map +1 -1
  100. package/dist/codemode/sandbox-factory.d.ts.map +1 -1
  101. package/dist/codemode/sandbox.d.ts +0 -3
  102. package/dist/codemode/sandbox.d.ts.map +1 -1
  103. package/dist/codemode/security.d.ts.map +1 -1
  104. package/dist/codemode/types.d.ts +2 -0
  105. package/dist/codemode/types.d.ts.map +1 -1
  106. package/dist/constants/server-instructions.d.ts +1 -1
  107. package/dist/constants/server-instructions.d.ts.map +1 -1
  108. package/dist/filtering/tool-constants.d.ts +11 -11
  109. package/dist/filtering/tool-constants.d.ts.map +1 -1
  110. package/dist/{http-HWTUVFIA.js → http-ZDMK46QU.js} +195 -32
  111. package/dist/index.js +4 -4
  112. package/dist/observability/metrics.d.ts +36 -0
  113. package/dist/observability/metrics.d.ts.map +1 -0
  114. package/dist/observability/system-db.d.ts +13 -0
  115. package/dist/observability/system-db.d.ts.map +1 -0
  116. package/dist/server/mcp-server.d.ts +3 -0
  117. package/dist/server/mcp-server.d.ts.map +1 -1
  118. package/dist/server/registration/admin-tools.d.ts +6 -0
  119. package/dist/server/registration/admin-tools.d.ts.map +1 -0
  120. package/dist/server/registration/audit-tools/backup.d.ts +8 -0
  121. package/dist/server/registration/audit-tools/backup.d.ts.map +1 -0
  122. package/dist/server/registration/audit-tools/helpers.d.ts +11 -0
  123. package/dist/server/registration/audit-tools/helpers.d.ts.map +1 -0
  124. package/dist/server/registration/audit-tools/index.d.ts +4 -0
  125. package/dist/server/registration/audit-tools/index.d.ts.map +1 -0
  126. package/dist/server/registration/audit-tools/resource.d.ts +8 -0
  127. package/dist/server/registration/audit-tools/resource.d.ts.map +1 -0
  128. package/dist/server/registration/audit-tools/search.d.ts +7 -0
  129. package/dist/server/registration/audit-tools/search.d.ts.map +1 -0
  130. package/dist/server/registration/help-resources.d.ts.map +1 -1
  131. package/dist/server/registration/index.d.ts +3 -1
  132. package/dist/server/registration/index.d.ts.map +1 -1
  133. package/dist/server/registration/observability-resources.d.ts +11 -0
  134. package/dist/server/registration/observability-resources.d.ts.map +1 -0
  135. package/dist/server/subscription-manager.d.ts +35 -0
  136. package/dist/server/subscription-manager.d.ts.map +1 -0
  137. package/dist/{sqlite-TB56Z7G3.js → sqlite-XWZPEZJO.js} +195 -19
  138. package/dist/{sqlite-native-IF75BB3O.js → sqlite-native-5GQ3WKCZ.js} +311 -147
  139. package/dist/transports/http/middleware.d.ts.map +1 -1
  140. package/dist/transports/http/oauth.d.ts.map +1 -1
  141. package/dist/transports/http/sessions/legacy-sse.d.ts.map +1 -1
  142. package/dist/transports/http/sessions/stateful.d.ts +5 -1
  143. package/dist/transports/http/sessions/stateful.d.ts.map +1 -1
  144. package/dist/transports/http/transport.d.ts +2 -0
  145. package/dist/transports/http/transport.d.ts.map +1 -1
  146. package/dist/transports/http/types.d.ts +11 -0
  147. package/dist/transports/http/types.d.ts.map +1 -1
  148. package/dist/types/auth.d.ts +4 -0
  149. package/dist/types/auth.d.ts.map +1 -1
  150. package/dist/types/server.d.ts +5 -0
  151. package/dist/types/server.d.ts.map +1 -1
  152. package/dist/utils/annotations.d.ts +8 -0
  153. package/dist/utils/annotations.d.ts.map +1 -1
  154. package/dist/utils/errors/categories.d.ts +16 -11
  155. package/dist/utils/errors/categories.d.ts.map +1 -1
  156. package/dist/utils/errors/classes.d.ts +37 -0
  157. package/dist/utils/errors/classes.d.ts.map +1 -1
  158. package/dist/utils/errors/format.d.ts +2 -2
  159. package/dist/utils/errors/format.d.ts.map +1 -1
  160. package/dist/utils/errors/index.d.ts +1 -1
  161. package/dist/utils/errors/index.d.ts.map +1 -1
  162. package/dist/utils/errors/suggestions.d.ts +1 -0
  163. package/dist/utils/errors/suggestions.d.ts.map +1 -1
  164. package/dist/utils/index.d.ts +3 -0
  165. package/dist/utils/index.d.ts.map +1 -1
  166. package/dist/utils/logger/types.d.ts +1 -1
  167. package/dist/utils/logger/types.d.ts.map +1 -1
  168. package/dist/utils/security-utils.d.ts +29 -0
  169. package/dist/utils/security-utils.d.ts.map +1 -0
  170. package/dist/utils/stream-utils.d.ts +14 -0
  171. package/dist/utils/stream-utils.d.ts.map +1 -0
  172. package/dist/utils/tokens.d.ts +15 -0
  173. package/dist/utils/tokens.d.ts.map +1 -0
  174. package/dist/utils/where-clause.d.ts +0 -13
  175. package/dist/utils/where-clause.d.ts.map +1 -1
  176. package/package.json +43 -44
  177. package/dist/adapters/sqlite-native/tools/window.d.ts +0 -7
  178. package/dist/adapters/sqlite-native/tools/window.d.ts.map +0 -1
  179. package/dist/chunk-E5IESRTK.js +0 -489
  180. package/dist/server/registration/audit-tools.d.ts +0 -13
  181. package/dist/server/registration/audit-tools.d.ts.map +0 -1
  182. package/dist/utils/insights-manager.d.ts +0 -39
  183. package/dist/utils/insights-manager.d.ts.map +0 -1
@@ -1,10 +1,10 @@
1
- import { validateQuery } from './chunk-W5WQVNVX.js';
2
- import { ErrorResponseFields, redactObject, SENSITIVE_KEY_PATTERN, HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY, ASSISTANT_FOCUSED, readOnly, write, idempotent, destructive, VacuumOutputSchema, admin, adminFs, BackupOutputSchema, BackupSchema, AnalyzeOutputSchema, AnalyzeSchema, IntegrityCheckOutputSchema, IntegrityCheckSchema, OptimizeOutputSchema, OptimizeSchema, RestoreOutputSchema, RestoreSchema, VerifyBackupOutputSchema, VerifyBackupSchema, IndexStatsOutputSchema, IndexStatsSchema, PragmaCompileOptionsOutputSchema, PragmaCompileOptionsSchema, PragmaDatabaseListOutputSchema, PragmaDatabaseListSchema, PragmaOptimizeOutputSchema, PragmaOptimizeSchema, PragmaSettingsOutputSchema, PragmaSettingsSchema, PragmaTableInfoOutputSchema, PragmaTableInfoSchema, AppendInsightOutputSchema, AppendInsightSchema, AttachDatabaseOutputSchema, AttachDatabaseSchema, DetachDatabaseOutputSchema, DetachDatabaseSchema, VacuumIntoCopyOutputSchema, VacuumIntoCopySchema, SqlDumpOutputSchema, SqlDumpSchema, ReindexOutputSchema, ReindexSchema, WalOutputSchema, WalSchema } from './chunk-E5IESRTK.js';
3
- import { createModuleLogger, logger, ResourceNotFoundError, DbMcpError, formatHandlerError, findSuggestion, ERROR_CODES, QueryError, ValidationError, ExtensionNotAvailableError, getAuthContext, scopesGrantToolAccess } from './chunk-L552U3QS.js';
1
+ import { validateQuery } from './chunk-27C5EE6U.js';
2
+ import { ErrorResponseFields, redactObject, SENSITIVE_KEY_PATTERN, HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY, ASSISTANT_FOCUSED, readOnly, sanitizeIdentifier, createColumnList, buildWhereClause, buildProgressContext, streamResultRows, write, idempotent, destructive, sanitizeWhereClause, validateJsonPath, validateAggregateFunction, VacuumOutputSchema, sendProgress, admin, assertSafeIoPath, validateSameDirPath, adminFs, BackupOutputSchema, BackupSchema, AnalyzeOutputSchema, AnalyzeSchema, IntegrityCheckOutputSchema, IntegrityCheckSchema, OptimizeOutputSchema, OptimizeSchema, RestoreOutputSchema, RestoreSchema, VerifyBackupOutputSchema, VerifyBackupSchema, IndexStatsOutputSchema, IndexStatsSchema, PragmaCompileOptionsOutputSchema, PragmaCompileOptionsSchema, PragmaDatabaseListOutputSchema, PragmaDatabaseListSchema, PragmaOptimizeOutputSchema, PragmaOptimizeSchema, PragmaSettingsOutputSchema, PragmaSettingsSchema, PragmaTableInfoOutputSchema, PragmaTableInfoSchema, AttachDatabaseOutputSchema, AttachDatabaseSchema, DetachDatabaseOutputSchema, DetachDatabaseSchema, VacuumIntoCopyOutputSchema, VacuumIntoCopySchema, SqlDumpOutputSchema, SqlDumpSchema, ReindexOutputSchema, ReindexSchema, WalOutputSchema, WalSchema } from './chunk-2TYICG7B.js';
3
+ import { createModuleLogger, logger, InternalError, ResourceNotFoundError, DbMcpError, ErrorCategory, formatHandlerError, RateLimitError, TimeoutError, findSuggestion, ERROR_CODES, QueryError, ValidationError, ConflictError, ExtensionNotAvailableError, getAuthContext, scopesGrantToolAccess, AuthorizationError } from './chunk-XLIYO3YA.js';
4
4
  import { z } from 'zod';
5
- import * as path from 'path';
6
- import path__default, { dirname, resolve, basename, normalize, relative } from 'path';
7
- import fs, { realpathSync } from 'fs';
5
+ import * as nodePath from 'path';
6
+ import nodePath__default from 'path';
7
+ import fs from 'fs';
8
8
  import { createHash } from 'crypto';
9
9
  import { createClient } from 'redis';
10
10
  import * as acorn from 'acorn';
@@ -41,7 +41,10 @@ var ReadQueryOutputSchema = z.object({
41
41
  success: z.boolean(),
42
42
  rowCount: z.number().optional(),
43
43
  rows: z.array(RowRecordSchema).optional(),
44
- executionTimeMs: z.number().optional()
44
+ nextCursor: z.string().optional(),
45
+ executionTimeMs: z.number().optional(),
46
+ streamed: z.boolean().optional(),
47
+ chunksEmitted: z.number().optional()
45
48
  }).extend(ErrorResponseFields.shape);
46
49
  var WriteQueryOutputSchema = z.object({
47
50
  success: z.boolean(),
@@ -122,12 +125,22 @@ var DateMathOutputSchema = z.object({
122
125
  var ReadQuerySchema = z.object({
123
126
  query: z.string().max(1e5).default("").describe("SELECT query to execute"),
124
127
  sql: z.string().max(1e5).optional().describe("Alias for query"),
125
- params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements")
128
+ params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements"),
129
+ cursor: z.string().optional().describe("Opaque cursor for pagination"),
130
+ stream: z.boolean().optional().default(false).describe(
131
+ "Stream results row-by-row via MCP progress notifications instead of returning all rows in the response. Requires a progressToken in the request _meta. Falls back to normal behavior if unavailable. The final response contains rowCount and metadata but not the rows themselves."
132
+ ),
133
+ chunkSize: z.number().int().positive().optional().describe(
134
+ "When streaming, the number of rows to include in each progress notification chunk (default 10)"
135
+ )
126
136
  });
127
137
  var WriteQuerySchema = z.object({
128
138
  query: z.string().max(1e5).default("").describe("INSERT/UPDATE/DELETE query to execute"),
129
139
  sql: z.string().max(1e5).optional().describe("Alias for query"),
130
- params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements")
140
+ params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements"),
141
+ expectedVersion: z.number().int().positive().optional().describe(
142
+ "If set and rowsAffected is 0, throws ConflictError instead of returning silently. Use with manual WHERE _version = ? guards."
143
+ )
131
144
  });
132
145
  var CreateTableSchema = z.object({
133
146
  table: z.string().default("").describe("Name of the table to create"),
@@ -242,7 +255,10 @@ var UpsertSchema = z.object({
242
255
  updateColumns: z.array(z.string()).optional().describe(
243
256
  "Columns to update on conflict (default: all except conflict columns). Only used if conflictColumns is provided."
244
257
  ),
245
- returning: z.union([z.boolean(), z.array(z.string())]).optional().describe("Columns to return, or true for all columns")
258
+ returning: z.union([z.boolean(), z.array(z.string())]).optional().describe("Columns to return, or true for all columns"),
259
+ expectedVersion: z.number().int().positive().optional().describe(
260
+ "Expected _version value for optimistic concurrency control. Requires conflictColumns and a versioned table."
261
+ )
246
262
  });
247
263
  var BatchInsertSchema = z.object({
248
264
  table: z.string().default("").describe("Table name"),
@@ -348,9 +364,64 @@ var DropTriggerOutputSchema = z.object({
348
364
  success: z.boolean(),
349
365
  message: z.string().optional()
350
366
  }).extend(ErrorResponseFields.shape);
351
- function resolveAliases(params, aliasMap) {
367
+ var EnableVersioningSchema = z.object({
368
+ table: z.string().describe("Name of the table to enable versioning for"),
369
+ tableName: z.string().optional().describe("Alias for table")
370
+ });
371
+ var EnableVersioningOutputSchema = z.object({
372
+ success: z.boolean(),
373
+ message: z.string().optional(),
374
+ sql: z.string().optional(),
375
+ alreadyEnabled: z.boolean().optional()
376
+ }).extend(ErrorResponseFields.shape);
377
+ var DisableVersioningSchema = z.object({
378
+ table: z.string().describe("Name of the table to disable versioning for"),
379
+ tableName: z.string().optional().describe("Alias for table"),
380
+ ifExists: z.boolean().optional().default(false)
381
+ });
382
+ var DisableVersioningOutputSchema = z.object({
383
+ success: z.boolean(),
384
+ message: z.string().optional(),
385
+ sql: z.string().optional()
386
+ }).extend(ErrorResponseFields.shape);
387
+ var CheckVersionSchema = z.object({
388
+ table: z.string().describe("Table name"),
389
+ tableName: z.string().optional().describe("Alias for table"),
390
+ rowId: z.union([z.number(), z.string()]).describe("Primary key or rowid value of the row"),
391
+ idColumn: z.string().optional().describe("Name of the primary key column (default: 'rowid')")
392
+ });
393
+ var CheckVersionOutputSchema = z.object({
394
+ success: z.boolean(),
395
+ version: z.number().optional(),
396
+ row: RowRecordSchema.optional()
397
+ }).extend(ErrorResponseFields.shape);
398
+ var ConditionalUpdateSchema = z.object({
399
+ table: z.string().describe("Table name"),
400
+ tableName: z.string().optional().describe("Alias for table"),
401
+ data: z.record(z.string(), z.unknown()).describe("Column-value pairs to update"),
402
+ conditions: z.array(WhereConditionSchema).describe("WHERE conditions to locate the row (e.g. id = 1)"),
403
+ expectedVersion: z.number().int().positive().describe("The _version value expected to be currently in the database")
404
+ });
405
+ var ConditionalUpdateOutputSchema = z.object({
406
+ success: z.boolean(),
407
+ rowsAffected: z.number().optional(),
408
+ currentVersion: z.number().optional(),
409
+ rows: z.array(RowRecordSchema).optional()
410
+ }).extend(ErrorResponseFields.shape);
411
+ function resolveAliases(params, aliasMap = {}) {
352
412
  if (typeof params !== "object" || params === null) return params;
353
- const obj = params;
413
+ const obj = { ...params };
414
+ for (const key of Object.keys(obj)) {
415
+ if (key.includes("_")) {
416
+ const camelKey = key.replace(
417
+ /_([a-z])/g,
418
+ (_, letter) => letter.toUpperCase()
419
+ );
420
+ if (obj[camelKey] === void 0 && obj[key] !== void 0) {
421
+ obj[camelKey] = obj[key];
422
+ }
423
+ }
424
+ }
354
425
  for (const [alias, canonical] of Object.entries(aliasMap)) {
355
426
  if ((obj[canonical] === void 0 || obj[canonical] === "") && obj[alias] !== void 0) {
356
427
  obj[canonical] = obj[alias];
@@ -541,16 +612,77 @@ function createReadQueryTool(adapter) {
541
612
  }
542
613
  try {
543
614
  let finalQuery = input.query.replace(/[\s;]+$/g, "");
615
+ let offset = 0;
616
+ if (input.cursor) {
617
+ try {
618
+ const cursorData = JSON.parse(
619
+ Buffer.from(input.cursor, "base64").toString("utf8")
620
+ );
621
+ if (typeof cursorData["offset"] === "number") {
622
+ offset = cursorData["offset"];
623
+ }
624
+ } catch {
625
+ return {
626
+ ...formatHandlerError(
627
+ new ValidationError(
628
+ "Invalid cursor format",
629
+ "VALIDATION_ERROR",
630
+ {
631
+ suggestion: "Use the nextCursor value returned from a previous query.",
632
+ details: {}
633
+ }
634
+ )
635
+ ),
636
+ rowCount: 0,
637
+ rows: []
638
+ };
639
+ }
640
+ }
544
641
  const upperForLimit = finalQuery.toUpperCase();
545
642
  const isLimitable = upperForLimit.startsWith("SELECT") || upperForLimit.startsWith("WITH");
546
- if (isLimitable && !/\bLIMIT\b/i.test(finalQuery)) {
547
- finalQuery = `${finalQuery} LIMIT 50`;
643
+ const limit = 50;
644
+ const hasLimit = /\bLIMIT\b/i.test(finalQuery);
645
+ if (isLimitable && !hasLimit) {
646
+ finalQuery = `${finalQuery} LIMIT ${limit}`;
647
+ if (offset > 0) {
648
+ finalQuery = `${finalQuery} OFFSET ${offset}`;
649
+ }
650
+ } else if (isLimitable && hasLimit && offset > 0) {
651
+ if (!/\bOFFSET\b/i.test(finalQuery)) {
652
+ finalQuery = `${finalQuery} OFFSET ${offset}`;
653
+ }
548
654
  }
549
655
  const result = await adapter.executeReadQuery(finalQuery, input.params);
656
+ let nextCursor;
657
+ if (isLimitable && !hasLimit && result.rows?.length === limit) {
658
+ const nextOffset = offset + limit;
659
+ nextCursor = Buffer.from(
660
+ JSON.stringify({ offset: nextOffset })
661
+ ).toString("base64");
662
+ }
663
+ if (input.stream && !_context.isCodeMode) {
664
+ const progressCtx = buildProgressContext(_context);
665
+ if (progressCtx) {
666
+ const chunksEmitted = await streamResultRows(
667
+ progressCtx,
668
+ result.rows ?? [],
669
+ input.chunkSize
670
+ );
671
+ return {
672
+ success: true,
673
+ rowCount: result.rows?.length ?? 0,
674
+ nextCursor,
675
+ executionTimeMs: result.executionTimeMs,
676
+ streamed: true,
677
+ chunksEmitted
678
+ };
679
+ }
680
+ }
550
681
  return {
551
682
  success: true,
552
683
  rowCount: result.rows?.length ?? 0,
553
684
  rows: result.rows,
685
+ nextCursor,
554
686
  executionTimeMs: result.executionTimeMs
555
687
  };
556
688
  } catch (error) {
@@ -650,11 +782,60 @@ function createWriteQueryTool(adapter) {
650
782
  rowsAffected: 0
651
783
  };
652
784
  }
785
+ if (input.expectedVersion === void 0) {
786
+ const tables = [];
787
+ const tableRegex = /\b(?:UPDATE|INSERT\s+INTO|DELETE\s+FROM|REPLACE\s+INTO)\s+(?:["'`]?([a-zA-Z0-9_]+)["'`]?)\b/gi;
788
+ let match;
789
+ while ((match = tableRegex.exec(input.query)) !== null) {
790
+ if (match[1]) tables.push(match[1]);
791
+ }
792
+ for (const tableName of tables) {
793
+ try {
794
+ const pragmaCheck = await adapter.executeReadQuery(
795
+ `PRAGMA table_info(${sanitizeIdentifier(tableName)})`
796
+ );
797
+ const isVersioned = (pragmaCheck.rows ?? []).some(
798
+ (col) => col["name"] === "_version"
799
+ );
800
+ if (isVersioned) {
801
+ return {
802
+ ...formatHandlerError(
803
+ new ConflictError(
804
+ `expectedVersion is required when updating versioned table '${tableName}'`,
805
+ "CONFLICT_ERROR",
806
+ {
807
+ conflictType: "missing_expected_version",
808
+ suggestion: "Use sqlite_check_version to get the current version, then include expectedVersion in your request."
809
+ }
810
+ )
811
+ ),
812
+ rowsAffected: 0
813
+ };
814
+ }
815
+ } catch {
816
+ }
817
+ }
818
+ }
653
819
  try {
654
820
  const result = await adapter.executeWriteQuery(
655
821
  input.query,
656
822
  input.params
657
823
  );
824
+ if (result.rowsAffected === 0 && input.expectedVersion !== void 0) {
825
+ return {
826
+ ...formatHandlerError(
827
+ new ConflictError(
828
+ `Version conflict or row not found. expectedVersion ${input.expectedVersion} was provided but 0 rows were affected.`,
829
+ "CONFLICT_ERROR",
830
+ {
831
+ conflictType: "version_mismatch_or_not_found",
832
+ suggestion: "Verify the row exists and the version matches."
833
+ }
834
+ )
835
+ ),
836
+ rowsAffected: 0
837
+ };
838
+ }
658
839
  return {
659
840
  success: true,
660
841
  rowsAffected: result.rowsAffected,
@@ -671,477 +852,6 @@ function createWriteQueryTool(adapter) {
671
852
  };
672
853
  }
673
854
 
674
- // src/utils/where-clause.ts
675
- var UnsafeWhereClauseError = class extends Error {
676
- constructor(reason) {
677
- super(`Unsafe WHERE clause: ${reason}`);
678
- this.name = "UnsafeWhereClauseError";
679
- }
680
- };
681
- var DANGEROUS_PATTERNS = [
682
- // Statement terminators and new statements
683
- // Comprehensive keyword list including statements missed in earlier versions:
684
- // REPLACE, VACUUM, ANALYZE, BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE
685
- {
686
- pattern: /;\s*(DROP|DELETE|TRUNCATE|INSERT|UPDATE|CREATE|ALTER|ATTACH|DETACH|SELECT|REPLACE|VACUUM|ANALYZE|BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE|REINDEX|EXPLAIN)\b/i,
687
- reason: "contains statement terminator followed by SQL keyword"
688
- },
689
- // Trailing semicolons (potential statement injection)
690
- {
691
- pattern: /;\s*$/,
692
- reason: "contains trailing semicolon"
693
- },
694
- // SQL comments (can be used to comment out security checks)
695
- {
696
- pattern: /--/,
697
- reason: "contains SQL line comment"
698
- },
699
- {
700
- pattern: /\/\*/,
701
- reason: "contains SQL block comment"
702
- },
703
- // REGEXP and MATCH injection (blind extraction oracles)
704
- {
705
- pattern: /\bREGEXP\s+/i,
706
- reason: "contains REGEXP (potential blind extraction oracle)"
707
- },
708
- {
709
- pattern: /\bMATCH\s+/i,
710
- reason: "contains MATCH (potential blind extraction oracle)"
711
- },
712
- // UNION injection (data exfiltration)
713
- {
714
- pattern: /\bUNION\s+(ALL\s+)?SELECT\b/i,
715
- reason: "contains UNION SELECT"
716
- },
717
- // SQLite-specific: Extension loading (code execution)
718
- {
719
- pattern: /\bload_extension\s*\(/i,
720
- reason: "contains load_extension (code execution)"
721
- },
722
- // SQLite-specific: ATTACH database (file system access)
723
- {
724
- pattern: /\bATTACH\s+(DATABASE\s+)?['"`]/i,
725
- reason: "contains ATTACH DATABASE (file system access)"
726
- },
727
- // SQLite-specific: Pragma manipulation
728
- {
729
- pattern: /\bPRAGMA\s+/i,
730
- reason: "contains PRAGMA statement"
731
- },
732
- // SQLite-specific: writefile/readfile (if using fileio extension)
733
- {
734
- pattern: /\b(writefile|readfile)\s*\(/i,
735
- reason: "contains file I/O function"
736
- },
737
- // SQLite-specific: fts3_tokenizer (potential code execution)
738
- {
739
- pattern: /\bfts3_tokenizer\s*\(/i,
740
- reason: "contains FTS tokenizer function"
741
- },
742
- // Subquery injection (data exfiltration via boolean-based blind)
743
- {
744
- pattern: /\(\s*SELECT\b/i,
745
- reason: "contains subquery (potential data exfiltration)"
746
- },
747
- // Generic: Hexadecimal string injection
748
- {
749
- pattern: /\bX'[0-9A-Fa-f]+'/,
750
- reason: "contains hex string literal (potential binary injection)"
751
- },
752
- // CASE WHEN (potential blind injection without subqueries)
753
- {
754
- pattern: /\bCASE\s+WHEN\b/i,
755
- reason: "contains CASE WHEN (potential blind injection)"
756
- },
757
- // IIF() — conditional bypass alternative to CASE WHEN
758
- {
759
- pattern: /\bIIF\s*\(/i,
760
- reason: "contains IIF() (potential blind injection)"
761
- },
762
- // GROUP_CONCAT() — data aggregation for exfiltration
763
- {
764
- pattern: /\bGROUP_CONCAT\s*\(/i,
765
- reason: "contains GROUP_CONCAT() (potential data exfiltration)"
766
- },
767
- // GLOB — case-sensitive wildcard operator usable for character extraction oracles
768
- {
769
- pattern: /\bGLOB\b/i,
770
- reason: "contains GLOB (potential case-sensitive oracle)"
771
- },
772
- // Conditional functions — blind injection oracles (bypass for CASE WHEN / IIF)
773
- {
774
- pattern: /\bCOALESCE\s*\(/i,
775
- reason: "contains COALESCE() (potential blind injection oracle)"
776
- },
777
- {
778
- pattern: /\bNULLIF\s*\(/i,
779
- reason: "contains NULLIF() (potential blind injection oracle)"
780
- },
781
- {
782
- pattern: /\bTYPEOF\s*\(/i,
783
- reason: "contains TYPEOF() (potential blind injection oracle)"
784
- },
785
- {
786
- pattern: /\bIFNULL\s*\(/i,
787
- reason: "contains IFNULL() (potential blind injection oracle)"
788
- },
789
- // Memory allocation DoS — RANDOMBLOB(N) / ZEROBLOB(N) can allocate large blocks (CWE-400)
790
- {
791
- pattern: /\bRANDOMBLOB\s*\(/i,
792
- reason: "contains RANDOMBLOB() (potential memory allocation DoS)"
793
- },
794
- {
795
- pattern: /\bZEROBLOB\s*\(/i,
796
- reason: "contains ZEROBLOB() (potential memory allocation DoS)"
797
- },
798
- // Blind extraction oracle functions — can be composed for character-by-character
799
- // data extraction without subqueries or CASE WHEN (CWE-89, H-1 audit finding).
800
- // Example: INSTR('abc...', SUBSTR(sqlite_version(), 1, 1)) = N
801
- {
802
- pattern: /\bINSTR\s*\(/i,
803
- reason: "contains INSTR() (potential blind extraction oracle)"
804
- },
805
- {
806
- pattern: /\bSUBSTR\s*\(/i,
807
- reason: "contains SUBSTR() (potential blind extraction oracle)"
808
- },
809
- {
810
- pattern: /\bSUBSTRING\s*\(/i,
811
- reason: "contains SUBSTRING() (potential blind extraction oracle)"
812
- },
813
- {
814
- pattern: /\bLENGTH\s*\(/i,
815
- reason: "contains LENGTH() (potential blind extraction oracle)"
816
- },
817
- // TRIM functions — blind extraction oracle (bypass for LIKE / SUBSTR)
818
- {
819
- pattern: /\b(?:L|R)?TRIM\s*\(/i,
820
- reason: "contains TRIM() (potential blind extraction oracle)"
821
- },
822
- {
823
- pattern: /\bUNICODE\s*\(/i,
824
- reason: "contains UNICODE() (potential blind extraction oracle)"
825
- },
826
- {
827
- pattern: /\bCHAR\s*\(/i,
828
- reason: "contains CHAR() (potential blind extraction oracle)"
829
- },
830
- {
831
- pattern: /\bREPLACE\s*\(/i,
832
- reason: "contains REPLACE() (potential blind extraction oracle)"
833
- },
834
- // ABS() — can be combined with subtraction to create numeric oracles (CWE-89)
835
- {
836
- pattern: /\bABS\s*\(/i,
837
- reason: "contains ABS() (potential blind extraction oracle)"
838
- },
839
- // HEX() — encodes values for character-by-character extraction (CWE-89)
840
- {
841
- pattern: /\bHEX\s*\(/i,
842
- reason: "contains HEX() (potential blind extraction oracle)"
843
- },
844
- // QUOTE() — returns SQL-literal representation, can leak type/value info (CWE-89)
845
- {
846
- pattern: /\bQUOTE\s*\(/i,
847
- reason: "contains QUOTE() (potential blind extraction oracle)"
848
- },
849
- // PRINTF() — format-string based extraction of internal values (CWE-89)
850
- {
851
- pattern: /\bPRINTF\s*\(/i,
852
- reason: "contains PRINTF() (potential blind extraction oracle)"
853
- },
854
- // FORMAT() — SQLite 3.38.0+ alias for printf() (CWE-89)
855
- {
856
- pattern: /\bFORMAT\s*\(/i,
857
- reason: "contains FORMAT() (potential blind extraction oracle)"
858
- },
859
- // CAST() — can be used to convert text to numeric oracles (CWE-89)
860
- {
861
- pattern: /\bCAST\s*\(/i,
862
- reason: "contains CAST() (potential blind extraction oracle)"
863
- }
864
- ];
865
- var FULLWIDTH_START = 65281;
866
- var FULLWIDTH_END = 65374;
867
- var FULLWIDTH_ASCII_OFFSET = 65248;
868
- function normalizeForPatternMatching(input) {
869
- let normalized = input.normalize("NFC");
870
- let hasFullwidth = false;
871
- for (let i = 0; i < normalized.length; i++) {
872
- const code = normalized.charCodeAt(i);
873
- if (code >= FULLWIDTH_START && code <= FULLWIDTH_END) {
874
- hasFullwidth = true;
875
- break;
876
- }
877
- }
878
- if (hasFullwidth) {
879
- const chars = [];
880
- for (let i = 0; i < normalized.length; i++) {
881
- const code = normalized.charCodeAt(i);
882
- if (code >= FULLWIDTH_START && code <= FULLWIDTH_END) {
883
- chars.push(String.fromCharCode(code - FULLWIDTH_ASCII_OFFSET));
884
- } else {
885
- chars.push(normalized.charAt(i));
886
- }
887
- }
888
- normalized = chars.join("");
889
- }
890
- return normalized;
891
- }
892
- function sanitizeWhereClause(where) {
893
- if (!where || typeof where !== "string") {
894
- throw new UnsafeWhereClauseError("WHERE clause must be a non-empty string");
895
- }
896
- const MAX_WHERE_LENGTH = 10240;
897
- if (where.length > MAX_WHERE_LENGTH) {
898
- throw new UnsafeWhereClauseError(
899
- `WHERE clause exceeds maximum length of ${String(MAX_WHERE_LENGTH)} characters`
900
- );
901
- }
902
- const normalized = normalizeForPatternMatching(where);
903
- for (const { pattern, reason } of DANGEROUS_PATTERNS) {
904
- if (pattern.test(normalized)) {
905
- throw new UnsafeWhereClauseError(reason);
906
- }
907
- }
908
- return where;
909
- }
910
- function buildWhereClause(conditions, whereClause) {
911
- let finalSql = "";
912
- const params = [];
913
- const clauses = [];
914
- if (conditions !== void 0 && conditions !== null && conditions.length > 0) {
915
- const allowedOperators = /* @__PURE__ */ new Set([
916
- "=",
917
- "!=",
918
- ">",
919
- ">=",
920
- "<",
921
- "<=",
922
- "LIKE",
923
- "IN",
924
- "IS",
925
- "IS NOT",
926
- "NOT LIKE"
927
- ]);
928
- for (const cond of conditions) {
929
- if (!allowedOperators.has(cond.operator.toUpperCase())) {
930
- throw new Error(`Invalid operator: ${cond.operator}`);
931
- }
932
- const col = `"${cond.column.replace(/"/g, '""')}"`;
933
- if (cond.operator.toUpperCase() === "IN" && Array.isArray(cond.value)) {
934
- const placeholders = cond.value.map(() => "?").join(", ");
935
- clauses.push(`${col} IN (${placeholders})`);
936
- params.push(...cond.value);
937
- } else {
938
- clauses.push(`${col} ${cond.operator} ?`);
939
- params.push(cond.value);
940
- }
941
- }
942
- finalSql = clauses.join(" AND ");
943
- }
944
- if (whereClause) {
945
- const safeWhere = sanitizeWhereClause(whereClause);
946
- if (finalSql) {
947
- finalSql = `${finalSql} AND ${safeWhere}`;
948
- } else {
949
- finalSql = safeWhere;
950
- }
951
- }
952
- return { sql: finalSql, params };
953
- }
954
-
955
- // src/utils/identifiers.ts
956
- var IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
957
- var MAX_IDENTIFIER_LENGTH = 255;
958
- var InvalidIdentifierError = class extends Error {
959
- constructor(identifier, reason) {
960
- super(`Invalid identifier "${identifier}": ${reason}`);
961
- this.identifier = identifier;
962
- this.reason = reason;
963
- this.name = "InvalidIdentifierError";
964
- }
965
- identifier;
966
- reason;
967
- };
968
- function validateIdentifier(name) {
969
- if (!name || typeof name !== "string") {
970
- throw new InvalidIdentifierError(
971
- name,
972
- "Identifier must be a non-empty string"
973
- );
974
- }
975
- if (name.length > MAX_IDENTIFIER_LENGTH) {
976
- throw new InvalidIdentifierError(
977
- name,
978
- `Identifier exceeds maximum length of ${String(MAX_IDENTIFIER_LENGTH)} characters`
979
- );
980
- }
981
- if (!IDENTIFIER_PATTERN.test(name)) {
982
- throw new InvalidIdentifierError(
983
- name,
984
- "Identifier contains invalid characters. Must start with a letter or underscore and contain only letters, digits, or underscores"
985
- );
986
- }
987
- }
988
- function sanitizeIdentifier(name) {
989
- validateIdentifier(name);
990
- const escaped = name.replace(/"/g, '""');
991
- return `"${escaped}"`;
992
- }
993
- function sanitizeIdentifiers(names) {
994
- return names.map(sanitizeIdentifier);
995
- }
996
- function createColumnList(columns) {
997
- return sanitizeIdentifiers(columns).join(", ");
998
- }
999
- function validateSameDirPath(targetPath, dbPath) {
1000
- if (targetPath.includes("\0")) {
1001
- return {
1002
- valid: false,
1003
- error: "Security: path must not contain null bytes.",
1004
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1005
- };
1006
- }
1007
- if (targetPath.startsWith("file:") || targetPath.includes("?")) {
1008
- return {
1009
- valid: false,
1010
- error: "Security: SQLite URIs and query parameters are not allowed.",
1011
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1012
- };
1013
- }
1014
- const base = basename(targetPath);
1015
- if (base.startsWith(".")) {
1016
- return {
1017
- valid: false,
1018
- error: "Security: Cannot access hidden files or dotfiles.",
1019
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1020
- };
1021
- }
1022
- const validExtensions = [
1023
- ".db",
1024
- ".sqlite",
1025
- ".sqlite3",
1026
- ".sql",
1027
- ".csv",
1028
- ".tsv"
1029
- ];
1030
- const hasValidExt = validExtensions.some(
1031
- (ext) => base.toLowerCase().endsWith(ext)
1032
- );
1033
- if (!hasValidExt) {
1034
- return {
1035
- valid: false,
1036
- error: `Security: Invalid file extension. Allowed extensions are: ${validExtensions.join(", ")}`,
1037
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1038
- };
1039
- }
1040
- if (dbPath === ":memory:") {
1041
- return {
1042
- valid: false,
1043
- error: "Security: Filesystem operations are not permitted for :memory: databases.",
1044
- dbDir: ""
1045
- };
1046
- }
1047
- const dbDir = dirname(resolve(dbPath));
1048
- let resolvedTarget = resolve(targetPath);
1049
- try {
1050
- resolvedTarget = realpathSync(resolvedTarget);
1051
- } catch {
1052
- try {
1053
- const parentDir = realpathSync(dirname(resolvedTarget));
1054
- resolvedTarget = resolve(parentDir, basename(resolvedTarget));
1055
- } catch {
1056
- return {
1057
- valid: false,
1058
- error: "Security: Target parent directory must exist.",
1059
- dbDir
1060
- };
1061
- }
1062
- }
1063
- let resolvedDbDir;
1064
- try {
1065
- resolvedDbDir = realpathSync(dbDir);
1066
- } catch {
1067
- return {
1068
- valid: false,
1069
- error: "Security: Database directory must exist.",
1070
- dbDir
1071
- };
1072
- }
1073
- const normalizedTarget = normalize(resolvedTarget);
1074
- const normalizedDir = normalize(resolvedDbDir);
1075
- const targetDir = normalize(dirname(resolvedTarget));
1076
- if (relative(normalizedDir, targetDir) !== "") {
1077
- return {
1078
- valid: false,
1079
- error: `Security: path must be within the database directory. Path traversal is not allowed.`,
1080
- dbDir
1081
- };
1082
- }
1083
- return { valid: true, resolvedPath: normalizedTarget };
1084
- }
1085
-
1086
- // src/utils/validate-json-path.ts
1087
- var VALID_JSON_PATH_PATTERN = /^\$(\.[a-zA-Z_][a-zA-Z0-9_ -]*|\[\d+\]|\[#\]|\[\*\])*$/;
1088
- var MAX_JSON_PATH_LENGTH = 500;
1089
- function validateJsonPath(path3, param = "path") {
1090
- if (!path3.startsWith("$")) {
1091
- throw new ValidationError(
1092
- `JSON ${param} must start with $`,
1093
- "INVALID_JSON_PATH",
1094
- {
1095
- suggestion: "Use a valid JSON path starting with $. For example: $.key or $[0]"
1096
- }
1097
- );
1098
- }
1099
- if (path3.length > MAX_JSON_PATH_LENGTH) {
1100
- throw new ValidationError(
1101
- `JSON ${param} exceeds maximum length of ${MAX_JSON_PATH_LENGTH} characters`,
1102
- "INVALID_JSON_PATH"
1103
- );
1104
- }
1105
- if (!VALID_JSON_PATH_PATTERN.test(path3)) {
1106
- throw new ValidationError(
1107
- `Invalid JSON ${param} syntax: ${path3}`,
1108
- "INVALID_JSON_PATH",
1109
- {
1110
- suggestion: "Use $.key, $[0], or $[*] patterns. Member names must start with a letter or underscore."
1111
- }
1112
- );
1113
- }
1114
- }
1115
- var ALLOWED_AGGREGATE_FUNCTIONS = /* @__PURE__ */ new Set([
1116
- "count",
1117
- "sum",
1118
- "avg",
1119
- "min",
1120
- "max",
1121
- "group_concat",
1122
- "total"
1123
- ]);
1124
- var AGGREGATE_FUNCTION_PATTERN = /^([A-Za-z_]+)\(\s*(\*|"[a-zA-Z_][a-zA-Z0-9_]*"|[a-zA-Z_][a-zA-Z0-9_]*)(?:\s*,\s*'[^']*')?\s*\)$/;
1125
- function validateAggregateFunction(fn) {
1126
- const match = AGGREGATE_FUNCTION_PATTERN.exec(fn.trim());
1127
- if (!match) {
1128
- throw new ValidationError(
1129
- `Invalid aggregate function syntax: ${fn}`,
1130
- "INVALID_AGGREGATE_FUNCTION",
1131
- {
1132
- suggestion: "Use a standard aggregate function like COUNT(*), SUM(column), AVG(column), MIN(column), MAX(column), GROUP_CONCAT(column), or TOTAL(column)."
1133
- }
1134
- );
1135
- }
1136
- const funcName = match[1]?.toLowerCase();
1137
- if (!funcName || !ALLOWED_AGGREGATE_FUNCTIONS.has(funcName)) {
1138
- throw new ValidationError(
1139
- `Disallowed aggregate function: ${match[1]}. Allowed: ${[...ALLOWED_AGGREGATE_FUNCTIONS].join(", ").toUpperCase()}`,
1140
- "INVALID_AGGREGATE_FUNCTION"
1141
- );
1142
- }
1143
- }
1144
-
1145
855
  // src/adapters/sqlite/tools/core/tables.ts
1146
856
  var SPATIALITE_SYSTEM_PREFIXES = [
1147
857
  "geometry_columns",
@@ -1396,7 +1106,7 @@ function createDescribeTableTool(adapter) {
1396
1106
  try {
1397
1107
  const tableInfo = await adapter.describeTable(input.table);
1398
1108
  const rawColumns = tableInfo.columns ?? [];
1399
- let enrichedColumns = rawColumns.map((c) => ({
1109
+ const enrichedColumns = rawColumns.map((c) => ({
1400
1110
  name: c.name,
1401
1111
  type: c.type,
1402
1112
  nullable: c.nullable,
@@ -1422,28 +1132,39 @@ function createDescribeTableTool(adapter) {
1422
1132
  [input.table]
1423
1133
  );
1424
1134
  const ddl = ddlResult.rows?.[0]?.["sql"] ?? "";
1425
- enrichedColumns = enrichedColumns.map((col) => {
1426
- const hidden = xinfoMap.get(col.name);
1427
- if (hidden !== 2 && hidden !== 3) return col;
1428
- const enriched = {
1429
- ...col,
1430
- isGenerated: true,
1431
- generatedType: hidden === 3 ? "STORED" : "VIRTUAL"
1432
- };
1433
- const escapedName = col.name.replace(
1434
- /[.*+?^${}()|[\]\\]/g,
1435
- "\\$&"
1436
- );
1437
- const exprPattern = new RegExp(
1438
- `"?${escapedName}"?\\s+\\w+[^,]*GENERATED\\s+ALWAYS\\s+AS\\s*\\(([^)]+)\\)`,
1439
- "i"
1440
- );
1441
- const match = exprPattern.exec(ddl);
1442
- if (match?.[1]) {
1443
- enriched.generatedExpression = match[1].trim();
1135
+ for (const row of xinfoResult.rows) {
1136
+ const colName = row["name"];
1137
+ const hidden = row["hidden"];
1138
+ if (hidden === 2 || hidden === 3) {
1139
+ let enriched = enrichedColumns.find(
1140
+ (c) => c.name === colName
1141
+ );
1142
+ if (!enriched) {
1143
+ enriched = {
1144
+ name: colName,
1145
+ type: row["type"],
1146
+ nullable: row["notnull"] === 0,
1147
+ primaryKey: row["pk"] === 1,
1148
+ defaultValue: row["dflt_value"]
1149
+ };
1150
+ enrichedColumns.push(enriched);
1151
+ }
1152
+ enriched.isGenerated = true;
1153
+ enriched.generatedType = hidden === 3 ? "STORED" : "VIRTUAL";
1154
+ const escapedName = colName.replace(
1155
+ /[.*+?^${}()|[\]\\]/g,
1156
+ "\\$&"
1157
+ );
1158
+ const exprPattern = new RegExp(
1159
+ `"?${escapedName}"?\\s+\\w+[^,]*GENERATED\\s+ALWAYS\\s+AS\\s*\\(([^)]+)\\)`,
1160
+ "i"
1161
+ );
1162
+ const match = exprPattern.exec(ddl);
1163
+ if (match?.[1]) {
1164
+ enriched.generatedExpression = match[1].trim();
1165
+ }
1444
1166
  }
1445
- return enriched;
1446
- });
1167
+ }
1447
1168
  }
1448
1169
  }
1449
1170
  } catch {
@@ -1831,7 +1552,7 @@ async function validateTableExists2(adapter, table) {
1831
1552
  success: false,
1832
1553
  error: `Table or view '${table}' not found. Use sqlite_list_tables to see available tables.`,
1833
1554
  code: "TABLE_NOT_FOUND",
1834
- category: "resource" /* RESOURCE */,
1555
+ category: ErrorCategory.RESOURCE,
1835
1556
  suggestion: "Table or view does not exist. Run sqlite_list_tables to see available tables.",
1836
1557
  details: void 0,
1837
1558
  recoverable: false
@@ -1866,12 +1587,38 @@ function createUpsertTool(adapter) {
1866
1587
  conflictColumns: parsed.conflictColumns !== void 0 ? Array.isArray(parsed.conflictColumns) ? parsed.conflictColumns : [parsed.conflictColumns] : []
1867
1588
  };
1868
1589
  if (Object.keys(input.data).length === 0)
1869
- throw new Error("data (or values alias) is required");
1590
+ throw new ValidationError("data (or values alias) is required");
1870
1591
  } catch (error) {
1871
1592
  return { ...formatHandlerError(error), rowsAffected: 0 };
1872
1593
  }
1873
1594
  const validationError = await validateTableExists2(adapter, input.table);
1874
1595
  if (validationError) return { ...validationError, rowsAffected: 0 };
1596
+ if (input.expectedVersion === void 0) {
1597
+ try {
1598
+ const pragmaCheck = await adapter.executeReadQuery(
1599
+ `PRAGMA table_info(${sanitizeIdentifier(input.table)})`
1600
+ );
1601
+ const isVersioned = (pragmaCheck.rows ?? []).some(
1602
+ (col) => col["name"] === "_version"
1603
+ );
1604
+ if (isVersioned) {
1605
+ return {
1606
+ ...formatHandlerError(
1607
+ new ConflictError(
1608
+ `expectedVersion is required when updating versioned table '${input.table}'`,
1609
+ "CONFLICT_ERROR",
1610
+ {
1611
+ conflictType: "missing_expected_version",
1612
+ suggestion: "Use sqlite_check_version to get the current version, then include expectedVersion in your request."
1613
+ }
1614
+ )
1615
+ ),
1616
+ rowsAffected: 0
1617
+ };
1618
+ }
1619
+ } catch {
1620
+ }
1621
+ }
1875
1622
  const columns = Object.keys(input.data);
1876
1623
  const values = Object.values(input.data);
1877
1624
  const placeholders = columns.map(() => "?").join(", ");
@@ -1886,11 +1633,35 @@ function createUpsertTool(adapter) {
1886
1633
  const updateSets = colsToUpdate.map(
1887
1634
  (c) => `${sanitizeIdentifier(c)} = EXCLUDED.${sanitizeIdentifier(c)}`
1888
1635
  ).join(", ");
1889
- sql += ` ON CONFLICT (${conflictCols}) DO UPDATE SET ${updateSets}`;
1636
+ let updateClause = ` ON CONFLICT (${conflictCols}) DO UPDATE SET ${updateSets}`;
1637
+ if (input.expectedVersion !== void 0) {
1638
+ updateClause += ` WHERE _version = ${input.expectedVersion}`;
1639
+ }
1640
+ sql += updateClause;
1890
1641
  } else {
1642
+ if (input.expectedVersion !== void 0) {
1643
+ return {
1644
+ ...formatHandlerError(
1645
+ new ValidationError(
1646
+ "expectedVersion requires colsToUpdate (cannot use with DO NOTHING)"
1647
+ )
1648
+ ),
1649
+ rowsAffected: 0
1650
+ };
1651
+ }
1891
1652
  sql += ` ON CONFLICT (${conflictCols}) DO NOTHING`;
1892
1653
  }
1893
1654
  } else {
1655
+ if (input.expectedVersion !== void 0) {
1656
+ return {
1657
+ ...formatHandlerError(
1658
+ new ValidationError(
1659
+ "expectedVersion requires explicit conflictColumns for OCC"
1660
+ )
1661
+ ),
1662
+ rowsAffected: 0
1663
+ };
1664
+ }
1894
1665
  sql = `INSERT OR REPLACE INTO ${safeTable} (${safeColumns.join(", ")}) VALUES (${placeholders})`;
1895
1666
  }
1896
1667
  if (input.returning !== void 0 && input.returning !== false) {
@@ -1903,6 +1674,21 @@ function createUpsertTool(adapter) {
1903
1674
  }
1904
1675
  try {
1905
1676
  const result = await adapter.executeWriteQuery(sql, queryParams);
1677
+ if (result.rowsAffected === 0 && input.expectedVersion !== void 0) {
1678
+ return {
1679
+ ...formatHandlerError(
1680
+ new ConflictError(
1681
+ `Version conflict during upsert: expected version ${input.expectedVersion}. Re-read the row and retry.`,
1682
+ "CONFLICT_ERROR",
1683
+ {
1684
+ conflictType: "version_mismatch",
1685
+ suggestion: "Re-read the row to get the current version, then retry the update."
1686
+ }
1687
+ )
1688
+ ),
1689
+ rowsAffected: 0
1690
+ };
1691
+ }
1906
1692
  const response = {
1907
1693
  success: true,
1908
1694
  rowsAffected: result.rowsAffected,
@@ -2986,6 +2772,269 @@ function createAlterTableTool(adapter) {
2986
2772
  };
2987
2773
  }
2988
2774
 
2775
+ // src/adapters/sqlite/tools/core/versioning.ts
2776
+ function createEnableVersioningTool(adapter) {
2777
+ return {
2778
+ name: "sqlite_enable_versioning",
2779
+ description: "Enable optimistic concurrency control (OCC) on a table. Adds a _version column and an auto-increment trigger.",
2780
+ group: "core",
2781
+ inputSchema: EnableVersioningSchema,
2782
+ outputSchema: EnableVersioningOutputSchema,
2783
+ requiredScopes: ["admin"],
2784
+ annotations: write("Enable Versioning"),
2785
+ handler: async (params, _context) => {
2786
+ let input;
2787
+ try {
2788
+ input = EnableVersioningSchema.parse(params);
2789
+ } catch (error) {
2790
+ return { ...formatHandlerError(error), sql: "" };
2791
+ }
2792
+ const validationError = await validateTableExists2(adapter, input.table);
2793
+ if (validationError) return { ...validationError, sql: "" };
2794
+ const safeTable = sanitizeIdentifier(input.table);
2795
+ const triggerName = sanitizeIdentifier(`_mcp_version_${input.table}`);
2796
+ try {
2797
+ const pragmaCheck = await adapter.executeReadQuery(
2798
+ `PRAGMA table_info(${safeTable})`
2799
+ );
2800
+ const hasVersionColumn = (pragmaCheck.rows ?? []).some(
2801
+ (col) => col["name"] === "_version"
2802
+ );
2803
+ const statements = [];
2804
+ if (!hasVersionColumn) {
2805
+ statements.push(
2806
+ `ALTER TABLE ${safeTable} ADD COLUMN _version INTEGER NOT NULL DEFAULT 1;`
2807
+ );
2808
+ }
2809
+ const triggerSql = `
2810
+ CREATE TRIGGER IF NOT EXISTS ${triggerName}
2811
+ BEFORE UPDATE ON ${safeTable}
2812
+ FOR EACH ROW
2813
+ BEGIN
2814
+ UPDATE ${safeTable} SET _version = OLD._version + 1 WHERE rowid = OLD.rowid;
2815
+ END;`;
2816
+ statements.push(triggerSql.trim());
2817
+ for (const stmt of statements) {
2818
+ await adapter.executeWriteQuery(stmt);
2819
+ }
2820
+ const sql = statements.join("\n");
2821
+ return {
2822
+ success: true,
2823
+ message: hasVersionColumn ? `Versioning already active on '${input.table}', trigger ensured.` : `Versioning enabled on '${input.table}'. Added _version column and trigger.`,
2824
+ sql,
2825
+ alreadyEnabled: hasVersionColumn
2826
+ };
2827
+ } catch (error) {
2828
+ return { ...formatHandlerError(error), sql: "" };
2829
+ }
2830
+ }
2831
+ };
2832
+ }
2833
+ function createDisableVersioningTool(adapter) {
2834
+ return {
2835
+ name: "sqlite_disable_versioning",
2836
+ description: "Disable optimistic concurrency control (OCC) on a table. Drops the _version column and its trigger.",
2837
+ group: "core",
2838
+ inputSchema: DisableVersioningSchema,
2839
+ outputSchema: DisableVersioningOutputSchema,
2840
+ requiredScopes: ["admin"],
2841
+ annotations: write("Disable Versioning"),
2842
+ handler: async (params, _context) => {
2843
+ let input;
2844
+ try {
2845
+ input = DisableVersioningSchema.parse(params);
2846
+ } catch (error) {
2847
+ return { ...formatHandlerError(error), sql: "" };
2848
+ }
2849
+ const safeTable = sanitizeIdentifier(input.table);
2850
+ const triggerName = sanitizeIdentifier(`_mcp_version_${input.table}`);
2851
+ const tableCheck = await adapter.executeReadQuery(
2852
+ `SELECT 1 FROM pragma_table_list(?) WHERE type IN ('table', 'view') LIMIT 1`,
2853
+ [input.table]
2854
+ );
2855
+ if (!tableCheck.rows || tableCheck.rows.length === 0) {
2856
+ if (input.ifExists) {
2857
+ return {
2858
+ success: true,
2859
+ message: `Table '${input.table}' does not exist (no changes made).`,
2860
+ sql: ""
2861
+ };
2862
+ }
2863
+ return {
2864
+ ...formatHandlerError(
2865
+ new ValidationError(`Table '${input.table}' does not exist.`)
2866
+ ),
2867
+ sql: ""
2868
+ };
2869
+ }
2870
+ try {
2871
+ const pragmaCheck = await adapter.executeReadQuery(
2872
+ `PRAGMA table_info(${safeTable})`
2873
+ );
2874
+ const hasVersionColumn = (pragmaCheck.rows ?? []).some(
2875
+ (col) => col["name"] === "_version"
2876
+ );
2877
+ const statements = [];
2878
+ statements.push(`DROP TRIGGER IF EXISTS ${triggerName};`);
2879
+ if (hasVersionColumn) {
2880
+ statements.push(`ALTER TABLE ${safeTable} DROP COLUMN _version;`);
2881
+ }
2882
+ for (const stmt of statements) {
2883
+ await adapter.executeWriteQuery(stmt);
2884
+ }
2885
+ const sql = statements.join("\n");
2886
+ return {
2887
+ success: true,
2888
+ message: hasVersionColumn ? `Versioning disabled on '${input.table}'. Dropped _version column and trigger.` : `Versioning already disabled on '${input.table}', trigger dropped if existed.`,
2889
+ sql
2890
+ };
2891
+ } catch (error) {
2892
+ return { ...formatHandlerError(error), sql: "" };
2893
+ }
2894
+ }
2895
+ };
2896
+ }
2897
+ function createCheckVersionTool(adapter) {
2898
+ return {
2899
+ name: "sqlite_check_version",
2900
+ description: "Read the current _version of a specific row for optimistic concurrency control.",
2901
+ group: "core",
2902
+ inputSchema: CheckVersionSchema,
2903
+ outputSchema: CheckVersionOutputSchema,
2904
+ requiredScopes: ["read"],
2905
+ annotations: readOnly("Check Version"),
2906
+ handler: async (params, _context) => {
2907
+ let input;
2908
+ try {
2909
+ input = CheckVersionSchema.parse(params);
2910
+ } catch (error) {
2911
+ return formatHandlerError(error);
2912
+ }
2913
+ const validationError = await validateTableExists2(adapter, input.table);
2914
+ if (validationError) return validationError;
2915
+ const safeTable = sanitizeIdentifier(input.table);
2916
+ const safeIdCol = sanitizeIdentifier(input.idColumn ?? "rowid");
2917
+ try {
2918
+ const sql = `SELECT * FROM ${safeTable} WHERE ${safeIdCol} = ? LIMIT 1`;
2919
+ const result = await adapter.executeReadQuery(sql, [input.rowId]);
2920
+ if (!result.rows || result.rows.length === 0) {
2921
+ return formatHandlerError(
2922
+ new ResourceNotFoundError(
2923
+ `Row not found in table '${input.table}' with ${safeIdCol} = ${input.rowId}`
2924
+ )
2925
+ );
2926
+ }
2927
+ const row = result.rows[0];
2928
+ if (row && typeof row["_version"] === "number") {
2929
+ return { success: true, version: row["_version"], row };
2930
+ } else {
2931
+ return formatHandlerError(
2932
+ new ValidationError(
2933
+ `Table '${input.table}' does not appear to have versioning enabled (missing _version column).`
2934
+ )
2935
+ );
2936
+ }
2937
+ } catch (error) {
2938
+ return formatHandlerError(error);
2939
+ }
2940
+ }
2941
+ };
2942
+ }
2943
+ function createConditionalUpdateTool(adapter) {
2944
+ return {
2945
+ name: "sqlite_conditional_update",
2946
+ description: "Update a row only if its _version matches expectedVersion. Prevents lost updates in multi-agent environments.",
2947
+ group: "core",
2948
+ inputSchema: ConditionalUpdateSchema,
2949
+ outputSchema: ConditionalUpdateOutputSchema,
2950
+ requiredScopes: ["write"],
2951
+ annotations: write("Conditional Update"),
2952
+ handler: async (params, _context) => {
2953
+ let input;
2954
+ try {
2955
+ input = ConditionalUpdateSchema.parse(params);
2956
+ } catch (error) {
2957
+ return formatHandlerError(error);
2958
+ }
2959
+ const validationError = await validateTableExists2(adapter, input.table);
2960
+ if (validationError) return validationError;
2961
+ const safeTable = sanitizeIdentifier(input.table);
2962
+ const columns = Object.keys(input.data);
2963
+ if (columns.length === 0) {
2964
+ return formatHandlerError(
2965
+ new ValidationError("Update data cannot be empty")
2966
+ );
2967
+ }
2968
+ if (input.conditions.length === 0) {
2969
+ return formatHandlerError(
2970
+ new ValidationError("Conditions are required to identify the row")
2971
+ );
2972
+ }
2973
+ try {
2974
+ const queryParams = [];
2975
+ const safeColumns = columns.map(sanitizeIdentifier);
2976
+ const setClauses = safeColumns.map((c) => `${c} = ?`).join(", ");
2977
+ queryParams.push(...Object.values(input.data));
2978
+ const { sql: whereSql, params: whereParams } = buildWhereClause(
2979
+ input.conditions
2980
+ );
2981
+ queryParams.push(...whereParams);
2982
+ queryParams.push(input.expectedVersion);
2983
+ const updateSql = `UPDATE ${safeTable} SET ${setClauses} WHERE (${whereSql}) AND _version = ? RETURNING *`;
2984
+ const result = await adapter.executeWriteQuery(updateSql, queryParams);
2985
+ if (result.rowsAffected === 0) {
2986
+ const checkSql = `SELECT _version FROM ${safeTable} WHERE ${whereSql}`;
2987
+ const checkResult = await adapter.executeReadQuery(
2988
+ checkSql,
2989
+ whereParams
2990
+ );
2991
+ if (!checkResult.rows || checkResult.rows.length === 0) {
2992
+ return formatHandlerError(
2993
+ new ResourceNotFoundError(
2994
+ "Row not found matching the provided conditions"
2995
+ )
2996
+ );
2997
+ }
2998
+ const currentVersionRaw = checkResult.rows[0]?.["_version"];
2999
+ if (currentVersionRaw === void 0 || currentVersionRaw === null) {
3000
+ return formatHandlerError(
3001
+ new ValidationError(
3002
+ `Table '${input.table}' does not appear to have versioning enabled (missing _version column).`
3003
+ )
3004
+ );
3005
+ }
3006
+ const currentVersion2 = Number(currentVersionRaw);
3007
+ return formatHandlerError(
3008
+ new ConflictError(
3009
+ `Version conflict: expected version ${input.expectedVersion} but row has version ${currentVersion2}. Re-read the row and retry.`,
3010
+ "CONFLICT_ERROR",
3011
+ {
3012
+ conflictType: "version_mismatch",
3013
+ suggestion: "Re-read the row to get the current version, then retry the update.",
3014
+ details: {
3015
+ table: input.table,
3016
+ expectedVersion: input.expectedVersion,
3017
+ currentVersion: currentVersion2
3018
+ }
3019
+ }
3020
+ )
3021
+ );
3022
+ }
3023
+ const newRow = result.rows?.[0];
3024
+ const currentVersion = newRow ? newRow["_version"] : void 0;
3025
+ return {
3026
+ success: true,
3027
+ rowsAffected: result.rowsAffected,
3028
+ currentVersion,
3029
+ rows: result.rows
3030
+ };
3031
+ } catch (error) {
3032
+ return formatHandlerError(error);
3033
+ }
3034
+ }
3035
+ };
3036
+ }
3037
+
2989
3038
  // src/adapters/sqlite/tools/core/index.ts
2990
3039
  function getCoreTools(adapter) {
2991
3040
  return [
@@ -3009,7 +3058,11 @@ function getCoreTools(adapter) {
3009
3058
  createListConstraintsTool(adapter),
3010
3059
  createDateAddTool(adapter),
3011
3060
  createDateDiffTool(adapter),
3012
- createAlterTableTool(adapter)
3061
+ createAlterTableTool(adapter),
3062
+ createEnableVersioningTool(adapter),
3063
+ createDisableVersioningTool(adapter),
3064
+ createCheckVersionTool(adapter),
3065
+ createConditionalUpdateTool(adapter)
3013
3066
  ];
3014
3067
  }
3015
3068
 
@@ -3129,7 +3182,7 @@ var SchemaManager = class {
3129
3182
  throw new DbMcpError(
3130
3183
  "Invalid table name",
3131
3184
  "INVALID_TABLE",
3132
- "validation" /* VALIDATION */
3185
+ ErrorCategory.VALIDATION
3133
3186
  );
3134
3187
  }
3135
3188
  const result = await this.executor.executeReadQuery(
@@ -3139,7 +3192,7 @@ var SchemaManager = class {
3139
3192
  throw new DbMcpError(
3140
3193
  `Table '${tableName}' does not exist`,
3141
3194
  "TABLE_NOT_FOUND",
3142
- "resource" /* RESOURCE */
3195
+ ErrorCategory.RESOURCE
3143
3196
  );
3144
3197
  }
3145
3198
  const columns = (result.rows ?? []).map((row) => ({
@@ -3227,55 +3280,6 @@ var SchemaManager = class {
3227
3280
  }
3228
3281
  };
3229
3282
 
3230
- // src/utils/insights-manager.ts
3231
- var InsightsManager = class {
3232
- insights = [];
3233
- /**
3234
- * Append a new business insight
3235
- */
3236
- append(insight) {
3237
- if (insight.trim()) {
3238
- this.insights.push(insight.trim());
3239
- }
3240
- }
3241
- /**
3242
- * Get all insights
3243
- */
3244
- getAll() {
3245
- return [...this.insights];
3246
- }
3247
- /**
3248
- * Get insight count
3249
- */
3250
- count() {
3251
- return this.insights.length;
3252
- }
3253
- /**
3254
- * Clear all insights
3255
- */
3256
- clear() {
3257
- this.insights = [];
3258
- }
3259
- /**
3260
- * Synthesize insights into a formatted memo
3261
- */
3262
- synthesizeMemo() {
3263
- if (this.insights.length === 0) {
3264
- return "No business insights have been discovered yet.";
3265
- }
3266
- const insightsList = this.insights.map((insight) => `- ${insight}`).join("\n");
3267
- let memo = "\u{1F4CA} Business Intelligence Memo \u{1F4CA}\n\n";
3268
- memo += "Key Insights Discovered:\n\n";
3269
- memo += insightsList;
3270
- if (this.insights.length > 1) {
3271
- memo += "\n\nSummary:\n";
3272
- memo += `Analysis has revealed ${this.insights.length} key business insights that suggest opportunities for strategic optimization and growth.`;
3273
- }
3274
- return memo;
3275
- }
3276
- };
3277
- var insightsManager = new InsightsManager();
3278
-
3279
3283
  // src/adapters/sqlite/resources.ts
3280
3284
  function getResourceDefinitions(adapter) {
3281
3285
  return [
@@ -3287,8 +3291,7 @@ function getResourceDefinitions(adapter) {
3287
3291
  createHealthResource(adapter),
3288
3292
  createMetaResource(adapter),
3289
3293
  createCompileOptionsResource(adapter),
3290
- createPragmaResource(adapter),
3291
- createInsightsResource()
3294
+ createPragmaResource(adapter)
3292
3295
  ];
3293
3296
  }
3294
3297
  function createSchemaResource(adapter) {
@@ -3358,7 +3361,7 @@ function createTableSchemaResource(adapter) {
3358
3361
  throw new DbMcpError(
3359
3362
  "Invalid table URI format",
3360
3363
  "RESOURCE_INVALID_URI",
3361
- "validation" /* VALIDATION */
3364
+ ErrorCategory.VALIDATION
3362
3365
  );
3363
3366
  }
3364
3367
  const tableName = decodeURIComponent(match[1]);
@@ -3499,36 +3502,16 @@ function createMetaResource(adapter) {
3499
3502
  } catch {
3500
3503
  meta[pragma] = null;
3501
3504
  }
3502
- }
3503
- return {
3504
- contents: [
3505
- {
3506
- uri: "sqlite://meta",
3507
- mimeType: "application/json",
3508
- text: JSON.stringify(meta, null, 2)
3509
- }
3510
- ]
3511
- };
3512
- }
3513
- };
3514
- }
3515
- function createInsightsResource() {
3516
- return {
3517
- name: "sqlite_insights",
3518
- uri: "memo://insights",
3519
- description: "Business insights memo that gets updated during database analysis",
3520
- mimeType: "text/plain",
3521
- annotations: HIGH_PRIORITY,
3522
- handler: () => {
3523
- return Promise.resolve({
3505
+ }
3506
+ return {
3524
3507
  contents: [
3525
3508
  {
3526
- uri: "memo://insights",
3527
- mimeType: "text/plain",
3528
- text: insightsManager.synthesizeMemo()
3509
+ uri: "sqlite://meta",
3510
+ mimeType: "application/json",
3511
+ text: JSON.stringify(meta, null, 2)
3529
3512
  }
3530
3513
  ]
3531
- });
3514
+ };
3532
3515
  }
3533
3516
  };
3534
3517
  }
@@ -4161,7 +4144,6 @@ You are guiding an interactive demo of the SQLite MCP Server capabilities.
4161
4144
 
4162
4145
  **Prompts**: Pre-written templates that structure AI conversations (like this one)
4163
4146
  **Tools**: SQL operations (read_query, write_query, create_table, etc.)
4164
- **Resources**: Living documents like \`memo://insights\` that update during analysis
4165
4147
 
4166
4148
  ## Demo Workflow
4167
4149
 
@@ -4187,17 +4169,10 @@ After setup, present 3-4 natural language query choices:
4187
4169
  For each user selection:
4188
4170
  - Run the appropriate SQL query
4189
4171
  - Explain the results
4190
- - Use \`sqlite_append_insight\` to capture findings
4191
-
4192
- ### 5. Capture Insights
4193
- Remind the user about \`memo://insights\`:
4194
- - Each insight is automatically added to this resource
4195
- - They can attach it to see all discoveries
4196
4172
 
4197
- ### 6. Wrap Up
4173
+ ### 5. Wrap Up
4198
4174
  Summarize what was demonstrated:
4199
4175
  - Data creation and querying
4200
- - Insight capture via resources
4201
4176
  - The power of MCP for database workflows
4202
4177
 
4203
4178
  ---
@@ -4228,6 +4203,150 @@ function getPromptDefinitions(adapter) {
4228
4203
  createDemoPrompt()
4229
4204
  ];
4230
4205
  }
4206
+
4207
+ // src/adapters/sqlite/json-utils.ts
4208
+ var DEFAULT_NORMALIZATION_OPTIONS = {
4209
+ sortKeys: true,
4210
+ compact: true,
4211
+ unicodeNormalize: true,
4212
+ typeCoercion: false,
4213
+ duplicateKeys: "last"
4214
+ };
4215
+ function normalizeJson(value, options = {}) {
4216
+ const opts = { ...DEFAULT_NORMALIZATION_OPTIONS, ...options };
4217
+ const changes = [];
4218
+ let parsed;
4219
+ if (typeof value === "string") {
4220
+ try {
4221
+ parsed = JSON.parse(value);
4222
+ } catch {
4223
+ return {
4224
+ normalized: value,
4225
+ wasModified: false,
4226
+ changes: ["Input is not valid JSON"]
4227
+ };
4228
+ }
4229
+ } else {
4230
+ parsed = value;
4231
+ }
4232
+ const normalized = normalizeValue(parsed, opts, changes, "");
4233
+ const normalizedStr = opts.compact ? JSON.stringify(normalized) : JSON.stringify(normalized, null, 2);
4234
+ const originalStr = typeof value === "string" ? value : JSON.stringify(value);
4235
+ const wasModified = normalizedStr !== originalStr;
4236
+ return {
4237
+ normalized: normalizedStr,
4238
+ wasModified,
4239
+ changes
4240
+ };
4241
+ }
4242
+ function normalizeValue(value, options, changes, path3) {
4243
+ if (value === null || value === void 0) {
4244
+ return null;
4245
+ }
4246
+ if (typeof value === "string") {
4247
+ let result = value;
4248
+ if (options.unicodeNormalize && value !== value.normalize("NFC")) {
4249
+ result = result.normalize("NFC");
4250
+ changes.push(`${path3 || "$"}: Unicode normalized`);
4251
+ }
4252
+ if (options.typeCoercion) {
4253
+ if (result.toLowerCase() === "true") {
4254
+ changes.push(`${path3 || "$"}: Coerced "true" to boolean`);
4255
+ return true;
4256
+ }
4257
+ if (result.toLowerCase() === "false") {
4258
+ changes.push(`${path3 || "$"}: Coerced "false" to boolean`);
4259
+ return false;
4260
+ }
4261
+ const num = Number(result);
4262
+ if (!isNaN(num) && result.trim() !== "") {
4263
+ changes.push(`${path3 || "$"}: Coerced "${result}" to number`);
4264
+ return num;
4265
+ }
4266
+ }
4267
+ return result;
4268
+ }
4269
+ if (typeof value === "number" || typeof value === "boolean") {
4270
+ return value;
4271
+ }
4272
+ if (Array.isArray(value)) {
4273
+ return value.map(
4274
+ (item, index) => normalizeValue(item, options, changes, `${path3 || "$"}[${index}]`)
4275
+ );
4276
+ }
4277
+ if (typeof value === "object") {
4278
+ const obj = value;
4279
+ let keys = Object.keys(obj);
4280
+ if (options.sortKeys) {
4281
+ const originalOrder = keys.join(",");
4282
+ keys = keys.sort();
4283
+ if (keys.join(",") !== originalOrder) {
4284
+ changes.push(`${path3 || "$"}: Keys sorted`);
4285
+ }
4286
+ }
4287
+ const result = {};
4288
+ for (const key of keys) {
4289
+ result[key] = normalizeValue(
4290
+ obj[key],
4291
+ options,
4292
+ changes,
4293
+ `${path3 || "$"}.${key}`
4294
+ );
4295
+ }
4296
+ return result;
4297
+ }
4298
+ return value;
4299
+ }
4300
+ var jsonbSupportedCache = null;
4301
+ function isJsonbSupportedVersion(versionString) {
4302
+ const versionRegex = /^(\d+)\.(\d+)\.?(\d+)?/;
4303
+ const match = versionRegex.exec(versionString);
4304
+ if (!match) return false;
4305
+ const major = parseInt(match[1] ?? "0", 10);
4306
+ const minor = parseInt(match[2] ?? "0", 10);
4307
+ return major > 3 || major === 3 && minor >= 45;
4308
+ }
4309
+ function isJsonbSupported() {
4310
+ return jsonbSupportedCache ?? false;
4311
+ }
4312
+ function setJsonbSupported(supported) {
4313
+ jsonbSupportedCache = supported;
4314
+ }
4315
+ function detectJsonStorageFormat(value) {
4316
+ if (value instanceof Buffer || value instanceof Uint8Array) {
4317
+ return "jsonb";
4318
+ }
4319
+ if (typeof value === "string") {
4320
+ return "text";
4321
+ }
4322
+ return "unknown";
4323
+ }
4324
+ function parseJsonValue(value) {
4325
+ if (value === null || value === void 0) {
4326
+ return null;
4327
+ }
4328
+ if (typeof value === "object" && !(value instanceof Buffer)) {
4329
+ return value;
4330
+ }
4331
+ if (value instanceof Buffer || value instanceof Uint8Array) {
4332
+ throw new DbMcpError(
4333
+ "JSONB blob cannot be parsed directly. Use json() in SQL query.",
4334
+ "JSON_PARSE_ERROR",
4335
+ ErrorCategory.VALIDATION
4336
+ );
4337
+ }
4338
+ if (typeof value === "string") {
4339
+ try {
4340
+ return JSON.parse(value);
4341
+ } catch {
4342
+ return value;
4343
+ }
4344
+ }
4345
+ if (typeof value === "number" || typeof value === "boolean") {
4346
+ return value;
4347
+ }
4348
+ return null;
4349
+ }
4231
4350
  var coerceNumber2 = (val) => {
4232
4351
  if (typeof val === "string") {
4233
4352
  if (val.trim() === "") return void 0;
@@ -4702,7 +4821,7 @@ function createJsonExtractTool(adapter) {
4702
4821
  return {
4703
4822
  success: true,
4704
4823
  rowCount: result.rows?.length ?? 0,
4705
- values: result.rows?.map((r) => r["value"])
4824
+ values: result.rows?.map((r) => parseJsonValue(r["value"]))
4706
4825
  };
4707
4826
  } catch (error) {
4708
4827
  return formatHandlerError(error);
@@ -4750,6 +4869,7 @@ function createJsonSetTool(adapter) {
4750
4869
  const result = await adapter.executeWriteQuery(sql, queryParams);
4751
4870
  return {
4752
4871
  success: true,
4872
+ message: `Set value at ${input.path} in ${table}.${column}`,
4753
4873
  rowsAffected: result.rowsAffected,
4754
4874
  ...result.rowsAffected === 0 ? {
4755
4875
  warning: "No rows matched the WHERE clause \u2014 no changes were made"
@@ -4791,6 +4911,7 @@ function createJsonRemoveTool(adapter) {
4791
4911
  const result = await adapter.executeWriteQuery(sql, queryParams);
4792
4912
  return {
4793
4913
  success: true,
4914
+ message: `Removed value at ${input.path} from ${table}.${column}`,
4794
4915
  rowsAffected: result.rowsAffected,
4795
4916
  ...result.rowsAffected === 0 ? {
4796
4917
  warning: "No rows matched the WHERE clause \u2014 no changes were made"
@@ -4933,6 +5054,7 @@ function createJsonArrayAppendTool(adapter) {
4933
5054
  const result = await adapter.executeWriteQuery(sql, queryParams);
4934
5055
  return {
4935
5056
  success: true,
5057
+ message: `Appended to ${input.path} in ${table}.${column}`,
4936
5058
  rowsAffected: result.rowsAffected
4937
5059
  };
4938
5060
  } catch (error) {
@@ -5032,10 +5154,14 @@ function createJsonEachTool(adapter) {
5032
5154
  }
5033
5155
  sql += ` LIMIT ${input.limit}`;
5034
5156
  const result = await adapter.executeReadQuery(sql, queryParams);
5157
+ const parsedElements = result.rows?.map((row) => ({
5158
+ ...row,
5159
+ value: parseJsonValue(row["value"])
5160
+ })) ?? [];
5035
5161
  return {
5036
5162
  success: true,
5037
- rowCount: result.rows?.length ?? 0,
5038
- elements: result.rows
5163
+ rowCount: parsedElements.length,
5164
+ elements: parsedElements
5039
5165
  };
5040
5166
  } catch (error) {
5041
5167
  return formatHandlerError(error);
@@ -5089,10 +5215,17 @@ function createJsonGroupArrayTool(adapter) {
5089
5215
  }
5090
5216
  sql += groupByClause;
5091
5217
  const result = await adapter.executeReadQuery(sql, queryParams);
5218
+ const parsedRows = result.rows?.map((row) => {
5219
+ const parsedRow = {};
5220
+ for (const [key, value] of Object.entries(row)) {
5221
+ parsedRow[key] = parseJsonValue(value);
5222
+ }
5223
+ return parsedRow;
5224
+ }) ?? [];
5092
5225
  return {
5093
5226
  success: true,
5094
- rowCount: result.rows?.length ?? 0,
5095
- rows: result.rows ?? []
5227
+ rowCount: parsedRows.length,
5228
+ rows: parsedRows
5096
5229
  };
5097
5230
  } catch (error) {
5098
5231
  return formatHandlerError(error);
@@ -5144,10 +5277,17 @@ function createJsonGroupObjectTool(adapter) {
5144
5277
  }
5145
5278
  const sql2 = `SELECT ${outerSelect} FROM (${subquery})${outerGroupBy}`;
5146
5279
  const result2 = await adapter.executeReadQuery(sql2, queryParams);
5280
+ const parsedRows2 = result2.rows?.map((row) => {
5281
+ const parsedRow = {};
5282
+ for (const [key, value] of Object.entries(row)) {
5283
+ parsedRow[key] = parseJsonValue(value);
5284
+ }
5285
+ return parsedRow;
5286
+ }) ?? [];
5147
5287
  return {
5148
5288
  success: true,
5149
- rowCount: result2.rows?.length ?? 0,
5150
- rows: result2.rows ?? []
5289
+ rowCount: parsedRows2.length,
5290
+ rows: parsedRows2
5151
5291
  };
5152
5292
  }
5153
5293
  if (!input.valueColumn) {
@@ -5171,151 +5311,40 @@ function createJsonGroupObjectTool(adapter) {
5171
5311
  if (input.groupByColumn) {
5172
5312
  const groupByCol = input.allowExpressions ? input.groupByColumn : sanitizeIdentifier(input.groupByColumn);
5173
5313
  const groupAlias = input.allowExpressions ? "group_key" : input.groupByColumn;
5174
- selectClause = `${groupByCol} AS ${groupAlias}, ${selectClause}`;
5175
- groupByClause = ` GROUP BY ${groupByCol}`;
5176
- }
5177
- let sql = `SELECT ${selectClause} FROM ${table}`;
5178
- if (input.conditions || input.whereClause) {
5179
- const { sql: whereSql, params: whereParams } = buildWhereClause(
5180
- input.conditions,
5181
- input.whereClause
5182
- );
5183
- if (whereSql !== "") {
5184
- sql += ` WHERE ${whereSql}`;
5185
- queryParams.push(...whereParams);
5186
- }
5187
- }
5188
- sql += groupByClause;
5189
- const result = await adapter.executeReadQuery(sql, queryParams);
5190
- return {
5191
- success: true,
5192
- rowCount: result.rows?.length ?? 0,
5193
- rows: result.rows ?? [],
5194
- ...duplicateKeyWarning && { hint: duplicateKeyWarning }
5195
- };
5196
- } catch (error) {
5197
- return formatHandlerError(error);
5198
- }
5199
- }
5200
- };
5201
- }
5202
-
5203
- // src/adapters/sqlite/json-utils.ts
5204
- var DEFAULT_NORMALIZATION_OPTIONS = {
5205
- sortKeys: true,
5206
- compact: true,
5207
- unicodeNormalize: true,
5208
- typeCoercion: false,
5209
- duplicateKeys: "last"
5210
- };
5211
- function normalizeJson(value, options = {}) {
5212
- const opts = { ...DEFAULT_NORMALIZATION_OPTIONS, ...options };
5213
- const changes = [];
5214
- let parsed;
5215
- if (typeof value === "string") {
5216
- try {
5217
- parsed = JSON.parse(value);
5218
- } catch {
5219
- return {
5220
- normalized: value,
5221
- wasModified: false,
5222
- changes: ["Input is not valid JSON"]
5223
- };
5224
- }
5225
- } else {
5226
- parsed = value;
5227
- }
5228
- const normalized = normalizeValue(parsed, opts, changes, "");
5229
- const normalizedStr = opts.compact ? JSON.stringify(normalized) : JSON.stringify(normalized, null, 2);
5230
- const originalStr = typeof value === "string" ? value : JSON.stringify(value);
5231
- const wasModified = normalizedStr !== originalStr;
5232
- return {
5233
- normalized: normalizedStr,
5234
- wasModified,
5235
- changes
5236
- };
5237
- }
5238
- function normalizeValue(value, options, changes, path3) {
5239
- if (value === null || value === void 0) {
5240
- return null;
5241
- }
5242
- if (typeof value === "string") {
5243
- let result = value;
5244
- if (options.unicodeNormalize && value !== value.normalize("NFC")) {
5245
- result = result.normalize("NFC");
5246
- changes.push(`${path3 || "$"}: Unicode normalized`);
5247
- }
5248
- if (options.typeCoercion) {
5249
- if (result.toLowerCase() === "true") {
5250
- changes.push(`${path3 || "$"}: Coerced "true" to boolean`);
5251
- return true;
5252
- }
5253
- if (result.toLowerCase() === "false") {
5254
- changes.push(`${path3 || "$"}: Coerced "false" to boolean`);
5255
- return false;
5256
- }
5257
- const num = Number(result);
5258
- if (!isNaN(num) && result.trim() !== "") {
5259
- changes.push(`${path3 || "$"}: Coerced "${result}" to number`);
5260
- return num;
5261
- }
5262
- }
5263
- return result;
5264
- }
5265
- if (typeof value === "number" || typeof value === "boolean") {
5266
- return value;
5267
- }
5268
- if (Array.isArray(value)) {
5269
- return value.map(
5270
- (item, index) => normalizeValue(item, options, changes, `${path3 || "$"}[${index}]`)
5271
- );
5272
- }
5273
- if (typeof value === "object") {
5274
- const obj = value;
5275
- let keys = Object.keys(obj);
5276
- if (options.sortKeys) {
5277
- const originalOrder = keys.join(",");
5278
- keys = keys.sort();
5279
- if (keys.join(",") !== originalOrder) {
5280
- changes.push(`${path3 || "$"}: Keys sorted`);
5314
+ selectClause = `${groupByCol} AS ${groupAlias}, ${selectClause}`;
5315
+ groupByClause = ` GROUP BY ${groupByCol}`;
5316
+ }
5317
+ let sql = `SELECT ${selectClause} FROM ${table}`;
5318
+ if (input.conditions || input.whereClause) {
5319
+ const { sql: whereSql, params: whereParams } = buildWhereClause(
5320
+ input.conditions,
5321
+ input.whereClause
5322
+ );
5323
+ if (whereSql !== "") {
5324
+ sql += ` WHERE ${whereSql}`;
5325
+ queryParams.push(...whereParams);
5326
+ }
5327
+ }
5328
+ sql += groupByClause;
5329
+ const result = await adapter.executeReadQuery(sql, queryParams);
5330
+ const parsedRows = result.rows?.map((row) => {
5331
+ const parsedRow = {};
5332
+ for (const [key, value] of Object.entries(row)) {
5333
+ parsedRow[key] = parseJsonValue(value);
5334
+ }
5335
+ return parsedRow;
5336
+ }) ?? [];
5337
+ return {
5338
+ success: true,
5339
+ rowCount: parsedRows.length,
5340
+ rows: parsedRows,
5341
+ ...duplicateKeyWarning && { hint: duplicateKeyWarning }
5342
+ };
5343
+ } catch (error) {
5344
+ return formatHandlerError(error);
5281
5345
  }
5282
5346
  }
5283
- const result = {};
5284
- for (const key of keys) {
5285
- result[key] = normalizeValue(
5286
- obj[key],
5287
- options,
5288
- changes,
5289
- `${path3 || "$"}.${key}`
5290
- );
5291
- }
5292
- return result;
5293
- }
5294
- return value;
5295
- }
5296
- var jsonbSupportedCache = null;
5297
- function isJsonbSupportedVersion(versionString) {
5298
- const versionRegex = /^(\d+)\.(\d+)\.?(\d+)?/;
5299
- const match = versionRegex.exec(versionString);
5300
- if (!match) return false;
5301
- const major = parseInt(match[1] ?? "0", 10);
5302
- const minor = parseInt(match[2] ?? "0", 10);
5303
- return major > 3 || major === 3 && minor >= 45;
5304
- }
5305
- function isJsonbSupported() {
5306
- return jsonbSupportedCache ?? false;
5307
- }
5308
- function setJsonbSupported(supported) {
5309
- jsonbSupportedCache = supported;
5310
- }
5311
- function detectJsonStorageFormat(value) {
5312
- if (value instanceof Buffer || value instanceof Uint8Array) {
5313
- return "jsonb";
5314
- }
5315
- if (typeof value === "string") {
5316
- return "text";
5317
- }
5318
- return "unknown";
5347
+ };
5319
5348
  }
5320
5349
 
5321
5350
  // src/adapters/sqlite/tools/json-operations/transform.ts
@@ -5616,7 +5645,7 @@ function createJsonSecurityScanTool(adapter) {
5616
5645
  const issueMap = /* @__PURE__ */ new Map();
5617
5646
  for (let i = 0; i < rows.length; i++) {
5618
5647
  if (i > 0 && i % 500 === 0) {
5619
- await new Promise((resolve4) => setTimeout(resolve4, 0));
5648
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
5620
5649
  }
5621
5650
  const row = rows[i];
5622
5651
  if (!row) continue;
@@ -5877,10 +5906,17 @@ function createJsonSelectTool(adapter) {
5877
5906
  }
5878
5907
  sql += ` LIMIT ${input.limit ?? 100}`;
5879
5908
  const result = await adapter.executeReadQuery(sql, queryParams);
5909
+ const parsedRows = result.rows?.map((row) => {
5910
+ const parsedRow = {};
5911
+ for (const [key, value] of Object.entries(row)) {
5912
+ parsedRow[key] = parseJsonValue(value);
5913
+ }
5914
+ return parsedRow;
5915
+ }) ?? [];
5880
5916
  return {
5881
5917
  success: true,
5882
- rowCount: result.rows?.length ?? 0,
5883
- rows: result.rows
5918
+ rowCount: parsedRows.length,
5919
+ rows: parsedRows
5884
5920
  };
5885
5921
  } catch (error) {
5886
5922
  return formatHandlerError(error);
@@ -5937,10 +5973,17 @@ function createJsonQueryTool(adapter) {
5937
5973
  }
5938
5974
  sql += ` LIMIT ${input.limit ?? 100}`;
5939
5975
  const result = await adapter.executeReadQuery(sql, queryParams);
5976
+ const parsedRows = result.rows?.map((row) => {
5977
+ const parsedRow = {};
5978
+ for (const [key, value] of Object.entries(row)) {
5979
+ parsedRow[key] = parseJsonValue(value);
5980
+ }
5981
+ return parsedRow;
5982
+ }) ?? [];
5940
5983
  return {
5941
5984
  success: true,
5942
- rowCount: result.rows?.length ?? 0,
5943
- rows: result.rows
5985
+ rowCount: parsedRows.length,
5986
+ rows: parsedRows
5944
5987
  };
5945
5988
  } catch (error) {
5946
5989
  return formatHandlerError(error);
@@ -6010,7 +6053,7 @@ function createAnalyzeJsonSchemaTool(adapter) {
6010
6053
  const rows = result.rows ?? [];
6011
6054
  for (let i = 0; i < rows.length; i++) {
6012
6055
  if (i > 0 && i % 500 === 0) {
6013
- await new Promise((resolve4) => setTimeout(resolve4, 0));
6056
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
6014
6057
  }
6015
6058
  const row = rows[i];
6016
6059
  if (!row) continue;
@@ -6354,6 +6397,18 @@ var VALID_VALIDATE_PATTERNS = [
6354
6397
  var VALID_PHONETIC_ALGORITHMS = ["soundex", "metaphone"];
6355
6398
  var VALID_TRIM_MODES = ["both", "left", "right"];
6356
6399
  var VALID_SEARCH_TECHNIQUES = ["exact", "fuzzy", "phonetic"];
6400
+ function sanitizeFtsQuery(query) {
6401
+ if (!query) return "";
6402
+ let clean = query.replace(/\s+/g, " ").trim();
6403
+ const quoteCount = (clean.match(/"/g) ?? []).length;
6404
+ if (quoteCount % 2 !== 0) {
6405
+ clean = clean.replace(/"/g, "");
6406
+ }
6407
+ clean = clean.replace(/^(?:\s*(?:AND|OR|NOT))+(?=\s|$)/i, "");
6408
+ clean = clean.replace(/(?:^|\s)(?:AND|OR|NOT)(?:\s+(?:AND|OR|NOT))*$/i, "");
6409
+ clean = clean.replace(/(?:\s+(?:AND|OR|NOT)){2,}\s+/gi, " AND ");
6410
+ return clean.trim();
6411
+ }
6357
6412
  var coerceNumber3 = (val) => {
6358
6413
  if (typeof val === "string") {
6359
6414
  if (val.trim() === "") return void 0;
@@ -6460,6 +6515,20 @@ var AdvancedSearchOutputSchema = z.object({
6460
6515
  bestScore: z.number(),
6461
6516
  bestType: z.string()
6462
6517
  })
6518
+ ).optional(),
6519
+ facets: z.record(z.string(), z.number()).optional()
6520
+ }).extend(ErrorResponseFields.shape);
6521
+ var HybridSearchOutputSchema = z.object({
6522
+ success: z.boolean(),
6523
+ query: z.string().optional(),
6524
+ matchCount: z.number().optional(),
6525
+ results: z.array(
6526
+ z.object({
6527
+ rowid: z.number().optional(),
6528
+ rrfScore: z.number().optional(),
6529
+ ftsRank: z.number().nullable().optional(),
6530
+ vectorSimilarity: z.number().nullable().optional()
6531
+ }).loose()
6463
6532
  ).optional()
6464
6533
  }).extend(ErrorResponseFields.shape);
6465
6534
  var SentimentResultItemSchema = z.object({
@@ -6630,7 +6699,20 @@ var AdvancedSearchSchema = z.object({
6630
6699
  ),
6631
6700
  conditions: z.array(WhereConditionSchema).optional().describe("Optional WHERE conditions"),
6632
6701
  whereClause: z.string().optional().describe("Deprecated: Use conditions instead"),
6633
- limit: z.preprocess(coerceNumber3, z.number().optional().default(100))
6702
+ limit: z.preprocess(coerceNumber3, z.number().optional().default(100)),
6703
+ includeFacets: z.boolean().optional().default(false).describe("Return match counts per type")
6704
+ });
6705
+ var HybridSearchSchema = z.object({
6706
+ table: z.string().describe("Table name"),
6707
+ query: z.string().describe("Text search query (FTS5)"),
6708
+ vectorColumn: z.string().describe("Vector column to search"),
6709
+ queryVector: z.array(z.number()).describe("Vector embedding of the query"),
6710
+ limit: z.preprocess(coerceNumber3, z.number().optional().default(100)),
6711
+ rrfK: z.preprocess(
6712
+ coerceNumber3,
6713
+ z.number().optional().default(60).describe("RRF k constant")
6714
+ ),
6715
+ metric: z.enum(["cosine", "euclidean", "dot"]).optional().default("cosine").describe("Vector similarity metric")
6634
6716
  });
6635
6717
  var TextSentimentSchema = z.object({
6636
6718
  table: z.string().optional().describe("Table name"),
@@ -7199,6 +7281,84 @@ var VALIDATION_PATTERNS = {
7199
7281
  ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
7200
7282
  };
7201
7283
 
7284
+ // src/adapters/sqlite/tools/vector/helpers.ts
7285
+ function cosineSimilarity(a, b) {
7286
+ if (a.length !== b.length) {
7287
+ throw new DbMcpError(
7288
+ "Vector dimensions must match",
7289
+ "VECTOR_MISMATCH",
7290
+ ErrorCategory.VALIDATION
7291
+ );
7292
+ }
7293
+ let dotProd = 0;
7294
+ let normA = 0;
7295
+ let normB = 0;
7296
+ for (let i = 0; i < a.length; i++) {
7297
+ const aVal = a[i] ?? 0;
7298
+ const bVal = b[i] ?? 0;
7299
+ dotProd += aVal * bVal;
7300
+ normA += aVal * aVal;
7301
+ normB += bVal * bVal;
7302
+ }
7303
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
7304
+ return magnitude === 0 ? 0 : dotProd / magnitude;
7305
+ }
7306
+ function euclideanDistance(a, b) {
7307
+ if (a.length !== b.length) {
7308
+ throw new DbMcpError(
7309
+ "Vector dimensions must match",
7310
+ "VECTOR_MISMATCH",
7311
+ ErrorCategory.VALIDATION
7312
+ );
7313
+ }
7314
+ let sum = 0;
7315
+ for (let i = 0; i < a.length; i++) {
7316
+ const aVal = a[i] ?? 0;
7317
+ const bVal = b[i] ?? 0;
7318
+ const diff = aVal - bVal;
7319
+ sum += diff * diff;
7320
+ }
7321
+ return Math.sqrt(sum);
7322
+ }
7323
+ function dotProduct(a, b) {
7324
+ if (a.length !== b.length) {
7325
+ throw new DbMcpError(
7326
+ "Vector dimensions must match",
7327
+ "VECTOR_MISMATCH",
7328
+ ErrorCategory.VALIDATION
7329
+ );
7330
+ }
7331
+ let sum = 0;
7332
+ for (let i = 0; i < a.length; i++) {
7333
+ sum += (a[i] ?? 0) * (b[i] ?? 0);
7334
+ }
7335
+ return sum;
7336
+ }
7337
+ function normalizeVector(v) {
7338
+ const norm = Math.sqrt(v.reduce((sum, x) => sum + x * x, 0));
7339
+ if (norm === 0) return v;
7340
+ return v.map((x) => x / norm);
7341
+ }
7342
+ function parseVector(value) {
7343
+ if (Array.isArray(value)) {
7344
+ return value.map(Number);
7345
+ }
7346
+ if (typeof value === "string") {
7347
+ try {
7348
+ const parsed = JSON.parse(value);
7349
+ if (Array.isArray(parsed)) {
7350
+ return parsed.map(Number);
7351
+ }
7352
+ } catch {
7353
+ }
7354
+ }
7355
+ throw new DbMcpError(
7356
+ "Invalid vector format",
7357
+ "VECTOR_INVALID",
7358
+ ErrorCategory.VALIDATION
7359
+ );
7360
+ }
7361
+
7202
7362
  // src/adapters/sqlite/tools/text/search.ts
7203
7363
  function createFuzzyMatchTool(adapter) {
7204
7364
  return {
@@ -7429,12 +7589,131 @@ function createAdvancedSearchTool(adapter) {
7429
7589
  }
7430
7590
  allMatches.sort((a, b) => b.bestScore - a.bestScore);
7431
7591
  const limited = allMatches.slice(0, input.limit);
7592
+ const facets = {};
7593
+ if (input.includeFacets) {
7594
+ for (const technique of input.techniques) {
7595
+ facets[technique] = 0;
7596
+ }
7597
+ for (const match of allMatches) {
7598
+ facets[match.bestType] = (facets[match.bestType] ?? 0) + 1;
7599
+ }
7600
+ }
7432
7601
  return {
7433
7602
  success: true,
7434
7603
  searchTerm: input.searchTerm,
7435
7604
  techniques: input.techniques,
7436
7605
  matchCount: limited.length,
7437
- matches: limited
7606
+ matches: limited,
7607
+ ...input.includeFacets ? { facets } : {}
7608
+ };
7609
+ } catch (error) {
7610
+ return formatHandlerError(error);
7611
+ }
7612
+ }
7613
+ };
7614
+ }
7615
+ function createHybridSearchTool(adapter) {
7616
+ return {
7617
+ name: "sqlite_hybrid_search",
7618
+ description: "Hybrid search combining full-text search and vector similarity using Reciprocal Rank Fusion (RRF).",
7619
+ group: "text",
7620
+ inputSchema: HybridSearchSchema,
7621
+ outputSchema: HybridSearchOutputSchema,
7622
+ requiredScopes: ["read"],
7623
+ annotations: readOnly("Hybrid Search"),
7624
+ handler: async (params, _context) => {
7625
+ try {
7626
+ const input = HybridSearchSchema.parse(params);
7627
+ const table = sanitizeIdentifier(input.table);
7628
+ const vectorColumn = sanitizeIdentifier(input.vectorColumn);
7629
+ await validateColumnExists(adapter, input.table, input.vectorColumn);
7630
+ let textResults = [];
7631
+ try {
7632
+ const sanitizedQuery = sanitizeFtsQuery(input.query);
7633
+ if (sanitizedQuery) {
7634
+ const queryEscaped = sanitizedQuery.replace(/'/g, "''");
7635
+ const ftsSql = `SELECT rowid as id, rank as score FROM ${table} WHERE ${table} MATCH '${queryEscaped}' ORDER BY rank LIMIT 1000`;
7636
+ const result = await adapter.executeReadQuery(ftsSql, []);
7637
+ textResults = (result.rows ?? []).map((r, i) => ({
7638
+ id: Number(r["id"] ?? r["rowid"]),
7639
+ rank: i + 1
7640
+ }));
7641
+ }
7642
+ } catch {
7643
+ }
7644
+ const vecSql = `SELECT rowid as id, ${vectorColumn} as vec FROM ${table} WHERE ${vectorColumn} IS NOT NULL LIMIT 10000`;
7645
+ const vecResult = await adapter.executeReadQuery(vecSql, []);
7646
+ const queryVector = input.queryVector;
7647
+ const vecScored = [];
7648
+ for (const row of vecResult.rows ?? []) {
7649
+ try {
7650
+ const storedVector = parseVector(row["vec"]);
7651
+ let score;
7652
+ switch (input.metric) {
7653
+ case "euclidean":
7654
+ score = 1 / (1 + euclideanDistance(queryVector, storedVector));
7655
+ break;
7656
+ case "dot":
7657
+ score = dotProduct(queryVector, storedVector);
7658
+ break;
7659
+ case "cosine":
7660
+ default:
7661
+ score = cosineSimilarity(queryVector, storedVector);
7662
+ break;
7663
+ }
7664
+ vecScored.push({ id: Number(row["id"] ?? row["rowid"]), score });
7665
+ } catch {
7666
+ }
7667
+ }
7668
+ vecScored.sort((a, b) => b.score - a.score);
7669
+ const vectorResults = vecScored.map((r, i) => ({
7670
+ id: r.id,
7671
+ rank: i + 1,
7672
+ score: r.score
7673
+ }));
7674
+ const combined = /* @__PURE__ */ new Map();
7675
+ const k = input.rrfK;
7676
+ for (const f of textResults) {
7677
+ if (!combined.has(f.id)) {
7678
+ combined.set(f.id, {
7679
+ id: f.id,
7680
+ rrfScore: 0,
7681
+ ftsRank: null,
7682
+ vectorSimilarity: null
7683
+ });
7684
+ }
7685
+ const entry = combined.get(f.id);
7686
+ if (entry) {
7687
+ entry.ftsRank = f.rank;
7688
+ entry.rrfScore += 1 / (k + f.rank);
7689
+ }
7690
+ }
7691
+ for (const v of vectorResults) {
7692
+ if (!combined.has(v.id)) {
7693
+ combined.set(v.id, {
7694
+ id: v.id,
7695
+ rrfScore: 0,
7696
+ ftsRank: null,
7697
+ vectorSimilarity: null
7698
+ });
7699
+ }
7700
+ const entry = combined.get(v.id);
7701
+ if (entry) {
7702
+ entry.vectorSimilarity = v.score;
7703
+ entry.rrfScore += 1 / (k + v.rank);
7704
+ }
7705
+ }
7706
+ const sortedCombined = Array.from(combined.values()).sort((a, b) => b.rrfScore - a.rrfScore).slice(0, input.limit).map((r) => ({
7707
+ rowid: r.id,
7708
+ rrfScore: Math.round(r.rrfScore * 1e4) / 1e4,
7709
+ ftsRank: r.ftsRank,
7710
+ vectorSimilarity: r.vectorSimilarity !== null ? Math.round(r.vectorSimilarity * 1e4) / 1e4 : null
7711
+ }));
7712
+ return {
7713
+ success: true,
7714
+ query: input.query,
7715
+ matchCount: sortedCombined.length,
7716
+ results: sortedCombined
7438
7717
  };
7439
7718
  } catch (error) {
7440
7719
  return formatHandlerError(error);
@@ -7768,6 +8047,7 @@ function getTextTools(adapter) {
7768
8047
  createTextNormalizeTool(adapter),
7769
8048
  createTextValidateTool(adapter),
7770
8049
  createAdvancedSearchTool(adapter),
8050
+ createHybridSearchTool(adapter),
7771
8051
  createTextSentimentTool(adapter)
7772
8052
  ];
7773
8053
  }
@@ -8196,8 +8476,8 @@ var StatsSampleSchema = z.object({
8196
8476
  table: z.string().describe("Table name"),
8197
8477
  sampleSize: z.preprocess(
8198
8478
  coerceNumber4,
8199
- z.number().optional().default(100).describe(
8200
- "Number of random rows to return (default: 100, max: 1000). WARNING: Uses ORDER BY RANDOM() which is O(N) \u2014 slow on very large tables."
8479
+ z.number().optional().default(20).describe(
8480
+ "Number of random rows to return (default: 20, max: 50). WARNING: Uses ORDER BY RANDOM() which is O(N) \u2014 slow on very large tables."
8201
8481
  )
8202
8482
  ),
8203
8483
  conditions: z.array(WhereConditionSchema).optional().describe("Optional WHERE conditions"),
@@ -8620,7 +8900,7 @@ function createPercentileTool(adapter) {
8620
8900
  }
8621
8901
  };
8622
8902
  }
8623
- var MAX_SAMPLE_SIZE = 1e3;
8903
+ var MAX_SAMPLE_SIZE = 50;
8624
8904
  function createSampleTool(adapter) {
8625
8905
  return {
8626
8906
  name: "sqlite_stats_sample",
@@ -9310,7 +9590,7 @@ function matrixInverse(A) {
9310
9590
  throw new DbMcpError(
9311
9591
  "Matrix is singular, cannot compute inverse",
9312
9592
  "MATH_SINGULAR",
9313
- "validation" /* VALIDATION */
9593
+ ErrorCategory.VALIDATION
9314
9594
  );
9315
9595
  }
9316
9596
  for (let j = 0; j < 2 * n; j++) {
@@ -9377,7 +9657,7 @@ function createRegressionTool(adapter) {
9377
9657
  throw new DbMcpError(
9378
9658
  `Insufficient data in table '${input.table}' for degree ${degree} regression (need at least ${degree + 1} points, got ${pairs.length})`,
9379
9659
  "STATS_INSUFFICIENT_SAMPLE",
9380
- "validation" /* VALIDATION */
9660
+ ErrorCategory.VALIDATION
9381
9661
  );
9382
9662
  }
9383
9663
  const X = pairs.map(
@@ -9486,7 +9766,7 @@ function createHypothesisTool(adapter) {
9486
9766
  throw new DbMcpError(
9487
9767
  "Insufficient sample size for t-test",
9488
9768
  "STATS_INSUFFICIENT_SAMPLE",
9489
- "validation" /* VALIDATION */
9769
+ ErrorCategory.VALIDATION
9490
9770
  );
9491
9771
  }
9492
9772
  const tStatistic = (mean - expectedMean) / (stdDev / Math.sqrt(n));
@@ -9494,7 +9774,7 @@ function createHypothesisTool(adapter) {
9494
9774
  throw new DbMcpError(
9495
9775
  `Cannot compute t-statistic: stdDev=${stdDev.toFixed(4)}, n=${n}. This may indicate zero variance, non-numeric data, or that column "${input.column}" does not exist.`,
9496
9776
  "STATS_COMPUTATION_FAILED",
9497
- "validation" /* VALIDATION */
9777
+ ErrorCategory.VALIDATION
9498
9778
  );
9499
9779
  }
9500
9780
  const pValue = tDistPValue(tStatistic, df);
@@ -9517,7 +9797,7 @@ function createHypothesisTool(adapter) {
9517
9797
  throw new DbMcpError(
9518
9798
  "column2 is required for two-sample t-test",
9519
9799
  "STATS_MISSING_COLUMN",
9520
- "validation" /* VALIDATION */
9800
+ ErrorCategory.VALIDATION
9521
9801
  );
9522
9802
  }
9523
9803
  await validateColumnExists(adapter, input.table, input.column2);
@@ -9544,7 +9824,7 @@ function createHypothesisTool(adapter) {
9544
9824
  throw new DbMcpError(
9545
9825
  "Insufficient sample size for t-test",
9546
9826
  "STATS_INSUFFICIENT_SAMPLE",
9547
- "validation" /* VALIDATION */
9827
+ ErrorCategory.VALIDATION
9548
9828
  );
9549
9829
  }
9550
9830
  const tStatistic = (mean1 - mean2) / Math.sqrt(var1 / n1 + var2 / n2);
@@ -9552,7 +9832,7 @@ function createHypothesisTool(adapter) {
9552
9832
  throw new DbMcpError(
9553
9833
  `Cannot compute t-statistic: var1=${var1.toFixed(4)}, var2=${var2.toFixed(4)}. This may indicate zero variance or non-numeric data.`,
9554
9834
  "STATS_COMPUTATION_FAILED",
9555
- "validation" /* VALIDATION */
9835
+ ErrorCategory.VALIDATION
9556
9836
  );
9557
9837
  }
9558
9838
  const dfNum = Math.pow(var1 / n1 + var2 / n2, 2);
@@ -9576,7 +9856,7 @@ function createHypothesisTool(adapter) {
9576
9856
  throw new DbMcpError(
9577
9857
  "groupColumn is required for chi-square test",
9578
9858
  "STATS_MISSING_COLUMN",
9579
- "validation" /* VALIDATION */
9859
+ ErrorCategory.VALIDATION
9580
9860
  );
9581
9861
  }
9582
9862
  await validateColumnExists(adapter, input.table, input.groupColumn);
@@ -9613,7 +9893,7 @@ function createHypothesisTool(adapter) {
9613
9893
  throw new DbMcpError(
9614
9894
  `Insufficient categories for chi-square test: "${input.column}" has ${rowTotals.size} category(s), "${input.groupColumn}" has ${colTotals.size} category(s). Both columns must have at least 2 distinct values.`,
9615
9895
  "STATS_INSUFFICIENT_CATEGORIES",
9616
- "validation" /* VALIDATION */
9896
+ ErrorCategory.VALIDATION
9617
9897
  );
9618
9898
  }
9619
9899
  const pValue = Math.exp(-chiSquare / 2);
@@ -10280,7 +10560,7 @@ var DropViewSchema = z.object({
10280
10560
  });
10281
10561
  var DbStatSchema = z.object({
10282
10562
  table: z.string().optional().describe("Optional table name to filter"),
10283
- summarize: z.boolean().optional().default(false).describe(
10563
+ summarize: z.boolean().optional().default(true).describe(
10284
10564
  "If true, return aggregated per-table stats instead of raw page-level data"
10285
10565
  ),
10286
10566
  limit: z.preprocess(
@@ -10535,34 +10815,6 @@ function createDropViewTool(adapter) {
10535
10815
  };
10536
10816
  }
10537
10817
 
10538
- // src/utils/progress-utils.ts
10539
- function buildProgressContext(ctx) {
10540
- if (ctx?.server === void 0 || ctx.progressToken === void 0) {
10541
- return void 0;
10542
- }
10543
- return {
10544
- server: ctx.server,
10545
- progressToken: ctx.progressToken
10546
- };
10547
- }
10548
- async function sendProgress(ctx, progress, total, message) {
10549
- if (ctx === void 0) return;
10550
- if (ctx.progressToken === void 0) return;
10551
- try {
10552
- const notification = {
10553
- method: "notifications/progress",
10554
- params: {
10555
- progressToken: ctx.progressToken,
10556
- progress,
10557
- ...total !== void 0 && { total },
10558
- ...message !== void 0 && message !== "" && { message }
10559
- }
10560
- };
10561
- await ctx.server.notification(notification);
10562
- } catch {
10563
- }
10564
- }
10565
-
10566
10818
  // src/adapters/sqlite/tools/virtual/analysis.ts
10567
10819
  function createDbStatTool(adapter) {
10568
10820
  return {
@@ -10829,7 +11081,9 @@ function createVirtualTableInfoTool(adapter) {
10829
11081
  module: "unknown",
10830
11082
  sql: "",
10831
11083
  error: `Virtual table '${input.tableName}' not found`,
10832
- code: "TABLE_NOT_FOUND"
11084
+ code: "TABLE_NOT_FOUND",
11085
+ category: "resource",
11086
+ recoverable: false
10833
11087
  };
10834
11088
  }
10835
11089
  const sqlStr = typeof sqlResult.rows[0]?.["sql"] === "string" ? sqlResult.rows[0]["sql"] : "";
@@ -10947,10 +11201,10 @@ function createCsvTableTool(adapter) {
10947
11201
  try {
10948
11202
  const input = CreateCsvTableSchema.parse(params);
10949
11203
  const safeTableName = sanitizeIdentifier(input.tableName);
10950
- if (!path.isAbsolute(input.filePath)) {
11204
+ if (!nodePath.isAbsolute(input.filePath)) {
10951
11205
  return {
10952
11206
  success: false,
10953
- error: `Relative path not supported. Please use an absolute path. Example: ${path.resolve(input.filePath)}`,
11207
+ error: `Relative path not supported. Please use an absolute path. Example: ${nodePath.resolve(input.filePath)}`,
10954
11208
  code: "VALIDATION_ERROR",
10955
11209
  category: "validation",
10956
11210
  message: "",
@@ -10958,20 +11212,37 @@ function createCsvTableTool(adapter) {
10958
11212
  columns: []
10959
11213
  };
10960
11214
  }
10961
- const pathCheck = validateSameDirPath(
10962
- input.filePath,
10963
- adapter.getConfiguredPath()
10964
- );
10965
- if (!pathCheck.valid) {
10966
- return {
10967
- success: false,
10968
- error: pathCheck.error,
10969
- code: "SECURITY_ERROR",
10970
- category: "security",
10971
- message: "",
10972
- sql: "",
10973
- columns: []
10974
- };
11215
+ const allowedIoRoots = adapter.getAllowedIoRoots();
11216
+ if (allowedIoRoots !== void 0) {
11217
+ try {
11218
+ assertSafeIoPath(input.filePath, allowedIoRoots, false);
11219
+ } catch (error) {
11220
+ return {
11221
+ success: false,
11222
+ error: error instanceof Error ? error.message : "Security error",
11223
+ code: "SECURITY_ERROR",
11224
+ category: "security",
11225
+ message: "",
11226
+ sql: "",
11227
+ columns: []
11228
+ };
11229
+ }
11230
+ } else {
11231
+ const pathCheck = validateSameDirPath(
11232
+ input.filePath,
11233
+ adapter.getConfiguredPath()
11234
+ );
11235
+ if (!pathCheck.valid) {
11236
+ return {
11237
+ success: false,
11238
+ error: pathCheck.error,
11239
+ code: "SECURITY_ERROR",
11240
+ category: "security",
11241
+ message: "",
11242
+ sql: "",
11243
+ columns: []
11244
+ };
11245
+ }
10975
11246
  }
10976
11247
  const { available: csvAvailable } = await isCsvModuleAvailable(adapter);
10977
11248
  if (!csvAvailable) {
@@ -11054,10 +11325,10 @@ function createAnalyzeCsvSchemaTool(adapter) {
11054
11325
  columns: []
11055
11326
  };
11056
11327
  }
11057
- if (!path.isAbsolute(input.filePath)) {
11328
+ if (!nodePath.isAbsolute(input.filePath)) {
11058
11329
  return {
11059
11330
  success: false,
11060
- error: `Relative path not supported. Please use an absolute path. Example: ${path.resolve(input.filePath)}`,
11331
+ error: `Relative path not supported. Please use an absolute path. Example: ${nodePath.resolve(input.filePath)}`,
11061
11332
  code: "VALIDATION_ERROR",
11062
11333
  category: "validation",
11063
11334
  hasHeader: false,
@@ -11065,20 +11336,37 @@ function createAnalyzeCsvSchemaTool(adapter) {
11065
11336
  columns: []
11066
11337
  };
11067
11338
  }
11068
- const validation = validateSameDirPath(
11069
- input.filePath,
11070
- adapter.getConfiguredPath()
11071
- );
11072
- if (!validation.valid) {
11073
- return {
11074
- success: false,
11075
- error: validation.error || "Path validation failed",
11076
- code: "SECURITY_ERROR",
11077
- category: "validation",
11078
- hasHeader: false,
11079
- rowCount: 0,
11080
- columns: []
11081
- };
11339
+ const allowedIoRoots = adapter.getAllowedIoRoots();
11340
+ if (allowedIoRoots !== void 0) {
11341
+ try {
11342
+ assertSafeIoPath(input.filePath, allowedIoRoots, false);
11343
+ } catch (error) {
11344
+ return {
11345
+ success: false,
11346
+ error: error instanceof Error ? error.message : "Security error",
11347
+ code: "SECURITY_ERROR",
11348
+ category: "security",
11349
+ hasHeader: false,
11350
+ rowCount: 0,
11351
+ columns: []
11352
+ };
11353
+ }
11354
+ } else {
11355
+ const validation = validateSameDirPath(
11356
+ input.filePath,
11357
+ adapter.getConfiguredPath()
11358
+ );
11359
+ if (!validation.valid) {
11360
+ return {
11361
+ success: false,
11362
+ error: validation.error || "Path validation failed",
11363
+ code: "SECURITY_ERROR",
11364
+ category: "security",
11365
+ hasHeader: false,
11366
+ rowCount: 0,
11367
+ columns: []
11368
+ };
11369
+ }
11082
11370
  }
11083
11371
  const tempName = `_csv_analyze_${Date.now()}`;
11084
11372
  try {
@@ -11509,10 +11797,7 @@ function createVectorCreateTableTool(adapter) {
11509
11797
  try {
11510
11798
  const input = VectorCreateTableSchema.parse(params);
11511
11799
  if (input.dimensions < 1) {
11512
- return {
11513
- success: false,
11514
- error: "Dimensions must be at least 1"
11515
- };
11800
+ throw new ValidationError("Dimensions must be at least 1");
11516
11801
  }
11517
11802
  const tableName = sanitizeIdentifier(input.tableName);
11518
11803
  const columns = [
@@ -11598,15 +11883,15 @@ function createVectorStoreTool(adapter) {
11598
11883
  try {
11599
11884
  const input = VectorStoreSchema.parse(params);
11600
11885
  if (input.vector.length === 0) {
11601
- return {
11602
- success: false,
11603
- error: "vector is required and must be a non-empty array of numbers"
11604
- };
11886
+ throw new ValidationError(
11887
+ "vector is required and must be a non-empty array of numbers"
11888
+ );
11605
11889
  }
11606
11890
  const table = sanitizeIdentifier(input.table);
11607
11891
  const idColumn = sanitizeIdentifier(input.idColumn);
11608
11892
  const vectorColumn = sanitizeIdentifier(input.vectorColumn);
11609
11893
  let hasDimsColumn = false;
11894
+ let expectedDims;
11610
11895
  try {
11611
11896
  const dims = await validateDimensions(
11612
11897
  adapter,
@@ -11615,15 +11900,15 @@ function createVectorStoreTool(adapter) {
11615
11900
  input.vectorColumn
11616
11901
  );
11617
11902
  hasDimsColumn = dims.hasDimsColumn;
11618
- if (dims.expectedDims !== void 0 && input.vector.length !== dims.expectedDims) {
11619
- return {
11620
- success: false,
11621
- error: `Dimension mismatch: vector has ${input.vector.length} dimensions but table expects ${dims.expectedDims}`,
11622
- code: "DIMENSION_MISMATCH"
11623
- };
11624
- }
11903
+ expectedDims = dims.expectedDims;
11625
11904
  } catch {
11626
11905
  }
11906
+ if (expectedDims !== void 0 && input.vector.length !== expectedDims) {
11907
+ throw new ValidationError(
11908
+ `Dimension mismatch: vector has ${input.vector.length} dimensions but table expects ${expectedDims}`,
11909
+ "DIMENSION_MISMATCH"
11910
+ );
11911
+ }
11627
11912
  const vectorJson = JSON.stringify(input.vector);
11628
11913
  const updateSql = `UPDATE ${table} SET ${vectorColumn} = ? WHERE ${idColumn} = ?`;
11629
11914
  const updateResult = await adapter.executeWriteQuery(updateSql, [
@@ -11665,10 +11950,11 @@ function createVectorBatchStoreTool(adapter) {
11665
11950
  input.table
11666
11951
  ]);
11667
11952
  if (!checkResult.rows || checkResult.rows.length === 0) {
11668
- return {
11669
- success: false,
11670
- error: `Table '${input.table}' does not exist`
11671
- };
11953
+ throw new ResourceNotFoundError(
11954
+ `Table '${input.table}' does not exist`,
11955
+ "TABLE_NOT_FOUND",
11956
+ { resourceType: "table", resourceName: input.table }
11957
+ );
11672
11958
  }
11673
11959
  return {
11674
11960
  success: true,
@@ -11679,6 +11965,7 @@ function createVectorBatchStoreTool(adapter) {
11679
11965
  const idColumn = sanitizeIdentifier(input.idColumn);
11680
11966
  const vectorColumn = sanitizeIdentifier(input.vectorColumn);
11681
11967
  let hasDimsColumn = false;
11968
+ let expectedDims;
11682
11969
  try {
11683
11970
  const dims = await validateDimensions(
11684
11971
  adapter,
@@ -11687,19 +11974,19 @@ function createVectorBatchStoreTool(adapter) {
11687
11974
  input.vectorColumn
11688
11975
  );
11689
11976
  hasDimsColumn = dims.hasDimsColumn;
11690
- if (dims.expectedDims !== void 0) {
11691
- for (let i = 0; i < input.items.length; i++) {
11692
- const item = input.items[i];
11693
- if (item && item.vector.length !== dims.expectedDims) {
11694
- return {
11695
- success: false,
11696
- error: `Dimension mismatch at item[${i}]: vector has ${item.vector.length} dimensions but table expects ${dims.expectedDims}`,
11697
- code: "DIMENSION_MISMATCH"
11698
- };
11699
- }
11977
+ expectedDims = dims.expectedDims;
11978
+ } catch {
11979
+ }
11980
+ if (expectedDims !== void 0) {
11981
+ for (let i = 0; i < input.items.length; i++) {
11982
+ const item = input.items[i];
11983
+ if (item && item.vector.length !== expectedDims) {
11984
+ throw new ValidationError(
11985
+ `Dimension mismatch at item[${i}]: vector has ${item.vector.length} dimensions but table expects ${expectedDims}`,
11986
+ "DIMENSION_MISMATCH"
11987
+ );
11700
11988
  }
11701
11989
  }
11702
- } catch {
11703
11990
  }
11704
11991
  let stored = 0;
11705
11992
  for (const item of input.items) {
@@ -11754,84 +12041,6 @@ function createVectorDeleteTool(adapter) {
11754
12041
  };
11755
12042
  }
11756
12043
 
11757
- // src/adapters/sqlite/tools/vector/helpers.ts
11758
- function cosineSimilarity(a, b) {
11759
- if (a.length !== b.length) {
11760
- throw new DbMcpError(
11761
- "Vector dimensions must match",
11762
- "VECTOR_MISMATCH",
11763
- "validation" /* VALIDATION */
11764
- );
11765
- }
11766
- let dotProd = 0;
11767
- let normA = 0;
11768
- let normB = 0;
11769
- for (let i = 0; i < a.length; i++) {
11770
- const aVal = a[i] ?? 0;
11771
- const bVal = b[i] ?? 0;
11772
- dotProd += aVal * bVal;
11773
- normA += aVal * aVal;
11774
- normB += bVal * bVal;
11775
- }
11776
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
11777
- return magnitude === 0 ? 0 : dotProd / magnitude;
11778
- }
11779
- function euclideanDistance(a, b) {
11780
- if (a.length !== b.length) {
11781
- throw new DbMcpError(
11782
- "Vector dimensions must match",
11783
- "VECTOR_MISMATCH",
11784
- "validation" /* VALIDATION */
11785
- );
11786
- }
11787
- let sum = 0;
11788
- for (let i = 0; i < a.length; i++) {
11789
- const aVal = a[i] ?? 0;
11790
- const bVal = b[i] ?? 0;
11791
- const diff = aVal - bVal;
11792
- sum += diff * diff;
11793
- }
11794
- return Math.sqrt(sum);
11795
- }
11796
- function dotProduct(a, b) {
11797
- if (a.length !== b.length) {
11798
- throw new DbMcpError(
11799
- "Vector dimensions must match",
11800
- "VECTOR_MISMATCH",
11801
- "validation" /* VALIDATION */
11802
- );
11803
- }
11804
- let sum = 0;
11805
- for (let i = 0; i < a.length; i++) {
11806
- sum += (a[i] ?? 0) * (b[i] ?? 0);
11807
- }
11808
- return sum;
11809
- }
11810
- function normalizeVector(v) {
11811
- const norm = Math.sqrt(v.reduce((sum, x) => sum + x * x, 0));
11812
- if (norm === 0) return v;
11813
- return v.map((x) => x / norm);
11814
- }
11815
- function parseVector(value) {
11816
- if (Array.isArray(value)) {
11817
- return value.map(Number);
11818
- }
11819
- if (typeof value === "string") {
11820
- try {
11821
- const parsed = JSON.parse(value);
11822
- if (Array.isArray(parsed)) {
11823
- return parsed.map(Number);
11824
- }
11825
- } catch {
11826
- }
11827
- }
11828
- throw new DbMcpError(
11829
- "Invalid vector format",
11830
- "VECTOR_INVALID",
11831
- "validation" /* VALIDATION */
11832
- );
11833
- }
11834
-
11835
12044
  // src/adapters/sqlite/tools/vector/search.ts
11836
12045
  function createVectorSearchTool(adapter) {
11837
12046
  return {
@@ -11847,10 +12056,9 @@ function createVectorSearchTool(adapter) {
11847
12056
  try {
11848
12057
  const input = VectorSearchSchema.parse(params);
11849
12058
  if (input.queryVector.length === 0) {
11850
- return {
11851
- success: false,
11852
- error: "queryVector is required and must be a non-empty array of numbers"
11853
- };
12059
+ throw new ValidationError(
12060
+ "queryVector is required and must be a non-empty array of numbers"
12061
+ );
11854
12062
  }
11855
12063
  const table = sanitizeIdentifier(input.table);
11856
12064
  const vectorColumn = sanitizeIdentifier(input.vectorColumn);
@@ -11880,7 +12088,7 @@ function createVectorSearchTool(adapter) {
11880
12088
  const rows = result.rows ?? [];
11881
12089
  for (let i = 0; i < rows.length; i++) {
11882
12090
  if (i > 0 && i % 500 === 0) {
11883
- await new Promise((resolve4) => setTimeout(resolve4, 0));
12091
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
11884
12092
  }
11885
12093
  const row = rows[i];
11886
12094
  if (!row) continue;
@@ -11898,10 +12106,9 @@ function createVectorSearchTool(adapter) {
11898
12106
  score = cosineSimilarity(queryVector, storedVector);
11899
12107
  break;
11900
12108
  default:
11901
- return {
11902
- success: false,
11903
- error: `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`
11904
- };
12109
+ throw new ValidationError(
12110
+ `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`
12111
+ );
11905
12112
  }
11906
12113
  scored.push({
11907
12114
  ...row,
@@ -11969,26 +12176,17 @@ function createVectorGetTool(adapter) {
11969
12176
  const sql = `SELECT * FROM ${table} WHERE ${idColumn} = ?`;
11970
12177
  const result = await adapter.executeReadQuery(sql, [input.id]);
11971
12178
  if (!result.rows || result.rows.length === 0) {
11972
- return {
11973
- success: false,
11974
- error: "Vector not found",
11975
- code: "VECTOR_NOT_FOUND"
11976
- };
12179
+ throw new ResourceNotFoundError("Vector not found", "VECTOR_NOT_FOUND", { resourceType: "vector", resourceName: String(input.id) });
11977
12180
  }
11978
12181
  const row = result.rows[0];
11979
12182
  if (!row) {
11980
- return {
11981
- success: false,
11982
- error: "Vector not found",
11983
- code: "VECTOR_NOT_FOUND"
11984
- };
12183
+ throw new ResourceNotFoundError("Vector not found", "VECTOR_NOT_FOUND", { resourceType: "vector", resourceName: String(input.id) });
11985
12184
  }
11986
12185
  const rawVector = row[input.vectorColumn];
11987
12186
  if (rawVector === void 0 || rawVector === null) {
11988
- return {
11989
- success: false,
11990
- error: `Column '${input.vectorColumn}' not found or contains NULL. Available columns: ${Object.keys(row).join(", ")}`
11991
- };
12187
+ throw new ValidationError(
12188
+ `Column '${input.vectorColumn}' not found or contains NULL. Available columns: ${Object.keys(row).join(", ")}`
12189
+ );
11992
12190
  }
11993
12191
  const vectorData = parseVector(rawVector);
11994
12192
  const metadata = Object.fromEntries(
@@ -12059,7 +12257,7 @@ function createVectorStatsTool(adapter) {
12059
12257
  const rows = result.rows ?? [];
12060
12258
  for (let i = 0; i < rows.length; i++) {
12061
12259
  if (i > 0 && i % 500 === 0) {
12062
- await new Promise((resolve4) => setTimeout(resolve4, 0));
12260
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
12063
12261
  }
12064
12262
  try {
12065
12263
  const row = rows[i];
@@ -12160,13 +12358,9 @@ function createVectorNormalizeTool() {
12160
12358
  try {
12161
12359
  const input = VectorNormalizeSchema.parse(params);
12162
12360
  if (input.vector.length === 0) {
12163
- return Promise.resolve({
12164
- success: false,
12165
- error: "vector is required and must be a non-empty array of numbers",
12166
- code: "VALIDATION_ERROR",
12167
- category: "validation",
12168
- recoverable: false
12169
- });
12361
+ throw new ValidationError(
12362
+ "vector is required and must be a non-empty array of numbers"
12363
+ );
12170
12364
  }
12171
12365
  const normalized = normalizeVector(input.vector);
12172
12366
  return Promise.resolve({
@@ -12196,20 +12390,12 @@ function createVectorDistanceTool() {
12196
12390
  try {
12197
12391
  const input = VectorDistanceSchema.parse(params);
12198
12392
  if (input.vector1.length === 0 || input.vector2.length === 0) {
12199
- return Promise.resolve({
12200
- success: false,
12201
- error: "vector1 and vector2 are required and must be non-empty arrays of numbers",
12202
- code: "VALIDATION_ERROR",
12203
- category: "validation",
12204
- recoverable: false
12205
- });
12393
+ throw new ValidationError(
12394
+ "vector1 and vector2 are required and must be non-empty arrays of numbers"
12395
+ );
12206
12396
  }
12207
12397
  if (input.vector1.length !== input.vector2.length) {
12208
- return Promise.resolve({
12209
- success: false,
12210
- error: "Vector dimensions must match",
12211
- code: "DIMENSION_MISMATCH"
12212
- });
12398
+ throw new ValidationError("Vector dimensions must match", "DIMENSION_MISMATCH");
12213
12399
  }
12214
12400
  let result;
12215
12401
  switch (input.metric) {
@@ -12223,13 +12409,9 @@ function createVectorDistanceTool() {
12223
12409
  result = 1 - cosineSimilarity(input.vector1, input.vector2);
12224
12410
  break;
12225
12411
  default:
12226
- return Promise.resolve({
12227
- success: false,
12228
- error: `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`,
12229
- code: "VALIDATION_ERROR",
12230
- category: "validation",
12231
- recoverable: false
12232
- });
12412
+ throw new ValidationError(
12413
+ `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`
12414
+ );
12233
12415
  }
12234
12416
  return Promise.resolve({
12235
12417
  success: true,
@@ -12372,14 +12554,14 @@ function requireCoordinate(value, name, min, max) {
12372
12554
  throw new DbMcpError(
12373
12555
  `Invalid ${name}: value is not a valid number.`,
12374
12556
  "GEO_INVALID_COORDINATES",
12375
- "validation" /* VALIDATION */
12557
+ ErrorCategory.VALIDATION
12376
12558
  );
12377
12559
  }
12378
12560
  if (value < min || value > max) {
12379
12561
  throw new DbMcpError(
12380
12562
  `Invalid ${name}: ${String(value)}. Must be between ${String(min)} and ${String(max)}.`,
12381
12563
  "GEO_INVALID_COORDINATES",
12382
- "validation" /* VALIDATION */
12564
+ ErrorCategory.VALIDATION
12383
12565
  );
12384
12566
  }
12385
12567
  return value;
@@ -12389,7 +12571,7 @@ function requireNumber(value, name) {
12389
12571
  throw new DbMcpError(
12390
12572
  `Invalid ${name}: value is not a valid number.`,
12391
12573
  "GEO_INVALID_COORDINATES",
12392
- "validation" /* VALIDATION */
12574
+ ErrorCategory.VALIDATION
12393
12575
  );
12394
12576
  }
12395
12577
  return value;
@@ -12665,19 +12847,35 @@ function createBackupTool(adapter) {
12665
12847
  path: ""
12666
12848
  };
12667
12849
  }
12668
- const pathCheck = validateSameDirPath(
12669
- input.targetPath,
12670
- adapter.getConfiguredPath()
12671
- );
12672
- if (input.targetPath.includes("..") || !pathCheck.valid) {
12673
- return {
12674
- success: false,
12675
- error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12676
- code: "SECURITY_ERROR",
12677
- path: input.targetPath
12678
- };
12850
+ let resolvedPath;
12851
+ const allowedIoRoots = adapter.getAllowedIoRoots();
12852
+ if (allowedIoRoots !== void 0) {
12853
+ try {
12854
+ assertSafeIoPath(input.targetPath, allowedIoRoots);
12855
+ resolvedPath = nodePath__default.resolve(input.targetPath);
12856
+ } catch (error) {
12857
+ return {
12858
+ success: false,
12859
+ error: error instanceof Error ? error.message : "Security error",
12860
+ code: "SECURITY_ERROR",
12861
+ path: input.targetPath
12862
+ };
12863
+ }
12864
+ } else {
12865
+ const pathCheck = validateSameDirPath(
12866
+ input.targetPath,
12867
+ adapter.getConfiguredPath()
12868
+ );
12869
+ if (input.targetPath.includes("..") || !pathCheck.valid) {
12870
+ return {
12871
+ success: false,
12872
+ error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12873
+ code: "SECURITY_ERROR",
12874
+ path: input.targetPath
12875
+ };
12876
+ }
12877
+ resolvedPath = pathCheck.resolvedPath;
12679
12878
  }
12680
- const resolvedPath = pathCheck.resolvedPath;
12681
12879
  const escapedPath = resolvedPath.replace(/'/g, "''");
12682
12880
  const sql = `VACUUM INTO '${escapedPath}'`;
12683
12881
  const start = Date.now();
@@ -12687,7 +12885,7 @@ function createBackupTool(adapter) {
12687
12885
  progress,
12688
12886
  1,
12689
12887
  2,
12690
- `Creating backup at ${path__default.basename(resolvedPath)}...`
12888
+ `Creating backup at ${nodePath__default.basename(resolvedPath)}...`
12691
12889
  );
12692
12890
  await adapter.executeQuery(sql);
12693
12891
  await sendProgress(progress, 2, 2, "Backup complete");
@@ -12737,18 +12935,34 @@ function createVacuumIntoTool(adapter) {
12737
12935
  ...formatHandlerError(new ValidationError("outputPath is required"))
12738
12936
  };
12739
12937
  }
12740
- const pathCheck = validateSameDirPath(
12741
- input.outputPath,
12742
- adapter.getConfiguredPath()
12743
- );
12744
- if (input.outputPath.includes("..") || !pathCheck.valid) {
12745
- return {
12746
- success: false,
12747
- error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12748
- code: "SECURITY_ERROR"
12749
- };
12938
+ let resolvedPath;
12939
+ const allowedIoRoots = adapter.getAllowedIoRoots();
12940
+ if (allowedIoRoots !== void 0) {
12941
+ try {
12942
+ assertSafeIoPath(input.outputPath, allowedIoRoots);
12943
+ resolvedPath = nodePath__default.resolve(input.outputPath);
12944
+ } catch (error) {
12945
+ return {
12946
+ success: false,
12947
+ error: error instanceof Error ? error.message : "Security error",
12948
+ code: "SECURITY_ERROR",
12949
+ outputPath: input.outputPath
12950
+ };
12951
+ }
12952
+ } else {
12953
+ const pathCheck = validateSameDirPath(
12954
+ input.outputPath,
12955
+ adapter.getConfiguredPath()
12956
+ );
12957
+ if (input.outputPath.includes("..") || !pathCheck.valid) {
12958
+ return {
12959
+ success: false,
12960
+ error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12961
+ code: "SECURITY_ERROR"
12962
+ };
12963
+ }
12964
+ resolvedPath = pathCheck.resolvedPath;
12750
12965
  }
12751
- const resolvedPath = pathCheck.resolvedPath;
12752
12966
  const escapedPath = resolvedPath.replace(/'/g, "''");
12753
12967
  const sql = `VACUUM INTO '${escapedPath}'`;
12754
12968
  const start = Date.now();
@@ -12975,26 +13189,53 @@ function createRestoreTool(adapter) {
12975
13189
  sourcePath: ""
12976
13190
  };
12977
13191
  }
12978
- const resolvedPath = path__default.resolve(input.sourcePath);
12979
- const pathCheck = validateSameDirPath(
12980
- input.sourcePath,
12981
- adapter.getConfiguredPath()
12982
- );
12983
- if (!pathCheck.valid) {
12984
- return {
12985
- ...formatHandlerError(new ValidationError(pathCheck.error)),
12986
- sourcePath: input.sourcePath
12987
- };
12988
- }
12989
- if (!fs.existsSync(resolvedPath)) {
12990
- return {
12991
- ...formatHandlerError(
12992
- new ValidationError(`Source file not found: ${input.sourcePath}`)
12993
- ),
12994
- sourcePath: input.sourcePath
12995
- };
13192
+ let escapedPath;
13193
+ const allowedIoRoots = adapter.getAllowedIoRoots();
13194
+ if (allowedIoRoots !== void 0) {
13195
+ try {
13196
+ assertSafeIoPath(input.sourcePath, allowedIoRoots);
13197
+ const resolvedPath = nodePath__default.resolve(input.sourcePath);
13198
+ if (!fs.existsSync(resolvedPath)) {
13199
+ return {
13200
+ ...formatHandlerError(
13201
+ new ValidationError(
13202
+ `Source file not found: ${input.sourcePath}`
13203
+ )
13204
+ ),
13205
+ sourcePath: input.sourcePath
13206
+ };
13207
+ }
13208
+ escapedPath = resolvedPath.replace(/'/g, "''");
13209
+ } catch (error) {
13210
+ return {
13211
+ success: false,
13212
+ error: error instanceof Error ? error.message : "Security error",
13213
+ code: "SECURITY_ERROR",
13214
+ sourcePath: input.sourcePath
13215
+ };
13216
+ }
13217
+ } else {
13218
+ const resolvedPath = nodePath__default.resolve(input.sourcePath);
13219
+ const pathCheck = validateSameDirPath(
13220
+ input.sourcePath,
13221
+ adapter.getConfiguredPath()
13222
+ );
13223
+ if (!pathCheck.valid) {
13224
+ return {
13225
+ ...formatHandlerError(new ValidationError(pathCheck.error)),
13226
+ sourcePath: input.sourcePath
13227
+ };
13228
+ }
13229
+ if (!fs.existsSync(resolvedPath)) {
13230
+ return {
13231
+ ...formatHandlerError(
13232
+ new ValidationError(`Source file not found: ${input.sourcePath}`)
13233
+ ),
13234
+ sourcePath: input.sourcePath
13235
+ };
13236
+ }
13237
+ escapedPath = pathCheck.resolvedPath.replace(/'/g, "''");
12996
13238
  }
12997
- const escapedPath = pathCheck.resolvedPath.replace(/'/g, "''");
12998
13239
  await adapter.executeReadQuery("PRAGMA integrity_check(1)");
12999
13240
  await sendProgress(progress, 2, 5, "Attaching backup database...");
13000
13241
  try {
@@ -13203,25 +13444,41 @@ function createDumpTool(adapter) {
13203
13444
  };
13204
13445
  }
13205
13446
  if (!input.outputPath?.trim()) {
13206
- return {
13207
- ...formatHandlerError(new ValidationError("outputPath is required"))
13208
- };
13209
- }
13210
- const pathCheck = validateSameDirPath(
13211
- input.outputPath,
13212
- adapter.getConfiguredPath()
13213
- );
13214
- if (input.outputPath.includes("..") || !pathCheck.valid) {
13215
- return {
13216
- success: false,
13217
- error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
13218
- code: "SECURITY_ERROR",
13219
- path: input.outputPath
13447
+ return {
13448
+ ...formatHandlerError(new ValidationError("outputPath is required"))
13220
13449
  };
13221
13450
  }
13451
+ let resolvedPath;
13452
+ const allowedIoRoots = adapter.getAllowedIoRoots();
13453
+ if (allowedIoRoots !== void 0) {
13454
+ try {
13455
+ assertSafeIoPath(input.outputPath, allowedIoRoots);
13456
+ resolvedPath = nodePath__default.resolve(input.outputPath);
13457
+ } catch (error) {
13458
+ return {
13459
+ success: false,
13460
+ error: error instanceof Error ? error.message : "Security error",
13461
+ code: "SECURITY_ERROR",
13462
+ path: input.outputPath
13463
+ };
13464
+ }
13465
+ } else {
13466
+ const pathCheck = validateSameDirPath(
13467
+ input.outputPath,
13468
+ adapter.getConfiguredPath()
13469
+ );
13470
+ if (input.outputPath.includes("..") || !pathCheck.valid) {
13471
+ return {
13472
+ success: false,
13473
+ error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
13474
+ code: "SECURITY_ERROR",
13475
+ path: input.outputPath
13476
+ };
13477
+ }
13478
+ resolvedPath = pathCheck.resolvedPath;
13479
+ }
13222
13480
  const progress = buildProgressContext(context);
13223
13481
  const start = Date.now();
13224
- const resolvedPath = pathCheck.resolvedPath;
13225
13482
  await sendProgress(progress, 1, 4, "Preparing dump file...");
13226
13483
  const stream = fs.createWriteStream(resolvedPath, { encoding: "utf8" });
13227
13484
  try {
@@ -13273,9 +13530,9 @@ function createDumpTool(adapter) {
13273
13530
  }
13274
13531
  }
13275
13532
  stream.write("COMMIT;\n");
13276
- await new Promise((resolve4, reject) => {
13533
+ await new Promise((resolve3, reject) => {
13277
13534
  stream.end(
13278
- (err) => err ? reject(err) : resolve4()
13535
+ (err) => err ? reject(err) : resolve3()
13279
13536
  );
13280
13537
  });
13281
13538
  } catch (err) {
@@ -13327,29 +13584,57 @@ function createVerifyBackupTool(adapter) {
13327
13584
  backupPath: ""
13328
13585
  };
13329
13586
  }
13330
- const resolvedPath = path__default.resolve(input.backupPath);
13331
- const pathCheck = validateSameDirPath(
13332
- input.backupPath,
13333
- adapter.getConfiguredPath()
13334
- );
13335
- if (!pathCheck.valid) {
13336
- return {
13337
- ...formatHandlerError(new ValidationError(pathCheck.error)),
13338
- backupPath: input.backupPath
13339
- };
13340
- }
13341
- if (!fs.existsSync(resolvedPath)) {
13342
- return {
13343
- ...formatHandlerError(
13344
- new ValidationError(
13345
- `Backup file not found: ${input.backupPath}`,
13346
- "FILE_NOT_FOUND"
13347
- )
13348
- ),
13349
- backupPath: input.backupPath
13350
- };
13587
+ let escapedPath;
13588
+ const allowedIoRoots = adapter.getAllowedIoRoots();
13589
+ if (allowedIoRoots !== void 0) {
13590
+ try {
13591
+ assertSafeIoPath(input.backupPath, allowedIoRoots);
13592
+ const resolvedPath = nodePath__default.resolve(input.backupPath);
13593
+ if (!fs.existsSync(resolvedPath)) {
13594
+ return {
13595
+ ...formatHandlerError(
13596
+ new ValidationError(
13597
+ `Backup file not found: ${input.backupPath}`,
13598
+ "FILE_NOT_FOUND"
13599
+ )
13600
+ ),
13601
+ backupPath: input.backupPath
13602
+ };
13603
+ }
13604
+ escapedPath = resolvedPath.replace(/'/g, "''");
13605
+ } catch (error) {
13606
+ return {
13607
+ success: false,
13608
+ error: error instanceof Error ? error.message : "Security error",
13609
+ code: "SECURITY_ERROR",
13610
+ backupPath: input.backupPath
13611
+ };
13612
+ }
13613
+ } else {
13614
+ const resolvedPath = nodePath__default.resolve(input.backupPath);
13615
+ const pathCheck = validateSameDirPath(
13616
+ input.backupPath,
13617
+ adapter.getConfiguredPath()
13618
+ );
13619
+ if (!pathCheck.valid) {
13620
+ return {
13621
+ ...formatHandlerError(new ValidationError(pathCheck.error)),
13622
+ backupPath: input.backupPath
13623
+ };
13624
+ }
13625
+ if (!fs.existsSync(resolvedPath)) {
13626
+ return {
13627
+ ...formatHandlerError(
13628
+ new ValidationError(
13629
+ `Backup file not found: ${input.backupPath}`,
13630
+ "FILE_NOT_FOUND"
13631
+ )
13632
+ ),
13633
+ backupPath: input.backupPath
13634
+ };
13635
+ }
13636
+ escapedPath = resolvedPath.replace(/'/g, "''");
13351
13637
  }
13352
- const escapedPath = resolvedPath.replace(/'/g, "''");
13353
13638
  try {
13354
13639
  await adapter.rawQuery(
13355
13640
  `ATTACH DATABASE '${escapedPath}' AS backup_verify`
@@ -13521,10 +13806,10 @@ function createPragmaDatabaseListTool(adapter) {
13521
13806
  };
13522
13807
  });
13523
13808
  const configuredPath = adapter.getConfiguredPath();
13524
- const normalize2 = (p) => p.replace(/\\/g, "/");
13809
+ const normalize = (p) => p.replace(/\\/g, "/");
13525
13810
  const mainDb = databases.find((db) => db.name === "main");
13526
13811
  const internalPathDiffers = Boolean(
13527
- mainDb?.file && normalize2(mainDb.file) !== normalize2(configuredPath.split(/[\\/]/).pop() ?? "")
13812
+ mainDb?.file && normalize(mainDb.file) !== normalize(configuredPath.split(/[\\/]/).pop() ?? "")
13528
13813
  );
13529
13814
  return {
13530
13815
  success: true,
@@ -13702,46 +13987,6 @@ function createPragmaTableInfoTool(adapter) {
13702
13987
  }
13703
13988
  };
13704
13989
  }
13705
- function createAppendInsightTool() {
13706
- return {
13707
- name: "sqlite_append_insight",
13708
- description: "Add a business insight to the memo://insights resource. Use this to capture key findings during data analysis.",
13709
- group: "admin",
13710
- inputSchema: AppendInsightSchema,
13711
- outputSchema: AppendInsightOutputSchema,
13712
- requiredScopes: ["write"],
13713
- annotations: write("Append Insight"),
13714
- handler: (_params, _context) => {
13715
- try {
13716
- const input = AppendInsightSchema.parse(_params);
13717
- let sanitizedInsight = input.insight.replace(/[<>]/g, "");
13718
- if (sanitizedInsight.length > 500) {
13719
- sanitizedInsight = sanitizedInsight.substring(0, 500) + "...";
13720
- }
13721
- if (!sanitizedInsight || sanitizedInsight.trim().length === 0) {
13722
- return Promise.resolve({
13723
- success: false,
13724
- error: "Insight must be a non-empty string",
13725
- message: "",
13726
- insightCount: insightsManager.count()
13727
- });
13728
- }
13729
- insightsManager.append(sanitizedInsight);
13730
- return Promise.resolve({
13731
- success: true,
13732
- message: "Insight added to memo",
13733
- insightCount: insightsManager.count()
13734
- });
13735
- } catch (error) {
13736
- return Promise.resolve({
13737
- ...formatHandlerError(error),
13738
- message: "",
13739
- insightCount: insightsManager.count()
13740
- });
13741
- }
13742
- }
13743
- };
13744
- }
13745
13990
  function createAttachDatabaseTool(adapter) {
13746
13991
  return {
13747
13992
  name: "sqlite_attach_database",
@@ -13762,18 +14007,37 @@ function createAttachDatabaseTool(adapter) {
13762
14007
  code: "VALIDATION_ERROR"
13763
14008
  };
13764
14009
  }
13765
- const pathCheck = validateSameDirPath(
13766
- input.filepath,
13767
- adapter.getConfiguredPath()
13768
- );
13769
- if (!pathCheck.valid) {
13770
- return {
13771
- success: false,
13772
- error: pathCheck.error,
13773
- code: "SECURITY_ERROR"
13774
- };
14010
+ let escapedPath;
14011
+ let finalResolvedPath;
14012
+ const allowedIoRoots = adapter.getAllowedIoRoots();
14013
+ if (allowedIoRoots !== void 0) {
14014
+ try {
14015
+ assertSafeIoPath(input.filepath, allowedIoRoots);
14016
+ const resolvedPath = input.filepath;
14017
+ escapedPath = resolvedPath.replace(/'/g, "''");
14018
+ finalResolvedPath = resolvedPath;
14019
+ } catch (error) {
14020
+ return {
14021
+ success: false,
14022
+ error: error instanceof Error ? error.message : "Security error",
14023
+ code: "SECURITY_ERROR"
14024
+ };
14025
+ }
14026
+ } else {
14027
+ const pathCheck = validateSameDirPath(
14028
+ input.filepath,
14029
+ adapter.getConfiguredPath()
14030
+ );
14031
+ if (!pathCheck.valid) {
14032
+ return {
14033
+ success: false,
14034
+ error: pathCheck.error,
14035
+ code: "SECURITY_ERROR"
14036
+ };
14037
+ }
14038
+ escapedPath = pathCheck.resolvedPath.replace(/'/g, "''");
14039
+ finalResolvedPath = pathCheck.resolvedPath;
13775
14040
  }
13776
- const escapedPath = pathCheck.resolvedPath.replace(/'/g, "''");
13777
14041
  await adapter.rawQuery(
13778
14042
  `ATTACH DATABASE '${escapedPath}' AS "${input.alias.replace(/"/g, '""')}"`
13779
14043
  );
@@ -13781,7 +14045,7 @@ function createAttachDatabaseTool(adapter) {
13781
14045
  success: true,
13782
14046
  message: `Database attached as '${input.alias}'`,
13783
14047
  alias: input.alias,
13784
- filepath: pathCheck.resolvedPath.split(/[\\/]/).pop() ?? ""
14048
+ filepath: finalResolvedPath.split(/[\\/]/).pop() ?? ""
13785
14049
  };
13786
14050
  } catch (error) {
13787
14051
  return formatHandlerError(error);
@@ -13990,7 +14254,6 @@ function getAdminTools(adapter) {
13990
14254
  createPragmaOptimizeTool(adapter),
13991
14255
  createPragmaSettingsTool(adapter),
13992
14256
  createPragmaTableInfoTool(adapter),
13993
- createAppendInsightTool(),
13994
14257
  createAttachDatabaseTool(adapter),
13995
14258
  createDetachDatabaseTool(adapter),
13996
14259
  createVacuumIntoTool(adapter),
@@ -14262,7 +14525,8 @@ var IndexAuditOutputSchema = z.object({
14262
14525
  type: z.enum([
14263
14526
  "redundant",
14264
14527
  "missing_fk_index",
14265
- "unindexed_large_table"
14528
+ "unindexed_large_table",
14529
+ "missing_composite_index"
14266
14530
  ]),
14267
14531
  severity: z.enum(["info", "warning", "error"]),
14268
14532
  table: z.string(),
@@ -14315,23 +14579,6 @@ var coerceNumber8 = (val) => {
14315
14579
  }
14316
14580
  return val;
14317
14581
  };
14318
- var VALID_SECTIONS = ["tables", "views", "indexes", "triggers"];
14319
- var coerceSections = (val) => Array.isArray(val) ? val.filter(
14320
- (v) => typeof v === "string" && VALID_SECTIONS.includes(v)
14321
- ) : val;
14322
- var VALID_CHECKS = [
14323
- "missing_pk",
14324
- "missing_not_null",
14325
- "unindexed_fk",
14326
- "missing_fk"
14327
- ];
14328
- var coerceChecks = (val) => Array.isArray(val) ? val.filter(
14329
- (v) => typeof v === "string" && VALID_CHECKS.includes(v)
14330
- ) : val;
14331
- var VALID_DIRECTIONS = ["create", "drop"];
14332
- var coerceDirection = (val) => typeof val === "string" && VALID_DIRECTIONS.includes(val) ? val : typeof val === "string" ? void 0 : val;
14333
- var VALID_OPERATIONS = ["DELETE", "DROP", "TRUNCATE"];
14334
- var coerceOperation = (val) => typeof val === "string" && VALID_OPERATIONS.includes(val) ? val : typeof val === "string" ? void 0 : val;
14335
14582
  var StorageAnalysisSchema = z.object({
14336
14583
  includeTableDetails: z.boolean().optional().describe("Include per-table size breakdown (default: true)"),
14337
14584
  excludeSystemTables: z.boolean().optional().describe(
@@ -14350,41 +14597,42 @@ var IndexAuditSchema = z.object({
14350
14597
  ),
14351
14598
  minSeverity: z.enum(["info", "warning", "error"]).optional().describe(
14352
14599
  "Minimum severity to include in findings (default: all). Reduces payload for large databases."
14600
+ ),
14601
+ recommendComposite: z.boolean().optional().default(false).describe(
14602
+ "Analyze queriesToAnalyze to recommend composite/partial indexes"
14603
+ ),
14604
+ queriesToAnalyze: z.array(z.string()).optional().describe(
14605
+ "Queries to analyze using EXPLAIN QUERY PLAN (used if recommendComposite is true)"
14353
14606
  )
14354
- }).default({});
14607
+ }).default({ recommendComposite: false });
14355
14608
  var SchemaSnapshotSchema = z.object({
14356
- sections: z.preprocess(
14357
- coerceSections,
14358
- z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional()
14359
- ).describe("Specific sections to include (default: all)"),
14360
- compact: z.boolean().optional().describe(
14361
- "Omit column details from tables section for reduced payload (default: false)"
14609
+ sections: z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional().describe("Specific sections to include (default: all)"),
14610
+ compact: z.boolean().optional().default(true).describe(
14611
+ "Omit column details from tables section for reduced payload (default: true)"
14362
14612
  ),
14363
14613
  excludeSystemTables: z.boolean().optional().describe(
14364
14614
  "Exclude SpatiaLite system tables, views, indexes, and triggers (default: true)"
14365
14615
  )
14366
- }).default({});
14616
+ }).default({ compact: true });
14367
14617
  var MigrationRisksSchema = z.object({
14368
14618
  statements: z.array(z.string()).optional().default([]).describe("Array of DDL statements to analyze for risks")
14369
14619
  }).default({ statements: [] });
14370
14620
  var ConstraintAnalysisSchema = z.object({
14371
14621
  table: z.string().optional().describe("Analyze constraints for a specific table only"),
14372
- checks: z.preprocess(
14373
- coerceChecks,
14374
- z.array(
14375
- z.enum([
14376
- "missing_pk",
14377
- "missing_not_null",
14378
- "unindexed_fk",
14379
- "missing_fk"
14380
- ])
14381
- ).optional()
14382
- ).describe("Specific checks to run (default: all)"),
14622
+ checks: z.array(
14623
+ z.enum([
14624
+ "missing_pk",
14625
+ "missing_not_null",
14626
+ "unindexed_fk",
14627
+ "missing_fk"
14628
+ ])
14629
+ ).optional().describe("Specific checks to run (default: all)"),
14383
14630
  excludeSystemTables: z.boolean().optional().describe(
14384
14631
  "Exclude SpatiaLite system tables from constraint analysis (default: true)"
14385
14632
  )
14386
14633
  }).default({});
14387
14634
  var DependencyGraphSchema = z.object({
14635
+ table: z.string().optional().describe("Optional table name to filter the graph"),
14388
14636
  includeRowCounts: z.boolean().optional().describe("Include row counts per table (default: true)"),
14389
14637
  nodesOnly: z.boolean().optional().describe(
14390
14638
  "Return only nodes without edges for a lightweight response (default: false)"
@@ -14394,7 +14642,7 @@ var DependencyGraphSchema = z.object({
14394
14642
  )
14395
14643
  }).default({});
14396
14644
  var TopologicalSortSchema = z.object({
14397
- direction: z.preprocess(coerceDirection, z.enum(["create", "drop"]).optional()).describe(
14645
+ direction: z.enum(["create", "drop"]).optional().describe(
14398
14646
  "Sort direction: 'create' = dependencies first, 'drop' = dependents first (default: create)"
14399
14647
  ),
14400
14648
  excludeSystemTables: z.boolean().optional().describe(
@@ -14404,9 +14652,9 @@ var TopologicalSortSchema = z.object({
14404
14652
  var CascadeSimulatorSchema = z.object({
14405
14653
  table: z.string().optional().default("").describe("Table name to simulate deletion from"),
14406
14654
  operation: z.preprocess(
14407
- coerceOperation,
14408
- z.enum(["DELETE", "DROP", "TRUNCATE"]).optional()
14409
- ).describe("Operation to simulate (default: DELETE)"),
14655
+ (val) => typeof val === "string" ? val.toUpperCase() : val,
14656
+ z.enum(["DELETE", "DROP", "TRUNCATE"])
14657
+ ).optional().describe("Operation to simulate (default: DELETE)"),
14410
14658
  compact: z.boolean().optional().describe(
14411
14659
  "Omit path arrays from affected entries to reduce payload (default: false)"
14412
14660
  )
@@ -14472,21 +14720,20 @@ var SchemaDiffOutputSchema = z.object({
14472
14720
  }).optional(),
14473
14721
  comparedAt: z.string().optional()
14474
14722
  }).extend(ErrorResponseFields.shape);
14475
- var VALID_DIFF_SECTIONS = ["tables", "views", "indexes", "triggers"];
14476
- var coerceDiffSections = (val) => Array.isArray(val) ? val.filter(
14477
- (v) => typeof v === "string" && VALID_DIFF_SECTIONS.includes(v)
14478
- ) : val;
14479
14723
  var SchemaDiffSchema = z.object({
14480
- baseline: z.union([z.literal("current"), SchemaSnapshotShape]).optional().describe(
14724
+ baseline: z.preprocess(
14725
+ (val) => Array.isArray(val) ? val.length === 0 ? {} : { tables: val } : val,
14726
+ z.union([z.literal("current"), SchemaSnapshotShape])
14727
+ ).optional().describe(
14481
14728
  "Baseline schema \u2014 'current' to snapshot live DB, or an inline snapshot object from a previous sqlite_schema_snapshot call"
14482
14729
  ),
14483
- target: z.union([z.literal("current"), SchemaSnapshotShape]).optional().describe(
14730
+ target: z.preprocess(
14731
+ (val) => Array.isArray(val) ? val.length === 0 ? {} : { tables: val } : val,
14732
+ z.union([z.literal("current"), SchemaSnapshotShape])
14733
+ ).optional().describe(
14484
14734
  "Target schema to compare against baseline \u2014 'current' to snapshot live DB, or an inline snapshot object"
14485
14735
  ),
14486
- sections: z.preprocess(
14487
- coerceDiffSections,
14488
- z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional()
14489
- ).describe("Sections to compare (default: all)"),
14736
+ sections: z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional().describe("Sections to compare (default: all)"),
14490
14737
  excludeSystemTables: z.boolean().optional().describe(
14491
14738
  "Exclude SpatiaLite system tables when capturing live schema (default: true)"
14492
14739
  )
@@ -14505,42 +14752,80 @@ function createDependencyGraphTool(adapter) {
14505
14752
  handler: async (params, _context) => {
14506
14753
  try {
14507
14754
  const input = DependencyGraphSchema.parse(params);
14755
+ if (input.table) {
14756
+ const tableCheck = await adapter.executeReadQuery(
14757
+ `SELECT 1 FROM sqlite_master WHERE type='table' AND name=?`,
14758
+ [input.table]
14759
+ );
14760
+ if ((tableCheck.rows?.length ?? 0) === 0) {
14761
+ return {
14762
+ success: false,
14763
+ error: `Table '${input.table}' does not exist`,
14764
+ code: "TABLE_NOT_FOUND",
14765
+ category: "resource",
14766
+ suggestion: "Table not found. Run sqlite_list_tables to see available tables.",
14767
+ recoverable: false
14768
+ };
14769
+ }
14770
+ }
14508
14771
  const includeRowCounts = input.includeRowCounts !== false;
14509
14772
  const { nodes, edges } = await buildForeignKeyGraph(adapter, {
14510
14773
  excludeSystemTables: input.excludeSystemTables,
14511
14774
  includeRowCounts
14512
14775
  });
14776
+ let finalNodes = nodes;
14777
+ let finalEdges = edges;
14778
+ if (input.table) {
14779
+ const connected = /* @__PURE__ */ new Set([input.table]);
14780
+ let added = true;
14781
+ while (added) {
14782
+ added = false;
14783
+ for (const e of edges) {
14784
+ if (connected.has(e.from) && !connected.has(e.to)) {
14785
+ connected.add(e.to);
14786
+ added = true;
14787
+ } else if (connected.has(e.to) && !connected.has(e.from)) {
14788
+ connected.add(e.from);
14789
+ added = true;
14790
+ }
14791
+ }
14792
+ }
14793
+ finalNodes = nodes.filter((n) => connected.has(n.table));
14794
+ finalEdges = edges.filter(
14795
+ (e) => connected.has(e.from) && connected.has(e.to)
14796
+ );
14797
+ }
14513
14798
  const adjacency = /* @__PURE__ */ new Map();
14514
- for (const node of nodes) {
14799
+ for (const node of finalNodes) {
14515
14800
  adjacency.set(node.table, []);
14516
14801
  }
14517
- for (const edge of edges) {
14802
+ for (const edge of finalEdges) {
14518
14803
  adjacency.get(edge.from)?.push(edge.to);
14519
14804
  }
14520
14805
  const cycles = detectCycles(
14521
14806
  adjacency,
14522
- nodes.map((n) => n.table)
14807
+ finalNodes.map((n) => n.table)
14523
14808
  );
14524
- const referencedTables = new Set(edges.map((e) => e.to));
14525
- const referencingTables = new Set(edges.map((e) => e.from));
14526
- const rootTables = nodes.filter(
14809
+ const referencedTables = new Set(finalEdges.map((e) => e.to));
14810
+ const referencingTables = new Set(finalEdges.map((e) => e.from));
14811
+ const rootTables = finalNodes.filter(
14527
14812
  (n) => referencedTables.has(n.table) && !referencingTables.has(n.table)
14528
14813
  ).map((n) => n.table);
14529
- const leafTables = nodes.filter(
14814
+ const leafTables = finalNodes.filter(
14530
14815
  (n) => referencingTables.has(n.table) && !referencedTables.has(n.table)
14531
14816
  ).map((n) => n.table);
14532
14817
  const nodesOnly = input.nodesOnly === true;
14533
14818
  return {
14534
14819
  success: true,
14535
- nodes: nodes.map((n) => ({
14820
+ nodes: finalNodes.map((n) => ({
14536
14821
  table: n.table,
14537
14822
  ...includeRowCounts ? { rowCount: n.rowCount } : {}
14538
14823
  })),
14539
- ...!nodesOnly ? { edges } : {},
14824
+ ...!nodesOnly ? { edges: finalEdges } : {},
14540
14825
  circularDependencies: cycles.length > 0 ? cycles : void 0,
14541
14826
  stats: {
14542
- totalTables: nodes.length,
14543
- totalRelationships: edges.length,
14827
+ totalTables: finalNodes.length,
14828
+ totalRelationships: finalEdges.length,
14544
14829
  rootTables,
14545
14830
  leafTables
14546
14831
  }
@@ -15845,6 +16130,35 @@ function createIndexAuditTool(adapter) {
15845
16130
  } catch {
15846
16131
  }
15847
16132
  }
16133
+ if (input.recommendComposite && input.queriesToAnalyze && input.queriesToAnalyze.length > 0) {
16134
+ for (const query of input.queriesToAnalyze) {
16135
+ try {
16136
+ if (/^\s*SELECT/i.test(query)) {
16137
+ const planResult = await adapter.executeReadQuery(
16138
+ `EXPLAIN QUERY PLAN ${query}`
16139
+ );
16140
+ const planRows = planResult.rows ?? [];
16141
+ for (const row of planRows) {
16142
+ const detailVal = row["detail"];
16143
+ const detail = typeof detailVal === "string" ? detailVal : "";
16144
+ if (detail.startsWith("SCAN TABLE") || detail.startsWith("SCAN ")) {
16145
+ const match = /SCAN (?:TABLE )?([\w_]+)/i.exec(detail);
16146
+ const tableName = match ? match[1] : "unknown";
16147
+ if (tableName && tableName !== "unknown") {
16148
+ findings.push({
16149
+ type: "missing_composite_index",
16150
+ severity: "warning",
16151
+ table: tableName,
16152
+ suggestion: `Query resulted in SCAN TABLE "${tableName}". Consider analyzing WHERE/JOIN clauses to add a composite index.`
16153
+ });
16154
+ }
16155
+ }
16156
+ }
16157
+ }
16158
+ } catch {
16159
+ }
16160
+ }
16161
+ }
15848
16162
  const redundantCount = findings.filter(
15849
16163
  (f) => f.type === "redundant"
15850
16164
  ).length;
@@ -16088,7 +16402,7 @@ var MigrationStatusOutputSchema = z.object({
16088
16402
  }).optional(),
16089
16403
  sourceSystems: z.array(z.string()).optional()
16090
16404
  }).extend(ErrorResponseFields.shape);
16091
- var MigrationInitSchema = z.object({}).default({});
16405
+ var MigrationInitSchema = z.object({}).strict().default({});
16092
16406
  var MigrationRecordSchema = z.object({
16093
16407
  version: z.string().describe("Version identifier (e.g., '1.0.0', '2024-01-15-add-users')"),
16094
16408
  description: z.string().optional().describe("Human-readable description of the migration"),
@@ -16163,7 +16477,7 @@ var MigrationHistorySchema = z.object({
16163
16477
  "Omit migrationHash and sourceSystem from records to reduce payload (default: false)"
16164
16478
  )
16165
16479
  }).default({});
16166
- var MigrationStatusSchema = z.object({}).default({});
16480
+ var MigrationStatusSchema = z.object({}).strict().default({});
16167
16481
  var MIGRATIONS_TABLE = "_mcp_migrations";
16168
16482
  function toMigrationRecord(row) {
16169
16483
  return {
@@ -16191,7 +16505,7 @@ function validateMigrationSql(sql) {
16191
16505
  const cleanSql = sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
16192
16506
  const upperSql = cleanSql.toUpperCase();
16193
16507
  if (upperSql.includes("ATTACH ") || upperSql.includes("DETACH ") || upperSql.includes("PRAGMA ") || upperSql.includes("LOAD_EXTENSION(")) {
16194
- throw new Error(
16508
+ throw new ValidationError(
16195
16509
  `Migration validation failed: unauthorized command or function call`
16196
16510
  );
16197
16511
  }
@@ -16884,6 +17198,10 @@ var POSITIONAL_PARAM_MAP = {
16884
17198
  listConstraints: "table",
16885
17199
  dateAdd: ["table", "column", "amount", "unit", "whereClause"],
16886
17200
  dateDiff: ["table", "column1", "column2", "unit", "whereClause"],
17201
+ enableVersioning: "table",
17202
+ disableVersioning: "table",
17203
+ checkVersion: ["table", "rowId"],
17204
+ conditionalUpdate: ["table", "data", "conditions", "expectedVersion"],
16887
17205
  // JSON
16888
17206
  extract: ["table", "column", "path", "whereClause"],
16889
17207
  set: ["table", "column", "path", "value", "whereClause"],
@@ -16909,6 +17227,7 @@ var POSITIONAL_PARAM_MAP = {
16909
17227
  jsonbConvert: ["table", "column"],
16910
17228
  createJsonCollection: "tableName",
16911
17229
  // Text
17230
+ sentiment: "text",
16912
17231
  regexExtract: ["table", "column", "pattern"],
16913
17232
  regexMatch: ["table", "column", "pattern"],
16914
17233
  split: ["table", "column", "delimiter"],
@@ -16966,7 +17285,6 @@ var POSITIONAL_PARAM_MAP = {
16966
17285
  pragmaSettings: "pragma",
16967
17286
  pragmaTableInfo: "table",
16968
17287
  pragmaCompileOptions: "filter",
16969
- appendInsight: "insight",
16970
17288
  generateSeries: ["start", "stop", "step"],
16971
17289
  createView: ["viewName", "selectQuery"],
16972
17290
  dropView: "viewName",
@@ -17038,7 +17356,7 @@ function normalizeParams(methodName, args) {
17038
17356
  if (Array.isArray(paramMapping2) && paramMapping2[0] !== void 0) {
17039
17357
  return { [paramMapping2[0]]: arg };
17040
17358
  }
17041
- throw new Error(
17359
+ throw new ValidationError(
17042
17360
  `Positional arguments are not supported for method: ${methodName}. Please use an options object.`
17043
17361
  );
17044
17362
  }
@@ -17046,7 +17364,7 @@ function normalizeParams(methodName, args) {
17046
17364
  }
17047
17365
  const paramMapping = POSITIONAL_PARAM_MAP[methodName];
17048
17366
  if (paramMapping === void 0) {
17049
- throw new Error(
17367
+ throw new ValidationError(
17050
17368
  `Positional arguments are not supported for method: ${methodName}. Please use an options object.`
17051
17369
  );
17052
17370
  }
@@ -17096,7 +17414,7 @@ function createGroupApi(groupName, tools, baseContext) {
17096
17414
  api[methodName] = async (...args) => {
17097
17415
  if (baseContext?.auth !== void 0) {
17098
17416
  if (!scopesGrantToolAccess(baseContext.auth.scopes ?? [], tool.name)) {
17099
- throw new Error(
17417
+ throw new AuthorizationError(
17100
17418
  `Forbidden: Required scope for tool '${tool.name}' not granted.`
17101
17419
  );
17102
17420
  }
@@ -17107,7 +17425,8 @@ function createGroupApi(groupName, tools, baseContext) {
17107
17425
  requestId: crypto.randomUUID(),
17108
17426
  ...baseContext?.server !== void 0 ? { server: baseContext.server } : {},
17109
17427
  ...baseContext?.progressToken !== void 0 ? { progressToken: baseContext.progressToken } : {},
17110
- ...baseContext?.auth !== void 0 ? { auth: baseContext.auth } : {}
17428
+ ...baseContext?.auth !== void 0 ? { auth: baseContext.auth } : {},
17429
+ isCodeMode: true
17111
17430
  };
17112
17431
  const result = await tool.handler(normalizedParams, context);
17113
17432
  return result;
@@ -17269,7 +17588,8 @@ function createSqliteApi(tools, baseContext) {
17269
17588
  var DEFAULT_SANDBOX_OPTIONS = {
17270
17589
  memoryLimitMb: 128,
17271
17590
  timeoutMs: 5e3,
17272
- cpuLimitMs: 1e4
17591
+ cpuLimitMs: 1e4,
17592
+ strictIsolation: true
17273
17593
  };
17274
17594
  var DEFAULT_POOL_OPTIONS = {
17275
17595
  minInstances: 2,
@@ -17465,7 +17785,7 @@ var CodeModeSecurityManager = class {
17465
17785
  const redactedResult = redactObject(result, 0, 15);
17466
17786
  const serialized = JSON.stringify(redactedResult);
17467
17787
  if (serialized === void 0) {
17468
- throw new Error("Not serializable");
17788
+ throw new InternalError("Not serializable");
17469
17789
  }
17470
17790
  if (serialized.length > this.config.maxResultSize) {
17471
17791
  return {
@@ -17565,6 +17885,9 @@ return ${lastStmt}`;
17565
17885
  }
17566
17886
 
17567
17887
  // src/codemode/sandbox.ts
17888
+ var GROUP_NAME_REGEX = /^[a-zA-Z0-9_]+$/;
17889
+ var astCache = /* @__PURE__ */ new Map();
17890
+ var MAX_AST_CACHE_SIZE = 500;
17568
17891
  var CodeModeSandbox = class _CodeModeSandbox {
17569
17892
  options;
17570
17893
  disposed = false;
@@ -17593,9 +17916,8 @@ var CodeModeSandbox = class _CodeModeSandbox {
17593
17916
  metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 }
17594
17917
  };
17595
17918
  }
17596
- const groupNameRegex = /^[a-zA-Z0-9_]+$/;
17597
17919
  for (const groupName of Object.keys(apiBindings)) {
17598
- if (!groupNameRegex.test(groupName) || groupName === "__proto__" || groupName === "constructor" || groupName === "prototype") {
17920
+ if (!GROUP_NAME_REGEX.test(groupName) || groupName === "__proto__" || groupName === "constructor" || groupName === "prototype") {
17599
17921
  return {
17600
17922
  success: false,
17601
17923
  error: `Security Error: Invalid tool group name '${groupName}'`,
@@ -17605,30 +17927,39 @@ var CodeModeSandbox = class _CodeModeSandbox {
17605
17927
  }
17606
17928
  try {
17607
17929
  const wrappedCode = `async function __wrapper() { ${code} }`;
17608
- const ast = acorn.parse(wrappedCode, {
17609
- ecmaVersion: "latest",
17610
- sourceType: "script"
17611
- });
17612
- const validateAst = (node) => {
17613
- if (node === null || node === void 0 || typeof node !== "object")
17614
- return;
17615
- const n = node;
17616
- if (n["type"] === "WithStatement") {
17617
- throw new Error("'with' statements are forbidden in sandbox code.");
17618
- }
17619
- if (n["type"] === "MemberExpression" && n["object"] !== null && n["object"] !== void 0 && typeof n["object"] === "object" && n["object"]["type"] === "Identifier") {
17620
- const objName = n["object"]["name"];
17621
- if (["process", "require", "global", "globalThis"].includes(objName)) {
17622
- throw new Error(`Access to '${objName}' is forbidden.`);
17930
+ if (!astCache.has(wrappedCode)) {
17931
+ const ast = acorn.parse(wrappedCode, {
17932
+ ecmaVersion: "latest",
17933
+ sourceType: "script"
17934
+ });
17935
+ const validateAst = (node) => {
17936
+ if (node === null || node === void 0 || typeof node !== "object")
17937
+ return;
17938
+ const n = node;
17939
+ if (n["type"] === "WithStatement") {
17940
+ throw new ValidationError(
17941
+ "'with' statements are forbidden in sandbox code."
17942
+ );
17623
17943
  }
17624
- }
17625
- for (const key in n) {
17626
- if (key !== "loc" && key !== "start" && key !== "end") {
17627
- validateAst(n[key]);
17944
+ if (n["type"] === "MemberExpression" && n["object"] !== null && n["object"] !== void 0 && typeof n["object"] === "object" && n["object"]["type"] === "Identifier") {
17945
+ const objName = n["object"]["name"];
17946
+ if (["process", "require", "global", "globalThis"].includes(objName)) {
17947
+ throw new ValidationError(`Access to '${objName}' is forbidden.`);
17948
+ }
17628
17949
  }
17950
+ for (const key in n) {
17951
+ if (key !== "loc" && key !== "start" && key !== "end") {
17952
+ validateAst(n[key]);
17953
+ }
17954
+ }
17955
+ };
17956
+ validateAst(ast);
17957
+ if (astCache.size >= MAX_AST_CACHE_SIZE) {
17958
+ const firstKey = astCache.keys().next().value;
17959
+ if (firstKey) astCache.delete(firstKey);
17629
17960
  }
17630
- };
17631
- validateAst(ast);
17961
+ astCache.set(wrappedCode, ast);
17962
+ }
17632
17963
  } catch (e) {
17633
17964
  return {
17634
17965
  success: false,
@@ -17644,6 +17975,13 @@ var CodeModeSandbox = class _CodeModeSandbox {
17644
17975
  } catch {
17645
17976
  }
17646
17977
  if (!ivmLib) {
17978
+ if (this.options.strictIsolation) {
17979
+ return {
17980
+ success: false,
17981
+ error: "Security Error: isolated-vm native bindings failed to load. Code Mode strict isolation is enabled and node:vm fallback is prohibited.",
17982
+ metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 }
17983
+ };
17984
+ }
17647
17985
  const vm = await import('vm');
17648
17986
  const logs2 = [];
17649
17987
  const sandboxEnv = {
@@ -17670,6 +18008,28 @@ var CodeModeSandbox = class _CodeModeSandbox {
17670
18008
  }
17671
18009
  }
17672
18010
  const context2 = vm.createContext(sandboxEnv);
18011
+ vm.runInContext(
18012
+ `
18013
+ const proxyHandler = {
18014
+ get(target, prop, receiver) {
18015
+ if (typeof prop === "string" && !(prop in target)) {
18016
+ const camelProp = prop.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
18017
+ if (camelProp in target) {
18018
+ return Reflect.get(target, camelProp, receiver);
18019
+ }
18020
+ }
18021
+ return Reflect.get(target, prop, receiver);
18022
+ }
18023
+ };
18024
+ sqlite = new Proxy(sqlite, proxyHandler);
18025
+ for (const key of Object.keys(sqlite)) {
18026
+ if (typeof sqlite[key] === "object" && sqlite[key] !== null) {
18027
+ sqlite[key] = new Proxy(sqlite[key], proxyHandler);
18028
+ }
18029
+ }
18030
+ `,
18031
+ context2
18032
+ );
17673
18033
  const startTime2 = performance.now();
17674
18034
  let result2;
17675
18035
  let success2 = true;
@@ -17699,94 +18059,125 @@ var CodeModeSandbox = class _CodeModeSandbox {
17699
18059
  const isolate = new ivmLib.Isolate({
17700
18060
  memoryLimit: this.options.memoryLimitMb
17701
18061
  });
17702
- const context = isolate.createContextSync();
17703
- const jail = context.global;
17704
- jail.setSync("global", jail.derefInto());
17705
- const logs = [];
17706
- const logRef = new ivmLib.Reference((level, ...args) => {
17707
- const msg = args.map(
17708
- (a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)
17709
- ).join(" ");
17710
- logs.push(level === "LOG" ? msg : `[${level}] ${msg}`);
17711
- });
17712
- context.global.setSync("logRef", logRef);
17713
- const setupScript = `
17714
- globalThis.console = {
17715
- log: (...args) => logRef.applyIgnored(undefined, ['LOG', ...args], { arguments: { copy: true } }),
17716
- error: (...args) => logRef.applyIgnored(undefined, ['ERROR', ...args], { arguments: { copy: true } }),
17717
- warn: (...args) => logRef.applyIgnored(undefined, ['WARN', ...args], { arguments: { copy: true } }),
17718
- info: (...args) => logRef.applyIgnored(undefined, ['INFO', ...args], { arguments: { copy: true } }),
17719
- debug: (...args) => logRef.applyIgnored(undefined, ['DEBUG', ...args], { arguments: { copy: true } })
17720
- };
17721
- globalThis.sqlite = {};
17722
- `;
17723
- context.evalSync(setupScript);
17724
- let rpcCount = 0;
17725
- const MAX_RPC_CALLS = 100;
18062
+ let context;
18063
+ let logRef;
18064
+ let script;
17726
18065
  const refCleanup = [];
17727
- for (const [groupName, groupValue] of Object.entries(apiBindings)) {
17728
- if (typeof groupValue === "object" && groupValue !== null) {
17729
- context.evalSync(
17730
- `globalThis.sqlite[${JSON.stringify(groupName)}] = {};`
17731
- );
17732
- for (const [methodName, methodFn] of Object.entries(groupValue)) {
17733
- if (typeof methodFn === "function") {
17734
- const fnRef = new ivmLib.Reference(async (...args) => {
17735
- if (++rpcCount > MAX_RPC_CALLS) {
17736
- throw new Error(
17737
- `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17738
- );
17739
- }
17740
- try {
17741
- return await methodFn(...args);
17742
- } catch (e) {
17743
- throw new Error(e instanceof Error ? e.message : String(e), {
17744
- cause: e
17745
- });
17746
- }
17747
- });
17748
- refCleanup.push(fnRef);
17749
- const refName = `fnRef_${groupName}_${methodName}`;
17750
- context.global.setSync(refName, fnRef);
17751
- context.evalSync(`
17752
- globalThis.sqlite[${JSON.stringify(groupName)}][${JSON.stringify(methodName)}] = async (...args) => {
17753
- return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17754
- };
17755
- `);
17756
- }
17757
- }
17758
- } else if (typeof groupValue === "function") {
17759
- const fnRef = new ivmLib.Reference(async (...args) => {
17760
- if (++rpcCount > MAX_RPC_CALLS) {
17761
- throw new Error(
17762
- `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17763
- );
17764
- }
17765
- try {
17766
- return await groupValue(...args);
17767
- } catch (e) {
17768
- throw new Error(e instanceof Error ? e.message : String(e), {
17769
- cause: e
17770
- });
17771
- }
17772
- });
17773
- refCleanup.push(fnRef);
17774
- const refName = `fnRef_${groupName}`;
17775
- context.global.setSync(refName, fnRef);
17776
- context.evalSync(`
17777
- globalThis.sqlite[${JSON.stringify(groupName)}] = async (...args) => {
17778
- return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17779
- };
17780
- `);
17781
- }
17782
- }
18066
+ const logs = [];
17783
18067
  const startTime = performance.now();
17784
18068
  let result;
17785
18069
  let success = true;
17786
18070
  let errorMsg;
17787
18071
  try {
18072
+ context = isolate.createContextSync();
18073
+ const jail = context.global;
18074
+ jail.setSync("global", jail.derefInto());
18075
+ logRef = new ivmLib.Reference((level, ...args) => {
18076
+ const msg = args.map(
18077
+ (a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)
18078
+ ).join(" ");
18079
+ logs.push(level === "LOG" ? msg : `[${level}] ${msg}`);
18080
+ });
18081
+ context.global.setSync("logRef", logRef);
18082
+ const setupScript = `
18083
+ globalThis.console = {
18084
+ log: (...args) => logRef.applyIgnored(undefined, ['LOG', ...args], { arguments: { copy: true } }),
18085
+ error: (...args) => logRef.applyIgnored(undefined, ['ERROR', ...args], { arguments: { copy: true } }),
18086
+ warn: (...args) => logRef.applyIgnored(undefined, ['WARN', ...args], { arguments: { copy: true } }),
18087
+ info: (...args) => logRef.applyIgnored(undefined, ['INFO', ...args], { arguments: { copy: true } }),
18088
+ debug: (...args) => logRef.applyIgnored(undefined, ['DEBUG', ...args], { arguments: { copy: true } })
18089
+ };
18090
+ globalThis.sqlite = {};
18091
+ `;
18092
+ context.evalSync(setupScript);
18093
+ let rpcCount = 0;
18094
+ const MAX_RPC_CALLS = 100;
18095
+ let batchedScript = "";
18096
+ for (const [groupName, groupValue] of Object.entries(apiBindings)) {
18097
+ if (typeof groupValue === "object" && groupValue !== null) {
18098
+ batchedScript += `globalThis.sqlite[${JSON.stringify(groupName)}] = {};
18099
+ `;
18100
+ for (const [methodName, methodFn] of Object.entries(groupValue)) {
18101
+ if (typeof methodFn === "function") {
18102
+ const fnRef = new ivmLib.Reference(async (...args) => {
18103
+ if (++rpcCount > MAX_RPC_CALLS) {
18104
+ throw new RateLimitError(
18105
+ `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
18106
+ );
18107
+ }
18108
+ try {
18109
+ return await methodFn(...args);
18110
+ } catch (e) {
18111
+ throw new InternalError(
18112
+ e instanceof Error ? e.message : String(e),
18113
+ "RPC_EXECUTION_ERROR",
18114
+ {
18115
+ cause: e instanceof Error ? e : void 0
18116
+ }
18117
+ );
18118
+ }
18119
+ });
18120
+ refCleanup.push(fnRef);
18121
+ const refName = `fnRef_${groupName}_${methodName}`;
18122
+ context.global.setSync(refName, fnRef);
18123
+ batchedScript += `globalThis.sqlite[${JSON.stringify(groupName)}][${JSON.stringify(methodName)}] = async (...args) => {
18124
+ return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
18125
+ };
18126
+ `;
18127
+ }
18128
+ }
18129
+ } else if (typeof groupValue === "function") {
18130
+ const fnRef = new ivmLib.Reference(async (...args) => {
18131
+ if (++rpcCount > MAX_RPC_CALLS) {
18132
+ throw new RateLimitError(
18133
+ `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
18134
+ );
18135
+ }
18136
+ try {
18137
+ return await groupValue(...args);
18138
+ } catch (e) {
18139
+ throw new InternalError(
18140
+ e instanceof Error ? e.message : String(e),
18141
+ "RPC_EXECUTION_ERROR",
18142
+ {
18143
+ cause: e instanceof Error ? e : void 0
18144
+ }
18145
+ );
18146
+ }
18147
+ });
18148
+ refCleanup.push(fnRef);
18149
+ const refName = `fnRef_${groupName}`;
18150
+ context.global.setSync(refName, fnRef);
18151
+ batchedScript += `globalThis.sqlite[${JSON.stringify(groupName)}] = async (...args) => {
18152
+ return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
18153
+ };
18154
+ `;
18155
+ }
18156
+ }
18157
+ if (batchedScript.length > 0) {
18158
+ context.evalSync(batchedScript);
18159
+ }
18160
+ context.evalSync(`
18161
+ const proxyHandler = {
18162
+ get(target, prop, receiver) {
18163
+ if (typeof prop === "string" && !(prop in target)) {
18164
+ const camelProp = prop.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
18165
+ if (camelProp in target) {
18166
+ return Reflect.get(target, camelProp, receiver);
18167
+ }
18168
+ }
18169
+ return Reflect.get(target, prop, receiver);
18170
+ }
18171
+ };
18172
+ globalThis.sqlite = new Proxy(globalThis.sqlite, proxyHandler);
18173
+ for (const key of Object.keys(globalThis.sqlite)) {
18174
+ if (typeof globalThis.sqlite[key] === "object" && globalThis.sqlite[key] !== null) {
18175
+ globalThis.sqlite[key] = new Proxy(globalThis.sqlite[key], proxyHandler);
18176
+ }
18177
+ }
18178
+ `);
17788
18179
  const wrappedCode = `(async () => { ${transformAutoReturn(code)} })()`;
17789
- const script = isolate.compileScriptSync(wrappedCode, {
18180
+ script = isolate.compileScriptSync(wrappedCode, {
17790
18181
  filename: `code-mode.js`
17791
18182
  });
17792
18183
  result = await script.run(context, {
@@ -17799,11 +18190,27 @@ var CodeModeSandbox = class _CodeModeSandbox {
17799
18190
  errorMsg = error instanceof Error ? error.message : String(error);
17800
18191
  } finally {
17801
18192
  for (const ref of refCleanup) {
17802
- ref.release();
18193
+ try {
18194
+ ref.release();
18195
+ } catch {
18196
+ }
18197
+ }
18198
+ try {
18199
+ logRef?.release();
18200
+ } catch {
18201
+ }
18202
+ try {
18203
+ script?.release();
18204
+ } catch {
18205
+ }
18206
+ try {
18207
+ context?.release();
18208
+ } catch {
18209
+ }
18210
+ try {
18211
+ isolate.dispose();
18212
+ } catch {
17803
18213
  }
17804
- logRef.release();
17805
- context.release();
17806
- isolate.dispose();
17807
18214
  }
17808
18215
  const endTime = performance.now();
17809
18216
  this.accumulatedLogs.push(...logs);
@@ -17847,7 +18254,10 @@ var SandboxPool = class _SandboxPool {
17847
18254
  }
17848
18255
  static getIvmLib() {
17849
18256
  if (!_SandboxPool.cachedIvmLib) {
17850
- throw new Error("ivmLib not initialized");
18257
+ throw new InternalError(
18258
+ "ivmLib not initialized",
18259
+ "IVMLIB_NOT_INITIALIZED"
18260
+ );
17851
18261
  }
17852
18262
  return _SandboxPool.cachedIvmLib;
17853
18263
  }
@@ -17866,8 +18276,9 @@ var SandboxPool = class _SandboxPool {
17866
18276
  await _SandboxPool.initialize();
17867
18277
  }
17868
18278
  if (this.inUseCount >= this.options.maxInstances) {
17869
- throw new Error(
17870
- `Sandbox pool exhausted (max ${this.options.maxInstances})`
18279
+ throw new InternalError(
18280
+ `Sandbox pool exhausted (max ${this.options.maxInstances})`,
18281
+ "POOL_EXHAUSTED"
17871
18282
  );
17872
18283
  }
17873
18284
  this.inUseCount++;
@@ -18004,15 +18415,14 @@ function createExecuteCodeTool(adapter) {
18004
18415
  }
18005
18416
  const clientId = _context.auth?.sub ?? _context.clientIp ?? processId;
18006
18417
  if (!await security.checkRateLimit(clientId)) {
18007
- return {
18008
- success: false,
18009
- error: `Rate limit exceeded. Maximum ${String(codemodeRateLimit)} executions per minute.`,
18010
- code: "CODEMODE_RATE_LIMITED",
18011
- category: "permission",
18012
- suggestion: "Wait before retrying. Combine multiple operations into fewer execute_code calls.",
18013
- recoverable: true,
18014
- metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 }
18015
- };
18418
+ throw new RateLimitError(
18419
+ `Rate limit exceeded. Maximum ${String(codemodeRateLimit)} executions per minute.`,
18420
+ "CODEMODE_RATE_LIMITED",
18421
+ {
18422
+ suggestion: "Wait before retrying. Combine multiple operations into fewer execute_code calls.",
18423
+ limit: codemodeRateLimit
18424
+ }
18425
+ );
18016
18426
  }
18017
18427
  if (!pool) {
18018
18428
  initializePool();
@@ -18027,7 +18437,7 @@ function createExecuteCodeTool(adapter) {
18027
18437
  throw new DbMcpError(
18028
18438
  "Sandbox pool not initialized",
18029
18439
  "CODEMODE_POOL_UNINITIALIZED",
18030
- "internal" /* INTERNAL */
18440
+ ErrorCategory.INTERNAL
18031
18441
  );
18032
18442
  }
18033
18443
  const result = await pool.execute(code, bindings, timeoutMs);
@@ -18047,6 +18457,13 @@ function createExecuteCodeTool(adapter) {
18047
18457
  } catch {
18048
18458
  }
18049
18459
  }
18460
+ if (!result.success && typeof result.error === "string" && /script execution timed out/i.test(result.error)) {
18461
+ return formatHandlerError(
18462
+ new TimeoutError("Code execution timed out", "CODEMODE_TIMEOUT", {
18463
+ suggestion: "Reduce code complexity or increase the timeout parameter (max 30000ms). Break complex logic into smaller execute_code calls."
18464
+ })
18465
+ );
18466
+ }
18050
18467
  return {
18051
18468
  success: result.success,
18052
18469
  result: sanitizedResult,
@@ -18064,6 +18481,14 @@ function createExecuteCodeTool(adapter) {
18064
18481
  operation: "executeCode",
18065
18482
  error: error instanceof Error ? error : void 0
18066
18483
  });
18484
+ if (error instanceof Error && /script execution timed out/i.test(error.message)) {
18485
+ return formatHandlerError(
18486
+ new TimeoutError("Code execution timed out", "CODEMODE_TIMEOUT", {
18487
+ suggestion: "Reduce code complexity or increase the timeout parameter (max 30000ms). Break complex logic into smaller execute_code calls.",
18488
+ cause: error
18489
+ })
18490
+ );
18491
+ }
18067
18492
  return formatHandlerError(error);
18068
18493
  }
18069
18494
  }
@@ -18113,7 +18538,8 @@ function initializePool() {
18113
18538
  operation: "initialize"
18114
18539
  });
18115
18540
  setDefaultSandboxMode(mode);
18116
- pool = createSandboxPool(mode, void 0, { timeoutMs: 3e4 });
18541
+ const strictIsolation = process.env["CODE_MODE_STRICT_ISOLATION"] !== "false";
18542
+ pool = createSandboxPool(mode, void 0, { timeoutMs: 3e4, strictIsolation });
18117
18543
  pool.initialize();
18118
18544
  logger.info(`Code Mode initialized with ${mode} sandbox`, {
18119
18545
  module: "CODEMODE",
@@ -18173,6 +18599,8 @@ function detectAndSetJsonbSupport(getVersion, log2) {
18173
18599
  }
18174
18600
 
18175
18601
  // src/adapters/sqlite/query-executor.ts
18602
+ var RETURNING_REGEX = /\bRETURNING\b/i;
18603
+ var INSERT_REGEX = /^\s*INSERT\s/i;
18176
18604
  var log = createModuleLogger("SQLITE");
18177
18605
  function translateSqliteError(error, sql, operation, overrideLog) {
18178
18606
  const message = error instanceof Error ? error.message : String(error);
@@ -18180,8 +18608,7 @@ function translateSqliteError(error, sql, operation, overrideLog) {
18180
18608
  const improvedMessage = `${operation} failed: ${message}`;
18181
18609
  const details = { sql };
18182
18610
  if (match?.code === "TABLE_NOT_FOUND") {
18183
- const tableMatch = /no such table[:\s]*(['"]?)([\w.]+)\1/i.exec(message);
18184
- const tableName = tableMatch ? tableMatch[2] : "unknown";
18611
+ const tableName = match.match?.[2] ?? "unknown";
18185
18612
  throw new ResourceNotFoundError(
18186
18613
  `Table '${tableName}' not found`,
18187
18614
  "TABLE_NOT_FOUND",
@@ -18195,8 +18622,7 @@ function translateSqliteError(error, sql, operation, overrideLog) {
18195
18622
  );
18196
18623
  }
18197
18624
  if (match?.code === "COLUMN_NOT_FOUND") {
18198
- const colMatch = /no such column[:\s]*(['"]?)([\w.]+)\1/i.exec(message) ?? /has no column named[:\s]*(['"]?)([\w.]+)\1/i.exec(message);
18199
- const colName = colMatch ? colMatch[2] : "unknown";
18625
+ const colName = match.match?.[2] ?? "unknown";
18200
18626
  throw new ResourceNotFoundError(
18201
18627
  `Column '${colName}' not found`,
18202
18628
  "COLUMN_NOT_FOUND",
@@ -18276,7 +18702,7 @@ function executeWrite(db, sql, params) {
18276
18702
  try {
18277
18703
  const normalizedParams = normalizeSqliteParams(params);
18278
18704
  let rows = [];
18279
- if (/\bRETURNING\b/i.test(sql)) {
18705
+ if (RETURNING_REGEX.test(sql)) {
18280
18706
  const results = normalizedParams ? db.exec(sql, normalizedParams) : db.exec(sql);
18281
18707
  if (results.length > 0 && results[0]) {
18282
18708
  rows = rowsFromSqlJsResult(results[0]);
@@ -18291,7 +18717,7 @@ function executeWrite(db, sql, params) {
18291
18717
  const changes = db.getRowsModified();
18292
18718
  let lastInsertId;
18293
18719
  try {
18294
- if (/^\s*INSERT\s/i.test(sql) && !/\bRETURNING\b/i.test(sql)) {
18720
+ if (INSERT_REGEX.test(sql) && !RETURNING_REGEX.test(sql)) {
18295
18721
  const rowidResult = db.exec("SELECT last_insert_rowid()");
18296
18722
  if (rowidResult[0]?.values[0]) {
18297
18723
  lastInsertId = Number(rowidResult[0].values[0][0]);
@@ -18340,4 +18766,4 @@ function executeGeneral(db, sql, params) {
18340
18766
  }
18341
18767
  }
18342
18768
 
18343
- export { FtsHeadlineSchema, RowRecordSchema, SchemaManager, WhereConditionSchema, applyCommonPragmas, autoEnableWal, buildWhereClause, detectAndSetJsonbSupport, executeGeneral, executeRead, executeWrite, getAdminTools, getCodeModeTools, getCoreTools, getGeoTools, getIntrospectionTools, getJsonHelperTools, getJsonOperationTools, getMigrationTools, getPromptDefinitions, getResourceDefinitions, getStatsTools, getTextTools, getVectorTools, getVirtualTools, isDDL, normalizeSqliteParams, resolveAliases, sanitizeIdentifier, translateSqliteError, validateIdentifier, validateTableExists };
18769
+ export { FtsHeadlineSchema, RowRecordSchema, SchemaManager, WhereConditionSchema, applyCommonPragmas, autoEnableWal, detectAndSetJsonbSupport, executeGeneral, executeRead, executeWrite, getAdminTools, getCodeModeTools, getCoreTools, getGeoTools, getIntrospectionTools, getJsonHelperTools, getJsonOperationTools, getMigrationTools, getPromptDefinitions, getResourceDefinitions, getStatsTools, getTextTools, getVectorTools, getVirtualTools, isDDL, normalizeSqliteParams, resolveAliases, sanitizeFtsQuery, translateSqliteError, validateTableExists };