db-mcp 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +92 -75
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/adapters/database-adapter.d.ts +2 -1
  4. package/dist/adapters/database-adapter.d.ts.map +1 -1
  5. package/dist/adapters/registration/resources.d.ts.map +1 -1
  6. package/dist/adapters/registration/tools.d.ts.map +1 -1
  7. package/dist/adapters/sqlite/prompts/analysis.d.ts.map +1 -1
  8. package/dist/adapters/sqlite/resources.d.ts +1 -1
  9. package/dist/adapters/sqlite/resources.d.ts.map +1 -1
  10. package/dist/adapters/sqlite/schemas/admin.d.ts +29 -15
  11. package/dist/adapters/sqlite/schemas/admin.d.ts.map +1 -1
  12. package/dist/adapters/sqlite/schemas/core.d.ts +2 -0
  13. package/dist/adapters/sqlite/schemas/core.d.ts.map +1 -1
  14. package/dist/adapters/sqlite/schemas/fts.d.ts +4 -0
  15. package/dist/adapters/sqlite/schemas/fts.d.ts.map +1 -1
  16. package/dist/adapters/sqlite/schemas/introspection.d.ts +20 -11
  17. package/dist/adapters/sqlite/schemas/introspection.d.ts.map +1 -1
  18. package/dist/adapters/sqlite/schemas/spatialite.d.ts +1 -0
  19. package/dist/adapters/sqlite/schemas/spatialite.d.ts.map +1 -1
  20. package/dist/adapters/sqlite/schemas/text.d.ts +36 -0
  21. package/dist/adapters/sqlite/schemas/text.d.ts.map +1 -1
  22. package/dist/adapters/sqlite/sqlite-adapter.d.ts.map +1 -1
  23. package/dist/adapters/sqlite/tools/admin/index.d.ts.map +1 -1
  24. package/dist/adapters/sqlite/tools/admin/pragma.d.ts +0 -4
  25. package/dist/adapters/sqlite/tools/admin/pragma.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/tables.d.ts.map +1 -1
  28. package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -1
  29. package/dist/adapters/sqlite/tools/introspection/diagnostics/indexes.d.ts.map +1 -1
  30. package/dist/adapters/sqlite/tools/json-helpers/read.d.ts.map +1 -1
  31. package/dist/adapters/sqlite/tools/json-operations/crud.d.ts.map +1 -1
  32. package/dist/adapters/sqlite/tools/json-operations/query.d.ts.map +1 -1
  33. package/dist/adapters/sqlite/tools/text/helpers.d.ts +9 -0
  34. package/dist/adapters/sqlite/tools/text/helpers.d.ts.map +1 -1
  35. package/dist/adapters/sqlite/tools/text/index.d.ts.map +1 -1
  36. package/dist/adapters/sqlite/tools/text/search.d.ts +4 -0
  37. package/dist/adapters/sqlite/tools/text/search.d.ts.map +1 -1
  38. package/dist/adapters/sqlite/types.d.ts +4 -0
  39. package/dist/adapters/sqlite/types.d.ts.map +1 -1
  40. package/dist/adapters/sqlite-native/native-sqlite-adapter.d.ts.map +1 -1
  41. package/dist/adapters/sqlite-native/tools/spatialite/tools.d.ts.map +1 -1
  42. package/dist/audit/interceptor.d.ts.map +1 -1
  43. package/dist/audit/logger.d.ts +26 -25
  44. package/dist/audit/logger.d.ts.map +1 -1
  45. package/dist/{chunk-W5WQVNVX.js → chunk-EQF3WZHX.js} +10 -17
  46. package/dist/{chunk-37G4DLL6.js → chunk-NIC5Z2CA.js} +732 -138
  47. package/dist/chunk-R5M4WKDM.js +987 -0
  48. package/dist/{chunk-NV34JSQM.js → chunk-TGE7LFAR.js} +708 -996
  49. package/dist/{chunk-L552U3QS.js → chunk-Y57WFKAP.js} +227 -9
  50. package/dist/cli.js +103 -10
  51. package/dist/codemode/sandbox.d.ts.map +1 -1
  52. package/dist/constants/server-instructions.d.ts +1 -1
  53. package/dist/constants/server-instructions.d.ts.map +1 -1
  54. package/dist/filtering/tool-constants.d.ts +9 -9
  55. package/dist/{http-HWTUVFIA.js → http-2WGR3E5K.js} +13 -1
  56. package/dist/index.js +4 -4
  57. package/dist/observability/metrics.d.ts +36 -0
  58. package/dist/observability/metrics.d.ts.map +1 -0
  59. package/dist/observability/system-db.d.ts +13 -0
  60. package/dist/observability/system-db.d.ts.map +1 -0
  61. package/dist/server/mcp-server.d.ts +3 -0
  62. package/dist/server/mcp-server.d.ts.map +1 -1
  63. package/dist/server/registration/admin-tools.d.ts +6 -0
  64. package/dist/server/registration/admin-tools.d.ts.map +1 -0
  65. package/dist/server/registration/audit-tools.d.ts +4 -0
  66. package/dist/server/registration/audit-tools.d.ts.map +1 -1
  67. package/dist/server/registration/help-resources.d.ts.map +1 -1
  68. package/dist/server/registration/index.d.ts +3 -1
  69. package/dist/server/registration/index.d.ts.map +1 -1
  70. package/dist/server/registration/observability-resources.d.ts +11 -0
  71. package/dist/server/registration/observability-resources.d.ts.map +1 -0
  72. package/dist/server/subscription-manager.d.ts +35 -0
  73. package/dist/server/subscription-manager.d.ts.map +1 -0
  74. package/dist/{sqlite-TB56Z7G3.js → sqlite-D4WCNPU4.js} +8 -5
  75. package/dist/{sqlite-native-IF75BB3O.js → sqlite-native-DLVS2T52.js} +159 -34
  76. package/dist/transports/http/sessions/legacy-sse.d.ts.map +1 -1
  77. package/dist/transports/http/sessions/stateful.d.ts.map +1 -1
  78. package/dist/transports/http/transport.d.ts.map +1 -1
  79. package/dist/transports/http/types.d.ts +2 -0
  80. package/dist/transports/http/types.d.ts.map +1 -1
  81. package/dist/types/server.d.ts +2 -0
  82. package/dist/types/server.d.ts.map +1 -1
  83. package/dist/utils/index.d.ts +1 -0
  84. package/dist/utils/index.d.ts.map +1 -1
  85. package/dist/utils/logger/types.d.ts +1 -1
  86. package/dist/utils/logger/types.d.ts.map +1 -1
  87. package/dist/utils/tokens.d.ts +15 -0
  88. package/dist/utils/tokens.d.ts.map +1 -0
  89. package/package.json +10 -7
  90. package/dist/chunk-E5IESRTK.js +0 -489
  91. package/dist/utils/insights-manager.d.ts +0 -39
  92. package/dist/utils/insights-manager.d.ts.map +0 -1
@@ -1,10 +1,10 @@
1
- import { validateQuery } from './chunk-W5WQVNVX.js';
2
- import { ErrorResponseFields, redactObject, SENSITIVE_KEY_PATTERN, HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY, ASSISTANT_FOCUSED, readOnly, write, idempotent, destructive, VacuumOutputSchema, admin, adminFs, BackupOutputSchema, BackupSchema, AnalyzeOutputSchema, AnalyzeSchema, IntegrityCheckOutputSchema, IntegrityCheckSchema, OptimizeOutputSchema, OptimizeSchema, RestoreOutputSchema, RestoreSchema, VerifyBackupOutputSchema, VerifyBackupSchema, IndexStatsOutputSchema, IndexStatsSchema, PragmaCompileOptionsOutputSchema, PragmaCompileOptionsSchema, PragmaDatabaseListOutputSchema, PragmaDatabaseListSchema, PragmaOptimizeOutputSchema, PragmaOptimizeSchema, PragmaSettingsOutputSchema, PragmaSettingsSchema, PragmaTableInfoOutputSchema, PragmaTableInfoSchema, AppendInsightOutputSchema, AppendInsightSchema, AttachDatabaseOutputSchema, AttachDatabaseSchema, DetachDatabaseOutputSchema, DetachDatabaseSchema, VacuumIntoCopyOutputSchema, VacuumIntoCopySchema, SqlDumpOutputSchema, SqlDumpSchema, ReindexOutputSchema, ReindexSchema, WalOutputSchema, WalSchema } from './chunk-E5IESRTK.js';
3
- import { createModuleLogger, logger, ResourceNotFoundError, DbMcpError, formatHandlerError, findSuggestion, ERROR_CODES, QueryError, ValidationError, ExtensionNotAvailableError, getAuthContext, scopesGrantToolAccess } from './chunk-L552U3QS.js';
1
+ import { validateQuery } from './chunk-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';
4
4
  import { z } from 'zod';
5
5
  import * as path from 'path';
6
- import path__default, { dirname, resolve, basename, normalize, relative } from 'path';
7
- import fs, { realpathSync } from 'fs';
6
+ import path__default from 'path';
7
+ import fs from 'fs';
8
8
  import { createHash } from 'crypto';
9
9
  import { createClient } from 'redis';
10
10
  import * as acorn from 'acorn';
@@ -41,6 +41,7 @@ var ReadQueryOutputSchema = z.object({
41
41
  success: z.boolean(),
42
42
  rowCount: z.number().optional(),
43
43
  rows: z.array(RowRecordSchema).optional(),
44
+ nextCursor: z.string().optional(),
44
45
  executionTimeMs: z.number().optional()
45
46
  }).extend(ErrorResponseFields.shape);
46
47
  var WriteQueryOutputSchema = z.object({
@@ -122,7 +123,8 @@ var DateMathOutputSchema = z.object({
122
123
  var ReadQuerySchema = z.object({
123
124
  query: z.string().max(1e5).default("").describe("SELECT query to execute"),
124
125
  sql: z.string().max(1e5).optional().describe("Alias for query"),
125
- params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements")
126
+ params: z.array(z.unknown()).optional().describe("Query parameters for prepared statements"),
127
+ cursor: z.string().optional().describe("Opaque cursor for pagination")
126
128
  });
127
129
  var WriteQuerySchema = z.object({
128
130
  query: z.string().max(1e5).default("").describe("INSERT/UPDATE/DELETE query to execute"),
@@ -541,16 +543,59 @@ function createReadQueryTool(adapter) {
541
543
  }
542
544
  try {
543
545
  let finalQuery = input.query.replace(/[\s;]+$/g, "");
546
+ let offset = 0;
547
+ if (input.cursor) {
548
+ try {
549
+ const cursorData = JSON.parse(
550
+ Buffer.from(input.cursor, "base64").toString("utf8")
551
+ );
552
+ if (typeof cursorData["offset"] === "number") {
553
+ offset = cursorData["offset"];
554
+ }
555
+ } catch {
556
+ return {
557
+ ...formatHandlerError(
558
+ new ValidationError(
559
+ "Invalid cursor format",
560
+ "VALIDATION_ERROR",
561
+ {
562
+ suggestion: "Use the nextCursor value returned from a previous query.",
563
+ details: {}
564
+ }
565
+ )
566
+ ),
567
+ rowCount: 0,
568
+ rows: []
569
+ };
570
+ }
571
+ }
544
572
  const upperForLimit = finalQuery.toUpperCase();
545
573
  const isLimitable = upperForLimit.startsWith("SELECT") || upperForLimit.startsWith("WITH");
546
- if (isLimitable && !/\bLIMIT\b/i.test(finalQuery)) {
547
- finalQuery = `${finalQuery} LIMIT 50`;
574
+ const limit = 50;
575
+ const hasLimit = /\bLIMIT\b/i.test(finalQuery);
576
+ if (isLimitable && !hasLimit) {
577
+ finalQuery = `${finalQuery} LIMIT ${limit}`;
578
+ if (offset > 0) {
579
+ finalQuery = `${finalQuery} OFFSET ${offset}`;
580
+ }
581
+ } else if (isLimitable && hasLimit && offset > 0) {
582
+ if (!/\bOFFSET\b/i.test(finalQuery)) {
583
+ finalQuery = `${finalQuery} OFFSET ${offset}`;
584
+ }
548
585
  }
549
586
  const result = await adapter.executeReadQuery(finalQuery, input.params);
587
+ let nextCursor;
588
+ if (isLimitable && !hasLimit && result.rows?.length === limit) {
589
+ const nextOffset = offset + limit;
590
+ nextCursor = Buffer.from(
591
+ JSON.stringify({ offset: nextOffset })
592
+ ).toString("base64");
593
+ }
550
594
  return {
551
595
  success: true,
552
596
  rowCount: result.rows?.length ?? 0,
553
597
  rows: result.rows,
598
+ nextCursor,
554
599
  executionTimeMs: result.executionTimeMs
555
600
  };
556
601
  } catch (error) {
@@ -671,477 +716,6 @@ function createWriteQueryTool(adapter) {
671
716
  };
672
717
  }
673
718
 
674
- // src/utils/where-clause.ts
675
- var UnsafeWhereClauseError = class extends Error {
676
- constructor(reason) {
677
- super(`Unsafe WHERE clause: ${reason}`);
678
- this.name = "UnsafeWhereClauseError";
679
- }
680
- };
681
- var DANGEROUS_PATTERNS = [
682
- // Statement terminators and new statements
683
- // Comprehensive keyword list including statements missed in earlier versions:
684
- // REPLACE, VACUUM, ANALYZE, BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE
685
- {
686
- pattern: /;\s*(DROP|DELETE|TRUNCATE|INSERT|UPDATE|CREATE|ALTER|ATTACH|DETACH|SELECT|REPLACE|VACUUM|ANALYZE|BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE|REINDEX|EXPLAIN)\b/i,
687
- reason: "contains statement terminator followed by SQL keyword"
688
- },
689
- // Trailing semicolons (potential statement injection)
690
- {
691
- pattern: /;\s*$/,
692
- reason: "contains trailing semicolon"
693
- },
694
- // SQL comments (can be used to comment out security checks)
695
- {
696
- pattern: /--/,
697
- reason: "contains SQL line comment"
698
- },
699
- {
700
- pattern: /\/\*/,
701
- reason: "contains SQL block comment"
702
- },
703
- // REGEXP and MATCH injection (blind extraction oracles)
704
- {
705
- pattern: /\bREGEXP\s+/i,
706
- reason: "contains REGEXP (potential blind extraction oracle)"
707
- },
708
- {
709
- pattern: /\bMATCH\s+/i,
710
- reason: "contains MATCH (potential blind extraction oracle)"
711
- },
712
- // UNION injection (data exfiltration)
713
- {
714
- pattern: /\bUNION\s+(ALL\s+)?SELECT\b/i,
715
- reason: "contains UNION SELECT"
716
- },
717
- // SQLite-specific: Extension loading (code execution)
718
- {
719
- pattern: /\bload_extension\s*\(/i,
720
- reason: "contains load_extension (code execution)"
721
- },
722
- // SQLite-specific: ATTACH database (file system access)
723
- {
724
- pattern: /\bATTACH\s+(DATABASE\s+)?['"`]/i,
725
- reason: "contains ATTACH DATABASE (file system access)"
726
- },
727
- // SQLite-specific: Pragma manipulation
728
- {
729
- pattern: /\bPRAGMA\s+/i,
730
- reason: "contains PRAGMA statement"
731
- },
732
- // SQLite-specific: writefile/readfile (if using fileio extension)
733
- {
734
- pattern: /\b(writefile|readfile)\s*\(/i,
735
- reason: "contains file I/O function"
736
- },
737
- // SQLite-specific: fts3_tokenizer (potential code execution)
738
- {
739
- pattern: /\bfts3_tokenizer\s*\(/i,
740
- reason: "contains FTS tokenizer function"
741
- },
742
- // Subquery injection (data exfiltration via boolean-based blind)
743
- {
744
- pattern: /\(\s*SELECT\b/i,
745
- reason: "contains subquery (potential data exfiltration)"
746
- },
747
- // Generic: Hexadecimal string injection
748
- {
749
- pattern: /\bX'[0-9A-Fa-f]+'/,
750
- reason: "contains hex string literal (potential binary injection)"
751
- },
752
- // CASE WHEN (potential blind injection without subqueries)
753
- {
754
- pattern: /\bCASE\s+WHEN\b/i,
755
- reason: "contains CASE WHEN (potential blind injection)"
756
- },
757
- // IIF() — conditional bypass alternative to CASE WHEN
758
- {
759
- pattern: /\bIIF\s*\(/i,
760
- reason: "contains IIF() (potential blind injection)"
761
- },
762
- // GROUP_CONCAT() — data aggregation for exfiltration
763
- {
764
- pattern: /\bGROUP_CONCAT\s*\(/i,
765
- reason: "contains GROUP_CONCAT() (potential data exfiltration)"
766
- },
767
- // GLOB — case-sensitive wildcard operator usable for character extraction oracles
768
- {
769
- pattern: /\bGLOB\b/i,
770
- reason: "contains GLOB (potential case-sensitive oracle)"
771
- },
772
- // Conditional functions — blind injection oracles (bypass for CASE WHEN / IIF)
773
- {
774
- pattern: /\bCOALESCE\s*\(/i,
775
- reason: "contains COALESCE() (potential blind injection oracle)"
776
- },
777
- {
778
- pattern: /\bNULLIF\s*\(/i,
779
- reason: "contains NULLIF() (potential blind injection oracle)"
780
- },
781
- {
782
- pattern: /\bTYPEOF\s*\(/i,
783
- reason: "contains TYPEOF() (potential blind injection oracle)"
784
- },
785
- {
786
- pattern: /\bIFNULL\s*\(/i,
787
- reason: "contains IFNULL() (potential blind injection oracle)"
788
- },
789
- // Memory allocation DoS — RANDOMBLOB(N) / ZEROBLOB(N) can allocate large blocks (CWE-400)
790
- {
791
- pattern: /\bRANDOMBLOB\s*\(/i,
792
- reason: "contains RANDOMBLOB() (potential memory allocation DoS)"
793
- },
794
- {
795
- pattern: /\bZEROBLOB\s*\(/i,
796
- reason: "contains ZEROBLOB() (potential memory allocation DoS)"
797
- },
798
- // Blind extraction oracle functions — can be composed for character-by-character
799
- // data extraction without subqueries or CASE WHEN (CWE-89, H-1 audit finding).
800
- // Example: INSTR('abc...', SUBSTR(sqlite_version(), 1, 1)) = N
801
- {
802
- pattern: /\bINSTR\s*\(/i,
803
- reason: "contains INSTR() (potential blind extraction oracle)"
804
- },
805
- {
806
- pattern: /\bSUBSTR\s*\(/i,
807
- reason: "contains SUBSTR() (potential blind extraction oracle)"
808
- },
809
- {
810
- pattern: /\bSUBSTRING\s*\(/i,
811
- reason: "contains SUBSTRING() (potential blind extraction oracle)"
812
- },
813
- {
814
- pattern: /\bLENGTH\s*\(/i,
815
- reason: "contains LENGTH() (potential blind extraction oracle)"
816
- },
817
- // TRIM functions — blind extraction oracle (bypass for LIKE / SUBSTR)
818
- {
819
- pattern: /\b(?:L|R)?TRIM\s*\(/i,
820
- reason: "contains TRIM() (potential blind extraction oracle)"
821
- },
822
- {
823
- pattern: /\bUNICODE\s*\(/i,
824
- reason: "contains UNICODE() (potential blind extraction oracle)"
825
- },
826
- {
827
- pattern: /\bCHAR\s*\(/i,
828
- reason: "contains CHAR() (potential blind extraction oracle)"
829
- },
830
- {
831
- pattern: /\bREPLACE\s*\(/i,
832
- reason: "contains REPLACE() (potential blind extraction oracle)"
833
- },
834
- // ABS() — can be combined with subtraction to create numeric oracles (CWE-89)
835
- {
836
- pattern: /\bABS\s*\(/i,
837
- reason: "contains ABS() (potential blind extraction oracle)"
838
- },
839
- // HEX() — encodes values for character-by-character extraction (CWE-89)
840
- {
841
- pattern: /\bHEX\s*\(/i,
842
- reason: "contains HEX() (potential blind extraction oracle)"
843
- },
844
- // QUOTE() — returns SQL-literal representation, can leak type/value info (CWE-89)
845
- {
846
- pattern: /\bQUOTE\s*\(/i,
847
- reason: "contains QUOTE() (potential blind extraction oracle)"
848
- },
849
- // PRINTF() — format-string based extraction of internal values (CWE-89)
850
- {
851
- pattern: /\bPRINTF\s*\(/i,
852
- reason: "contains PRINTF() (potential blind extraction oracle)"
853
- },
854
- // FORMAT() — SQLite 3.38.0+ alias for printf() (CWE-89)
855
- {
856
- pattern: /\bFORMAT\s*\(/i,
857
- reason: "contains FORMAT() (potential blind extraction oracle)"
858
- },
859
- // CAST() — can be used to convert text to numeric oracles (CWE-89)
860
- {
861
- pattern: /\bCAST\s*\(/i,
862
- reason: "contains CAST() (potential blind extraction oracle)"
863
- }
864
- ];
865
- var FULLWIDTH_START = 65281;
866
- var FULLWIDTH_END = 65374;
867
- var FULLWIDTH_ASCII_OFFSET = 65248;
868
- function normalizeForPatternMatching(input) {
869
- let normalized = input.normalize("NFC");
870
- let hasFullwidth = false;
871
- for (let i = 0; i < normalized.length; i++) {
872
- const code = normalized.charCodeAt(i);
873
- if (code >= FULLWIDTH_START && code <= FULLWIDTH_END) {
874
- hasFullwidth = true;
875
- break;
876
- }
877
- }
878
- if (hasFullwidth) {
879
- const chars = [];
880
- for (let i = 0; i < normalized.length; i++) {
881
- const code = normalized.charCodeAt(i);
882
- if (code >= FULLWIDTH_START && code <= FULLWIDTH_END) {
883
- chars.push(String.fromCharCode(code - FULLWIDTH_ASCII_OFFSET));
884
- } else {
885
- chars.push(normalized.charAt(i));
886
- }
887
- }
888
- normalized = chars.join("");
889
- }
890
- return normalized;
891
- }
892
- function sanitizeWhereClause(where) {
893
- if (!where || typeof where !== "string") {
894
- throw new UnsafeWhereClauseError("WHERE clause must be a non-empty string");
895
- }
896
- const MAX_WHERE_LENGTH = 10240;
897
- if (where.length > MAX_WHERE_LENGTH) {
898
- throw new UnsafeWhereClauseError(
899
- `WHERE clause exceeds maximum length of ${String(MAX_WHERE_LENGTH)} characters`
900
- );
901
- }
902
- const normalized = normalizeForPatternMatching(where);
903
- for (const { pattern, reason } of DANGEROUS_PATTERNS) {
904
- if (pattern.test(normalized)) {
905
- throw new UnsafeWhereClauseError(reason);
906
- }
907
- }
908
- return where;
909
- }
910
- function buildWhereClause(conditions, whereClause) {
911
- let finalSql = "";
912
- const params = [];
913
- const clauses = [];
914
- if (conditions !== void 0 && conditions !== null && conditions.length > 0) {
915
- const allowedOperators = /* @__PURE__ */ new Set([
916
- "=",
917
- "!=",
918
- ">",
919
- ">=",
920
- "<",
921
- "<=",
922
- "LIKE",
923
- "IN",
924
- "IS",
925
- "IS NOT",
926
- "NOT LIKE"
927
- ]);
928
- for (const cond of conditions) {
929
- if (!allowedOperators.has(cond.operator.toUpperCase())) {
930
- throw new Error(`Invalid operator: ${cond.operator}`);
931
- }
932
- const col = `"${cond.column.replace(/"/g, '""')}"`;
933
- if (cond.operator.toUpperCase() === "IN" && Array.isArray(cond.value)) {
934
- const placeholders = cond.value.map(() => "?").join(", ");
935
- clauses.push(`${col} IN (${placeholders})`);
936
- params.push(...cond.value);
937
- } else {
938
- clauses.push(`${col} ${cond.operator} ?`);
939
- params.push(cond.value);
940
- }
941
- }
942
- finalSql = clauses.join(" AND ");
943
- }
944
- if (whereClause) {
945
- const safeWhere = sanitizeWhereClause(whereClause);
946
- if (finalSql) {
947
- finalSql = `${finalSql} AND ${safeWhere}`;
948
- } else {
949
- finalSql = safeWhere;
950
- }
951
- }
952
- return { sql: finalSql, params };
953
- }
954
-
955
- // src/utils/identifiers.ts
956
- var IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
957
- var MAX_IDENTIFIER_LENGTH = 255;
958
- var InvalidIdentifierError = class extends Error {
959
- constructor(identifier, reason) {
960
- super(`Invalid identifier "${identifier}": ${reason}`);
961
- this.identifier = identifier;
962
- this.reason = reason;
963
- this.name = "InvalidIdentifierError";
964
- }
965
- identifier;
966
- reason;
967
- };
968
- function validateIdentifier(name) {
969
- if (!name || typeof name !== "string") {
970
- throw new InvalidIdentifierError(
971
- name,
972
- "Identifier must be a non-empty string"
973
- );
974
- }
975
- if (name.length > MAX_IDENTIFIER_LENGTH) {
976
- throw new InvalidIdentifierError(
977
- name,
978
- `Identifier exceeds maximum length of ${String(MAX_IDENTIFIER_LENGTH)} characters`
979
- );
980
- }
981
- if (!IDENTIFIER_PATTERN.test(name)) {
982
- throw new InvalidIdentifierError(
983
- name,
984
- "Identifier contains invalid characters. Must start with a letter or underscore and contain only letters, digits, or underscores"
985
- );
986
- }
987
- }
988
- function sanitizeIdentifier(name) {
989
- validateIdentifier(name);
990
- const escaped = name.replace(/"/g, '""');
991
- return `"${escaped}"`;
992
- }
993
- function sanitizeIdentifiers(names) {
994
- return names.map(sanitizeIdentifier);
995
- }
996
- function createColumnList(columns) {
997
- return sanitizeIdentifiers(columns).join(", ");
998
- }
999
- function validateSameDirPath(targetPath, dbPath) {
1000
- if (targetPath.includes("\0")) {
1001
- return {
1002
- valid: false,
1003
- error: "Security: path must not contain null bytes.",
1004
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1005
- };
1006
- }
1007
- if (targetPath.startsWith("file:") || targetPath.includes("?")) {
1008
- return {
1009
- valid: false,
1010
- error: "Security: SQLite URIs and query parameters are not allowed.",
1011
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1012
- };
1013
- }
1014
- const base = basename(targetPath);
1015
- if (base.startsWith(".")) {
1016
- return {
1017
- valid: false,
1018
- error: "Security: Cannot access hidden files or dotfiles.",
1019
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1020
- };
1021
- }
1022
- const validExtensions = [
1023
- ".db",
1024
- ".sqlite",
1025
- ".sqlite3",
1026
- ".sql",
1027
- ".csv",
1028
- ".tsv"
1029
- ];
1030
- const hasValidExt = validExtensions.some(
1031
- (ext) => base.toLowerCase().endsWith(ext)
1032
- );
1033
- if (!hasValidExt) {
1034
- return {
1035
- valid: false,
1036
- error: `Security: Invalid file extension. Allowed extensions are: ${validExtensions.join(", ")}`,
1037
- dbDir: dbPath === ":memory:" ? "" : dirname(resolve(dbPath))
1038
- };
1039
- }
1040
- if (dbPath === ":memory:") {
1041
- return {
1042
- valid: false,
1043
- error: "Security: Filesystem operations are not permitted for :memory: databases.",
1044
- dbDir: ""
1045
- };
1046
- }
1047
- const dbDir = dirname(resolve(dbPath));
1048
- let resolvedTarget = resolve(targetPath);
1049
- try {
1050
- resolvedTarget = realpathSync(resolvedTarget);
1051
- } catch {
1052
- try {
1053
- const parentDir = realpathSync(dirname(resolvedTarget));
1054
- resolvedTarget = resolve(parentDir, basename(resolvedTarget));
1055
- } catch {
1056
- return {
1057
- valid: false,
1058
- error: "Security: Target parent directory must exist.",
1059
- dbDir
1060
- };
1061
- }
1062
- }
1063
- let resolvedDbDir;
1064
- try {
1065
- resolvedDbDir = realpathSync(dbDir);
1066
- } catch {
1067
- return {
1068
- valid: false,
1069
- error: "Security: Database directory must exist.",
1070
- dbDir
1071
- };
1072
- }
1073
- const normalizedTarget = normalize(resolvedTarget);
1074
- const normalizedDir = normalize(resolvedDbDir);
1075
- const targetDir = normalize(dirname(resolvedTarget));
1076
- if (relative(normalizedDir, targetDir) !== "") {
1077
- return {
1078
- valid: false,
1079
- error: `Security: path must be within the database directory. Path traversal is not allowed.`,
1080
- dbDir
1081
- };
1082
- }
1083
- return { valid: true, resolvedPath: normalizedTarget };
1084
- }
1085
-
1086
- // src/utils/validate-json-path.ts
1087
- var VALID_JSON_PATH_PATTERN = /^\$(\.[a-zA-Z_][a-zA-Z0-9_ -]*|\[\d+\]|\[#\]|\[\*\])*$/;
1088
- var MAX_JSON_PATH_LENGTH = 500;
1089
- function validateJsonPath(path3, param = "path") {
1090
- if (!path3.startsWith("$")) {
1091
- throw new ValidationError(
1092
- `JSON ${param} must start with $`,
1093
- "INVALID_JSON_PATH",
1094
- {
1095
- suggestion: "Use a valid JSON path starting with $. For example: $.key or $[0]"
1096
- }
1097
- );
1098
- }
1099
- if (path3.length > MAX_JSON_PATH_LENGTH) {
1100
- throw new ValidationError(
1101
- `JSON ${param} exceeds maximum length of ${MAX_JSON_PATH_LENGTH} characters`,
1102
- "INVALID_JSON_PATH"
1103
- );
1104
- }
1105
- if (!VALID_JSON_PATH_PATTERN.test(path3)) {
1106
- throw new ValidationError(
1107
- `Invalid JSON ${param} syntax: ${path3}`,
1108
- "INVALID_JSON_PATH",
1109
- {
1110
- suggestion: "Use $.key, $[0], or $[*] patterns. Member names must start with a letter or underscore."
1111
- }
1112
- );
1113
- }
1114
- }
1115
- var ALLOWED_AGGREGATE_FUNCTIONS = /* @__PURE__ */ new Set([
1116
- "count",
1117
- "sum",
1118
- "avg",
1119
- "min",
1120
- "max",
1121
- "group_concat",
1122
- "total"
1123
- ]);
1124
- var AGGREGATE_FUNCTION_PATTERN = /^([A-Za-z_]+)\(\s*(\*|"[a-zA-Z_][a-zA-Z0-9_]*"|[a-zA-Z_][a-zA-Z0-9_]*)(?:\s*,\s*'[^']*')?\s*\)$/;
1125
- function validateAggregateFunction(fn) {
1126
- const match = AGGREGATE_FUNCTION_PATTERN.exec(fn.trim());
1127
- if (!match) {
1128
- throw new ValidationError(
1129
- `Invalid aggregate function syntax: ${fn}`,
1130
- "INVALID_AGGREGATE_FUNCTION",
1131
- {
1132
- suggestion: "Use a standard aggregate function like COUNT(*), SUM(column), AVG(column), MIN(column), MAX(column), GROUP_CONCAT(column), or TOTAL(column)."
1133
- }
1134
- );
1135
- }
1136
- const funcName = match[1]?.toLowerCase();
1137
- if (!funcName || !ALLOWED_AGGREGATE_FUNCTIONS.has(funcName)) {
1138
- throw new ValidationError(
1139
- `Disallowed aggregate function: ${match[1]}. Allowed: ${[...ALLOWED_AGGREGATE_FUNCTIONS].join(", ").toUpperCase()}`,
1140
- "INVALID_AGGREGATE_FUNCTION"
1141
- );
1142
- }
1143
- }
1144
-
1145
719
  // src/adapters/sqlite/tools/core/tables.ts
1146
720
  var SPATIALITE_SYSTEM_PREFIXES = [
1147
721
  "geometry_columns",
@@ -1396,7 +970,7 @@ function createDescribeTableTool(adapter) {
1396
970
  try {
1397
971
  const tableInfo = await adapter.describeTable(input.table);
1398
972
  const rawColumns = tableInfo.columns ?? [];
1399
- let enrichedColumns = rawColumns.map((c) => ({
973
+ const enrichedColumns = rawColumns.map((c) => ({
1400
974
  name: c.name,
1401
975
  type: c.type,
1402
976
  nullable: c.nullable,
@@ -1422,28 +996,39 @@ function createDescribeTableTool(adapter) {
1422
996
  [input.table]
1423
997
  );
1424
998
  const ddl = ddlResult.rows?.[0]?.["sql"] ?? "";
1425
- enrichedColumns = enrichedColumns.map((col) => {
1426
- const hidden = xinfoMap.get(col.name);
1427
- if (hidden !== 2 && hidden !== 3) return col;
1428
- const enriched = {
1429
- ...col,
1430
- isGenerated: true,
1431
- generatedType: hidden === 3 ? "STORED" : "VIRTUAL"
1432
- };
1433
- const escapedName = col.name.replace(
1434
- /[.*+?^${}()|[\]\\]/g,
1435
- "\\$&"
1436
- );
1437
- const exprPattern = new RegExp(
1438
- `"?${escapedName}"?\\s+\\w+[^,]*GENERATED\\s+ALWAYS\\s+AS\\s*\\(([^)]+)\\)`,
1439
- "i"
1440
- );
1441
- const match = exprPattern.exec(ddl);
1442
- if (match?.[1]) {
1443
- enriched.generatedExpression = match[1].trim();
999
+ for (const row of xinfoResult.rows) {
1000
+ const colName = row["name"];
1001
+ const hidden = row["hidden"];
1002
+ if (hidden === 2 || hidden === 3) {
1003
+ let enriched = enrichedColumns.find(
1004
+ (c) => c.name === colName
1005
+ );
1006
+ if (!enriched) {
1007
+ enriched = {
1008
+ name: colName,
1009
+ type: row["type"],
1010
+ nullable: row["notnull"] === 0,
1011
+ primaryKey: row["pk"] === 1,
1012
+ defaultValue: row["dflt_value"]
1013
+ };
1014
+ enrichedColumns.push(enriched);
1015
+ }
1016
+ enriched.isGenerated = true;
1017
+ enriched.generatedType = hidden === 3 ? "STORED" : "VIRTUAL";
1018
+ const escapedName = colName.replace(
1019
+ /[.*+?^${}()|[\]\\]/g,
1020
+ "\\$&"
1021
+ );
1022
+ const exprPattern = new RegExp(
1023
+ `"?${escapedName}"?\\s+\\w+[^,]*GENERATED\\s+ALWAYS\\s+AS\\s*\\(([^)]+)\\)`,
1024
+ "i"
1025
+ );
1026
+ const match = exprPattern.exec(ddl);
1027
+ if (match?.[1]) {
1028
+ enriched.generatedExpression = match[1].trim();
1029
+ }
1444
1030
  }
1445
- return enriched;
1446
- });
1031
+ }
1447
1032
  }
1448
1033
  }
1449
1034
  } catch {
@@ -3227,55 +2812,6 @@ var SchemaManager = class {
3227
2812
  }
3228
2813
  };
3229
2814
 
3230
- // src/utils/insights-manager.ts
3231
- var InsightsManager = class {
3232
- insights = [];
3233
- /**
3234
- * Append a new business insight
3235
- */
3236
- append(insight) {
3237
- if (insight.trim()) {
3238
- this.insights.push(insight.trim());
3239
- }
3240
- }
3241
- /**
3242
- * Get all insights
3243
- */
3244
- getAll() {
3245
- return [...this.insights];
3246
- }
3247
- /**
3248
- * Get insight count
3249
- */
3250
- count() {
3251
- return this.insights.length;
3252
- }
3253
- /**
3254
- * Clear all insights
3255
- */
3256
- clear() {
3257
- this.insights = [];
3258
- }
3259
- /**
3260
- * Synthesize insights into a formatted memo
3261
- */
3262
- synthesizeMemo() {
3263
- if (this.insights.length === 0) {
3264
- return "No business insights have been discovered yet.";
3265
- }
3266
- const insightsList = this.insights.map((insight) => `- ${insight}`).join("\n");
3267
- let memo = "\u{1F4CA} Business Intelligence Memo \u{1F4CA}\n\n";
3268
- memo += "Key Insights Discovered:\n\n";
3269
- memo += insightsList;
3270
- if (this.insights.length > 1) {
3271
- memo += "\n\nSummary:\n";
3272
- memo += `Analysis has revealed ${this.insights.length} key business insights that suggest opportunities for strategic optimization and growth.`;
3273
- }
3274
- return memo;
3275
- }
3276
- };
3277
- var insightsManager = new InsightsManager();
3278
-
3279
2815
  // src/adapters/sqlite/resources.ts
3280
2816
  function getResourceDefinitions(adapter) {
3281
2817
  return [
@@ -3287,8 +2823,7 @@ function getResourceDefinitions(adapter) {
3287
2823
  createHealthResource(adapter),
3288
2824
  createMetaResource(adapter),
3289
2825
  createCompileOptionsResource(adapter),
3290
- createPragmaResource(adapter),
3291
- createInsightsResource()
2826
+ createPragmaResource(adapter)
3292
2827
  ];
3293
2828
  }
3294
2829
  function createSchemaResource(adapter) {
@@ -3512,26 +3047,6 @@ function createMetaResource(adapter) {
3512
3047
  }
3513
3048
  };
3514
3049
  }
3515
- function createInsightsResource() {
3516
- return {
3517
- name: "sqlite_insights",
3518
- uri: "memo://insights",
3519
- description: "Business insights memo that gets updated during database analysis",
3520
- mimeType: "text/plain",
3521
- annotations: HIGH_PRIORITY,
3522
- handler: () => {
3523
- return Promise.resolve({
3524
- contents: [
3525
- {
3526
- uri: "memo://insights",
3527
- mimeType: "text/plain",
3528
- text: insightsManager.synthesizeMemo()
3529
- }
3530
- ]
3531
- });
3532
- }
3533
- };
3534
- }
3535
3050
  function createCompileOptionsResource(adapter) {
3536
3051
  return {
3537
3052
  name: "sqlite_compile_options",
@@ -4161,7 +3676,6 @@ You are guiding an interactive demo of the SQLite MCP Server capabilities.
4161
3676
 
4162
3677
  **Prompts**: Pre-written templates that structure AI conversations (like this one)
4163
3678
  **Tools**: SQL operations (read_query, write_query, create_table, etc.)
4164
- **Resources**: Living documents like \`memo://insights\` that update during analysis
4165
3679
 
4166
3680
  ## Demo Workflow
4167
3681
 
@@ -4187,17 +3701,10 @@ After setup, present 3-4 natural language query choices:
4187
3701
  For each user selection:
4188
3702
  - Run the appropriate SQL query
4189
3703
  - Explain the results
4190
- - Use \`sqlite_append_insight\` to capture findings
4191
3704
 
4192
- ### 5. Capture Insights
4193
- Remind the user about \`memo://insights\`:
4194
- - Each insight is automatically added to this resource
4195
- - They can attach it to see all discoveries
4196
-
4197
- ### 6. Wrap Up
3705
+ ### 5. Wrap Up
4198
3706
  Summarize what was demonstrated:
4199
3707
  - Data creation and querying
4200
- - Insight capture via resources
4201
3708
  - The power of MCP for database workflows
4202
3709
 
4203
3710
  ---
@@ -4228,20 +3735,164 @@ function getPromptDefinitions(adapter) {
4228
3735
  createDemoPrompt()
4229
3736
  ];
4230
3737
  }
4231
- var coerceNumber2 = (val) => {
4232
- if (typeof val === "string") {
4233
- if (val.trim() === "") return void 0;
4234
- const num = Number(val);
4235
- return isNaN(num) ? val : num;
4236
- }
4237
- return val;
3738
+
3739
+ // src/adapters/sqlite/json-utils.ts
3740
+ var DEFAULT_NORMALIZATION_OPTIONS = {
3741
+ sortKeys: true,
3742
+ compact: true,
3743
+ unicodeNormalize: true,
3744
+ typeCoercion: false,
3745
+ duplicateKeys: "last"
4238
3746
  };
4239
- var JsonInsertOutputSchema = z.object({
4240
- success: z.boolean(),
4241
- message: z.string().optional(),
4242
- rowsAffected: z.number().optional(),
4243
- warning: z.string().optional(),
4244
- lastInsertRowid: z.number().optional()
3747
+ function normalizeJson(value, options = {}) {
3748
+ const opts = { ...DEFAULT_NORMALIZATION_OPTIONS, ...options };
3749
+ const changes = [];
3750
+ let parsed;
3751
+ if (typeof value === "string") {
3752
+ try {
3753
+ parsed = JSON.parse(value);
3754
+ } catch {
3755
+ return {
3756
+ normalized: value,
3757
+ wasModified: false,
3758
+ changes: ["Input is not valid JSON"]
3759
+ };
3760
+ }
3761
+ } else {
3762
+ parsed = value;
3763
+ }
3764
+ const normalized = normalizeValue(parsed, opts, changes, "");
3765
+ const normalizedStr = opts.compact ? JSON.stringify(normalized) : JSON.stringify(normalized, null, 2);
3766
+ const originalStr = typeof value === "string" ? value : JSON.stringify(value);
3767
+ const wasModified = normalizedStr !== originalStr;
3768
+ return {
3769
+ normalized: normalizedStr,
3770
+ wasModified,
3771
+ changes
3772
+ };
3773
+ }
3774
+ function normalizeValue(value, options, changes, path3) {
3775
+ if (value === null || value === void 0) {
3776
+ return null;
3777
+ }
3778
+ if (typeof value === "string") {
3779
+ let result = value;
3780
+ if (options.unicodeNormalize && value !== value.normalize("NFC")) {
3781
+ result = result.normalize("NFC");
3782
+ changes.push(`${path3 || "$"}: Unicode normalized`);
3783
+ }
3784
+ if (options.typeCoercion) {
3785
+ if (result.toLowerCase() === "true") {
3786
+ changes.push(`${path3 || "$"}: Coerced "true" to boolean`);
3787
+ return true;
3788
+ }
3789
+ if (result.toLowerCase() === "false") {
3790
+ changes.push(`${path3 || "$"}: Coerced "false" to boolean`);
3791
+ return false;
3792
+ }
3793
+ const num = Number(result);
3794
+ if (!isNaN(num) && result.trim() !== "") {
3795
+ changes.push(`${path3 || "$"}: Coerced "${result}" to number`);
3796
+ return num;
3797
+ }
3798
+ }
3799
+ return result;
3800
+ }
3801
+ if (typeof value === "number" || typeof value === "boolean") {
3802
+ return value;
3803
+ }
3804
+ if (Array.isArray(value)) {
3805
+ return value.map(
3806
+ (item, index) => normalizeValue(item, options, changes, `${path3 || "$"}[${index}]`)
3807
+ );
3808
+ }
3809
+ if (typeof value === "object") {
3810
+ const obj = value;
3811
+ let keys = Object.keys(obj);
3812
+ if (options.sortKeys) {
3813
+ const originalOrder = keys.join(",");
3814
+ keys = keys.sort();
3815
+ if (keys.join(",") !== originalOrder) {
3816
+ changes.push(`${path3 || "$"}: Keys sorted`);
3817
+ }
3818
+ }
3819
+ const result = {};
3820
+ for (const key of keys) {
3821
+ result[key] = normalizeValue(
3822
+ obj[key],
3823
+ options,
3824
+ changes,
3825
+ `${path3 || "$"}.${key}`
3826
+ );
3827
+ }
3828
+ return result;
3829
+ }
3830
+ return value;
3831
+ }
3832
+ var jsonbSupportedCache = null;
3833
+ function isJsonbSupportedVersion(versionString) {
3834
+ const versionRegex = /^(\d+)\.(\d+)\.?(\d+)?/;
3835
+ const match = versionRegex.exec(versionString);
3836
+ if (!match) return false;
3837
+ const major = parseInt(match[1] ?? "0", 10);
3838
+ const minor = parseInt(match[2] ?? "0", 10);
3839
+ return major > 3 || major === 3 && minor >= 45;
3840
+ }
3841
+ function isJsonbSupported() {
3842
+ return jsonbSupportedCache ?? false;
3843
+ }
3844
+ function setJsonbSupported(supported) {
3845
+ jsonbSupportedCache = supported;
3846
+ }
3847
+ function detectJsonStorageFormat(value) {
3848
+ if (value instanceof Buffer || value instanceof Uint8Array) {
3849
+ return "jsonb";
3850
+ }
3851
+ if (typeof value === "string") {
3852
+ return "text";
3853
+ }
3854
+ return "unknown";
3855
+ }
3856
+ function parseJsonValue(value) {
3857
+ if (value === null || value === void 0) {
3858
+ return null;
3859
+ }
3860
+ if (typeof value === "object" && !(value instanceof Buffer)) {
3861
+ return value;
3862
+ }
3863
+ if (value instanceof Buffer || value instanceof Uint8Array) {
3864
+ throw new DbMcpError(
3865
+ "JSONB blob cannot be parsed directly. Use json() in SQL query.",
3866
+ "JSON_PARSE_ERROR",
3867
+ "validation" /* VALIDATION */
3868
+ );
3869
+ }
3870
+ if (typeof value === "string") {
3871
+ try {
3872
+ return JSON.parse(value);
3873
+ } catch {
3874
+ return value;
3875
+ }
3876
+ }
3877
+ if (typeof value === "number" || typeof value === "boolean") {
3878
+ return value;
3879
+ }
3880
+ return null;
3881
+ }
3882
+ var coerceNumber2 = (val) => {
3883
+ if (typeof val === "string") {
3884
+ if (val.trim() === "") return void 0;
3885
+ const num = Number(val);
3886
+ return isNaN(num) ? val : num;
3887
+ }
3888
+ return val;
3889
+ };
3890
+ var JsonInsertOutputSchema = z.object({
3891
+ success: z.boolean(),
3892
+ message: z.string().optional(),
3893
+ rowsAffected: z.number().optional(),
3894
+ warning: z.string().optional(),
3895
+ lastInsertRowid: z.number().optional()
4245
3896
  }).extend(ErrorResponseFields.shape);
4246
3897
  var JsonUpdateOutputSchema = z.object({
4247
3898
  success: z.boolean(),
@@ -4702,7 +4353,7 @@ function createJsonExtractTool(adapter) {
4702
4353
  return {
4703
4354
  success: true,
4704
4355
  rowCount: result.rows?.length ?? 0,
4705
- values: result.rows?.map((r) => r["value"])
4356
+ values: result.rows?.map((r) => parseJsonValue(r["value"]))
4706
4357
  };
4707
4358
  } catch (error) {
4708
4359
  return formatHandlerError(error);
@@ -5032,10 +4683,14 @@ function createJsonEachTool(adapter) {
5032
4683
  }
5033
4684
  sql += ` LIMIT ${input.limit}`;
5034
4685
  const result = await adapter.executeReadQuery(sql, queryParams);
4686
+ const parsedElements = result.rows?.map((row) => ({
4687
+ ...row,
4688
+ value: parseJsonValue(row["value"])
4689
+ })) ?? [];
5035
4690
  return {
5036
4691
  success: true,
5037
- rowCount: result.rows?.length ?? 0,
5038
- elements: result.rows
4692
+ rowCount: parsedElements.length,
4693
+ elements: parsedElements
5039
4694
  };
5040
4695
  } catch (error) {
5041
4696
  return formatHandlerError(error);
@@ -5089,10 +4744,17 @@ function createJsonGroupArrayTool(adapter) {
5089
4744
  }
5090
4745
  sql += groupByClause;
5091
4746
  const result = await adapter.executeReadQuery(sql, queryParams);
4747
+ const parsedRows = result.rows?.map((row) => {
4748
+ const parsedRow = {};
4749
+ for (const [key, value] of Object.entries(row)) {
4750
+ parsedRow[key] = parseJsonValue(value);
4751
+ }
4752
+ return parsedRow;
4753
+ }) ?? [];
5092
4754
  return {
5093
4755
  success: true,
5094
- rowCount: result.rows?.length ?? 0,
5095
- rows: result.rows ?? []
4756
+ rowCount: parsedRows.length,
4757
+ rows: parsedRows
5096
4758
  };
5097
4759
  } catch (error) {
5098
4760
  return formatHandlerError(error);
@@ -5144,10 +4806,17 @@ function createJsonGroupObjectTool(adapter) {
5144
4806
  }
5145
4807
  const sql2 = `SELECT ${outerSelect} FROM (${subquery})${outerGroupBy}`;
5146
4808
  const result2 = await adapter.executeReadQuery(sql2, queryParams);
4809
+ const parsedRows2 = result2.rows?.map((row) => {
4810
+ const parsedRow = {};
4811
+ for (const [key, value] of Object.entries(row)) {
4812
+ parsedRow[key] = parseJsonValue(value);
4813
+ }
4814
+ return parsedRow;
4815
+ }) ?? [];
5147
4816
  return {
5148
4817
  success: true,
5149
- rowCount: result2.rows?.length ?? 0,
5150
- rows: result2.rows ?? []
4818
+ rowCount: parsedRows2.length,
4819
+ rows: parsedRows2
5151
4820
  };
5152
4821
  }
5153
4822
  if (!input.valueColumn) {
@@ -5187,10 +4856,17 @@ function createJsonGroupObjectTool(adapter) {
5187
4856
  }
5188
4857
  sql += groupByClause;
5189
4858
  const result = await adapter.executeReadQuery(sql, queryParams);
4859
+ const parsedRows = result.rows?.map((row) => {
4860
+ const parsedRow = {};
4861
+ for (const [key, value] of Object.entries(row)) {
4862
+ parsedRow[key] = parseJsonValue(value);
4863
+ }
4864
+ return parsedRow;
4865
+ }) ?? [];
5190
4866
  return {
5191
4867
  success: true,
5192
- rowCount: result.rows?.length ?? 0,
5193
- rows: result.rows ?? [],
4868
+ rowCount: parsedRows.length,
4869
+ rows: parsedRows,
5194
4870
  ...duplicateKeyWarning && { hint: duplicateKeyWarning }
5195
4871
  };
5196
4872
  } catch (error) {
@@ -5200,124 +4876,6 @@ function createJsonGroupObjectTool(adapter) {
5200
4876
  };
5201
4877
  }
5202
4878
 
5203
- // src/adapters/sqlite/json-utils.ts
5204
- var DEFAULT_NORMALIZATION_OPTIONS = {
5205
- sortKeys: true,
5206
- compact: true,
5207
- unicodeNormalize: true,
5208
- typeCoercion: false,
5209
- duplicateKeys: "last"
5210
- };
5211
- function normalizeJson(value, options = {}) {
5212
- const opts = { ...DEFAULT_NORMALIZATION_OPTIONS, ...options };
5213
- const changes = [];
5214
- let parsed;
5215
- if (typeof value === "string") {
5216
- try {
5217
- parsed = JSON.parse(value);
5218
- } catch {
5219
- return {
5220
- normalized: value,
5221
- wasModified: false,
5222
- changes: ["Input is not valid JSON"]
5223
- };
5224
- }
5225
- } else {
5226
- parsed = value;
5227
- }
5228
- const normalized = normalizeValue(parsed, opts, changes, "");
5229
- const normalizedStr = opts.compact ? JSON.stringify(normalized) : JSON.stringify(normalized, null, 2);
5230
- const originalStr = typeof value === "string" ? value : JSON.stringify(value);
5231
- const wasModified = normalizedStr !== originalStr;
5232
- return {
5233
- normalized: normalizedStr,
5234
- wasModified,
5235
- changes
5236
- };
5237
- }
5238
- function normalizeValue(value, options, changes, path3) {
5239
- if (value === null || value === void 0) {
5240
- return null;
5241
- }
5242
- if (typeof value === "string") {
5243
- let result = value;
5244
- if (options.unicodeNormalize && value !== value.normalize("NFC")) {
5245
- result = result.normalize("NFC");
5246
- changes.push(`${path3 || "$"}: Unicode normalized`);
5247
- }
5248
- if (options.typeCoercion) {
5249
- if (result.toLowerCase() === "true") {
5250
- changes.push(`${path3 || "$"}: Coerced "true" to boolean`);
5251
- return true;
5252
- }
5253
- if (result.toLowerCase() === "false") {
5254
- changes.push(`${path3 || "$"}: Coerced "false" to boolean`);
5255
- return false;
5256
- }
5257
- const num = Number(result);
5258
- if (!isNaN(num) && result.trim() !== "") {
5259
- changes.push(`${path3 || "$"}: Coerced "${result}" to number`);
5260
- return num;
5261
- }
5262
- }
5263
- return result;
5264
- }
5265
- if (typeof value === "number" || typeof value === "boolean") {
5266
- return value;
5267
- }
5268
- if (Array.isArray(value)) {
5269
- return value.map(
5270
- (item, index) => normalizeValue(item, options, changes, `${path3 || "$"}[${index}]`)
5271
- );
5272
- }
5273
- if (typeof value === "object") {
5274
- const obj = value;
5275
- let keys = Object.keys(obj);
5276
- if (options.sortKeys) {
5277
- const originalOrder = keys.join(",");
5278
- keys = keys.sort();
5279
- if (keys.join(",") !== originalOrder) {
5280
- changes.push(`${path3 || "$"}: Keys sorted`);
5281
- }
5282
- }
5283
- const result = {};
5284
- for (const key of keys) {
5285
- result[key] = normalizeValue(
5286
- obj[key],
5287
- options,
5288
- changes,
5289
- `${path3 || "$"}.${key}`
5290
- );
5291
- }
5292
- return result;
5293
- }
5294
- return value;
5295
- }
5296
- var jsonbSupportedCache = null;
5297
- function isJsonbSupportedVersion(versionString) {
5298
- const versionRegex = /^(\d+)\.(\d+)\.?(\d+)?/;
5299
- const match = versionRegex.exec(versionString);
5300
- if (!match) return false;
5301
- const major = parseInt(match[1] ?? "0", 10);
5302
- const minor = parseInt(match[2] ?? "0", 10);
5303
- return major > 3 || major === 3 && minor >= 45;
5304
- }
5305
- function isJsonbSupported() {
5306
- return jsonbSupportedCache ?? false;
5307
- }
5308
- function setJsonbSupported(supported) {
5309
- jsonbSupportedCache = supported;
5310
- }
5311
- function detectJsonStorageFormat(value) {
5312
- if (value instanceof Buffer || value instanceof Uint8Array) {
5313
- return "jsonb";
5314
- }
5315
- if (typeof value === "string") {
5316
- return "text";
5317
- }
5318
- return "unknown";
5319
- }
5320
-
5321
4879
  // src/adapters/sqlite/tools/json-operations/transform.ts
5322
4880
  function createJsonPrettyTool() {
5323
4881
  return {
@@ -5616,7 +5174,7 @@ function createJsonSecurityScanTool(adapter) {
5616
5174
  const issueMap = /* @__PURE__ */ new Map();
5617
5175
  for (let i = 0; i < rows.length; i++) {
5618
5176
  if (i > 0 && i % 500 === 0) {
5619
- await new Promise((resolve4) => setTimeout(resolve4, 0));
5177
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
5620
5178
  }
5621
5179
  const row = rows[i];
5622
5180
  if (!row) continue;
@@ -5877,10 +5435,17 @@ function createJsonSelectTool(adapter) {
5877
5435
  }
5878
5436
  sql += ` LIMIT ${input.limit ?? 100}`;
5879
5437
  const result = await adapter.executeReadQuery(sql, queryParams);
5438
+ const parsedRows = result.rows?.map((row) => {
5439
+ const parsedRow = {};
5440
+ for (const [key, value] of Object.entries(row)) {
5441
+ parsedRow[key] = parseJsonValue(value);
5442
+ }
5443
+ return parsedRow;
5444
+ }) ?? [];
5880
5445
  return {
5881
5446
  success: true,
5882
- rowCount: result.rows?.length ?? 0,
5883
- rows: result.rows
5447
+ rowCount: parsedRows.length,
5448
+ rows: parsedRows
5884
5449
  };
5885
5450
  } catch (error) {
5886
5451
  return formatHandlerError(error);
@@ -5937,10 +5502,17 @@ function createJsonQueryTool(adapter) {
5937
5502
  }
5938
5503
  sql += ` LIMIT ${input.limit ?? 100}`;
5939
5504
  const result = await adapter.executeReadQuery(sql, queryParams);
5505
+ const parsedRows = result.rows?.map((row) => {
5506
+ const parsedRow = {};
5507
+ for (const [key, value] of Object.entries(row)) {
5508
+ parsedRow[key] = parseJsonValue(value);
5509
+ }
5510
+ return parsedRow;
5511
+ }) ?? [];
5940
5512
  return {
5941
5513
  success: true,
5942
- rowCount: result.rows?.length ?? 0,
5943
- rows: result.rows
5514
+ rowCount: parsedRows.length,
5515
+ rows: parsedRows
5944
5516
  };
5945
5517
  } catch (error) {
5946
5518
  return formatHandlerError(error);
@@ -6010,7 +5582,7 @@ function createAnalyzeJsonSchemaTool(adapter) {
6010
5582
  const rows = result.rows ?? [];
6011
5583
  for (let i = 0; i < rows.length; i++) {
6012
5584
  if (i > 0 && i % 500 === 0) {
6013
- await new Promise((resolve4) => setTimeout(resolve4, 0));
5585
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
6014
5586
  }
6015
5587
  const row = rows[i];
6016
5588
  if (!row) continue;
@@ -6354,6 +5926,18 @@ var VALID_VALIDATE_PATTERNS = [
6354
5926
  var VALID_PHONETIC_ALGORITHMS = ["soundex", "metaphone"];
6355
5927
  var VALID_TRIM_MODES = ["both", "left", "right"];
6356
5928
  var VALID_SEARCH_TECHNIQUES = ["exact", "fuzzy", "phonetic"];
5929
+ function sanitizeFtsQuery(query) {
5930
+ if (!query) return "";
5931
+ let clean = query.replace(/\s+/g, " ").trim();
5932
+ const quoteCount = (clean.match(/"/g) ?? []).length;
5933
+ if (quoteCount % 2 !== 0) {
5934
+ clean = clean.replace(/"/g, "");
5935
+ }
5936
+ clean = clean.replace(/^(?:\s*(?:AND|OR|NOT))+(?=\s|$)/i, "");
5937
+ clean = clean.replace(/(?:^|\s)(?:AND|OR|NOT)(?:\s+(?:AND|OR|NOT))*$/i, "");
5938
+ clean = clean.replace(/(?:\s+(?:AND|OR|NOT)){2,}\s+/gi, " AND ");
5939
+ return clean.trim();
5940
+ }
6357
5941
  var coerceNumber3 = (val) => {
6358
5942
  if (typeof val === "string") {
6359
5943
  if (val.trim() === "") return void 0;
@@ -6460,6 +6044,20 @@ var AdvancedSearchOutputSchema = z.object({
6460
6044
  bestScore: z.number(),
6461
6045
  bestType: z.string()
6462
6046
  })
6047
+ ).optional(),
6048
+ facets: z.record(z.string(), z.number()).optional()
6049
+ }).extend(ErrorResponseFields.shape);
6050
+ var HybridSearchOutputSchema = z.object({
6051
+ success: z.boolean(),
6052
+ query: z.string().optional(),
6053
+ matchCount: z.number().optional(),
6054
+ results: z.array(
6055
+ z.object({
6056
+ rowid: z.number().optional(),
6057
+ rrfScore: z.number().optional(),
6058
+ ftsRank: z.number().nullable().optional(),
6059
+ vectorSimilarity: z.number().nullable().optional()
6060
+ }).loose()
6463
6061
  ).optional()
6464
6062
  }).extend(ErrorResponseFields.shape);
6465
6063
  var SentimentResultItemSchema = z.object({
@@ -6630,7 +6228,20 @@ var AdvancedSearchSchema = z.object({
6630
6228
  ),
6631
6229
  conditions: z.array(WhereConditionSchema).optional().describe("Optional WHERE conditions"),
6632
6230
  whereClause: z.string().optional().describe("Deprecated: Use conditions instead"),
6633
- limit: z.preprocess(coerceNumber3, z.number().optional().default(100))
6231
+ limit: z.preprocess(coerceNumber3, z.number().optional().default(100)),
6232
+ includeFacets: z.boolean().optional().default(false).describe("Return match counts per type")
6233
+ });
6234
+ var HybridSearchSchema = z.object({
6235
+ table: z.string().describe("Table name"),
6236
+ query: z.string().describe("Text search query (FTS5)"),
6237
+ vectorColumn: z.string().describe("Vector column to search"),
6238
+ queryVector: z.array(z.number()).describe("Vector embedding of the query"),
6239
+ limit: z.preprocess(coerceNumber3, z.number().optional().default(100)),
6240
+ rrfK: z.preprocess(
6241
+ coerceNumber3,
6242
+ z.number().optional().default(60).describe("RRF k constant")
6243
+ ),
6244
+ metric: z.enum(["cosine", "euclidean", "dot"]).optional().default("cosine").describe("Vector similarity metric")
6634
6245
  });
6635
6246
  var TextSentimentSchema = z.object({
6636
6247
  table: z.string().optional().describe("Table name"),
@@ -7189,15 +6800,93 @@ function soundex(word) {
7189
6800
  }
7190
6801
  }
7191
6802
  }
7192
- return (result + "000").slice(0, 4);
6803
+ return (result + "000").slice(0, 4);
6804
+ }
6805
+ var VALIDATION_PATTERNS = {
6806
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
6807
+ phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]{7,}$/,
6808
+ url: /^https?:\/\/[^\s/$.?#].[^\s]*$/i,
6809
+ uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
6810
+ ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
6811
+ };
6812
+
6813
+ // src/adapters/sqlite/tools/vector/helpers.ts
6814
+ function cosineSimilarity(a, b) {
6815
+ if (a.length !== b.length) {
6816
+ throw new DbMcpError(
6817
+ "Vector dimensions must match",
6818
+ "VECTOR_MISMATCH",
6819
+ "validation" /* VALIDATION */
6820
+ );
6821
+ }
6822
+ let dotProd = 0;
6823
+ let normA = 0;
6824
+ let normB = 0;
6825
+ for (let i = 0; i < a.length; i++) {
6826
+ const aVal = a[i] ?? 0;
6827
+ const bVal = b[i] ?? 0;
6828
+ dotProd += aVal * bVal;
6829
+ normA += aVal * aVal;
6830
+ normB += bVal * bVal;
6831
+ }
6832
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
6833
+ return magnitude === 0 ? 0 : dotProd / magnitude;
6834
+ }
6835
+ function euclideanDistance(a, b) {
6836
+ if (a.length !== b.length) {
6837
+ throw new DbMcpError(
6838
+ "Vector dimensions must match",
6839
+ "VECTOR_MISMATCH",
6840
+ "validation" /* VALIDATION */
6841
+ );
6842
+ }
6843
+ let sum = 0;
6844
+ for (let i = 0; i < a.length; i++) {
6845
+ const aVal = a[i] ?? 0;
6846
+ const bVal = b[i] ?? 0;
6847
+ const diff = aVal - bVal;
6848
+ sum += diff * diff;
6849
+ }
6850
+ return Math.sqrt(sum);
6851
+ }
6852
+ function dotProduct(a, b) {
6853
+ if (a.length !== b.length) {
6854
+ throw new DbMcpError(
6855
+ "Vector dimensions must match",
6856
+ "VECTOR_MISMATCH",
6857
+ "validation" /* VALIDATION */
6858
+ );
6859
+ }
6860
+ let sum = 0;
6861
+ for (let i = 0; i < a.length; i++) {
6862
+ sum += (a[i] ?? 0) * (b[i] ?? 0);
6863
+ }
6864
+ return sum;
6865
+ }
6866
+ function normalizeVector(v) {
6867
+ const norm = Math.sqrt(v.reduce((sum, x) => sum + x * x, 0));
6868
+ if (norm === 0) return v;
6869
+ return v.map((x) => x / norm);
6870
+ }
6871
+ function parseVector(value) {
6872
+ if (Array.isArray(value)) {
6873
+ return value.map(Number);
6874
+ }
6875
+ if (typeof value === "string") {
6876
+ try {
6877
+ const parsed = JSON.parse(value);
6878
+ if (Array.isArray(parsed)) {
6879
+ return parsed.map(Number);
6880
+ }
6881
+ } catch {
6882
+ }
6883
+ }
6884
+ throw new DbMcpError(
6885
+ "Invalid vector format",
6886
+ "VECTOR_INVALID",
6887
+ "validation" /* VALIDATION */
6888
+ );
7193
6889
  }
7194
- var VALIDATION_PATTERNS = {
7195
- email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
7196
- phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]{7,}$/,
7197
- url: /^https?:\/\/[^\s/$.?#].[^\s]*$/i,
7198
- uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
7199
- ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
7200
- };
7201
6890
 
7202
6891
  // src/adapters/sqlite/tools/text/search.ts
7203
6892
  function createFuzzyMatchTool(adapter) {
@@ -7429,12 +7118,131 @@ function createAdvancedSearchTool(adapter) {
7429
7118
  }
7430
7119
  allMatches.sort((a, b) => b.bestScore - a.bestScore);
7431
7120
  const limited = allMatches.slice(0, input.limit);
7121
+ const facets = {};
7122
+ if (input.includeFacets) {
7123
+ for (const technique of input.techniques) {
7124
+ facets[technique] = 0;
7125
+ }
7126
+ for (const match of allMatches) {
7127
+ facets[match.bestType] = (facets[match.bestType] ?? 0) + 1;
7128
+ }
7129
+ }
7432
7130
  return {
7433
7131
  success: true,
7434
7132
  searchTerm: input.searchTerm,
7435
7133
  techniques: input.techniques,
7436
7134
  matchCount: limited.length,
7437
- matches: limited
7135
+ matches: limited,
7136
+ ...input.includeFacets ? { facets } : {}
7137
+ };
7138
+ } catch (error) {
7139
+ return formatHandlerError(error);
7140
+ }
7141
+ }
7142
+ };
7143
+ }
7144
+ function createHybridSearchTool(adapter) {
7145
+ return {
7146
+ name: "sqlite_hybrid_search",
7147
+ description: "Hybrid search combining full-text search and vector similarity using Reciprocal Rank Fusion (RRF).",
7148
+ group: "text",
7149
+ inputSchema: HybridSearchSchema,
7150
+ outputSchema: HybridSearchOutputSchema,
7151
+ requiredScopes: ["read"],
7152
+ annotations: readOnly("Hybrid Search"),
7153
+ handler: async (params, _context) => {
7154
+ try {
7155
+ const input = HybridSearchSchema.parse(params);
7156
+ const table = sanitizeIdentifier(input.table);
7157
+ const vectorColumn = sanitizeIdentifier(input.vectorColumn);
7158
+ await validateColumnExists(adapter, input.table, input.vectorColumn);
7159
+ let textResults = [];
7160
+ try {
7161
+ const sanitizedQuery = sanitizeFtsQuery(input.query);
7162
+ if (sanitizedQuery) {
7163
+ const queryEscaped = sanitizedQuery.replace(/'/g, "''");
7164
+ const ftsSql = `SELECT rowid as id, rank as score FROM ${table} WHERE ${table} MATCH '${queryEscaped}' ORDER BY rank LIMIT 1000`;
7165
+ const result = await adapter.executeReadQuery(ftsSql, []);
7166
+ textResults = (result.rows ?? []).map((r, i) => ({
7167
+ id: Number(r["id"] ?? r["rowid"]),
7168
+ rank: i + 1
7169
+ }));
7170
+ }
7171
+ } catch {
7172
+ }
7173
+ const vecSql = `SELECT rowid as id, ${vectorColumn} as vec FROM ${table} WHERE ${vectorColumn} IS NOT NULL LIMIT 10000`;
7174
+ const vecResult = await adapter.executeReadQuery(vecSql, []);
7175
+ const queryVector = input.queryVector;
7176
+ const vecScored = [];
7177
+ for (const row of vecResult.rows ?? []) {
7178
+ try {
7179
+ const storedVector = parseVector(row["vec"]);
7180
+ let score;
7181
+ switch (input.metric) {
7182
+ case "euclidean":
7183
+ score = 1 / (1 + euclideanDistance(queryVector, storedVector));
7184
+ break;
7185
+ case "dot":
7186
+ score = dotProduct(queryVector, storedVector);
7187
+ break;
7188
+ case "cosine":
7189
+ default:
7190
+ score = cosineSimilarity(queryVector, storedVector);
7191
+ break;
7192
+ }
7193
+ vecScored.push({ id: Number(row["id"] ?? row["rowid"]), score });
7194
+ } catch {
7195
+ }
7196
+ }
7197
+ vecScored.sort((a, b) => b.score - a.score);
7198
+ const vectorResults = vecScored.map((r, i) => ({
7199
+ id: r.id,
7200
+ rank: i + 1,
7201
+ score: r.score
7202
+ }));
7203
+ const combined = /* @__PURE__ */ new Map();
7204
+ const k = input.rrfK;
7205
+ for (const f of textResults) {
7206
+ if (!combined.has(f.id)) {
7207
+ combined.set(f.id, {
7208
+ id: f.id,
7209
+ rrfScore: 0,
7210
+ ftsRank: null,
7211
+ vectorSimilarity: null
7212
+ });
7213
+ }
7214
+ const entry = combined.get(f.id);
7215
+ if (entry) {
7216
+ entry.ftsRank = f.rank;
7217
+ entry.rrfScore += 1 / (k + f.rank);
7218
+ }
7219
+ }
7220
+ for (const v of vectorResults) {
7221
+ if (!combined.has(v.id)) {
7222
+ combined.set(v.id, {
7223
+ id: v.id,
7224
+ rrfScore: 0,
7225
+ ftsRank: null,
7226
+ vectorSimilarity: null
7227
+ });
7228
+ }
7229
+ const entry = combined.get(v.id);
7230
+ if (entry) {
7231
+ entry.vectorSimilarity = v.score;
7232
+ entry.rrfScore += 1 / (k + v.rank);
7233
+ }
7234
+ }
7235
+ const sortedCombined = Array.from(combined.values()).sort((a, b) => b.rrfScore - a.rrfScore).slice(0, input.limit).map((r) => ({
7236
+ rowid: r.id,
7237
+ rrfScore: Math.round(r.rrfScore * 1e4) / 1e4,
7238
+ ftsRank: r.ftsRank,
7239
+ vectorSimilarity: r.vectorSimilarity !== null ? Math.round(r.vectorSimilarity * 1e4) / 1e4 : null
7240
+ }));
7241
+ return {
7242
+ success: true,
7243
+ query: input.query,
7244
+ matchCount: sortedCombined.length,
7245
+ results: sortedCombined
7438
7246
  };
7439
7247
  } catch (error) {
7440
7248
  return formatHandlerError(error);
@@ -7768,6 +7576,7 @@ function getTextTools(adapter) {
7768
7576
  createTextNormalizeTool(adapter),
7769
7577
  createTextValidateTool(adapter),
7770
7578
  createAdvancedSearchTool(adapter),
7579
+ createHybridSearchTool(adapter),
7771
7580
  createTextSentimentTool(adapter)
7772
7581
  ];
7773
7582
  }
@@ -11754,84 +11563,6 @@ function createVectorDeleteTool(adapter) {
11754
11563
  };
11755
11564
  }
11756
11565
 
11757
- // src/adapters/sqlite/tools/vector/helpers.ts
11758
- function cosineSimilarity(a, b) {
11759
- if (a.length !== b.length) {
11760
- throw new DbMcpError(
11761
- "Vector dimensions must match",
11762
- "VECTOR_MISMATCH",
11763
- "validation" /* VALIDATION */
11764
- );
11765
- }
11766
- let dotProd = 0;
11767
- let normA = 0;
11768
- let normB = 0;
11769
- for (let i = 0; i < a.length; i++) {
11770
- const aVal = a[i] ?? 0;
11771
- const bVal = b[i] ?? 0;
11772
- dotProd += aVal * bVal;
11773
- normA += aVal * aVal;
11774
- normB += bVal * bVal;
11775
- }
11776
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
11777
- return magnitude === 0 ? 0 : dotProd / magnitude;
11778
- }
11779
- function euclideanDistance(a, b) {
11780
- if (a.length !== b.length) {
11781
- throw new DbMcpError(
11782
- "Vector dimensions must match",
11783
- "VECTOR_MISMATCH",
11784
- "validation" /* VALIDATION */
11785
- );
11786
- }
11787
- let sum = 0;
11788
- for (let i = 0; i < a.length; i++) {
11789
- const aVal = a[i] ?? 0;
11790
- const bVal = b[i] ?? 0;
11791
- const diff = aVal - bVal;
11792
- sum += diff * diff;
11793
- }
11794
- return Math.sqrt(sum);
11795
- }
11796
- function dotProduct(a, b) {
11797
- if (a.length !== b.length) {
11798
- throw new DbMcpError(
11799
- "Vector dimensions must match",
11800
- "VECTOR_MISMATCH",
11801
- "validation" /* VALIDATION */
11802
- );
11803
- }
11804
- let sum = 0;
11805
- for (let i = 0; i < a.length; i++) {
11806
- sum += (a[i] ?? 0) * (b[i] ?? 0);
11807
- }
11808
- return sum;
11809
- }
11810
- function normalizeVector(v) {
11811
- const norm = Math.sqrt(v.reduce((sum, x) => sum + x * x, 0));
11812
- if (norm === 0) return v;
11813
- return v.map((x) => x / norm);
11814
- }
11815
- function parseVector(value) {
11816
- if (Array.isArray(value)) {
11817
- return value.map(Number);
11818
- }
11819
- if (typeof value === "string") {
11820
- try {
11821
- const parsed = JSON.parse(value);
11822
- if (Array.isArray(parsed)) {
11823
- return parsed.map(Number);
11824
- }
11825
- } catch {
11826
- }
11827
- }
11828
- throw new DbMcpError(
11829
- "Invalid vector format",
11830
- "VECTOR_INVALID",
11831
- "validation" /* VALIDATION */
11832
- );
11833
- }
11834
-
11835
11566
  // src/adapters/sqlite/tools/vector/search.ts
11836
11567
  function createVectorSearchTool(adapter) {
11837
11568
  return {
@@ -11880,7 +11611,7 @@ function createVectorSearchTool(adapter) {
11880
11611
  const rows = result.rows ?? [];
11881
11612
  for (let i = 0; i < rows.length; i++) {
11882
11613
  if (i > 0 && i % 500 === 0) {
11883
- await new Promise((resolve4) => setTimeout(resolve4, 0));
11614
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
11884
11615
  }
11885
11616
  const row = rows[i];
11886
11617
  if (!row) continue;
@@ -12059,7 +11790,7 @@ function createVectorStatsTool(adapter) {
12059
11790
  const rows = result.rows ?? [];
12060
11791
  for (let i = 0; i < rows.length; i++) {
12061
11792
  if (i > 0 && i % 500 === 0) {
12062
- await new Promise((resolve4) => setTimeout(resolve4, 0));
11793
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
12063
11794
  }
12064
11795
  try {
12065
11796
  const row = rows[i];
@@ -13273,9 +13004,9 @@ function createDumpTool(adapter) {
13273
13004
  }
13274
13005
  }
13275
13006
  stream.write("COMMIT;\n");
13276
- await new Promise((resolve4, reject) => {
13007
+ await new Promise((resolve3, reject) => {
13277
13008
  stream.end(
13278
- (err) => err ? reject(err) : resolve4()
13009
+ (err) => err ? reject(err) : resolve3()
13279
13010
  );
13280
13011
  });
13281
13012
  } catch (err) {
@@ -13521,10 +13252,10 @@ function createPragmaDatabaseListTool(adapter) {
13521
13252
  };
13522
13253
  });
13523
13254
  const configuredPath = adapter.getConfiguredPath();
13524
- const normalize2 = (p) => p.replace(/\\/g, "/");
13255
+ const normalize = (p) => p.replace(/\\/g, "/");
13525
13256
  const mainDb = databases.find((db) => db.name === "main");
13526
13257
  const internalPathDiffers = Boolean(
13527
- mainDb?.file && normalize2(mainDb.file) !== normalize2(configuredPath.split(/[\\/]/).pop() ?? "")
13258
+ mainDb?.file && normalize(mainDb.file) !== normalize(configuredPath.split(/[\\/]/).pop() ?? "")
13528
13259
  );
13529
13260
  return {
13530
13261
  success: true,
@@ -13702,46 +13433,6 @@ function createPragmaTableInfoTool(adapter) {
13702
13433
  }
13703
13434
  };
13704
13435
  }
13705
- function createAppendInsightTool() {
13706
- return {
13707
- name: "sqlite_append_insight",
13708
- description: "Add a business insight to the memo://insights resource. Use this to capture key findings during data analysis.",
13709
- group: "admin",
13710
- inputSchema: AppendInsightSchema,
13711
- outputSchema: AppendInsightOutputSchema,
13712
- requiredScopes: ["write"],
13713
- annotations: write("Append Insight"),
13714
- handler: (_params, _context) => {
13715
- try {
13716
- const input = AppendInsightSchema.parse(_params);
13717
- let sanitizedInsight = input.insight.replace(/[<>]/g, "");
13718
- if (sanitizedInsight.length > 500) {
13719
- sanitizedInsight = sanitizedInsight.substring(0, 500) + "...";
13720
- }
13721
- if (!sanitizedInsight || sanitizedInsight.trim().length === 0) {
13722
- return Promise.resolve({
13723
- success: false,
13724
- error: "Insight must be a non-empty string",
13725
- message: "",
13726
- insightCount: insightsManager.count()
13727
- });
13728
- }
13729
- insightsManager.append(sanitizedInsight);
13730
- return Promise.resolve({
13731
- success: true,
13732
- message: "Insight added to memo",
13733
- insightCount: insightsManager.count()
13734
- });
13735
- } catch (error) {
13736
- return Promise.resolve({
13737
- ...formatHandlerError(error),
13738
- message: "",
13739
- insightCount: insightsManager.count()
13740
- });
13741
- }
13742
- }
13743
- };
13744
- }
13745
13436
  function createAttachDatabaseTool(adapter) {
13746
13437
  return {
13747
13438
  name: "sqlite_attach_database",
@@ -13990,7 +13681,6 @@ function getAdminTools(adapter) {
13990
13681
  createPragmaOptimizeTool(adapter),
13991
13682
  createPragmaSettingsTool(adapter),
13992
13683
  createPragmaTableInfoTool(adapter),
13993
- createAppendInsightTool(),
13994
13684
  createAttachDatabaseTool(adapter),
13995
13685
  createDetachDatabaseTool(adapter),
13996
13686
  createVacuumIntoTool(adapter),
@@ -14262,7 +13952,8 @@ var IndexAuditOutputSchema = z.object({
14262
13952
  type: z.enum([
14263
13953
  "redundant",
14264
13954
  "missing_fk_index",
14265
- "unindexed_large_table"
13955
+ "unindexed_large_table",
13956
+ "missing_composite_index"
14266
13957
  ]),
14267
13958
  severity: z.enum(["info", "warning", "error"]),
14268
13959
  table: z.string(),
@@ -14315,23 +14006,6 @@ var coerceNumber8 = (val) => {
14315
14006
  }
14316
14007
  return val;
14317
14008
  };
14318
- var VALID_SECTIONS = ["tables", "views", "indexes", "triggers"];
14319
- var coerceSections = (val) => Array.isArray(val) ? val.filter(
14320
- (v) => typeof v === "string" && VALID_SECTIONS.includes(v)
14321
- ) : val;
14322
- var VALID_CHECKS = [
14323
- "missing_pk",
14324
- "missing_not_null",
14325
- "unindexed_fk",
14326
- "missing_fk"
14327
- ];
14328
- var coerceChecks = (val) => Array.isArray(val) ? val.filter(
14329
- (v) => typeof v === "string" && VALID_CHECKS.includes(v)
14330
- ) : val;
14331
- var VALID_DIRECTIONS = ["create", "drop"];
14332
- var coerceDirection = (val) => typeof val === "string" && VALID_DIRECTIONS.includes(val) ? val : typeof val === "string" ? void 0 : val;
14333
- var VALID_OPERATIONS = ["DELETE", "DROP", "TRUNCATE"];
14334
- var coerceOperation = (val) => typeof val === "string" && VALID_OPERATIONS.includes(val) ? val : typeof val === "string" ? void 0 : val;
14335
14009
  var StorageAnalysisSchema = z.object({
14336
14010
  includeTableDetails: z.boolean().optional().describe("Include per-table size breakdown (default: true)"),
14337
14011
  excludeSystemTables: z.boolean().optional().describe(
@@ -14350,13 +14024,16 @@ var IndexAuditSchema = z.object({
14350
14024
  ),
14351
14025
  minSeverity: z.enum(["info", "warning", "error"]).optional().describe(
14352
14026
  "Minimum severity to include in findings (default: all). Reduces payload for large databases."
14027
+ ),
14028
+ recommendComposite: z.boolean().optional().default(false).describe(
14029
+ "Analyze queriesToAnalyze to recommend composite/partial indexes"
14030
+ ),
14031
+ queriesToAnalyze: z.array(z.string()).optional().describe(
14032
+ "Queries to analyze using EXPLAIN QUERY PLAN (used if recommendComposite is true)"
14353
14033
  )
14354
- }).default({});
14034
+ }).default({ recommendComposite: false });
14355
14035
  var SchemaSnapshotSchema = z.object({
14356
- sections: z.preprocess(
14357
- coerceSections,
14358
- z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional()
14359
- ).describe("Specific sections to include (default: all)"),
14036
+ sections: z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional().describe("Specific sections to include (default: all)"),
14360
14037
  compact: z.boolean().optional().describe(
14361
14038
  "Omit column details from tables section for reduced payload (default: false)"
14362
14039
  ),
@@ -14369,17 +14046,14 @@ var MigrationRisksSchema = z.object({
14369
14046
  }).default({ statements: [] });
14370
14047
  var ConstraintAnalysisSchema = z.object({
14371
14048
  table: z.string().optional().describe("Analyze constraints for a specific table only"),
14372
- checks: z.preprocess(
14373
- coerceChecks,
14374
- z.array(
14375
- z.enum([
14376
- "missing_pk",
14377
- "missing_not_null",
14378
- "unindexed_fk",
14379
- "missing_fk"
14380
- ])
14381
- ).optional()
14382
- ).describe("Specific checks to run (default: all)"),
14049
+ checks: z.array(
14050
+ z.enum([
14051
+ "missing_pk",
14052
+ "missing_not_null",
14053
+ "unindexed_fk",
14054
+ "missing_fk"
14055
+ ])
14056
+ ).optional().describe("Specific checks to run (default: all)"),
14383
14057
  excludeSystemTables: z.boolean().optional().describe(
14384
14058
  "Exclude SpatiaLite system tables from constraint analysis (default: true)"
14385
14059
  )
@@ -14394,7 +14068,7 @@ var DependencyGraphSchema = z.object({
14394
14068
  )
14395
14069
  }).default({});
14396
14070
  var TopologicalSortSchema = z.object({
14397
- direction: z.preprocess(coerceDirection, z.enum(["create", "drop"]).optional()).describe(
14071
+ direction: z.enum(["create", "drop"]).optional().describe(
14398
14072
  "Sort direction: 'create' = dependencies first, 'drop' = dependents first (default: create)"
14399
14073
  ),
14400
14074
  excludeSystemTables: z.boolean().optional().describe(
@@ -14403,10 +14077,7 @@ var TopologicalSortSchema = z.object({
14403
14077
  }).default({});
14404
14078
  var CascadeSimulatorSchema = z.object({
14405
14079
  table: z.string().optional().default("").describe("Table name to simulate deletion from"),
14406
- operation: z.preprocess(
14407
- coerceOperation,
14408
- z.enum(["DELETE", "DROP", "TRUNCATE"]).optional()
14409
- ).describe("Operation to simulate (default: DELETE)"),
14080
+ operation: z.enum(["DELETE", "DROP", "TRUNCATE"]).optional().describe("Operation to simulate (default: DELETE)"),
14410
14081
  compact: z.boolean().optional().describe(
14411
14082
  "Omit path arrays from affected entries to reduce payload (default: false)"
14412
14083
  )
@@ -14472,10 +14143,6 @@ var SchemaDiffOutputSchema = z.object({
14472
14143
  }).optional(),
14473
14144
  comparedAt: z.string().optional()
14474
14145
  }).extend(ErrorResponseFields.shape);
14475
- var VALID_DIFF_SECTIONS = ["tables", "views", "indexes", "triggers"];
14476
- var coerceDiffSections = (val) => Array.isArray(val) ? val.filter(
14477
- (v) => typeof v === "string" && VALID_DIFF_SECTIONS.includes(v)
14478
- ) : val;
14479
14146
  var SchemaDiffSchema = z.object({
14480
14147
  baseline: z.union([z.literal("current"), SchemaSnapshotShape]).optional().describe(
14481
14148
  "Baseline schema \u2014 'current' to snapshot live DB, or an inline snapshot object from a previous sqlite_schema_snapshot call"
@@ -14483,10 +14150,7 @@ var SchemaDiffSchema = z.object({
14483
14150
  target: z.union([z.literal("current"), SchemaSnapshotShape]).optional().describe(
14484
14151
  "Target schema to compare against baseline \u2014 'current' to snapshot live DB, or an inline snapshot object"
14485
14152
  ),
14486
- sections: z.preprocess(
14487
- coerceDiffSections,
14488
- z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional()
14489
- ).describe("Sections to compare (default: all)"),
14153
+ sections: z.array(z.enum(["tables", "views", "indexes", "triggers"])).optional().describe("Sections to compare (default: all)"),
14490
14154
  excludeSystemTables: z.boolean().optional().describe(
14491
14155
  "Exclude SpatiaLite system tables when capturing live schema (default: true)"
14492
14156
  )
@@ -15845,6 +15509,35 @@ function createIndexAuditTool(adapter) {
15845
15509
  } catch {
15846
15510
  }
15847
15511
  }
15512
+ if (input.recommendComposite && input.queriesToAnalyze && input.queriesToAnalyze.length > 0) {
15513
+ for (const query of input.queriesToAnalyze) {
15514
+ try {
15515
+ if (/^\s*SELECT/i.test(query)) {
15516
+ const planResult = await adapter.executeReadQuery(
15517
+ `EXPLAIN QUERY PLAN ${query}`
15518
+ );
15519
+ const planRows = planResult.rows ?? [];
15520
+ for (const row of planRows) {
15521
+ const detailVal = row["detail"];
15522
+ const detail = typeof detailVal === "string" ? detailVal : "";
15523
+ if (detail.startsWith("SCAN TABLE") || detail.startsWith("SCAN ")) {
15524
+ const match = /SCAN (?:TABLE )?([\w_]+)/i.exec(detail);
15525
+ const tableName = match ? match[1] : "unknown";
15526
+ if (tableName && tableName !== "unknown") {
15527
+ findings.push({
15528
+ type: "missing_composite_index",
15529
+ severity: "warning",
15530
+ table: tableName,
15531
+ suggestion: `Query resulted in SCAN TABLE "${tableName}". Consider analyzing WHERE/JOIN clauses to add a composite index.`
15532
+ });
15533
+ }
15534
+ }
15535
+ }
15536
+ }
15537
+ } catch {
15538
+ }
15539
+ }
15540
+ }
15848
15541
  const redundantCount = findings.filter(
15849
15542
  (f) => f.type === "redundant"
15850
15543
  ).length;
@@ -16909,6 +16602,7 @@ var POSITIONAL_PARAM_MAP = {
16909
16602
  jsonbConvert: ["table", "column"],
16910
16603
  createJsonCollection: "tableName",
16911
16604
  // Text
16605
+ sentiment: "text",
16912
16606
  regexExtract: ["table", "column", "pattern"],
16913
16607
  regexMatch: ["table", "column", "pattern"],
16914
16608
  split: ["table", "column", "delimiter"],
@@ -16966,7 +16660,6 @@ var POSITIONAL_PARAM_MAP = {
16966
16660
  pragmaSettings: "pragma",
16967
16661
  pragmaTableInfo: "table",
16968
16662
  pragmaCompileOptions: "filter",
16969
- appendInsight: "insight",
16970
16663
  generateSeries: ["start", "stop", "step"],
16971
16664
  createView: ["viewName", "selectQuery"],
16972
16665
  dropView: "viewName",
@@ -17699,94 +17392,97 @@ var CodeModeSandbox = class _CodeModeSandbox {
17699
17392
  const isolate = new ivmLib.Isolate({
17700
17393
  memoryLimit: this.options.memoryLimitMb
17701
17394
  });
17702
- const context = isolate.createContextSync();
17703
- const jail = context.global;
17704
- jail.setSync("global", jail.derefInto());
17705
- const logs = [];
17706
- const logRef = new ivmLib.Reference((level, ...args) => {
17707
- const msg = args.map(
17708
- (a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)
17709
- ).join(" ");
17710
- logs.push(level === "LOG" ? msg : `[${level}] ${msg}`);
17711
- });
17712
- context.global.setSync("logRef", logRef);
17713
- const setupScript = `
17714
- globalThis.console = {
17715
- log: (...args) => logRef.applyIgnored(undefined, ['LOG', ...args], { arguments: { copy: true } }),
17716
- error: (...args) => logRef.applyIgnored(undefined, ['ERROR', ...args], { arguments: { copy: true } }),
17717
- warn: (...args) => logRef.applyIgnored(undefined, ['WARN', ...args], { arguments: { copy: true } }),
17718
- info: (...args) => logRef.applyIgnored(undefined, ['INFO', ...args], { arguments: { copy: true } }),
17719
- debug: (...args) => logRef.applyIgnored(undefined, ['DEBUG', ...args], { arguments: { copy: true } })
17720
- };
17721
- globalThis.sqlite = {};
17722
- `;
17723
- context.evalSync(setupScript);
17724
- let rpcCount = 0;
17725
- const MAX_RPC_CALLS = 100;
17395
+ let context;
17396
+ let logRef;
17397
+ let script;
17726
17398
  const refCleanup = [];
17727
- for (const [groupName, groupValue] of Object.entries(apiBindings)) {
17728
- if (typeof groupValue === "object" && groupValue !== null) {
17729
- context.evalSync(
17730
- `globalThis.sqlite[${JSON.stringify(groupName)}] = {};`
17731
- );
17732
- for (const [methodName, methodFn] of Object.entries(groupValue)) {
17733
- if (typeof methodFn === "function") {
17734
- const fnRef = new ivmLib.Reference(async (...args) => {
17735
- if (++rpcCount > MAX_RPC_CALLS) {
17736
- throw new Error(
17737
- `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17738
- );
17739
- }
17740
- try {
17741
- return await methodFn(...args);
17742
- } catch (e) {
17743
- throw new Error(e instanceof Error ? e.message : String(e), {
17744
- cause: e
17745
- });
17746
- }
17747
- });
17748
- refCleanup.push(fnRef);
17749
- const refName = `fnRef_${groupName}_${methodName}`;
17750
- context.global.setSync(refName, fnRef);
17751
- context.evalSync(`
17752
- globalThis.sqlite[${JSON.stringify(groupName)}][${JSON.stringify(methodName)}] = async (...args) => {
17753
- return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17754
- };
17755
- `);
17756
- }
17757
- }
17758
- } else if (typeof groupValue === "function") {
17759
- const fnRef = new ivmLib.Reference(async (...args) => {
17760
- if (++rpcCount > MAX_RPC_CALLS) {
17761
- throw new Error(
17762
- `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17763
- );
17764
- }
17765
- try {
17766
- return await groupValue(...args);
17767
- } catch (e) {
17768
- throw new Error(e instanceof Error ? e.message : String(e), {
17769
- cause: e
17770
- });
17771
- }
17772
- });
17773
- refCleanup.push(fnRef);
17774
- const refName = `fnRef_${groupName}`;
17775
- context.global.setSync(refName, fnRef);
17776
- context.evalSync(`
17777
- globalThis.sqlite[${JSON.stringify(groupName)}] = async (...args) => {
17778
- return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17779
- };
17780
- `);
17781
- }
17782
- }
17399
+ const logs = [];
17783
17400
  const startTime = performance.now();
17784
17401
  let result;
17785
17402
  let success = true;
17786
17403
  let errorMsg;
17787
17404
  try {
17405
+ context = isolate.createContextSync();
17406
+ const jail = context.global;
17407
+ jail.setSync("global", jail.derefInto());
17408
+ logRef = new ivmLib.Reference((level, ...args) => {
17409
+ const msg = args.map(
17410
+ (a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)
17411
+ ).join(" ");
17412
+ logs.push(level === "LOG" ? msg : `[${level}] ${msg}`);
17413
+ });
17414
+ context.global.setSync("logRef", logRef);
17415
+ const setupScript = `
17416
+ globalThis.console = {
17417
+ log: (...args) => logRef.applyIgnored(undefined, ['LOG', ...args], { arguments: { copy: true } }),
17418
+ error: (...args) => logRef.applyIgnored(undefined, ['ERROR', ...args], { arguments: { copy: true } }),
17419
+ warn: (...args) => logRef.applyIgnored(undefined, ['WARN', ...args], { arguments: { copy: true } }),
17420
+ info: (...args) => logRef.applyIgnored(undefined, ['INFO', ...args], { arguments: { copy: true } }),
17421
+ debug: (...args) => logRef.applyIgnored(undefined, ['DEBUG', ...args], { arguments: { copy: true } })
17422
+ };
17423
+ globalThis.sqlite = {};
17424
+ `;
17425
+ context.evalSync(setupScript);
17426
+ let rpcCount = 0;
17427
+ const MAX_RPC_CALLS = 100;
17428
+ for (const [groupName, groupValue] of Object.entries(apiBindings)) {
17429
+ if (typeof groupValue === "object" && groupValue !== null) {
17430
+ context.evalSync(
17431
+ `globalThis.sqlite[${JSON.stringify(groupName)}] = {};`
17432
+ );
17433
+ for (const [methodName, methodFn] of Object.entries(groupValue)) {
17434
+ if (typeof methodFn === "function") {
17435
+ const fnRef = new ivmLib.Reference(async (...args) => {
17436
+ if (++rpcCount > MAX_RPC_CALLS) {
17437
+ throw new Error(
17438
+ `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17439
+ );
17440
+ }
17441
+ try {
17442
+ return await methodFn(...args);
17443
+ } catch (e) {
17444
+ throw new Error(e instanceof Error ? e.message : String(e), {
17445
+ cause: e
17446
+ });
17447
+ }
17448
+ });
17449
+ refCleanup.push(fnRef);
17450
+ const refName = `fnRef_${groupName}_${methodName}`;
17451
+ context.global.setSync(refName, fnRef);
17452
+ context.evalSync(`
17453
+ globalThis.sqlite[${JSON.stringify(groupName)}][${JSON.stringify(methodName)}] = async (...args) => {
17454
+ return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17455
+ };
17456
+ `);
17457
+ }
17458
+ }
17459
+ } else if (typeof groupValue === "function") {
17460
+ const fnRef = new ivmLib.Reference(async (...args) => {
17461
+ if (++rpcCount > MAX_RPC_CALLS) {
17462
+ throw new Error(
17463
+ `QuotaExceededError: Maximum number of host tool calls (${MAX_RPC_CALLS}) exceeded (attempted call ${rpcCount}).`
17464
+ );
17465
+ }
17466
+ try {
17467
+ return await groupValue(...args);
17468
+ } catch (e) {
17469
+ throw new Error(e instanceof Error ? e.message : String(e), {
17470
+ cause: e
17471
+ });
17472
+ }
17473
+ });
17474
+ refCleanup.push(fnRef);
17475
+ const refName = `fnRef_${groupName}`;
17476
+ context.global.setSync(refName, fnRef);
17477
+ context.evalSync(`
17478
+ globalThis.sqlite[${JSON.stringify(groupName)}] = async (...args) => {
17479
+ return await globalThis[${JSON.stringify(refName)}].apply(undefined, args, { arguments: { copy: true }, result: { promise: true, copy: true } });
17480
+ };
17481
+ `);
17482
+ }
17483
+ }
17788
17484
  const wrappedCode = `(async () => { ${transformAutoReturn(code)} })()`;
17789
- const script = isolate.compileScriptSync(wrappedCode, {
17485
+ script = isolate.compileScriptSync(wrappedCode, {
17790
17486
  filename: `code-mode.js`
17791
17487
  });
17792
17488
  result = await script.run(context, {
@@ -17799,11 +17495,27 @@ var CodeModeSandbox = class _CodeModeSandbox {
17799
17495
  errorMsg = error instanceof Error ? error.message : String(error);
17800
17496
  } finally {
17801
17497
  for (const ref of refCleanup) {
17802
- ref.release();
17498
+ try {
17499
+ ref.release();
17500
+ } catch {
17501
+ }
17502
+ }
17503
+ try {
17504
+ logRef?.release();
17505
+ } catch {
17506
+ }
17507
+ try {
17508
+ script?.release();
17509
+ } catch {
17510
+ }
17511
+ try {
17512
+ context?.release();
17513
+ } catch {
17514
+ }
17515
+ try {
17516
+ isolate.dispose();
17517
+ } catch {
17803
17518
  }
17804
- logRef.release();
17805
- context.release();
17806
- isolate.dispose();
17807
17519
  }
17808
17520
  const endTime = performance.now();
17809
17521
  this.accumulatedLogs.push(...logs);
@@ -18340,4 +18052,4 @@ function executeGeneral(db, sql, params) {
18340
18052
  }
18341
18053
  }
18342
18054
 
18343
- export { FtsHeadlineSchema, RowRecordSchema, SchemaManager, WhereConditionSchema, applyCommonPragmas, autoEnableWal, buildWhereClause, detectAndSetJsonbSupport, executeGeneral, executeRead, executeWrite, getAdminTools, getCodeModeTools, getCoreTools, getGeoTools, getIntrospectionTools, getJsonHelperTools, getJsonOperationTools, getMigrationTools, getPromptDefinitions, getResourceDefinitions, getStatsTools, getTextTools, getVectorTools, getVirtualTools, isDDL, normalizeSqliteParams, resolveAliases, sanitizeIdentifier, translateSqliteError, validateIdentifier, validateTableExists };
18055
+ export { FtsHeadlineSchema, RowRecordSchema, SchemaManager, WhereConditionSchema, applyCommonPragmas, autoEnableWal, detectAndSetJsonbSupport, executeGeneral, executeRead, executeWrite, getAdminTools, getCodeModeTools, getCoreTools, getGeoTools, getIntrospectionTools, getJsonHelperTools, getJsonOperationTools, getMigrationTools, getPromptDefinitions, getResourceDefinitions, getStatsTools, getTextTools, getVectorTools, getVirtualTools, isDDL, normalizeSqliteParams, resolveAliases, sanitizeFtsQuery, translateSqliteError, validateTableExists };