db-mcp 3.0.2 → 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.
- package/README.md +89 -57
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/database-adapter.d.ts +2 -1
- package/dist/adapters/database-adapter.d.ts.map +1 -1
- package/dist/adapters/registration/resources.d.ts.map +1 -1
- package/dist/adapters/registration/tools.d.ts.map +1 -1
- package/dist/adapters/sqlite/prompts/analysis.d.ts.map +1 -1
- package/dist/adapters/sqlite/resources.d.ts +1 -1
- package/dist/adapters/sqlite/resources.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/admin.d.ts +29 -15
- package/dist/adapters/sqlite/schemas/admin.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/core.d.ts +2 -0
- package/dist/adapters/sqlite/schemas/core.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/fts.d.ts +4 -0
- package/dist/adapters/sqlite/schemas/fts.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/introspection.d.ts +20 -11
- package/dist/adapters/sqlite/schemas/introspection.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/spatialite.d.ts +1 -0
- package/dist/adapters/sqlite/schemas/spatialite.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/text.d.ts +36 -0
- package/dist/adapters/sqlite/schemas/text.d.ts.map +1 -1
- package/dist/adapters/sqlite/sqlite-adapter.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/index.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/pragma.d.ts +0 -4
- package/dist/adapters/sqlite/tools/admin/pragma.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/queries.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/tables.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/introspection/diagnostics/indexes.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/json-helpers/read.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/json-operations/crud.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/json-operations/query.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/text/helpers.d.ts +9 -0
- package/dist/adapters/sqlite/tools/text/helpers.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/text/index.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/text/search.d.ts +4 -0
- package/dist/adapters/sqlite/tools/text/search.d.ts.map +1 -1
- package/dist/adapters/sqlite/types.d.ts +4 -0
- package/dist/adapters/sqlite/types.d.ts.map +1 -1
- package/dist/adapters/sqlite-native/native-sqlite-adapter.d.ts.map +1 -1
- package/dist/adapters/sqlite-native/tools/spatialite/tools.d.ts.map +1 -1
- package/dist/audit/interceptor.d.ts.map +1 -1
- package/dist/audit/logger.d.ts +26 -25
- package/dist/audit/logger.d.ts.map +1 -1
- package/dist/{chunk-W5WQVNVX.js → chunk-EQF3WZHX.js} +10 -17
- package/dist/{chunk-37G4DLL6.js → chunk-NIC5Z2CA.js} +732 -138
- package/dist/chunk-R5M4WKDM.js +987 -0
- package/dist/{chunk-NV34JSQM.js → chunk-TGE7LFAR.js} +708 -996
- package/dist/{chunk-L552U3QS.js → chunk-Y57WFKAP.js} +227 -9
- package/dist/cli.js +103 -10
- package/dist/codemode/sandbox.d.ts.map +1 -1
- package/dist/constants/server-instructions.d.ts +1 -1
- package/dist/constants/server-instructions.d.ts.map +1 -1
- package/dist/filtering/tool-constants.d.ts +9 -9
- package/dist/{http-HWTUVFIA.js → http-2WGR3E5K.js} +13 -1
- package/dist/index.js +4 -4
- package/dist/observability/metrics.d.ts +36 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/system-db.d.ts +13 -0
- package/dist/observability/system-db.d.ts.map +1 -0
- package/dist/server/mcp-server.d.ts +3 -0
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/registration/admin-tools.d.ts +6 -0
- package/dist/server/registration/admin-tools.d.ts.map +1 -0
- package/dist/server/registration/audit-tools.d.ts +4 -0
- package/dist/server/registration/audit-tools.d.ts.map +1 -1
- package/dist/server/registration/help-resources.d.ts.map +1 -1
- package/dist/server/registration/index.d.ts +3 -1
- package/dist/server/registration/index.d.ts.map +1 -1
- package/dist/server/registration/observability-resources.d.ts +11 -0
- package/dist/server/registration/observability-resources.d.ts.map +1 -0
- package/dist/server/subscription-manager.d.ts +35 -0
- package/dist/server/subscription-manager.d.ts.map +1 -0
- package/dist/{sqlite-TB56Z7G3.js → sqlite-D4WCNPU4.js} +8 -5
- package/dist/{sqlite-native-IF75BB3O.js → sqlite-native-DLVS2T52.js} +159 -34
- package/dist/transports/http/sessions/legacy-sse.d.ts.map +1 -1
- package/dist/transports/http/sessions/stateful.d.ts.map +1 -1
- package/dist/transports/http/transport.d.ts.map +1 -1
- package/dist/transports/http/types.d.ts +2 -0
- package/dist/transports/http/types.d.ts.map +1 -1
- package/dist/types/server.d.ts +2 -0
- package/dist/types/server.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/logger/types.d.ts +1 -1
- package/dist/utils/logger/types.d.ts.map +1 -1
- package/dist/utils/tokens.d.ts +15 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/package.json +9 -7
- package/dist/chunk-E5IESRTK.js +0 -489
- package/dist/utils/insights-manager.d.ts +0 -39
- package/dist/utils/insights-manager.d.ts.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { validateQuery } from './chunk-
|
|
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,
|
|
3
|
-
import { createModuleLogger, logger, ResourceNotFoundError, DbMcpError, formatHandlerError, findSuggestion, ERROR_CODES, QueryError, ValidationError, ExtensionNotAvailableError, getAuthContext, scopesGrantToolAccess } from './chunk-
|
|
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
|
|
7
|
-
import 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
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
enriched.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
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
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
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:
|
|
5038
|
-
elements:
|
|
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:
|
|
5095
|
-
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:
|
|
5150
|
-
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:
|
|
5193
|
-
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((
|
|
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:
|
|
5883
|
-
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:
|
|
5943
|
-
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((
|
|
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((
|
|
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((
|
|
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((
|
|
13007
|
+
await new Promise((resolve3, reject) => {
|
|
13277
13008
|
stream.end(
|
|
13278
|
-
(err) => err ? reject(err) :
|
|
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
|
|
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 &&
|
|
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.
|
|
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.
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 };
|