db-mcp 4.0.0 → 5.0.1

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 (138) hide show
  1. package/README.md +30 -25
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/adapters/database-adapter.d.ts +9 -0
  4. package/dist/adapters/database-adapter.d.ts.map +1 -1
  5. package/dist/adapters/sqlite/query-executor.d.ts.map +1 -1
  6. package/dist/adapters/sqlite/read-write-lock.d.ts +49 -0
  7. package/dist/adapters/sqlite/read-write-lock.d.ts.map +1 -0
  8. package/dist/adapters/sqlite/schemas/admin.d.ts +5 -5
  9. package/dist/adapters/sqlite/schemas/admin.d.ts.map +1 -1
  10. package/dist/adapters/sqlite/schemas/core.d.ts +94 -0
  11. package/dist/adapters/sqlite/schemas/core.d.ts.map +1 -1
  12. package/dist/adapters/sqlite/schemas/introspection.d.ts +8 -7
  13. package/dist/adapters/sqlite/schemas/introspection.d.ts.map +1 -1
  14. package/dist/adapters/sqlite/schemas/migration.d.ts +2 -2
  15. package/dist/adapters/sqlite/schemas/migration.d.ts.map +1 -1
  16. package/dist/adapters/sqlite/sqlite-adapter.d.ts +6 -0
  17. package/dist/adapters/sqlite/sqlite-adapter.d.ts.map +1 -1
  18. package/dist/adapters/sqlite/tools/admin/backup/create.d.ts.map +1 -1
  19. package/dist/adapters/sqlite/tools/admin/backup/dump.d.ts.map +1 -1
  20. package/dist/adapters/sqlite/tools/admin/backup/restore.d.ts.map +1 -1
  21. package/dist/adapters/sqlite/tools/admin/pragma.d.ts.map +1 -1
  22. package/dist/adapters/sqlite/tools/admin/verify.d.ts.map +1 -1
  23. package/dist/adapters/sqlite/tools/codemode.d.ts.map +1 -1
  24. package/dist/adapters/sqlite/tools/core/convenience.d.ts.map +1 -1
  25. package/dist/adapters/sqlite/tools/core/index.d.ts.map +1 -1
  26. package/dist/adapters/sqlite/tools/core/queries.d.ts.map +1 -1
  27. package/dist/adapters/sqlite/tools/core/versioning.d.ts +19 -0
  28. package/dist/adapters/sqlite/tools/core/versioning.d.ts.map +1 -0
  29. package/dist/adapters/sqlite/tools/introspection/graph/tools.d.ts.map +1 -1
  30. package/dist/adapters/sqlite/tools/json-operations/crud.d.ts.map +1 -1
  31. package/dist/adapters/sqlite/tools/migration/helpers.d.ts.map +1 -1
  32. package/dist/adapters/sqlite/tools/vector/metadata.d.ts.map +1 -1
  33. package/dist/adapters/sqlite/tools/vector/search.d.ts.map +1 -1
  34. package/dist/adapters/sqlite/tools/vector/storage.d.ts.map +1 -1
  35. package/dist/adapters/sqlite/tools/virtual/vtable/analyze-csv.d.ts.map +1 -1
  36. package/dist/adapters/sqlite/tools/virtual/vtable/csv.d.ts.map +1 -1
  37. package/dist/adapters/sqlite/tools/virtual/vtable/info.d.ts.map +1 -1
  38. package/dist/adapters/sqlite/types.d.ts +1 -1
  39. package/dist/adapters/sqlite/types.d.ts.map +1 -1
  40. package/dist/adapters/sqlite-native/index.d.ts +1 -1
  41. package/dist/adapters/sqlite-native/index.d.ts.map +1 -1
  42. package/dist/adapters/sqlite-native/tools/window/helpers.d.ts +52 -0
  43. package/dist/adapters/sqlite-native/tools/window/helpers.d.ts.map +1 -0
  44. package/dist/adapters/sqlite-native/tools/window/index.d.ts +7 -0
  45. package/dist/adapters/sqlite-native/tools/window/index.d.ts.map +1 -0
  46. package/dist/adapters/sqlite-native/tools/window/lag-lead.d.ts +7 -0
  47. package/dist/adapters/sqlite-native/tools/window/lag-lead.d.ts.map +1 -0
  48. package/dist/adapters/sqlite-native/tools/window/moving-average.d.ts +7 -0
  49. package/dist/adapters/sqlite-native/tools/window/moving-average.d.ts.map +1 -0
  50. package/dist/adapters/sqlite-native/tools/window/ntile.d.ts +7 -0
  51. package/dist/adapters/sqlite-native/tools/window/ntile.d.ts.map +1 -0
  52. package/dist/adapters/sqlite-native/tools/window/rank.d.ts +7 -0
  53. package/dist/adapters/sqlite-native/tools/window/rank.d.ts.map +1 -0
  54. package/dist/adapters/sqlite-native/tools/window/row-number.d.ts +7 -0
  55. package/dist/adapters/sqlite-native/tools/window/row-number.d.ts.map +1 -0
  56. package/dist/adapters/sqlite-native/tools/window/running-total.d.ts +7 -0
  57. package/dist/adapters/sqlite-native/tools/window/running-total.d.ts.map +1 -0
  58. package/dist/adapters/sqlite-native/tools/window/schemas.d.ts +159 -0
  59. package/dist/adapters/sqlite-native/tools/window/schemas.d.ts.map +1 -0
  60. package/dist/adapters/sqlite-native/transaction-methods.d.ts.map +1 -1
  61. package/dist/audit/logger.d.ts.map +1 -1
  62. package/dist/auth/authorization-server-discovery.d.ts.map +1 -1
  63. package/dist/auth/scopes/mapping.d.ts.map +1 -1
  64. package/dist/auth/token-validator.d.ts.map +1 -1
  65. package/dist/{chunk-EQF3WZHX.js → chunk-27C5EE6U.js} +15 -2
  66. package/dist/{chunk-R5M4WKDM.js → chunk-2TYICG7B.js} +381 -185
  67. package/dist/{chunk-TGE7LFAR.js → chunk-UE7GVOBV.js} +1069 -355
  68. package/dist/{chunk-NIC5Z2CA.js → chunk-WYBUO4FL.js} +394 -381
  69. package/dist/{chunk-Y57WFKAP.js → chunk-XLIYO3YA.js} +178 -72
  70. package/dist/cli.js +18 -6
  71. package/dist/codemode/api-constants.d.ts.map +1 -1
  72. package/dist/codemode/api.d.ts.map +1 -1
  73. package/dist/codemode/sandbox-factory.d.ts.map +1 -1
  74. package/dist/codemode/sandbox.d.ts +0 -3
  75. package/dist/codemode/sandbox.d.ts.map +1 -1
  76. package/dist/codemode/security.d.ts.map +1 -1
  77. package/dist/codemode/types.d.ts +2 -0
  78. package/dist/codemode/types.d.ts.map +1 -1
  79. package/dist/constants/server-instructions.d.ts.map +1 -1
  80. package/dist/filtering/tool-constants.d.ts +5 -5
  81. package/dist/filtering/tool-constants.d.ts.map +1 -1
  82. package/dist/{http-2WGR3E5K.js → http-2XDGZF4F.js} +177 -32
  83. package/dist/index.js +4 -4
  84. package/dist/observability/system-db.d.ts.map +1 -1
  85. package/dist/server/mcp-server.d.ts.map +1 -1
  86. package/dist/server/registration/admin-tools.d.ts.map +1 -1
  87. package/dist/server/registration/audit-tools/backup.d.ts +8 -0
  88. package/dist/server/registration/audit-tools/backup.d.ts.map +1 -0
  89. package/dist/server/registration/audit-tools/helpers.d.ts +11 -0
  90. package/dist/server/registration/audit-tools/helpers.d.ts.map +1 -0
  91. package/dist/server/registration/audit-tools/index.d.ts +4 -0
  92. package/dist/server/registration/audit-tools/index.d.ts.map +1 -0
  93. package/dist/server/registration/audit-tools/resource.d.ts +8 -0
  94. package/dist/server/registration/audit-tools/resource.d.ts.map +1 -0
  95. package/dist/server/registration/audit-tools/search.d.ts +7 -0
  96. package/dist/server/registration/audit-tools/search.d.ts.map +1 -0
  97. package/dist/server/registration/index.d.ts +1 -1
  98. package/dist/server/registration/index.d.ts.map +1 -1
  99. package/dist/{sqlite-D4WCNPU4.js → sqlite-XWZPEZJO.js} +192 -19
  100. package/dist/{sqlite-native-DLVS2T52.js → sqlite-native-5GQ3WKCZ.js} +156 -117
  101. package/dist/transports/http/middleware.d.ts.map +1 -1
  102. package/dist/transports/http/oauth.d.ts.map +1 -1
  103. package/dist/transports/http/sessions/legacy-sse.d.ts.map +1 -1
  104. package/dist/transports/http/sessions/stateful.d.ts +5 -1
  105. package/dist/transports/http/sessions/stateful.d.ts.map +1 -1
  106. package/dist/transports/http/transport.d.ts +2 -0
  107. package/dist/transports/http/transport.d.ts.map +1 -1
  108. package/dist/transports/http/types.d.ts +9 -0
  109. package/dist/transports/http/types.d.ts.map +1 -1
  110. package/dist/types/auth.d.ts +4 -0
  111. package/dist/types/auth.d.ts.map +1 -1
  112. package/dist/types/server.d.ts +3 -0
  113. package/dist/types/server.d.ts.map +1 -1
  114. package/dist/utils/annotations.d.ts +8 -0
  115. package/dist/utils/annotations.d.ts.map +1 -1
  116. package/dist/utils/errors/categories.d.ts +16 -11
  117. package/dist/utils/errors/categories.d.ts.map +1 -1
  118. package/dist/utils/errors/classes.d.ts +37 -0
  119. package/dist/utils/errors/classes.d.ts.map +1 -1
  120. package/dist/utils/errors/format.d.ts +2 -2
  121. package/dist/utils/errors/format.d.ts.map +1 -1
  122. package/dist/utils/errors/index.d.ts +1 -1
  123. package/dist/utils/errors/index.d.ts.map +1 -1
  124. package/dist/utils/errors/suggestions.d.ts +1 -0
  125. package/dist/utils/errors/suggestions.d.ts.map +1 -1
  126. package/dist/utils/index.d.ts +2 -0
  127. package/dist/utils/index.d.ts.map +1 -1
  128. package/dist/utils/security-utils.d.ts +29 -0
  129. package/dist/utils/security-utils.d.ts.map +1 -0
  130. package/dist/utils/stream-utils.d.ts +14 -0
  131. package/dist/utils/stream-utils.d.ts.map +1 -0
  132. package/dist/utils/where-clause.d.ts +0 -13
  133. package/dist/utils/where-clause.d.ts.map +1 -1
  134. package/package.json +36 -39
  135. package/dist/adapters/sqlite-native/tools/window.d.ts +0 -7
  136. package/dist/adapters/sqlite-native/tools/window.d.ts.map +0 -1
  137. package/dist/server/registration/audit-tools.d.ts +0 -17
  138. package/dist/server/registration/audit-tools.d.ts.map +0 -1
@@ -1,9 +1,9 @@
1
- import { validateQuery } from './chunk-EQF3WZHX.js';
2
- import { ErrorResponseFields, redactObject, SENSITIVE_KEY_PATTERN, HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY, ASSISTANT_FOCUSED, readOnly, sanitizeIdentifier, createColumnList, buildWhereClause, write, idempotent, destructive, sanitizeWhereClause, validateJsonPath, validateAggregateFunction, VacuumOutputSchema, admin, 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-R5M4WKDM.js';
3
- import { createModuleLogger, logger, ResourceNotFoundError, DbMcpError, formatHandlerError, findSuggestion, ERROR_CODES, QueryError, ValidationError, ExtensionNotAvailableError, getAuthContext, scopesGrantToolAccess } from './chunk-Y57WFKAP.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 from 'path';
5
+ import * as nodePath from 'path';
6
+ import nodePath__default from 'path';
7
7
  import fs from 'fs';
8
8
  import { createHash } from 'crypto';
9
9
  import { createClient } from 'redis';
@@ -42,7 +42,9 @@ var ReadQueryOutputSchema = z.object({
42
42
  rowCount: z.number().optional(),
43
43
  rows: z.array(RowRecordSchema).optional(),
44
44
  nextCursor: z.string().optional(),
45
- executionTimeMs: z.number().optional()
45
+ executionTimeMs: z.number().optional(),
46
+ streamed: z.boolean().optional(),
47
+ chunksEmitted: z.number().optional()
46
48
  }).extend(ErrorResponseFields.shape);
47
49
  var WriteQueryOutputSchema = z.object({
48
50
  success: z.boolean(),
@@ -124,12 +126,21 @@ var ReadQuerySchema = z.object({
124
126
  query: z.string().max(1e5).default("").describe("SELECT query to execute"),
125
127
  sql: z.string().max(1e5).optional().describe("Alias for query"),
126
128
  params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements"),
127
- cursor: z.string().optional().describe("Opaque cursor for pagination")
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
+ )
128
136
  });
129
137
  var WriteQuerySchema = z.object({
130
138
  query: z.string().max(1e5).default("").describe("INSERT/UPDATE/DELETE query to execute"),
131
139
  sql: z.string().max(1e5).optional().describe("Alias for query"),
132
- 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
+ )
133
144
  });
134
145
  var CreateTableSchema = z.object({
135
146
  table: z.string().default("").describe("Name of the table to create"),
@@ -244,7 +255,10 @@ var UpsertSchema = z.object({
244
255
  updateColumns: z.array(z.string()).optional().describe(
245
256
  "Columns to update on conflict (default: all except conflict columns). Only used if conflictColumns is provided."
246
257
  ),
247
- 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
+ )
248
262
  });
249
263
  var BatchInsertSchema = z.object({
250
264
  table: z.string().default("").describe("Table name"),
@@ -350,9 +364,64 @@ var DropTriggerOutputSchema = z.object({
350
364
  success: z.boolean(),
351
365
  message: z.string().optional()
352
366
  }).extend(ErrorResponseFields.shape);
353
- 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 = {}) {
354
412
  if (typeof params !== "object" || params === null) return params;
355
- 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
+ }
356
425
  for (const [alias, canonical] of Object.entries(aliasMap)) {
357
426
  if ((obj[canonical] === void 0 || obj[canonical] === "") && obj[alias] !== void 0) {
358
427
  obj[canonical] = obj[alias];
@@ -591,6 +660,24 @@ function createReadQueryTool(adapter) {
591
660
  JSON.stringify({ offset: nextOffset })
592
661
  ).toString("base64");
593
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
+ }
594
681
  return {
595
682
  success: true,
596
683
  rowCount: result.rows?.length ?? 0,
@@ -695,11 +782,60 @@ function createWriteQueryTool(adapter) {
695
782
  rowsAffected: 0
696
783
  };
697
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
+ }
698
819
  try {
699
820
  const result = await adapter.executeWriteQuery(
700
821
  input.query,
701
822
  input.params
702
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
+ }
703
839
  return {
704
840
  success: true,
705
841
  rowsAffected: result.rowsAffected,
@@ -1416,7 +1552,7 @@ async function validateTableExists2(adapter, table) {
1416
1552
  success: false,
1417
1553
  error: `Table or view '${table}' not found. Use sqlite_list_tables to see available tables.`,
1418
1554
  code: "TABLE_NOT_FOUND",
1419
- category: "resource" /* RESOURCE */,
1555
+ category: ErrorCategory.RESOURCE,
1420
1556
  suggestion: "Table or view does not exist. Run sqlite_list_tables to see available tables.",
1421
1557
  details: void 0,
1422
1558
  recoverable: false
@@ -1451,12 +1587,38 @@ function createUpsertTool(adapter) {
1451
1587
  conflictColumns: parsed.conflictColumns !== void 0 ? Array.isArray(parsed.conflictColumns) ? parsed.conflictColumns : [parsed.conflictColumns] : []
1452
1588
  };
1453
1589
  if (Object.keys(input.data).length === 0)
1454
- throw new Error("data (or values alias) is required");
1590
+ throw new ValidationError("data (or values alias) is required");
1455
1591
  } catch (error) {
1456
1592
  return { ...formatHandlerError(error), rowsAffected: 0 };
1457
1593
  }
1458
1594
  const validationError = await validateTableExists2(adapter, input.table);
1459
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
+ }
1460
1622
  const columns = Object.keys(input.data);
1461
1623
  const values = Object.values(input.data);
1462
1624
  const placeholders = columns.map(() => "?").join(", ");
@@ -1471,11 +1633,35 @@ function createUpsertTool(adapter) {
1471
1633
  const updateSets = colsToUpdate.map(
1472
1634
  (c) => `${sanitizeIdentifier(c)} = EXCLUDED.${sanitizeIdentifier(c)}`
1473
1635
  ).join(", ");
1474
- 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;
1475
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
+ }
1476
1652
  sql += ` ON CONFLICT (${conflictCols}) DO NOTHING`;
1477
1653
  }
1478
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
+ }
1479
1665
  sql = `INSERT OR REPLACE INTO ${safeTable} (${safeColumns.join(", ")}) VALUES (${placeholders})`;
1480
1666
  }
1481
1667
  if (input.returning !== void 0 && input.returning !== false) {
@@ -1488,6 +1674,21 @@ function createUpsertTool(adapter) {
1488
1674
  }
1489
1675
  try {
1490
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
+ }
1491
1692
  const response = {
1492
1693
  success: true,
1493
1694
  rowsAffected: result.rowsAffected,
@@ -2571,6 +2772,269 @@ function createAlterTableTool(adapter) {
2571
2772
  };
2572
2773
  }
2573
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
+
2574
3038
  // src/adapters/sqlite/tools/core/index.ts
2575
3039
  function getCoreTools(adapter) {
2576
3040
  return [
@@ -2594,7 +3058,11 @@ function getCoreTools(adapter) {
2594
3058
  createListConstraintsTool(adapter),
2595
3059
  createDateAddTool(adapter),
2596
3060
  createDateDiffTool(adapter),
2597
- createAlterTableTool(adapter)
3061
+ createAlterTableTool(adapter),
3062
+ createEnableVersioningTool(adapter),
3063
+ createDisableVersioningTool(adapter),
3064
+ createCheckVersionTool(adapter),
3065
+ createConditionalUpdateTool(adapter)
2598
3066
  ];
2599
3067
  }
2600
3068
 
@@ -2714,7 +3182,7 @@ var SchemaManager = class {
2714
3182
  throw new DbMcpError(
2715
3183
  "Invalid table name",
2716
3184
  "INVALID_TABLE",
2717
- "validation" /* VALIDATION */
3185
+ ErrorCategory.VALIDATION
2718
3186
  );
2719
3187
  }
2720
3188
  const result = await this.executor.executeReadQuery(
@@ -2724,7 +3192,7 @@ var SchemaManager = class {
2724
3192
  throw new DbMcpError(
2725
3193
  `Table '${tableName}' does not exist`,
2726
3194
  "TABLE_NOT_FOUND",
2727
- "resource" /* RESOURCE */
3195
+ ErrorCategory.RESOURCE
2728
3196
  );
2729
3197
  }
2730
3198
  const columns = (result.rows ?? []).map((row) => ({
@@ -2893,7 +3361,7 @@ function createTableSchemaResource(adapter) {
2893
3361
  throw new DbMcpError(
2894
3362
  "Invalid table URI format",
2895
3363
  "RESOURCE_INVALID_URI",
2896
- "validation" /* VALIDATION */
3364
+ ErrorCategory.VALIDATION
2897
3365
  );
2898
3366
  }
2899
3367
  const tableName = decodeURIComponent(match[1]);
@@ -3864,7 +4332,7 @@ function parseJsonValue(value) {
3864
4332
  throw new DbMcpError(
3865
4333
  "JSONB blob cannot be parsed directly. Use json() in SQL query.",
3866
4334
  "JSON_PARSE_ERROR",
3867
- "validation" /* VALIDATION */
4335
+ ErrorCategory.VALIDATION
3868
4336
  );
3869
4337
  }
3870
4338
  if (typeof value === "string") {
@@ -4401,6 +4869,7 @@ function createJsonSetTool(adapter) {
4401
4869
  const result = await adapter.executeWriteQuery(sql, queryParams);
4402
4870
  return {
4403
4871
  success: true,
4872
+ message: `Set value at ${input.path} in ${table}.${column}`,
4404
4873
  rowsAffected: result.rowsAffected,
4405
4874
  ...result.rowsAffected === 0 ? {
4406
4875
  warning: "No rows matched the WHERE clause \u2014 no changes were made"
@@ -4442,6 +4911,7 @@ function createJsonRemoveTool(adapter) {
4442
4911
  const result = await adapter.executeWriteQuery(sql, queryParams);
4443
4912
  return {
4444
4913
  success: true,
4914
+ message: `Removed value at ${input.path} from ${table}.${column}`,
4445
4915
  rowsAffected: result.rowsAffected,
4446
4916
  ...result.rowsAffected === 0 ? {
4447
4917
  warning: "No rows matched the WHERE clause \u2014 no changes were made"
@@ -4584,6 +5054,7 @@ function createJsonArrayAppendTool(adapter) {
4584
5054
  const result = await adapter.executeWriteQuery(sql, queryParams);
4585
5055
  return {
4586
5056
  success: true,
5057
+ message: `Appended to ${input.path} in ${table}.${column}`,
4587
5058
  rowsAffected: result.rowsAffected
4588
5059
  };
4589
5060
  } catch (error) {
@@ -6816,7 +7287,7 @@ function cosineSimilarity(a, b) {
6816
7287
  throw new DbMcpError(
6817
7288
  "Vector dimensions must match",
6818
7289
  "VECTOR_MISMATCH",
6819
- "validation" /* VALIDATION */
7290
+ ErrorCategory.VALIDATION
6820
7291
  );
6821
7292
  }
6822
7293
  let dotProd = 0;
@@ -6837,7 +7308,7 @@ function euclideanDistance(a, b) {
6837
7308
  throw new DbMcpError(
6838
7309
  "Vector dimensions must match",
6839
7310
  "VECTOR_MISMATCH",
6840
- "validation" /* VALIDATION */
7311
+ ErrorCategory.VALIDATION
6841
7312
  );
6842
7313
  }
6843
7314
  let sum = 0;
@@ -6854,7 +7325,7 @@ function dotProduct(a, b) {
6854
7325
  throw new DbMcpError(
6855
7326
  "Vector dimensions must match",
6856
7327
  "VECTOR_MISMATCH",
6857
- "validation" /* VALIDATION */
7328
+ ErrorCategory.VALIDATION
6858
7329
  );
6859
7330
  }
6860
7331
  let sum = 0;
@@ -6884,7 +7355,7 @@ function parseVector(value) {
6884
7355
  throw new DbMcpError(
6885
7356
  "Invalid vector format",
6886
7357
  "VECTOR_INVALID",
6887
- "validation" /* VALIDATION */
7358
+ ErrorCategory.VALIDATION
6888
7359
  );
6889
7360
  }
6890
7361
 
@@ -8005,8 +8476,8 @@ var StatsSampleSchema = z.object({
8005
8476
  table: z.string().describe("Table name"),
8006
8477
  sampleSize: z.preprocess(
8007
8478
  coerceNumber4,
8008
- z.number().optional().default(100).describe(
8009
- "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."
8010
8481
  )
8011
8482
  ),
8012
8483
  conditions: z.array(WhereConditionSchema).optional().describe("Optional WHERE conditions"),
@@ -8429,7 +8900,7 @@ function createPercentileTool(adapter) {
8429
8900
  }
8430
8901
  };
8431
8902
  }
8432
- var MAX_SAMPLE_SIZE = 1e3;
8903
+ var MAX_SAMPLE_SIZE = 50;
8433
8904
  function createSampleTool(adapter) {
8434
8905
  return {
8435
8906
  name: "sqlite_stats_sample",
@@ -9119,7 +9590,7 @@ function matrixInverse(A) {
9119
9590
  throw new DbMcpError(
9120
9591
  "Matrix is singular, cannot compute inverse",
9121
9592
  "MATH_SINGULAR",
9122
- "validation" /* VALIDATION */
9593
+ ErrorCategory.VALIDATION
9123
9594
  );
9124
9595
  }
9125
9596
  for (let j = 0; j < 2 * n; j++) {
@@ -9186,7 +9657,7 @@ function createRegressionTool(adapter) {
9186
9657
  throw new DbMcpError(
9187
9658
  `Insufficient data in table '${input.table}' for degree ${degree} regression (need at least ${degree + 1} points, got ${pairs.length})`,
9188
9659
  "STATS_INSUFFICIENT_SAMPLE",
9189
- "validation" /* VALIDATION */
9660
+ ErrorCategory.VALIDATION
9190
9661
  );
9191
9662
  }
9192
9663
  const X = pairs.map(
@@ -9295,7 +9766,7 @@ function createHypothesisTool(adapter) {
9295
9766
  throw new DbMcpError(
9296
9767
  "Insufficient sample size for t-test",
9297
9768
  "STATS_INSUFFICIENT_SAMPLE",
9298
- "validation" /* VALIDATION */
9769
+ ErrorCategory.VALIDATION
9299
9770
  );
9300
9771
  }
9301
9772
  const tStatistic = (mean - expectedMean) / (stdDev / Math.sqrt(n));
@@ -9303,7 +9774,7 @@ function createHypothesisTool(adapter) {
9303
9774
  throw new DbMcpError(
9304
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.`,
9305
9776
  "STATS_COMPUTATION_FAILED",
9306
- "validation" /* VALIDATION */
9777
+ ErrorCategory.VALIDATION
9307
9778
  );
9308
9779
  }
9309
9780
  const pValue = tDistPValue(tStatistic, df);
@@ -9326,7 +9797,7 @@ function createHypothesisTool(adapter) {
9326
9797
  throw new DbMcpError(
9327
9798
  "column2 is required for two-sample t-test",
9328
9799
  "STATS_MISSING_COLUMN",
9329
- "validation" /* VALIDATION */
9800
+ ErrorCategory.VALIDATION
9330
9801
  );
9331
9802
  }
9332
9803
  await validateColumnExists(adapter, input.table, input.column2);
@@ -9353,7 +9824,7 @@ function createHypothesisTool(adapter) {
9353
9824
  throw new DbMcpError(
9354
9825
  "Insufficient sample size for t-test",
9355
9826
  "STATS_INSUFFICIENT_SAMPLE",
9356
- "validation" /* VALIDATION */
9827
+ ErrorCategory.VALIDATION
9357
9828
  );
9358
9829
  }
9359
9830
  const tStatistic = (mean1 - mean2) / Math.sqrt(var1 / n1 + var2 / n2);
@@ -9361,7 +9832,7 @@ function createHypothesisTool(adapter) {
9361
9832
  throw new DbMcpError(
9362
9833
  `Cannot compute t-statistic: var1=${var1.toFixed(4)}, var2=${var2.toFixed(4)}. This may indicate zero variance or non-numeric data.`,
9363
9834
  "STATS_COMPUTATION_FAILED",
9364
- "validation" /* VALIDATION */
9835
+ ErrorCategory.VALIDATION
9365
9836
  );
9366
9837
  }
9367
9838
  const dfNum = Math.pow(var1 / n1 + var2 / n2, 2);
@@ -9385,7 +9856,7 @@ function createHypothesisTool(adapter) {
9385
9856
  throw new DbMcpError(
9386
9857
  "groupColumn is required for chi-square test",
9387
9858
  "STATS_MISSING_COLUMN",
9388
- "validation" /* VALIDATION */
9859
+ ErrorCategory.VALIDATION
9389
9860
  );
9390
9861
  }
9391
9862
  await validateColumnExists(adapter, input.table, input.groupColumn);
@@ -9422,7 +9893,7 @@ function createHypothesisTool(adapter) {
9422
9893
  throw new DbMcpError(
9423
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.`,
9424
9895
  "STATS_INSUFFICIENT_CATEGORIES",
9425
- "validation" /* VALIDATION */
9896
+ ErrorCategory.VALIDATION
9426
9897
  );
9427
9898
  }
9428
9899
  const pValue = Math.exp(-chiSquare / 2);
@@ -10089,7 +10560,7 @@ var DropViewSchema = z.object({
10089
10560
  });
10090
10561
  var DbStatSchema = z.object({
10091
10562
  table: z.string().optional().describe("Optional table name to filter"),
10092
- summarize: z.boolean().optional().default(false).describe(
10563
+ summarize: z.boolean().optional().default(true).describe(
10093
10564
  "If true, return aggregated per-table stats instead of raw page-level data"
10094
10565
  ),
10095
10566
  limit: z.preprocess(
@@ -10344,34 +10815,6 @@ function createDropViewTool(adapter) {
10344
10815
  };
10345
10816
  }
10346
10817
 
10347
- // src/utils/progress-utils.ts
10348
- function buildProgressContext(ctx) {
10349
- if (ctx?.server === void 0 || ctx.progressToken === void 0) {
10350
- return void 0;
10351
- }
10352
- return {
10353
- server: ctx.server,
10354
- progressToken: ctx.progressToken
10355
- };
10356
- }
10357
- async function sendProgress(ctx, progress, total, message) {
10358
- if (ctx === void 0) return;
10359
- if (ctx.progressToken === void 0) return;
10360
- try {
10361
- const notification = {
10362
- method: "notifications/progress",
10363
- params: {
10364
- progressToken: ctx.progressToken,
10365
- progress,
10366
- ...total !== void 0 && { total },
10367
- ...message !== void 0 && message !== "" && { message }
10368
- }
10369
- };
10370
- await ctx.server.notification(notification);
10371
- } catch {
10372
- }
10373
- }
10374
-
10375
10818
  // src/adapters/sqlite/tools/virtual/analysis.ts
10376
10819
  function createDbStatTool(adapter) {
10377
10820
  return {
@@ -10638,7 +11081,9 @@ function createVirtualTableInfoTool(adapter) {
10638
11081
  module: "unknown",
10639
11082
  sql: "",
10640
11083
  error: `Virtual table '${input.tableName}' not found`,
10641
- code: "TABLE_NOT_FOUND"
11084
+ code: "TABLE_NOT_FOUND",
11085
+ category: "resource",
11086
+ recoverable: false
10642
11087
  };
10643
11088
  }
10644
11089
  const sqlStr = typeof sqlResult.rows[0]?.["sql"] === "string" ? sqlResult.rows[0]["sql"] : "";
@@ -10756,10 +11201,10 @@ function createCsvTableTool(adapter) {
10756
11201
  try {
10757
11202
  const input = CreateCsvTableSchema.parse(params);
10758
11203
  const safeTableName = sanitizeIdentifier(input.tableName);
10759
- if (!path.isAbsolute(input.filePath)) {
11204
+ if (!nodePath.isAbsolute(input.filePath)) {
10760
11205
  return {
10761
11206
  success: false,
10762
- 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)}`,
10763
11208
  code: "VALIDATION_ERROR",
10764
11209
  category: "validation",
10765
11210
  message: "",
@@ -10767,20 +11212,37 @@ function createCsvTableTool(adapter) {
10767
11212
  columns: []
10768
11213
  };
10769
11214
  }
10770
- const pathCheck = validateSameDirPath(
10771
- input.filePath,
10772
- adapter.getConfiguredPath()
10773
- );
10774
- if (!pathCheck.valid) {
10775
- return {
10776
- success: false,
10777
- error: pathCheck.error,
10778
- code: "SECURITY_ERROR",
10779
- category: "security",
10780
- message: "",
10781
- sql: "",
10782
- columns: []
10783
- };
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
+ }
10784
11246
  }
10785
11247
  const { available: csvAvailable } = await isCsvModuleAvailable(adapter);
10786
11248
  if (!csvAvailable) {
@@ -10863,10 +11325,10 @@ function createAnalyzeCsvSchemaTool(adapter) {
10863
11325
  columns: []
10864
11326
  };
10865
11327
  }
10866
- if (!path.isAbsolute(input.filePath)) {
11328
+ if (!nodePath.isAbsolute(input.filePath)) {
10867
11329
  return {
10868
11330
  success: false,
10869
- 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)}`,
10870
11332
  code: "VALIDATION_ERROR",
10871
11333
  category: "validation",
10872
11334
  hasHeader: false,
@@ -10874,20 +11336,37 @@ function createAnalyzeCsvSchemaTool(adapter) {
10874
11336
  columns: []
10875
11337
  };
10876
11338
  }
10877
- const validation = validateSameDirPath(
10878
- input.filePath,
10879
- adapter.getConfiguredPath()
10880
- );
10881
- if (!validation.valid) {
10882
- return {
10883
- success: false,
10884
- error: validation.error || "Path validation failed",
10885
- code: "SECURITY_ERROR",
10886
- category: "validation",
10887
- hasHeader: false,
10888
- rowCount: 0,
10889
- columns: []
10890
- };
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
+ }
10891
11370
  }
10892
11371
  const tempName = `_csv_analyze_${Date.now()}`;
10893
11372
  try {
@@ -11318,10 +11797,7 @@ function createVectorCreateTableTool(adapter) {
11318
11797
  try {
11319
11798
  const input = VectorCreateTableSchema.parse(params);
11320
11799
  if (input.dimensions < 1) {
11321
- return {
11322
- success: false,
11323
- error: "Dimensions must be at least 1"
11324
- };
11800
+ throw new ValidationError("Dimensions must be at least 1");
11325
11801
  }
11326
11802
  const tableName = sanitizeIdentifier(input.tableName);
11327
11803
  const columns = [
@@ -11407,15 +11883,15 @@ function createVectorStoreTool(adapter) {
11407
11883
  try {
11408
11884
  const input = VectorStoreSchema.parse(params);
11409
11885
  if (input.vector.length === 0) {
11410
- return {
11411
- success: false,
11412
- error: "vector is required and must be a non-empty array of numbers"
11413
- };
11886
+ throw new ValidationError(
11887
+ "vector is required and must be a non-empty array of numbers"
11888
+ );
11414
11889
  }
11415
11890
  const table = sanitizeIdentifier(input.table);
11416
11891
  const idColumn = sanitizeIdentifier(input.idColumn);
11417
11892
  const vectorColumn = sanitizeIdentifier(input.vectorColumn);
11418
11893
  let hasDimsColumn = false;
11894
+ let expectedDims;
11419
11895
  try {
11420
11896
  const dims = await validateDimensions(
11421
11897
  adapter,
@@ -11424,15 +11900,15 @@ function createVectorStoreTool(adapter) {
11424
11900
  input.vectorColumn
11425
11901
  );
11426
11902
  hasDimsColumn = dims.hasDimsColumn;
11427
- if (dims.expectedDims !== void 0 && input.vector.length !== dims.expectedDims) {
11428
- return {
11429
- success: false,
11430
- error: `Dimension mismatch: vector has ${input.vector.length} dimensions but table expects ${dims.expectedDims}`,
11431
- code: "DIMENSION_MISMATCH"
11432
- };
11433
- }
11903
+ expectedDims = dims.expectedDims;
11434
11904
  } catch {
11435
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
+ }
11436
11912
  const vectorJson = JSON.stringify(input.vector);
11437
11913
  const updateSql = `UPDATE ${table} SET ${vectorColumn} = ? WHERE ${idColumn} = ?`;
11438
11914
  const updateResult = await adapter.executeWriteQuery(updateSql, [
@@ -11474,10 +11950,11 @@ function createVectorBatchStoreTool(adapter) {
11474
11950
  input.table
11475
11951
  ]);
11476
11952
  if (!checkResult.rows || checkResult.rows.length === 0) {
11477
- return {
11478
- success: false,
11479
- error: `Table '${input.table}' does not exist`
11480
- };
11953
+ throw new ResourceNotFoundError(
11954
+ `Table '${input.table}' does not exist`,
11955
+ "TABLE_NOT_FOUND",
11956
+ { resourceType: "table", resourceName: input.table }
11957
+ );
11481
11958
  }
11482
11959
  return {
11483
11960
  success: true,
@@ -11488,6 +11965,7 @@ function createVectorBatchStoreTool(adapter) {
11488
11965
  const idColumn = sanitizeIdentifier(input.idColumn);
11489
11966
  const vectorColumn = sanitizeIdentifier(input.vectorColumn);
11490
11967
  let hasDimsColumn = false;
11968
+ let expectedDims;
11491
11969
  try {
11492
11970
  const dims = await validateDimensions(
11493
11971
  adapter,
@@ -11496,19 +11974,19 @@ function createVectorBatchStoreTool(adapter) {
11496
11974
  input.vectorColumn
11497
11975
  );
11498
11976
  hasDimsColumn = dims.hasDimsColumn;
11499
- if (dims.expectedDims !== void 0) {
11500
- for (let i = 0; i < input.items.length; i++) {
11501
- const item = input.items[i];
11502
- if (item && item.vector.length !== dims.expectedDims) {
11503
- return {
11504
- success: false,
11505
- error: `Dimension mismatch at item[${i}]: vector has ${item.vector.length} dimensions but table expects ${dims.expectedDims}`,
11506
- code: "DIMENSION_MISMATCH"
11507
- };
11508
- }
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
+ );
11509
11988
  }
11510
11989
  }
11511
- } catch {
11512
11990
  }
11513
11991
  let stored = 0;
11514
11992
  for (const item of input.items) {
@@ -11578,10 +12056,9 @@ function createVectorSearchTool(adapter) {
11578
12056
  try {
11579
12057
  const input = VectorSearchSchema.parse(params);
11580
12058
  if (input.queryVector.length === 0) {
11581
- return {
11582
- success: false,
11583
- error: "queryVector is required and must be a non-empty array of numbers"
11584
- };
12059
+ throw new ValidationError(
12060
+ "queryVector is required and must be a non-empty array of numbers"
12061
+ );
11585
12062
  }
11586
12063
  const table = sanitizeIdentifier(input.table);
11587
12064
  const vectorColumn = sanitizeIdentifier(input.vectorColumn);
@@ -11629,10 +12106,9 @@ function createVectorSearchTool(adapter) {
11629
12106
  score = cosineSimilarity(queryVector, storedVector);
11630
12107
  break;
11631
12108
  default:
11632
- return {
11633
- success: false,
11634
- error: `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`
11635
- };
12109
+ throw new ValidationError(
12110
+ `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`
12111
+ );
11636
12112
  }
11637
12113
  scored.push({
11638
12114
  ...row,
@@ -11700,26 +12176,17 @@ function createVectorGetTool(adapter) {
11700
12176
  const sql = `SELECT * FROM ${table} WHERE ${idColumn} = ?`;
11701
12177
  const result = await adapter.executeReadQuery(sql, [input.id]);
11702
12178
  if (!result.rows || result.rows.length === 0) {
11703
- return {
11704
- success: false,
11705
- error: "Vector not found",
11706
- code: "VECTOR_NOT_FOUND"
11707
- };
12179
+ throw new ResourceNotFoundError("Vector not found", "VECTOR_NOT_FOUND", { resourceType: "vector", resourceName: String(input.id) });
11708
12180
  }
11709
12181
  const row = result.rows[0];
11710
12182
  if (!row) {
11711
- return {
11712
- success: false,
11713
- error: "Vector not found",
11714
- code: "VECTOR_NOT_FOUND"
11715
- };
12183
+ throw new ResourceNotFoundError("Vector not found", "VECTOR_NOT_FOUND", { resourceType: "vector", resourceName: String(input.id) });
11716
12184
  }
11717
12185
  const rawVector = row[input.vectorColumn];
11718
12186
  if (rawVector === void 0 || rawVector === null) {
11719
- return {
11720
- success: false,
11721
- error: `Column '${input.vectorColumn}' not found or contains NULL. Available columns: ${Object.keys(row).join(", ")}`
11722
- };
12187
+ throw new ValidationError(
12188
+ `Column '${input.vectorColumn}' not found or contains NULL. Available columns: ${Object.keys(row).join(", ")}`
12189
+ );
11723
12190
  }
11724
12191
  const vectorData = parseVector(rawVector);
11725
12192
  const metadata = Object.fromEntries(
@@ -11891,13 +12358,9 @@ function createVectorNormalizeTool() {
11891
12358
  try {
11892
12359
  const input = VectorNormalizeSchema.parse(params);
11893
12360
  if (input.vector.length === 0) {
11894
- return Promise.resolve({
11895
- success: false,
11896
- error: "vector is required and must be a non-empty array of numbers",
11897
- code: "VALIDATION_ERROR",
11898
- category: "validation",
11899
- recoverable: false
11900
- });
12361
+ throw new ValidationError(
12362
+ "vector is required and must be a non-empty array of numbers"
12363
+ );
11901
12364
  }
11902
12365
  const normalized = normalizeVector(input.vector);
11903
12366
  return Promise.resolve({
@@ -11925,22 +12388,14 @@ function createVectorDistanceTool() {
11925
12388
  annotations: readOnly("Vector Distance"),
11926
12389
  handler: (params, _context) => {
11927
12390
  try {
11928
- const input = VectorDistanceSchema.parse(params);
11929
- if (input.vector1.length === 0 || input.vector2.length === 0) {
11930
- return Promise.resolve({
11931
- success: false,
11932
- error: "vector1 and vector2 are required and must be non-empty arrays of numbers",
11933
- code: "VALIDATION_ERROR",
11934
- category: "validation",
11935
- recoverable: false
11936
- });
12391
+ const input = VectorDistanceSchema.parse(params);
12392
+ if (input.vector1.length === 0 || input.vector2.length === 0) {
12393
+ throw new ValidationError(
12394
+ "vector1 and vector2 are required and must be non-empty arrays of numbers"
12395
+ );
11937
12396
  }
11938
12397
  if (input.vector1.length !== input.vector2.length) {
11939
- return Promise.resolve({
11940
- success: false,
11941
- error: "Vector dimensions must match",
11942
- code: "DIMENSION_MISMATCH"
11943
- });
12398
+ throw new ValidationError("Vector dimensions must match", "DIMENSION_MISMATCH");
11944
12399
  }
11945
12400
  let result;
11946
12401
  switch (input.metric) {
@@ -11954,13 +12409,9 @@ function createVectorDistanceTool() {
11954
12409
  result = 1 - cosineSimilarity(input.vector1, input.vector2);
11955
12410
  break;
11956
12411
  default:
11957
- return Promise.resolve({
11958
- success: false,
11959
- error: `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`,
11960
- code: "VALIDATION_ERROR",
11961
- category: "validation",
11962
- recoverable: false
11963
- });
12412
+ throw new ValidationError(
12413
+ `Invalid metric '${input.metric}'. Valid values: cosine, euclidean, dot`
12414
+ );
11964
12415
  }
11965
12416
  return Promise.resolve({
11966
12417
  success: true,
@@ -12103,14 +12554,14 @@ function requireCoordinate(value, name, min, max) {
12103
12554
  throw new DbMcpError(
12104
12555
  `Invalid ${name}: value is not a valid number.`,
12105
12556
  "GEO_INVALID_COORDINATES",
12106
- "validation" /* VALIDATION */
12557
+ ErrorCategory.VALIDATION
12107
12558
  );
12108
12559
  }
12109
12560
  if (value < min || value > max) {
12110
12561
  throw new DbMcpError(
12111
12562
  `Invalid ${name}: ${String(value)}. Must be between ${String(min)} and ${String(max)}.`,
12112
12563
  "GEO_INVALID_COORDINATES",
12113
- "validation" /* VALIDATION */
12564
+ ErrorCategory.VALIDATION
12114
12565
  );
12115
12566
  }
12116
12567
  return value;
@@ -12120,7 +12571,7 @@ function requireNumber(value, name) {
12120
12571
  throw new DbMcpError(
12121
12572
  `Invalid ${name}: value is not a valid number.`,
12122
12573
  "GEO_INVALID_COORDINATES",
12123
- "validation" /* VALIDATION */
12574
+ ErrorCategory.VALIDATION
12124
12575
  );
12125
12576
  }
12126
12577
  return value;
@@ -12396,19 +12847,35 @@ function createBackupTool(adapter) {
12396
12847
  path: ""
12397
12848
  };
12398
12849
  }
12399
- const pathCheck = validateSameDirPath(
12400
- input.targetPath,
12401
- adapter.getConfiguredPath()
12402
- );
12403
- if (input.targetPath.includes("..") || !pathCheck.valid) {
12404
- return {
12405
- success: false,
12406
- error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12407
- code: "SECURITY_ERROR",
12408
- path: input.targetPath
12409
- };
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;
12410
12878
  }
12411
- const resolvedPath = pathCheck.resolvedPath;
12412
12879
  const escapedPath = resolvedPath.replace(/'/g, "''");
12413
12880
  const sql = `VACUUM INTO '${escapedPath}'`;
12414
12881
  const start = Date.now();
@@ -12418,7 +12885,7 @@ function createBackupTool(adapter) {
12418
12885
  progress,
12419
12886
  1,
12420
12887
  2,
12421
- `Creating backup at ${path__default.basename(resolvedPath)}...`
12888
+ `Creating backup at ${nodePath__default.basename(resolvedPath)}...`
12422
12889
  );
12423
12890
  await adapter.executeQuery(sql);
12424
12891
  await sendProgress(progress, 2, 2, "Backup complete");
@@ -12468,18 +12935,34 @@ function createVacuumIntoTool(adapter) {
12468
12935
  ...formatHandlerError(new ValidationError("outputPath is required"))
12469
12936
  };
12470
12937
  }
12471
- const pathCheck = validateSameDirPath(
12472
- input.outputPath,
12473
- adapter.getConfiguredPath()
12474
- );
12475
- if (input.outputPath.includes("..") || !pathCheck.valid) {
12476
- return {
12477
- success: false,
12478
- error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12479
- code: "SECURITY_ERROR"
12480
- };
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;
12481
12965
  }
12482
- const resolvedPath = pathCheck.resolvedPath;
12483
12966
  const escapedPath = resolvedPath.replace(/'/g, "''");
12484
12967
  const sql = `VACUUM INTO '${escapedPath}'`;
12485
12968
  const start = Date.now();
@@ -12706,26 +13189,53 @@ function createRestoreTool(adapter) {
12706
13189
  sourcePath: ""
12707
13190
  };
12708
13191
  }
12709
- const resolvedPath = path__default.resolve(input.sourcePath);
12710
- const pathCheck = validateSameDirPath(
12711
- input.sourcePath,
12712
- adapter.getConfiguredPath()
12713
- );
12714
- if (!pathCheck.valid) {
12715
- return {
12716
- ...formatHandlerError(new ValidationError(pathCheck.error)),
12717
- sourcePath: input.sourcePath
12718
- };
12719
- }
12720
- if (!fs.existsSync(resolvedPath)) {
12721
- return {
12722
- ...formatHandlerError(
12723
- new ValidationError(`Source file not found: ${input.sourcePath}`)
12724
- ),
12725
- sourcePath: input.sourcePath
12726
- };
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, "''");
12727
13238
  }
12728
- const escapedPath = pathCheck.resolvedPath.replace(/'/g, "''");
12729
13239
  await adapter.executeReadQuery("PRAGMA integrity_check(1)");
12730
13240
  await sendProgress(progress, 2, 5, "Attaching backup database...");
12731
13241
  try {
@@ -12938,21 +13448,37 @@ function createDumpTool(adapter) {
12938
13448
  ...formatHandlerError(new ValidationError("outputPath is required"))
12939
13449
  };
12940
13450
  }
12941
- const pathCheck = validateSameDirPath(
12942
- input.outputPath,
12943
- adapter.getConfiguredPath()
12944
- );
12945
- if (input.outputPath.includes("..") || !pathCheck.valid) {
12946
- return {
12947
- success: false,
12948
- error: !pathCheck.valid ? pathCheck.error : "Invalid path: must not contain '..'",
12949
- code: "SECURITY_ERROR",
12950
- path: input.outputPath
12951
- };
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;
12952
13479
  }
12953
13480
  const progress = buildProgressContext(context);
12954
13481
  const start = Date.now();
12955
- const resolvedPath = pathCheck.resolvedPath;
12956
13482
  await sendProgress(progress, 1, 4, "Preparing dump file...");
12957
13483
  const stream = fs.createWriteStream(resolvedPath, { encoding: "utf8" });
12958
13484
  try {
@@ -13058,29 +13584,57 @@ function createVerifyBackupTool(adapter) {
13058
13584
  backupPath: ""
13059
13585
  };
13060
13586
  }
13061
- const resolvedPath = path__default.resolve(input.backupPath);
13062
- const pathCheck = validateSameDirPath(
13063
- input.backupPath,
13064
- adapter.getConfiguredPath()
13065
- );
13066
- if (!pathCheck.valid) {
13067
- return {
13068
- ...formatHandlerError(new ValidationError(pathCheck.error)),
13069
- backupPath: input.backupPath
13070
- };
13071
- }
13072
- if (!fs.existsSync(resolvedPath)) {
13073
- return {
13074
- ...formatHandlerError(
13075
- new ValidationError(
13076
- `Backup file not found: ${input.backupPath}`,
13077
- "FILE_NOT_FOUND"
13078
- )
13079
- ),
13080
- backupPath: input.backupPath
13081
- };
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, "''");
13082
13637
  }
13083
- const escapedPath = resolvedPath.replace(/'/g, "''");
13084
13638
  try {
13085
13639
  await adapter.rawQuery(
13086
13640
  `ATTACH DATABASE '${escapedPath}' AS backup_verify`
@@ -13453,18 +14007,37 @@ function createAttachDatabaseTool(adapter) {
13453
14007
  code: "VALIDATION_ERROR"
13454
14008
  };
13455
14009
  }
13456
- const pathCheck = validateSameDirPath(
13457
- input.filepath,
13458
- adapter.getConfiguredPath()
13459
- );
13460
- if (!pathCheck.valid) {
13461
- return {
13462
- success: false,
13463
- error: pathCheck.error,
13464
- code: "SECURITY_ERROR"
13465
- };
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;
13466
14040
  }
13467
- const escapedPath = pathCheck.resolvedPath.replace(/'/g, "''");
13468
14041
  await adapter.rawQuery(
13469
14042
  `ATTACH DATABASE '${escapedPath}' AS "${input.alias.replace(/"/g, '""')}"`
13470
14043
  );
@@ -13472,7 +14045,7 @@ function createAttachDatabaseTool(adapter) {
13472
14045
  success: true,
13473
14046
  message: `Database attached as '${input.alias}'`,
13474
14047
  alias: input.alias,
13475
- filepath: pathCheck.resolvedPath.split(/[\\/]/).pop() ?? ""
14048
+ filepath: finalResolvedPath.split(/[\\/]/).pop() ?? ""
13476
14049
  };
13477
14050
  } catch (error) {
13478
14051
  return formatHandlerError(error);
@@ -14034,13 +14607,13 @@ var IndexAuditSchema = z.object({
14034
14607
  }).default({ recommendComposite: false });
14035
14608
  var SchemaSnapshotSchema = z.object({
14036
14609
  sections: z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional().describe("Specific sections to include (default: all)"),
14037
- compact: z.boolean().optional().describe(
14038
- "Omit column details from tables section for reduced payload (default: false)"
14610
+ compact: z.boolean().optional().default(true).describe(
14611
+ "Omit column details from tables section for reduced payload (default: true)"
14039
14612
  ),
14040
14613
  excludeSystemTables: z.boolean().optional().describe(
14041
14614
  "Exclude SpatiaLite system tables, views, indexes, and triggers (default: true)"
14042
14615
  )
14043
- }).default({});
14616
+ }).default({ compact: true });
14044
14617
  var MigrationRisksSchema = z.object({
14045
14618
  statements: z.array(z.string()).optional().default([]).describe("Array of DDL statements to analyze for risks")
14046
14619
  }).default({ statements: [] });
@@ -14059,6 +14632,7 @@ var ConstraintAnalysisSchema = z.object({
14059
14632
  )
14060
14633
  }).default({});
14061
14634
  var DependencyGraphSchema = z.object({
14635
+ table: z.string().optional().describe("Optional table name to filter the graph"),
14062
14636
  includeRowCounts: z.boolean().optional().describe("Include row counts per table (default: true)"),
14063
14637
  nodesOnly: z.boolean().optional().describe(
14064
14638
  "Return only nodes without edges for a lightweight response (default: false)"
@@ -14077,7 +14651,10 @@ var TopologicalSortSchema = z.object({
14077
14651
  }).default({});
14078
14652
  var CascadeSimulatorSchema = z.object({
14079
14653
  table: z.string().optional().default("").describe("Table name to simulate deletion from"),
14080
- operation: z.enum(["DELETE", "DROP", "TRUNCATE"]).optional().describe("Operation to simulate (default: DELETE)"),
14654
+ operation: z.preprocess(
14655
+ (val) => typeof val === "string" ? val.toUpperCase() : val,
14656
+ z.enum(["DELETE", "DROP", "TRUNCATE"])
14657
+ ).optional().describe("Operation to simulate (default: DELETE)"),
14081
14658
  compact: z.boolean().optional().describe(
14082
14659
  "Omit path arrays from affected entries to reduce payload (default: false)"
14083
14660
  )
@@ -14144,10 +14721,16 @@ var SchemaDiffOutputSchema = z.object({
14144
14721
  comparedAt: z.string().optional()
14145
14722
  }).extend(ErrorResponseFields.shape);
14146
14723
  var SchemaDiffSchema = z.object({
14147
- 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(
14148
14728
  "Baseline schema \u2014 'current' to snapshot live DB, or an inline snapshot object from a previous sqlite_schema_snapshot call"
14149
14729
  ),
14150
- 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(
14151
14734
  "Target schema to compare against baseline \u2014 'current' to snapshot live DB, or an inline snapshot object"
14152
14735
  ),
14153
14736
  sections: z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional().describe("Sections to compare (default: all)"),
@@ -14169,42 +14752,80 @@ function createDependencyGraphTool(adapter) {
14169
14752
  handler: async (params, _context) => {
14170
14753
  try {
14171
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
+ }
14172
14771
  const includeRowCounts = input.includeRowCounts !== false;
14173
14772
  const { nodes, edges } = await buildForeignKeyGraph(adapter, {
14174
14773
  excludeSystemTables: input.excludeSystemTables,
14175
14774
  includeRowCounts
14176
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
+ }
14177
14798
  const adjacency = /* @__PURE__ */ new Map();
14178
- for (const node of nodes) {
14799
+ for (const node of finalNodes) {
14179
14800
  adjacency.set(node.table, []);
14180
14801
  }
14181
- for (const edge of edges) {
14802
+ for (const edge of finalEdges) {
14182
14803
  adjacency.get(edge.from)?.push(edge.to);
14183
14804
  }
14184
14805
  const cycles = detectCycles(
14185
14806
  adjacency,
14186
- nodes.map((n) => n.table)
14807
+ finalNodes.map((n) => n.table)
14187
14808
  );
14188
- const referencedTables = new Set(edges.map((e) => e.to));
14189
- const referencingTables = new Set(edges.map((e) => e.from));
14190
- 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(
14191
14812
  (n) => referencedTables.has(n.table) && !referencingTables.has(n.table)
14192
14813
  ).map((n) => n.table);
14193
- const leafTables = nodes.filter(
14814
+ const leafTables = finalNodes.filter(
14194
14815
  (n) => referencingTables.has(n.table) && !referencedTables.has(n.table)
14195
14816
  ).map((n) => n.table);
14196
14817
  const nodesOnly = input.nodesOnly === true;
14197
14818
  return {
14198
14819
  success: true,
14199
- nodes: nodes.map((n) => ({
14820
+ nodes: finalNodes.map((n) => ({
14200
14821
  table: n.table,
14201
14822
  ...includeRowCounts ? { rowCount: n.rowCount } : {}
14202
14823
  })),
14203
- ...!nodesOnly ? { edges } : {},
14824
+ ...!nodesOnly ? { edges: finalEdges } : {},
14204
14825
  circularDependencies: cycles.length > 0 ? cycles : void 0,
14205
14826
  stats: {
14206
- totalTables: nodes.length,
14207
- totalRelationships: edges.length,
14827
+ totalTables: finalNodes.length,
14828
+ totalRelationships: finalEdges.length,
14208
14829
  rootTables,
14209
14830
  leafTables
14210
14831
  }
@@ -15781,7 +16402,7 @@ var MigrationStatusOutputSchema = z.object({
15781
16402
  }).optional(),
15782
16403
  sourceSystems: z.array(z.string()).optional()
15783
16404
  }).extend(ErrorResponseFields.shape);
15784
- var MigrationInitSchema = z.object({}).default({});
16405
+ var MigrationInitSchema = z.object({}).strict().default({});
15785
16406
  var MigrationRecordSchema = z.object({
15786
16407
  version: z.string().describe("Version identifier (e.g., '1.0.0', '2024-01-15-add-users')"),
15787
16408
  description: z.string().optional().describe("Human-readable description of the migration"),
@@ -15856,7 +16477,7 @@ var MigrationHistorySchema = z.object({
15856
16477
  "Omit migrationHash and sourceSystem from records to reduce payload (default: false)"
15857
16478
  )
15858
16479
  }).default({});
15859
- var MigrationStatusSchema = z.object({}).default({});
16480
+ var MigrationStatusSchema = z.object({}).strict().default({});
15860
16481
  var MIGRATIONS_TABLE = "_mcp_migrations";
15861
16482
  function toMigrationRecord(row) {
15862
16483
  return {
@@ -15884,7 +16505,7 @@ function validateMigrationSql(sql) {
15884
16505
  const cleanSql = sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
15885
16506
  const upperSql = cleanSql.toUpperCase();
15886
16507
  if (upperSql.includes("ATTACH ") || upperSql.includes("DETACH ") || upperSql.includes("PRAGMA ") || upperSql.includes("LOAD_EXTENSION(")) {
15887
- throw new Error(
16508
+ throw new ValidationError(
15888
16509
  `Migration validation failed: unauthorized command or function call`
15889
16510
  );
15890
16511
  }
@@ -16577,6 +17198,10 @@ var POSITIONAL_PARAM_MAP = {
16577
17198
  listConstraints: "table",
16578
17199
  dateAdd: ["table", "column", "amount", "unit", "whereClause"],
16579
17200
  dateDiff: ["table", "column1", "column2", "unit", "whereClause"],
17201
+ enableVersioning: "table",
17202
+ disableVersioning: "table",
17203
+ checkVersion: ["table", "rowId"],
17204
+ conditionalUpdate: ["table", "data", "conditions", "expectedVersion"],
16580
17205
  // JSON
16581
17206
  extract: ["table", "column", "path", "whereClause"],
16582
17207
  set: ["table", "column", "path", "value", "whereClause"],
@@ -16731,7 +17356,7 @@ function normalizeParams(methodName, args) {
16731
17356
  if (Array.isArray(paramMapping2) && paramMapping2[0] !== void 0) {
16732
17357
  return { [paramMapping2[0]]: arg };
16733
17358
  }
16734
- throw new Error(
17359
+ throw new ValidationError(
16735
17360
  `Positional arguments are not supported for method: ${methodName}. Please use an options object.`
16736
17361
  );
16737
17362
  }
@@ -16739,7 +17364,7 @@ function normalizeParams(methodName, args) {
16739
17364
  }
16740
17365
  const paramMapping = POSITIONAL_PARAM_MAP[methodName];
16741
17366
  if (paramMapping === void 0) {
16742
- throw new Error(
17367
+ throw new ValidationError(
16743
17368
  `Positional arguments are not supported for method: ${methodName}. Please use an options object.`
16744
17369
  );
16745
17370
  }
@@ -16789,7 +17414,7 @@ function createGroupApi(groupName, tools, baseContext) {
16789
17414
  api[methodName] = async (...args) => {
16790
17415
  if (baseContext?.auth !== void 0) {
16791
17416
  if (!scopesGrantToolAccess(baseContext.auth.scopes ?? [], tool.name)) {
16792
- throw new Error(
17417
+ throw new AuthorizationError(
16793
17418
  `Forbidden: Required scope for tool '${tool.name}' not granted.`
16794
17419
  );
16795
17420
  }
@@ -16800,7 +17425,8 @@ function createGroupApi(groupName, tools, baseContext) {
16800
17425
  requestId: crypto.randomUUID(),
16801
17426
  ...baseContext?.server !== void 0 ? { server: baseContext.server } : {},
16802
17427
  ...baseContext?.progressToken !== void 0 ? { progressToken: baseContext.progressToken } : {},
16803
- ...baseContext?.auth !== void 0 ? { auth: baseContext.auth } : {}
17428
+ ...baseContext?.auth !== void 0 ? { auth: baseContext.auth } : {},
17429
+ isCodeMode: true
16804
17430
  };
16805
17431
  const result = await tool.handler(normalizedParams, context);
16806
17432
  return result;
@@ -16962,7 +17588,8 @@ function createSqliteApi(tools, baseContext) {
16962
17588
  var DEFAULT_SANDBOX_OPTIONS = {
16963
17589
  memoryLimitMb: 128,
16964
17590
  timeoutMs: 5e3,
16965
- cpuLimitMs: 1e4
17591
+ cpuLimitMs: 1e4,
17592
+ strictIsolation: true
16966
17593
  };
16967
17594
  var DEFAULT_POOL_OPTIONS = {
16968
17595
  minInstances: 2,
@@ -17158,7 +17785,7 @@ var CodeModeSecurityManager = class {
17158
17785
  const redactedResult = redactObject(result, 0, 15);
17159
17786
  const serialized = JSON.stringify(redactedResult);
17160
17787
  if (serialized === void 0) {
17161
- throw new Error("Not serializable");
17788
+ throw new InternalError("Not serializable");
17162
17789
  }
17163
17790
  if (serialized.length > this.config.maxResultSize) {
17164
17791
  return {
@@ -17258,6 +17885,9 @@ return ${lastStmt}`;
17258
17885
  }
17259
17886
 
17260
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;
17261
17891
  var CodeModeSandbox = class _CodeModeSandbox {
17262
17892
  options;
17263
17893
  disposed = false;
@@ -17286,9 +17916,8 @@ var CodeModeSandbox = class _CodeModeSandbox {
17286
17916
  metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 }
17287
17917
  };
17288
17918
  }
17289
- const groupNameRegex = /^[a-zA-Z0-9_]+$/;
17290
17919
  for (const groupName of Object.keys(apiBindings)) {
17291
- if (!groupNameRegex.test(groupName) || groupName === "__proto__" || groupName === "constructor" || groupName === "prototype") {
17920
+ if (!GROUP_NAME_REGEX.test(groupName) || groupName === "__proto__" || groupName === "constructor" || groupName === "prototype") {
17292
17921
  return {
17293
17922
  success: false,
17294
17923
  error: `Security Error: Invalid tool group name '${groupName}'`,
@@ -17298,30 +17927,39 @@ var CodeModeSandbox = class _CodeModeSandbox {
17298
17927
  }
17299
17928
  try {
17300
17929
  const wrappedCode = `async function __wrapper() { ${code} }`;
17301
- const ast = acorn.parse(wrappedCode, {
17302
- ecmaVersion: "latest",
17303
- sourceType: "script"
17304
- });
17305
- const validateAst = (node) => {
17306
- if (node === null || node === void 0 || typeof node !== "object")
17307
- return;
17308
- const n = node;
17309
- if (n["type"] === "WithStatement") {
17310
- throw new Error("'with' statements are forbidden in sandbox code.");
17311
- }
17312
- if (n["type"] === "MemberExpression" && n["object"] !== null && n["object"] !== void 0 && typeof n["object"] === "object" && n["object"]["type"] === "Identifier") {
17313
- const objName = n["object"]["name"];
17314
- if (["process", "require", "global", "globalThis"].includes(objName)) {
17315
- 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
+ );
17316
17943
  }
17317
- }
17318
- for (const key in n) {
17319
- if (key !== "loc" && key !== "start" && key !== "end") {
17320
- 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
+ }
17949
+ }
17950
+ for (const key in n) {
17951
+ if (key !== "loc" && key !== "start" && key !== "end") {
17952
+ validateAst(n[key]);
17953
+ }
17321
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);
17322
17960
  }
17323
- };
17324
- validateAst(ast);
17961
+ astCache.set(wrappedCode, ast);
17962
+ }
17325
17963
  } catch (e) {
17326
17964
  return {
17327
17965
  success: false,
@@ -17337,6 +17975,13 @@ var CodeModeSandbox = class _CodeModeSandbox {
17337
17975
  } catch {
17338
17976
  }
17339
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
+ }
17340
17985
  const vm = await import('vm');
17341
17986
  const logs2 = [];
17342
17987
  const sandboxEnv = {
@@ -17363,6 +18008,28 @@ var CodeModeSandbox = class _CodeModeSandbox {
17363
18008
  }
17364
18009
  }
17365
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
+ );
17366
18033
  const startTime2 = performance.now();
17367
18034
  let result2;
17368
18035
  let success2 = true;
@@ -17425,62 +18092,90 @@ var CodeModeSandbox = class _CodeModeSandbox {
17425
18092
  context.evalSync(setupScript);
17426
18093
  let rpcCount = 0;
17427
18094
  const MAX_RPC_CALLS = 100;
18095
+ let batchedScript = "";
17428
18096
  for (const [groupName, groupValue] of Object.entries(apiBindings)) {
17429
18097
  if (typeof groupValue === "object" && groupValue !== null) {
17430
- context.evalSync(
17431
- `globalThis.sqlite[${JSON.stringify(groupName)}] = {};`
17432
- );
18098
+ batchedScript += `globalThis.sqlite[${JSON.stringify(groupName)}] = {};
18099
+ `;
17433
18100
  for (const [methodName, methodFn] of Object.entries(groupValue)) {
17434
18101
  if (typeof methodFn === "function") {
17435
18102
  const fnRef = new ivmLib.Reference(async (...args) => {
17436
18103
  if (++rpcCount > MAX_RPC_CALLS) {
17437
- throw new Error(
18104
+ throw new RateLimitError(
17438
18105
  `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17439
18106
  );
17440
18107
  }
17441
18108
  try {
17442
18109
  return await methodFn(...args);
17443
18110
  } catch (e) {
17444
- throw new Error(e instanceof Error ? e.message : String(e), {
17445
- cause: e
17446
- });
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
+ );
17447
18118
  }
17448
18119
  });
17449
18120
  refCleanup.push(fnRef);
17450
18121
  const refName = `fnRef_${groupName}_${methodName}`;
17451
18122
  context.global.setSync(refName, fnRef);
17452
- context.evalSync(`
17453
- globalThis.sqlite[${JSON.stringify(groupName)}][${JSON.stringify(methodName)}] = async (...args) => {
18123
+ batchedScript += `globalThis.sqlite[${JSON.stringify(groupName)}][${JSON.stringify(methodName)}] = async (...args) => {
17454
18124
  return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17455
18125
  };
17456
- `);
18126
+ `;
17457
18127
  }
17458
18128
  }
17459
18129
  } else if (typeof groupValue === "function") {
17460
18130
  const fnRef = new ivmLib.Reference(async (...args) => {
17461
18131
  if (++rpcCount > MAX_RPC_CALLS) {
17462
- throw new Error(
18132
+ throw new RateLimitError(
17463
18133
  `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17464
18134
  );
17465
18135
  }
17466
18136
  try {
17467
18137
  return await groupValue(...args);
17468
18138
  } catch (e) {
17469
- throw new Error(e instanceof Error ? e.message : String(e), {
17470
- cause: e
17471
- });
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
+ );
17472
18146
  }
17473
18147
  });
17474
18148
  refCleanup.push(fnRef);
17475
18149
  const refName = `fnRef_${groupName}`;
17476
18150
  context.global.setSync(refName, fnRef);
17477
- context.evalSync(`
17478
- globalThis.sqlite[${JSON.stringify(groupName)}] = async (...args) => {
18151
+ batchedScript += `globalThis.sqlite[${JSON.stringify(groupName)}] = async (...args) => {
17479
18152
  return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17480
18153
  };
17481
- `);
18154
+ `;
17482
18155
  }
17483
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
+ `);
17484
18179
  const wrappedCode = `(async () => { ${transformAutoReturn(code)} })()`;
17485
18180
  script = isolate.compileScriptSync(wrappedCode, {
17486
18181
  filename: `code-mode.js`
@@ -17559,7 +18254,10 @@ var SandboxPool = class _SandboxPool {
17559
18254
  }
17560
18255
  static getIvmLib() {
17561
18256
  if (!_SandboxPool.cachedIvmLib) {
17562
- throw new Error("ivmLib not initialized");
18257
+ throw new InternalError(
18258
+ "ivmLib not initialized",
18259
+ "IVMLIB_NOT_INITIALIZED"
18260
+ );
17563
18261
  }
17564
18262
  return _SandboxPool.cachedIvmLib;
17565
18263
  }
@@ -17578,8 +18276,9 @@ var SandboxPool = class _SandboxPool {
17578
18276
  await _SandboxPool.initialize();
17579
18277
  }
17580
18278
  if (this.inUseCount >= this.options.maxInstances) {
17581
- throw new Error(
17582
- `Sandbox pool exhausted (max ${this.options.maxInstances})`
18279
+ throw new InternalError(
18280
+ `Sandbox pool exhausted (max ${this.options.maxInstances})`,
18281
+ "POOL_EXHAUSTED"
17583
18282
  );
17584
18283
  }
17585
18284
  this.inUseCount++;
@@ -17716,15 +18415,14 @@ function createExecuteCodeTool(adapter) {
17716
18415
  }
17717
18416
  const clientId = _context.auth?.sub ?? _context.clientIp ?? processId;
17718
18417
  if (!await security.checkRateLimit(clientId)) {
17719
- return {
17720
- success: false,
17721
- error: `Rate limit exceeded. Maximum ${String(codemodeRateLimit)} executions per minute.`,
17722
- code: "CODEMODE_RATE_LIMITED",
17723
- category: "permission",
17724
- suggestion: "Wait before retrying. Combine multiple operations into fewer execute_code calls.",
17725
- recoverable: true,
17726
- metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 }
17727
- };
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
+ );
17728
18426
  }
17729
18427
  if (!pool) {
17730
18428
  initializePool();
@@ -17739,7 +18437,7 @@ function createExecuteCodeTool(adapter) {
17739
18437
  throw new DbMcpError(
17740
18438
  "Sandbox pool not initialized",
17741
18439
  "CODEMODE_POOL_UNINITIALIZED",
17742
- "internal" /* INTERNAL */
18440
+ ErrorCategory.INTERNAL
17743
18441
  );
17744
18442
  }
17745
18443
  const result = await pool.execute(code, bindings, timeoutMs);
@@ -17759,6 +18457,13 @@ function createExecuteCodeTool(adapter) {
17759
18457
  } catch {
17760
18458
  }
17761
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
+ }
17762
18467
  return {
17763
18468
  success: result.success,
17764
18469
  result: sanitizedResult,
@@ -17776,6 +18481,14 @@ function createExecuteCodeTool(adapter) {
17776
18481
  operation: "executeCode",
17777
18482
  error: error instanceof Error ? error : void 0
17778
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
+ }
17779
18492
  return formatHandlerError(error);
17780
18493
  }
17781
18494
  }
@@ -17825,7 +18538,8 @@ function initializePool() {
17825
18538
  operation: "initialize"
17826
18539
  });
17827
18540
  setDefaultSandboxMode(mode);
17828
- 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 });
17829
18543
  pool.initialize();
17830
18544
  logger.info(`Code Mode initialized with ${mode} sandbox`, {
17831
18545
  module: "CODEMODE",
@@ -17885,6 +18599,8 @@ function detectAndSetJsonbSupport(getVersion, log2) {
17885
18599
  }
17886
18600
 
17887
18601
  // src/adapters/sqlite/query-executor.ts
18602
+ var RETURNING_REGEX = /\bRETURNING\b/i;
18603
+ var INSERT_REGEX = /^\s*INSERT\s/i;
17888
18604
  var log = createModuleLogger("SQLITE");
17889
18605
  function translateSqliteError(error, sql, operation, overrideLog) {
17890
18606
  const message = error instanceof Error ? error.message : String(error);
@@ -17892,8 +18608,7 @@ function translateSqliteError(error, sql, operation, overrideLog) {
17892
18608
  const improvedMessage = `${operation} failed: ${message}`;
17893
18609
  const details = { sql };
17894
18610
  if (match?.code === "TABLE_NOT_FOUND") {
17895
- const tableMatch = /no such table[:\s]*(['"]?)([\w.]+)\1/i.exec(message);
17896
- const tableName = tableMatch ? tableMatch[2] : "unknown";
18611
+ const tableName = match.match?.[2] ?? "unknown";
17897
18612
  throw new ResourceNotFoundError(
17898
18613
  `Table '${tableName}' not found`,
17899
18614
  "TABLE_NOT_FOUND",
@@ -17907,8 +18622,7 @@ function translateSqliteError(error, sql, operation, overrideLog) {
17907
18622
  );
17908
18623
  }
17909
18624
  if (match?.code === "COLUMN_NOT_FOUND") {
17910
- const colMatch = /no such column[:\s]*(['"]?)([\w.]+)\1/i.exec(message) ?? /has no column named[:\s]*(['"]?)([\w.]+)\1/i.exec(message);
17911
- const colName = colMatch ? colMatch[2] : "unknown";
18625
+ const colName = match.match?.[2] ?? "unknown";
17912
18626
  throw new ResourceNotFoundError(
17913
18627
  `Column '${colName}' not found`,
17914
18628
  "COLUMN_NOT_FOUND",
@@ -17988,7 +18702,7 @@ function executeWrite(db, sql, params) {
17988
18702
  try {
17989
18703
  const normalizedParams = normalizeSqliteParams(params);
17990
18704
  let rows = [];
17991
- if (/\bRETURNING\b/i.test(sql)) {
18705
+ if (RETURNING_REGEX.test(sql)) {
17992
18706
  const results = normalizedParams ? db.exec(sql, normalizedParams) : db.exec(sql);
17993
18707
  if (results.length > 0 && results[0]) {
17994
18708
  rows = rowsFromSqlJsResult(results[0]);
@@ -18003,7 +18717,7 @@ function executeWrite(db, sql, params) {
18003
18717
  const changes = db.getRowsModified();
18004
18718
  let lastInsertId;
18005
18719
  try {
18006
- if (/^\s*INSERT\s/i.test(sql) && !/\bRETURNING\b/i.test(sql)) {
18720
+ if (INSERT_REGEX.test(sql) && !RETURNING_REGEX.test(sql)) {
18007
18721
  const rowidResult = db.exec("SELECT last_insert_rowid()");
18008
18722
  if (rowidResult[0]?.values[0]) {
18009
18723
  lastInsertId = Number(rowidResult[0].values[0][0]);