db-mcp 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/.gitattributes +2 -0
  2. package/LICENSE +1 -1
  3. package/README.md +244 -518
  4. package/dist/chunk-4IA3DB5C.js +135 -0
  5. package/dist/chunk-AOUL5SHS.js +930 -0
  6. package/dist/chunk-DWJXQEFA.js +13759 -0
  7. package/dist/chunk-DZQLDEQS.js +879 -0
  8. package/dist/chunk-S5IDDSSB.js +74 -0
  9. package/dist/chunk-Z2GFQU3G.js +363 -0
  10. package/dist/cli.d.ts +0 -7
  11. package/dist/cli.js +227 -183
  12. package/dist/http-VSB7DBJR.js +1541 -0
  13. package/dist/index.d.ts +842 -10
  14. package/dist/index.js +5 -17
  15. package/dist/sqlite-VHBA4ABV.js +600 -0
  16. package/dist/sqlite-native-D2LH5ZT7.js +2498 -0
  17. package/dist/worker-script.d.ts +2 -0
  18. package/dist/worker-script.js +126 -0
  19. package/package.json +26 -16
  20. package/playwright.config.ts +101 -0
  21. package/scripts/generate-server-instructions.ts +111 -0
  22. package/server.json +5 -6
  23. package/test-server/README.md +118 -0
  24. package/test-server/code-map.md +409 -0
  25. package/test-server/fixtures/sample.csv +6 -0
  26. package/test-server/reset-database.ps1 +373 -0
  27. package/test-server/sample.csv +11 -0
  28. package/test-server/test-agent-experience.md +243 -0
  29. package/test-server/test-database.sql +388 -0
  30. package/test-server/test-group-tools.md +861 -0
  31. package/test-server/test-help-resources.mjs +238 -0
  32. package/test-server/test-preflight.md +53 -0
  33. package/test-server/test-prompts.md +354 -0
  34. package/test-server/test-resources.md +245 -0
  35. package/test-server/test-tool-annotations.mjs +157 -0
  36. package/test-server/test-tools-advanced-1.md +517 -0
  37. package/test-server/test-tools-advanced-2.md +487 -0
  38. package/test-server/test-tools-codemode.md +629 -0
  39. package/test-server/test-tools.md +176 -0
  40. package/test-server/tool-reference.md +236 -0
  41. package/tsconfig.test.json +9 -0
  42. package/dist/adapters/DatabaseAdapter.d.ts +0 -141
  43. package/dist/adapters/DatabaseAdapter.d.ts.map +0 -1
  44. package/dist/adapters/DatabaseAdapter.js +0 -131
  45. package/dist/adapters/DatabaseAdapter.js.map +0 -1
  46. package/dist/adapters/sqlite/SchemaManager.d.ts +0 -58
  47. package/dist/adapters/sqlite/SchemaManager.d.ts.map +0 -1
  48. package/dist/adapters/sqlite/SchemaManager.js +0 -187
  49. package/dist/adapters/sqlite/SchemaManager.js.map +0 -1
  50. package/dist/adapters/sqlite/SqliteAdapter.d.ts +0 -161
  51. package/dist/adapters/sqlite/SqliteAdapter.d.ts.map +0 -1
  52. package/dist/adapters/sqlite/SqliteAdapter.js +0 -741
  53. package/dist/adapters/sqlite/SqliteAdapter.js.map +0 -1
  54. package/dist/adapters/sqlite/index.d.ts +0 -9
  55. package/dist/adapters/sqlite/index.d.ts.map +0 -1
  56. package/dist/adapters/sqlite/index.js +0 -8
  57. package/dist/adapters/sqlite/index.js.map +0 -1
  58. package/dist/adapters/sqlite/json-utils.d.ts +0 -100
  59. package/dist/adapters/sqlite/json-utils.d.ts.map +0 -1
  60. package/dist/adapters/sqlite/json-utils.js +0 -274
  61. package/dist/adapters/sqlite/json-utils.js.map +0 -1
  62. package/dist/adapters/sqlite/output-schemas.d.ts +0 -1187
  63. package/dist/adapters/sqlite/output-schemas.d.ts.map +0 -1
  64. package/dist/adapters/sqlite/output-schemas.js +0 -1337
  65. package/dist/adapters/sqlite/output-schemas.js.map +0 -1
  66. package/dist/adapters/sqlite/prompts.d.ts +0 -13
  67. package/dist/adapters/sqlite/prompts.d.ts.map +0 -1
  68. package/dist/adapters/sqlite/prompts.js +0 -605
  69. package/dist/adapters/sqlite/prompts.js.map +0 -1
  70. package/dist/adapters/sqlite/resources.d.ts +0 -13
  71. package/dist/adapters/sqlite/resources.d.ts.map +0 -1
  72. package/dist/adapters/sqlite/resources.js +0 -251
  73. package/dist/adapters/sqlite/resources.js.map +0 -1
  74. package/dist/adapters/sqlite/tools/admin.d.ts +0 -14
  75. package/dist/adapters/sqlite/tools/admin.d.ts.map +0 -1
  76. package/dist/adapters/sqlite/tools/admin.js +0 -788
  77. package/dist/adapters/sqlite/tools/admin.js.map +0 -1
  78. package/dist/adapters/sqlite/tools/core.d.ts +0 -25
  79. package/dist/adapters/sqlite/tools/core.d.ts.map +0 -1
  80. package/dist/adapters/sqlite/tools/core.js +0 -359
  81. package/dist/adapters/sqlite/tools/core.js.map +0 -1
  82. package/dist/adapters/sqlite/tools/fts.d.ts +0 -13
  83. package/dist/adapters/sqlite/tools/fts.d.ts.map +0 -1
  84. package/dist/adapters/sqlite/tools/fts.js +0 -347
  85. package/dist/adapters/sqlite/tools/fts.js.map +0 -1
  86. package/dist/adapters/sqlite/tools/geo.d.ts +0 -14
  87. package/dist/adapters/sqlite/tools/geo.d.ts.map +0 -1
  88. package/dist/adapters/sqlite/tools/geo.js +0 -252
  89. package/dist/adapters/sqlite/tools/geo.js.map +0 -1
  90. package/dist/adapters/sqlite/tools/index.d.ts +0 -30
  91. package/dist/adapters/sqlite/tools/index.d.ts.map +0 -1
  92. package/dist/adapters/sqlite/tools/index.js +0 -61
  93. package/dist/adapters/sqlite/tools/index.js.map +0 -1
  94. package/dist/adapters/sqlite/tools/json-helpers.d.ts +0 -14
  95. package/dist/adapters/sqlite/tools/json-helpers.d.ts.map +0 -1
  96. package/dist/adapters/sqlite/tools/json-helpers.js +0 -477
  97. package/dist/adapters/sqlite/tools/json-helpers.js.map +0 -1
  98. package/dist/adapters/sqlite/tools/json-operations.d.ts +0 -14
  99. package/dist/adapters/sqlite/tools/json-operations.d.ts.map +0 -1
  100. package/dist/adapters/sqlite/tools/json-operations.js +0 -839
  101. package/dist/adapters/sqlite/tools/json-operations.js.map +0 -1
  102. package/dist/adapters/sqlite/tools/stats.d.ts +0 -15
  103. package/dist/adapters/sqlite/tools/stats.d.ts.map +0 -1
  104. package/dist/adapters/sqlite/tools/stats.js +0 -1219
  105. package/dist/adapters/sqlite/tools/stats.js.map +0 -1
  106. package/dist/adapters/sqlite/tools/text.d.ts +0 -14
  107. package/dist/adapters/sqlite/tools/text.d.ts.map +0 -1
  108. package/dist/adapters/sqlite/tools/text.js +0 -1141
  109. package/dist/adapters/sqlite/tools/text.js.map +0 -1
  110. package/dist/adapters/sqlite/tools/vector.d.ts +0 -14
  111. package/dist/adapters/sqlite/tools/vector.d.ts.map +0 -1
  112. package/dist/adapters/sqlite/tools/vector.js +0 -613
  113. package/dist/adapters/sqlite/tools/vector.js.map +0 -1
  114. package/dist/adapters/sqlite/tools/virtual.d.ts +0 -13
  115. package/dist/adapters/sqlite/tools/virtual.d.ts.map +0 -1
  116. package/dist/adapters/sqlite/tools/virtual.js +0 -930
  117. package/dist/adapters/sqlite/tools/virtual.js.map +0 -1
  118. package/dist/adapters/sqlite/types.d.ts +0 -207
  119. package/dist/adapters/sqlite/types.d.ts.map +0 -1
  120. package/dist/adapters/sqlite/types.js +0 -186
  121. package/dist/adapters/sqlite/types.js.map +0 -1
  122. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts +0 -163
  123. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts.map +0 -1
  124. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js +0 -748
  125. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js.map +0 -1
  126. package/dist/adapters/sqlite-native/index.d.ts +0 -11
  127. package/dist/adapters/sqlite-native/index.d.ts.map +0 -1
  128. package/dist/adapters/sqlite-native/index.js +0 -11
  129. package/dist/adapters/sqlite-native/index.js.map +0 -1
  130. package/dist/adapters/sqlite-native/tools/spatialite.d.ts +0 -19
  131. package/dist/adapters/sqlite-native/tools/spatialite.d.ts.map +0 -1
  132. package/dist/adapters/sqlite-native/tools/spatialite.js +0 -628
  133. package/dist/adapters/sqlite-native/tools/spatialite.js.map +0 -1
  134. package/dist/adapters/sqlite-native/tools/transactions.d.ts +0 -12
  135. package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +0 -1
  136. package/dist/adapters/sqlite-native/tools/transactions.js +0 -255
  137. package/dist/adapters/sqlite-native/tools/transactions.js.map +0 -1
  138. package/dist/adapters/sqlite-native/tools/window.d.ts +0 -12
  139. package/dist/adapters/sqlite-native/tools/window.d.ts.map +0 -1
  140. package/dist/adapters/sqlite-native/tools/window.js +0 -370
  141. package/dist/adapters/sqlite-native/tools/window.js.map +0 -1
  142. package/dist/auth/AuthorizationServerDiscovery.d.ts +0 -90
  143. package/dist/auth/AuthorizationServerDiscovery.d.ts.map +0 -1
  144. package/dist/auth/AuthorizationServerDiscovery.js +0 -204
  145. package/dist/auth/AuthorizationServerDiscovery.js.map +0 -1
  146. package/dist/auth/OAuthResourceServer.d.ts +0 -65
  147. package/dist/auth/OAuthResourceServer.d.ts.map +0 -1
  148. package/dist/auth/OAuthResourceServer.js +0 -121
  149. package/dist/auth/OAuthResourceServer.js.map +0 -1
  150. package/dist/auth/TokenValidator.d.ts +0 -60
  151. package/dist/auth/TokenValidator.d.ts.map +0 -1
  152. package/dist/auth/TokenValidator.js +0 -235
  153. package/dist/auth/TokenValidator.js.map +0 -1
  154. package/dist/auth/errors.d.ts +0 -74
  155. package/dist/auth/errors.d.ts.map +0 -1
  156. package/dist/auth/errors.js +0 -133
  157. package/dist/auth/errors.js.map +0 -1
  158. package/dist/auth/index.d.ts +0 -13
  159. package/dist/auth/index.d.ts.map +0 -1
  160. package/dist/auth/index.js +0 -15
  161. package/dist/auth/index.js.map +0 -1
  162. package/dist/auth/middleware.d.ts +0 -81
  163. package/dist/auth/middleware.d.ts.map +0 -1
  164. package/dist/auth/middleware.js +0 -291
  165. package/dist/auth/middleware.js.map +0 -1
  166. package/dist/auth/scopes.d.ts +0 -136
  167. package/dist/auth/scopes.d.ts.map +0 -1
  168. package/dist/auth/scopes.js +0 -349
  169. package/dist/auth/scopes.js.map +0 -1
  170. package/dist/auth/types.d.ts +0 -257
  171. package/dist/auth/types.d.ts.map +0 -1
  172. package/dist/auth/types.js +0 -8
  173. package/dist/auth/types.js.map +0 -1
  174. package/dist/cli.d.ts.map +0 -1
  175. package/dist/cli.js.map +0 -1
  176. package/dist/constants/ServerInstructions.d.ts +0 -45
  177. package/dist/constants/ServerInstructions.d.ts.map +0 -1
  178. package/dist/constants/ServerInstructions.js +0 -356
  179. package/dist/constants/ServerInstructions.js.map +0 -1
  180. package/dist/filtering/ToolConstants.d.ts +0 -34
  181. package/dist/filtering/ToolConstants.d.ts.map +0 -1
  182. package/dist/filtering/ToolConstants.js +0 -174
  183. package/dist/filtering/ToolConstants.js.map +0 -1
  184. package/dist/filtering/ToolFilter.d.ts +0 -82
  185. package/dist/filtering/ToolFilter.d.ts.map +0 -1
  186. package/dist/filtering/ToolFilter.js +0 -296
  187. package/dist/filtering/ToolFilter.js.map +0 -1
  188. package/dist/index.d.ts.map +0 -1
  189. package/dist/index.js.map +0 -1
  190. package/dist/server/McpServer.d.ts +0 -61
  191. package/dist/server/McpServer.d.ts.map +0 -1
  192. package/dist/server/McpServer.js +0 -270
  193. package/dist/server/McpServer.js.map +0 -1
  194. package/dist/transports/http.d.ts +0 -134
  195. package/dist/transports/http.d.ts.map +0 -1
  196. package/dist/transports/http.js +0 -516
  197. package/dist/transports/http.js.map +0 -1
  198. package/dist/transports/index.d.ts +0 -5
  199. package/dist/transports/index.d.ts.map +0 -1
  200. package/dist/transports/index.js +0 -5
  201. package/dist/transports/index.js.map +0 -1
  202. package/dist/types/index.d.ts +0 -380
  203. package/dist/types/index.d.ts.map +0 -1
  204. package/dist/types/index.js +0 -68
  205. package/dist/types/index.js.map +0 -1
  206. package/dist/utils/annotations.d.ts +0 -44
  207. package/dist/utils/annotations.d.ts.map +0 -1
  208. package/dist/utils/annotations.js +0 -77
  209. package/dist/utils/annotations.js.map +0 -1
  210. package/dist/utils/errors.d.ts +0 -155
  211. package/dist/utils/errors.d.ts.map +0 -1
  212. package/dist/utils/errors.js +0 -329
  213. package/dist/utils/errors.js.map +0 -1
  214. package/dist/utils/identifiers.d.ts +0 -121
  215. package/dist/utils/identifiers.d.ts.map +0 -1
  216. package/dist/utils/identifiers.js +0 -319
  217. package/dist/utils/identifiers.js.map +0 -1
  218. package/dist/utils/index.d.ts +0 -7
  219. package/dist/utils/index.d.ts.map +0 -1
  220. package/dist/utils/index.js +0 -7
  221. package/dist/utils/index.js.map +0 -1
  222. package/dist/utils/insightsManager.d.ts +0 -39
  223. package/dist/utils/insightsManager.d.ts.map +0 -1
  224. package/dist/utils/insightsManager.js +0 -63
  225. package/dist/utils/insightsManager.js.map +0 -1
  226. package/dist/utils/logger.d.ts +0 -189
  227. package/dist/utils/logger.d.ts.map +0 -1
  228. package/dist/utils/logger.js +0 -394
  229. package/dist/utils/logger.js.map +0 -1
  230. package/dist/utils/progress-utils.d.ts +0 -54
  231. package/dist/utils/progress-utils.d.ts.map +0 -1
  232. package/dist/utils/progress-utils.js +0 -74
  233. package/dist/utils/progress-utils.js.map +0 -1
  234. package/dist/utils/resourceAnnotations.d.ts +0 -36
  235. package/dist/utils/resourceAnnotations.d.ts.map +0 -1
  236. package/dist/utils/resourceAnnotations.js +0 -57
  237. package/dist/utils/resourceAnnotations.js.map +0 -1
  238. package/dist/utils/where-clause.d.ts +0 -41
  239. package/dist/utils/where-clause.d.ts.map +0 -1
  240. package/dist/utils/where-clause.js +0 -116
  241. package/dist/utils/where-clause.js.map +0 -1
@@ -0,0 +1,2498 @@
1
+ import { TransactionBeginOutputSchema, TransactionCommitOutputSchema, TransactionRollbackOutputSchema, TransactionSavepointOutputSchema, TransactionReleaseOutputSchema, TransactionRollbackToOutputSchema, TransactionExecuteOutputSchema, WindowRowNumberOutputSchema, validateWhereClause, WindowRankOutputSchema, WindowLagLeadOutputSchema, WindowRunningTotalOutputSchema, resolveAliases, WindowMovingAvgOutputSchema, WindowNtileOutputSchema, autoEnableWal, detectAndSetJsonbSupport, SchemaManager, applyCommonPragmas, isDDL, getResourceDefinitions, getPromptDefinitions, SpatialiteLoadOutputSchema, SpatialiteCreateTableOutputSchema, SpatialiteQueryOutputSchema, SpatialiteAnalyzeOutputSchema, SpatialiteIndexOutputSchema, SpatialiteTransformOutputSchema, SpatialiteImportOutputSchema, normalizeSqliteParams, getCoreTools, getJsonOperationTools, getJsonHelperTools, getTextTools, getStatsTools, getVirtualTools, getVectorTools, getGeoTools, getAdminTools, getIntrospectionTools, getMigrationTools, getCodeModeTools, FtsCreateOutputSchema, sanitizeIdentifier, FtsSearchOutputSchema, validateTableExists, FtsRebuildOutputSchema } from './chunk-DWJXQEFA.js';
2
+ import { DatabaseAdapter } from './chunk-Z2GFQU3G.js';
3
+ import { write, readOnly, admin, getToolGroupIcon, idempotent } from './chunk-S5IDDSSB.js';
4
+ import { formatHandlerError, ValidationError, ConfigurationError, ConnectionError, ResourceNotFoundError, QueryError } from './chunk-4IA3DB5C.js';
5
+ import { logger, ERROR_CODES, DbMcpError } from './chunk-AOUL5SHS.js';
6
+ import Database from 'better-sqlite3';
7
+ import { z } from 'zod';
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ function buildFts5UnavailableError(tableName) {
13
+ return {
14
+ success: false,
15
+ message: "FTS5 module unavailable",
16
+ tableName: tableName ?? "",
17
+ error: "FTS5 module unavailable",
18
+ hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
19
+ };
20
+ }
21
+ function isFts5UnavailableError(error) {
22
+ return error instanceof Error && error.message.toLowerCase().includes("no such module: fts5");
23
+ }
24
+ function isFts5Available(adapter) {
25
+ return adapter.isNativeBackend();
26
+ }
27
+ function buildFts5SearchUnavailableError() {
28
+ return {
29
+ success: false,
30
+ rowCount: 0,
31
+ results: [],
32
+ error: "FTS5 module unavailable",
33
+ hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
34
+ };
35
+ }
36
+ var coerceEnumValues = (allowed) => (val) => typeof val === "string" && allowed.includes(val) ? val : void 0;
37
+ var FtsCreateSchema = z.object({
38
+ tableName: z.string().describe("Name of the FTS table to create"),
39
+ sourceTable: z.string().describe("Source table to index"),
40
+ columns: z.array(z.string()).describe("Columns to include in the index"),
41
+ contentTable: z.string().optional().describe("Content table for external content FTS"),
42
+ tokenizer: z.preprocess(
43
+ coerceEnumValues(["unicode61", "ascii", "porter"]),
44
+ z.enum(["unicode61", "ascii", "porter"]).optional().default("unicode61")
45
+ ),
46
+ createTriggers: z.boolean().optional().default(true).describe(
47
+ "Create INSERT/UPDATE/DELETE triggers for auto-sync (only for external content tables)"
48
+ )
49
+ });
50
+ var FtsSearchSchema = z.object({
51
+ table: z.string().describe("FTS table name"),
52
+ query: z.string().describe("Full-text search query"),
53
+ columns: z.array(z.string()).optional().describe("Specific columns to search"),
54
+ limit: z.preprocess(
55
+ (val) => typeof val === "string" ? isNaN(Number(val)) ? void 0 : Number(val) : val,
56
+ z.number().optional().default(100)
57
+ ),
58
+ highlight: z.boolean().optional().default(false).describe("Include highlighted snippets")
59
+ });
60
+ var FtsRebuildSchema = z.object({
61
+ table: z.string().describe("FTS table name to rebuild")
62
+ });
63
+ var FtsMatchInfoSchema = z.object({
64
+ table: z.string().describe("FTS table name"),
65
+ query: z.string().describe("Full-text search query"),
66
+ format: z.preprocess(
67
+ coerceEnumValues(["bm25", "rank"]),
68
+ z.enum(["bm25", "rank"]).optional().default("bm25")
69
+ )
70
+ });
71
+ function getFtsTools(adapter) {
72
+ return [
73
+ createFtsCreateTool(adapter),
74
+ createFtsSearchTool(adapter),
75
+ createFtsRebuildTool(adapter),
76
+ createFtsMatchInfoTool(adapter)
77
+ ];
78
+ }
79
+ function createFtsCreateTool(adapter) {
80
+ return {
81
+ name: "sqlite_fts_create",
82
+ description: "Create an FTS5 full-text search virtual table.",
83
+ group: "text",
84
+ inputSchema: FtsCreateSchema,
85
+ outputSchema: FtsCreateOutputSchema,
86
+ requiredScopes: ["write"],
87
+ annotations: idempotent("FTS Create"),
88
+ handler: async (params, _context) => {
89
+ try {
90
+ const input = FtsCreateSchema.parse(params);
91
+ sanitizeIdentifier(input.tableName);
92
+ sanitizeIdentifier(input.sourceTable);
93
+ for (const col of input.columns) {
94
+ sanitizeIdentifier(col);
95
+ }
96
+ const columnList = input.columns.join(", ");
97
+ let options = `tokenize="${input.tokenizer}"`;
98
+ const effectiveContentTable = input.contentTable ?? input.sourceTable;
99
+ sanitizeIdentifier(effectiveContentTable);
100
+ options += `, content="${effectiveContentTable}"`;
101
+ const sql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${input.tableName}" USING fts5(${columnList}, ${options})`;
102
+ await adapter.executeQuery(sql);
103
+ let triggersCreated = [];
104
+ if (input.createTriggers) {
105
+ triggersCreated = await createSyncTriggers(
106
+ adapter,
107
+ input.tableName,
108
+ effectiveContentTable,
109
+ input.columns
110
+ );
111
+ }
112
+ await adapter.executeQuery(
113
+ `INSERT INTO "${input.tableName}"("${input.tableName}") VALUES('rebuild')`
114
+ );
115
+ const message = triggersCreated.length ? `FTS5 table '${input.tableName}' created with ${triggersCreated.length} sync triggers` : `FTS5 table '${input.tableName}' created`;
116
+ return {
117
+ success: true,
118
+ message,
119
+ tableName: input.tableName,
120
+ triggersCreated: triggersCreated.length ? triggersCreated : void 0
121
+ };
122
+ } catch (error) {
123
+ if (isFts5UnavailableError(error)) {
124
+ return buildFts5UnavailableError(
125
+ params?.tableName
126
+ );
127
+ }
128
+ return formatHandlerError(error);
129
+ }
130
+ }
131
+ };
132
+ }
133
+ async function createSyncTriggers(adapter, ftsTable, contentTable, columns) {
134
+ const triggersCreated = [];
135
+ const colList = columns.map((c) => `"${c}"`).join(", ");
136
+ const newColList = columns.map((c) => `NEW."${c}"`).join(", ");
137
+ const oldColList = columns.map((c) => `OLD."${c}"`).join(", ");
138
+ const insertTriggerName = `${ftsTable}_ai`;
139
+ const insertTrigger = `
140
+ CREATE TRIGGER IF NOT EXISTS "${insertTriggerName}" AFTER INSERT ON "${contentTable}" BEGIN
141
+ INSERT INTO "${ftsTable}"(rowid, ${colList}) VALUES (NEW.rowid, ${newColList});
142
+ END;
143
+ `;
144
+ await adapter.executeQuery(insertTrigger);
145
+ triggersCreated.push(insertTriggerName);
146
+ const deleteTriggerName = `${ftsTable}_ad`;
147
+ const deleteTrigger = `
148
+ CREATE TRIGGER IF NOT EXISTS "${deleteTriggerName}" AFTER DELETE ON "${contentTable}" BEGIN
149
+ INSERT INTO "${ftsTable}"("${ftsTable}", rowid, ${colList}) VALUES('delete', OLD.rowid, ${oldColList});
150
+ END;
151
+ `;
152
+ await adapter.executeQuery(deleteTrigger);
153
+ triggersCreated.push(deleteTriggerName);
154
+ const updateTriggerName = `${ftsTable}_au`;
155
+ const updateTrigger = `
156
+ CREATE TRIGGER IF NOT EXISTS "${updateTriggerName}" AFTER UPDATE ON "${contentTable}" BEGIN
157
+ INSERT INTO "${ftsTable}"("${ftsTable}", rowid, ${colList}) VALUES('delete', OLD.rowid, ${oldColList});
158
+ INSERT INTO "${ftsTable}"(rowid, ${colList}) VALUES (NEW.rowid, ${newColList});
159
+ END;
160
+ `;
161
+ await adapter.executeQuery(updateTrigger);
162
+ triggersCreated.push(updateTriggerName);
163
+ return triggersCreated;
164
+ }
165
+ function createFtsSearchTool(adapter) {
166
+ return {
167
+ name: "sqlite_fts_search",
168
+ description: "Search an FTS5 table using full-text query syntax.",
169
+ group: "text",
170
+ inputSchema: FtsSearchSchema,
171
+ outputSchema: FtsSearchOutputSchema,
172
+ requiredScopes: ["read"],
173
+ annotations: readOnly("FTS Search"),
174
+ handler: async (params, _context) => {
175
+ try {
176
+ const input = FtsSearchSchema.parse(params);
177
+ if (!isFts5Available(adapter)) {
178
+ return buildFts5SearchUnavailableError();
179
+ }
180
+ sanitizeIdentifier(input.table);
181
+ await validateTableExists(adapter, input.table);
182
+ let selectClause = "*";
183
+ if (input.highlight) {
184
+ selectClause = `*, highlight("${input.table}", 0, '<b>', '</b>') as snippet`;
185
+ }
186
+ if (input.query === "*" || input.query.trim() === "") {
187
+ const sql2 = `SELECT ${selectClause}, NULL as rank FROM "${input.table}" ORDER BY rowid LIMIT ${input.limit}`;
188
+ const result2 = await adapter.executeReadQuery(sql2);
189
+ return {
190
+ success: true,
191
+ rowCount: result2.rows?.length ?? 0,
192
+ results: result2.rows
193
+ };
194
+ }
195
+ const queryEscaped = input.query.replace(/'/g, "''");
196
+ let matchExpr = `"${input.table}" MATCH '${queryEscaped}'`;
197
+ if (input.columns && input.columns.length > 0) {
198
+ for (const col of input.columns) {
199
+ sanitizeIdentifier(col);
200
+ }
201
+ const colFilter = input.columns.map((c) => `${c}:${queryEscaped}`).join(" OR ");
202
+ matchExpr = `"${input.table}" MATCH '${colFilter}'`;
203
+ }
204
+ const sql = `SELECT ${selectClause}, rank FROM "${input.table}" WHERE ${matchExpr} ORDER BY rank LIMIT ${input.limit}`;
205
+ const result = await adapter.executeReadQuery(sql);
206
+ return {
207
+ success: true,
208
+ rowCount: result.rows?.length ?? 0,
209
+ results: result.rows
210
+ };
211
+ } catch (error) {
212
+ if (isFts5UnavailableError(error)) {
213
+ return buildFts5SearchUnavailableError();
214
+ }
215
+ return formatHandlerError(error);
216
+ }
217
+ }
218
+ };
219
+ }
220
+ function createFtsRebuildTool(adapter) {
221
+ return {
222
+ name: "sqlite_fts_rebuild",
223
+ description: "Rebuild an FTS5 index to optimize search performance.",
224
+ group: "text",
225
+ inputSchema: FtsRebuildSchema,
226
+ outputSchema: FtsRebuildOutputSchema,
227
+ requiredScopes: ["admin"],
228
+ annotations: admin("FTS Rebuild"),
229
+ handler: async (params, _context) => {
230
+ try {
231
+ const input = FtsRebuildSchema.parse(params);
232
+ if (!isFts5Available(adapter)) {
233
+ return buildFts5UnavailableError(input.table);
234
+ }
235
+ sanitizeIdentifier(input.table);
236
+ await validateTableExists(adapter, input.table);
237
+ const sql = `INSERT INTO "${input.table}"("${input.table}") VALUES('rebuild')`;
238
+ await adapter.executeQuery(sql);
239
+ return {
240
+ success: true,
241
+ message: `FTS5 index '${input.table}' rebuilt`,
242
+ tableName: input.table
243
+ };
244
+ } catch (error) {
245
+ if (isFts5UnavailableError(error)) {
246
+ return buildFts5UnavailableError(
247
+ params?.table
248
+ );
249
+ }
250
+ return formatHandlerError(error);
251
+ }
252
+ }
253
+ };
254
+ }
255
+ function createFtsMatchInfoTool(adapter) {
256
+ return {
257
+ name: "sqlite_fts_match_info",
258
+ description: "Get FTS5 match ranking information using bm25.",
259
+ group: "text",
260
+ inputSchema: FtsMatchInfoSchema,
261
+ outputSchema: FtsSearchOutputSchema,
262
+ requiredScopes: ["read"],
263
+ annotations: readOnly("FTS Match Info"),
264
+ handler: async (params, _context) => {
265
+ try {
266
+ const input = FtsMatchInfoSchema.parse(params);
267
+ if (!isFts5Available(adapter)) {
268
+ return buildFts5SearchUnavailableError();
269
+ }
270
+ sanitizeIdentifier(input.table);
271
+ await validateTableExists(adapter, input.table);
272
+ const queryEscaped = input.query.replace(/'/g, "''");
273
+ let rankExpr;
274
+ if (input.format === "bm25") {
275
+ rankExpr = `bm25("${input.table}")`;
276
+ } else {
277
+ rankExpr = "rank";
278
+ }
279
+ const sql = `SELECT *, ${rankExpr} as score FROM "${input.table}" WHERE "${input.table}" MATCH '${queryEscaped}' ORDER BY score`;
280
+ const result = await adapter.executeReadQuery(sql);
281
+ return {
282
+ success: true,
283
+ rowCount: result.rows?.length ?? 0,
284
+ results: result.rows
285
+ };
286
+ } catch (error) {
287
+ if (isFts5UnavailableError(error)) {
288
+ return buildFts5SearchUnavailableError();
289
+ }
290
+ return formatHandlerError(error);
291
+ }
292
+ }
293
+ };
294
+ }
295
+ var VALID_MODES = ["deferred", "immediate", "exclusive"];
296
+ var BeginTransactionSchema = z.object({
297
+ mode: z.preprocess(
298
+ (val) => {
299
+ if (typeof val !== "string") return void 0;
300
+ const normalized = val.toLowerCase();
301
+ return VALID_MODES.includes(normalized) ? normalized : void 0;
302
+ },
303
+ z.enum(["deferred", "immediate", "exclusive"]).optional().default("deferred").describe(
304
+ "Transaction mode: deferred waits for first write, immediate acquires lock immediately, exclusive blocks all access"
305
+ )
306
+ )
307
+ });
308
+ var SavepointSchema = z.object({
309
+ name: z.string().describe("Savepoint name")
310
+ });
311
+ var ExecuteInTransactionSchema = z.object({
312
+ statements: z.array(z.string()).describe("Array of SQL statements to execute in order"),
313
+ rollbackOnError: z.boolean().optional().default(true).describe("If true, rollback all changes when any statement fails")
314
+ });
315
+ function getTransactionTools(adapter) {
316
+ return [
317
+ createBeginTransactionTool(adapter),
318
+ createCommitTransactionTool(adapter),
319
+ createRollbackTransactionTool(adapter),
320
+ createSavepointTool(adapter),
321
+ createReleaseSavepointTool(adapter),
322
+ createRollbackToSavepointTool(adapter),
323
+ createExecuteInTransactionTool(adapter)
324
+ ];
325
+ }
326
+ function createBeginTransactionTool(adapter) {
327
+ return {
328
+ name: "sqlite_transaction_begin",
329
+ description: "Begin a new transaction. Use immediate or exclusive mode for write-heavy operations.",
330
+ group: "admin",
331
+ inputSchema: BeginTransactionSchema,
332
+ outputSchema: TransactionBeginOutputSchema,
333
+ annotations: write("Begin Transaction"),
334
+ requiredScopes: ["write"],
335
+ handler: async (params, _context) => {
336
+ try {
337
+ const input = BeginTransactionSchema.parse(params);
338
+ const mode = input.mode.toUpperCase();
339
+ await adapter.executeWriteQuery(`BEGIN ${mode} TRANSACTION`);
340
+ return {
341
+ success: true,
342
+ message: `Transaction started (${input.mode} mode)`,
343
+ mode: input.mode
344
+ };
345
+ } catch (error) {
346
+ return formatHandlerError(error);
347
+ }
348
+ }
349
+ };
350
+ }
351
+ function createCommitTransactionTool(adapter) {
352
+ return {
353
+ name: "sqlite_transaction_commit",
354
+ description: "Commit the current transaction, making all changes permanent.",
355
+ group: "admin",
356
+ inputSchema: z.object({}),
357
+ outputSchema: TransactionCommitOutputSchema,
358
+ annotations: write("Commit Transaction"),
359
+ requiredScopes: ["write"],
360
+ handler: (_params, _context) => {
361
+ try {
362
+ adapter.commitTransaction();
363
+ return Promise.resolve({
364
+ success: true,
365
+ message: "Transaction committed"
366
+ });
367
+ } catch (error) {
368
+ return Promise.resolve(formatHandlerError(error));
369
+ }
370
+ }
371
+ };
372
+ }
373
+ function createRollbackTransactionTool(adapter) {
374
+ return {
375
+ name: "sqlite_transaction_rollback",
376
+ description: "Rollback the current transaction, discarding all changes.",
377
+ group: "admin",
378
+ outputSchema: TransactionRollbackOutputSchema,
379
+ inputSchema: z.object({}),
380
+ annotations: write("Rollback Transaction"),
381
+ requiredScopes: ["write"],
382
+ handler: (_params, _context) => {
383
+ try {
384
+ adapter.rollbackTransaction();
385
+ return Promise.resolve({
386
+ success: true,
387
+ message: "Transaction rolled back"
388
+ });
389
+ } catch (error) {
390
+ return Promise.resolve(formatHandlerError(error));
391
+ }
392
+ }
393
+ };
394
+ }
395
+ function createSavepointTool(adapter) {
396
+ return {
397
+ name: "sqlite_transaction_savepoint",
398
+ description: "Create a savepoint within the current transaction for partial rollback.",
399
+ group: "admin",
400
+ outputSchema: TransactionSavepointOutputSchema,
401
+ inputSchema: SavepointSchema,
402
+ annotations: write("Create Savepoint"),
403
+ requiredScopes: ["write"],
404
+ handler: (params, _context) => {
405
+ try {
406
+ const input = SavepointSchema.parse(params);
407
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.name)) {
408
+ return Promise.resolve(
409
+ formatHandlerError(
410
+ new ValidationError(
411
+ `Invalid savepoint name '${input.name}': must start with a letter or underscore and contain only alphanumeric characters`
412
+ )
413
+ )
414
+ );
415
+ }
416
+ adapter.savepoint(input.name);
417
+ return Promise.resolve({
418
+ success: true,
419
+ message: `Savepoint '${input.name}' created`,
420
+ savepoint: input.name
421
+ });
422
+ } catch (error) {
423
+ return Promise.resolve(formatHandlerError(error));
424
+ }
425
+ }
426
+ };
427
+ }
428
+ function createReleaseSavepointTool(adapter) {
429
+ return {
430
+ name: "sqlite_transaction_release",
431
+ description: "Release a savepoint, keeping the changes made since it was created.",
432
+ group: "admin",
433
+ inputSchema: SavepointSchema,
434
+ outputSchema: TransactionReleaseOutputSchema,
435
+ annotations: write("Release Savepoint"),
436
+ requiredScopes: ["write"],
437
+ handler: (params, _context) => {
438
+ try {
439
+ const input = SavepointSchema.parse(params);
440
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.name)) {
441
+ return Promise.resolve(
442
+ formatHandlerError(
443
+ new ValidationError(
444
+ `Invalid savepoint name '${input.name}': must start with a letter or underscore and contain only alphanumeric characters`
445
+ )
446
+ )
447
+ );
448
+ }
449
+ adapter.releaseSavepoint(input.name);
450
+ return Promise.resolve({
451
+ success: true,
452
+ message: `Savepoint '${input.name}' released`,
453
+ savepoint: input.name
454
+ });
455
+ } catch (error) {
456
+ return Promise.resolve(formatHandlerError(error));
457
+ }
458
+ }
459
+ };
460
+ }
461
+ function createRollbackToSavepointTool(adapter) {
462
+ return {
463
+ name: "sqlite_transaction_rollback_to",
464
+ description: "Rollback to a savepoint, discarding changes made after it was created.",
465
+ group: "admin",
466
+ inputSchema: SavepointSchema,
467
+ outputSchema: TransactionRollbackToOutputSchema,
468
+ annotations: write("Rollback to Savepoint"),
469
+ requiredScopes: ["write"],
470
+ handler: (params, _context) => {
471
+ try {
472
+ const input = SavepointSchema.parse(params);
473
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.name)) {
474
+ return Promise.resolve(
475
+ formatHandlerError(
476
+ new ValidationError(
477
+ `Invalid savepoint name '${input.name}': must start with a letter or underscore and contain only alphanumeric characters`
478
+ )
479
+ )
480
+ );
481
+ }
482
+ adapter.rollbackToSavepoint(input.name);
483
+ return Promise.resolve({
484
+ success: true,
485
+ message: `Rolled back to savepoint '${input.name}'`,
486
+ savepoint: input.name
487
+ });
488
+ } catch (error) {
489
+ return Promise.resolve(formatHandlerError(error));
490
+ }
491
+ }
492
+ };
493
+ }
494
+ function createExecuteInTransactionTool(adapter) {
495
+ return {
496
+ name: "sqlite_transaction_execute",
497
+ description: "Execute multiple SQL statements in a single transaction. Automatically commits on success or rolls back on error.",
498
+ group: "admin",
499
+ outputSchema: TransactionExecuteOutputSchema,
500
+ inputSchema: ExecuteInTransactionSchema,
501
+ annotations: write("Execute in Transaction"),
502
+ requiredScopes: ["write"],
503
+ handler: async (params, _context) => {
504
+ let input;
505
+ try {
506
+ input = ExecuteInTransactionSchema.parse(params);
507
+ } catch (error) {
508
+ return {
509
+ ...formatHandlerError(error),
510
+ message: "",
511
+ statementsExecuted: 0,
512
+ results: []
513
+ };
514
+ }
515
+ const results = [];
516
+ let success = true;
517
+ try {
518
+ adapter.beginTransaction();
519
+ for (const statement of input.statements) {
520
+ try {
521
+ const isSelect = statement.trim().toUpperCase().startsWith("SELECT");
522
+ if (isSelect) {
523
+ const result = await adapter.executeReadQuery(statement);
524
+ const rowCount = result.rows?.length ?? 0;
525
+ const statementResult = {
526
+ statement: statement.substring(0, 100) + (statement.length > 100 ? "..." : ""),
527
+ rowCount
528
+ };
529
+ if (result.rows) {
530
+ statementResult.rows = result.rows;
531
+ }
532
+ results.push(statementResult);
533
+ } else {
534
+ const result = await adapter.executeWriteQuery(statement);
535
+ results.push({
536
+ statement: statement.substring(0, 100) + (statement.length > 100 ? "..." : ""),
537
+ rowsAffected: result.rowsAffected ?? 0
538
+ });
539
+ }
540
+ } catch (error) {
541
+ const message = error instanceof Error ? error.message : String(error);
542
+ results.push({
543
+ statement: statement.substring(0, 100) + (statement.length > 100 ? "..." : ""),
544
+ error: message
545
+ });
546
+ if (input.rollbackOnError) {
547
+ throw error;
548
+ }
549
+ success = false;
550
+ }
551
+ }
552
+ adapter.commitTransaction();
553
+ return {
554
+ success,
555
+ message: success ? "Transaction completed successfully" : "Transaction completed with errors",
556
+ statementsExecuted: input.statements.length,
557
+ results
558
+ };
559
+ } catch (error) {
560
+ let rollbackFailure;
561
+ try {
562
+ adapter.rollbackTransaction();
563
+ } catch (rbError) {
564
+ rollbackFailure = rbError instanceof Error ? rbError.message : String(rbError);
565
+ }
566
+ const message = error instanceof Error ? error.message : String(error);
567
+ const formatted = formatHandlerError(error);
568
+ let rollbackMessage = `Transaction rolled back: ${message}`;
569
+ if (rollbackFailure) {
570
+ rollbackMessage += ` (rollback error: ${rollbackFailure})`;
571
+ }
572
+ return {
573
+ ...formatted,
574
+ error: formatted.error ?? rollbackMessage,
575
+ message: rollbackMessage,
576
+ statementsExecuted: results.length,
577
+ results
578
+ };
579
+ }
580
+ }
581
+ };
582
+ }
583
+ var coerceNumber = (val) => typeof val === "string" ? isNaN(Number(val)) ? void 0 : Number(val) : val;
584
+ var coerceEnumValues2 = (allowed) => (val) => typeof val === "string" && allowed.includes(val) ? val : void 0;
585
+ var VALID_DIRECTIONS = ["lag", "lead"];
586
+ var RowNumberSchema = z.object({
587
+ table: z.string().describe("Table name"),
588
+ orderBy: z.string().describe("Column(s) to order by"),
589
+ partitionBy: z.string().optional().describe("Column(s) to partition by"),
590
+ selectColumns: z.array(z.string()).optional().describe("Columns to include in result"),
591
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
592
+ limit: z.preprocess(
593
+ coerceNumber,
594
+ z.number().optional().default(100).describe("Maximum rows to return")
595
+ )
596
+ });
597
+ var RankSchema = z.object({
598
+ table: z.string().describe("Table name"),
599
+ orderBy: z.string().describe("Column(s) to order by (determines rank)"),
600
+ partitionBy: z.string().optional().describe("Column(s) to partition by"),
601
+ selectColumns: z.array(z.string()).optional().describe("Columns to include in result"),
602
+ rankType: z.preprocess(
603
+ coerceEnumValues2(["rank", "dense_rank", "percent_rank"]),
604
+ z.enum(["rank", "dense_rank", "percent_rank"]).optional().default("rank").describe("Rank function type")
605
+ ),
606
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
607
+ limit: z.preprocess(
608
+ coerceNumber,
609
+ z.number().optional().default(100).describe("Maximum rows to return")
610
+ )
611
+ });
612
+ var LagLeadSchema = z.object({
613
+ table: z.string().describe("Table name"),
614
+ column: z.string().describe("Column to get lag/lead value from"),
615
+ orderBy: z.string().describe("Column(s) to order by"),
616
+ direction: z.string().describe("LAG (previous) or LEAD (next) row"),
617
+ offset: z.preprocess(
618
+ coerceNumber,
619
+ z.number().optional().default(1).describe("Number of rows to look back/ahead")
620
+ ),
621
+ defaultValue: z.string().optional().describe("Default value if no row exists"),
622
+ partitionBy: z.string().optional().describe("Column(s) to partition by"),
623
+ selectColumns: z.array(z.string()).optional().describe("Columns to include in result"),
624
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
625
+ limit: z.preprocess(
626
+ coerceNumber,
627
+ z.number().optional().default(100).describe("Maximum rows to return")
628
+ )
629
+ });
630
+ var RunningTotalSchema = z.object({
631
+ table: z.string().describe("Table name"),
632
+ column: z.string().describe("Column to sum"),
633
+ orderBy: z.string().describe("Column(s) to order by"),
634
+ partitionBy: z.string().optional().describe("Reset running total for each partition"),
635
+ selectColumns: z.array(z.string()).optional().describe("Columns to include in result"),
636
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
637
+ limit: z.preprocess(
638
+ coerceNumber,
639
+ z.number().optional().default(100).describe("Maximum rows to return")
640
+ )
641
+ });
642
+ var MovingAverageSchema = z.object({
643
+ table: z.string().describe("Table name"),
644
+ column: z.string().describe("Column to average"),
645
+ orderBy: z.string().describe("Column(s) to order by"),
646
+ windowSize: z.preprocess(
647
+ coerceNumber,
648
+ z.number().optional().describe("Number of rows in the moving window")
649
+ ),
650
+ partitionBy: z.string().optional().describe("Column(s) to partition by"),
651
+ selectColumns: z.array(z.string()).optional().describe("Columns to include in result"),
652
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
653
+ limit: z.preprocess(
654
+ coerceNumber,
655
+ z.number().optional().default(100).describe("Maximum rows to return")
656
+ )
657
+ });
658
+ var NtileSchema = z.object({
659
+ table: z.string().describe("Table name"),
660
+ orderBy: z.string().describe("Column(s) to order by"),
661
+ buckets: z.preprocess(
662
+ coerceNumber,
663
+ z.number().optional().describe("Number of buckets (e.g., 4 for quartiles)")
664
+ ),
665
+ partitionBy: z.string().optional().describe("Column(s) to partition by"),
666
+ selectColumns: z.array(z.string()).optional().describe("Columns to include in result"),
667
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
668
+ limit: z.preprocess(
669
+ coerceNumber,
670
+ z.number().optional().default(100).describe("Maximum rows to return")
671
+ )
672
+ });
673
+ async function validateTableExists2(adapter, table) {
674
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
675
+ throw new DbMcpError(
676
+ "Invalid table name",
677
+ "NATIVE_WINDOW_INVALID_TABLE",
678
+ "validation" /* VALIDATION */
679
+ );
680
+ }
681
+ const result = await adapter.executeReadQuery(
682
+ `SELECT 1 FROM sqlite_master WHERE type IN ('table', 'view') AND name='${table}'`
683
+ );
684
+ if (!result.rows || result.rows.length === 0) {
685
+ throw new ResourceNotFoundError(
686
+ `Table '${table}' does not exist`,
687
+ "TABLE_NOT_FOUND",
688
+ {
689
+ suggestion: "Table not found. Run sqlite_list_tables to see available tables.",
690
+ resourceType: "table",
691
+ resourceName: table
692
+ }
693
+ );
694
+ }
695
+ }
696
+ async function validateColumnInTable(adapter, table, column) {
697
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(column)) {
698
+ throw new DbMcpError(
699
+ "Invalid column name",
700
+ "NATIVE_WINDOW_INVALID_COLUMN",
701
+ "validation" /* VALIDATION */
702
+ );
703
+ }
704
+ const tableInfo = await adapter.executeReadQuery(
705
+ `SELECT name FROM pragma_table_info('${table}') WHERE name='${column}'`
706
+ );
707
+ if (!tableInfo.rows || tableInfo.rows.length === 0) {
708
+ throw new ResourceNotFoundError(
709
+ `Column '${column}' not found in table '${table}'`,
710
+ "COLUMN_NOT_FOUND",
711
+ {
712
+ suggestion: "Column not found. Use sqlite_describe_table to see available columns.",
713
+ resourceType: "column",
714
+ resourceName: column
715
+ }
716
+ );
717
+ }
718
+ }
719
+ async function validateOrderByColumns(adapter, table, orderBy) {
720
+ const parts = orderBy.split(",");
721
+ for (const part of parts) {
722
+ const tokens = part.trim().split(/\s+/);
723
+ const firstToken = tokens[0];
724
+ if (!firstToken) continue;
725
+ const colName = firstToken.replace(/^"|"$/g, "");
726
+ if (/[.()+*/]/.test(colName)) continue;
727
+ if (/^(ASC|DESC)$/i.test(colName)) continue;
728
+ await validateColumnInTable(adapter, table, colName);
729
+ }
730
+ }
731
+ function formatColumns(selectColumns) {
732
+ if (selectColumns === void 0 || selectColumns.length === 0) {
733
+ return "*";
734
+ }
735
+ return selectColumns.map((c) => `"${c}"`).join(", ");
736
+ }
737
+ function getWindowTools(adapter) {
738
+ return [
739
+ createRowNumberTool(adapter),
740
+ createRankTool(adapter),
741
+ createLagLeadTool(adapter),
742
+ createRunningTotalTool(adapter),
743
+ createMovingAverageTool(adapter),
744
+ createNtileTool(adapter)
745
+ ];
746
+ }
747
+ function createRowNumberTool(adapter) {
748
+ return {
749
+ name: "sqlite_window_row_number",
750
+ description: "Assign sequential row numbers based on ordering. Useful for pagination and ranking.",
751
+ group: "stats",
752
+ inputSchema: RowNumberSchema,
753
+ outputSchema: WindowRowNumberOutputSchema,
754
+ annotations: readOnly("Window Row Number"),
755
+ requiredScopes: ["read"],
756
+ handler: async (params, _context) => {
757
+ try {
758
+ const input = RowNumberSchema.parse(params);
759
+ await validateTableExists2(adapter, input.table);
760
+ await validateOrderByColumns(adapter, input.table, input.orderBy);
761
+ const columns = formatColumns(input.selectColumns);
762
+ const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
763
+ let sql = `
764
+ SELECT ${columns},
765
+ ROW_NUMBER() OVER (${partition} ORDER BY ${input.orderBy}) as row_number
766
+ FROM "${input.table}"
767
+ `;
768
+ if (input.whereClause) {
769
+ validateWhereClause(input.whereClause);
770
+ sql += ` WHERE ${input.whereClause}`;
771
+ }
772
+ sql += ` LIMIT ${input.limit}`;
773
+ const result = await adapter.executeReadQuery(sql);
774
+ return {
775
+ success: true,
776
+ rowCount: result.rows?.length ?? 0,
777
+ rows: result.rows
778
+ };
779
+ } catch (error) {
780
+ return formatHandlerError(error);
781
+ }
782
+ }
783
+ };
784
+ }
785
+ function createRankTool(adapter) {
786
+ return {
787
+ name: "sqlite_window_rank",
788
+ description: "Calculate rank of rows. RANK leaves gaps after ties, DENSE_RANK does not, PERCENT_RANK gives 0-1 range.",
789
+ group: "stats",
790
+ inputSchema: RankSchema,
791
+ outputSchema: WindowRankOutputSchema,
792
+ annotations: readOnly("Window Rank"),
793
+ requiredScopes: ["read"],
794
+ handler: async (params, _context) => {
795
+ try {
796
+ const input = RankSchema.parse(params);
797
+ await validateTableExists2(adapter, input.table);
798
+ await validateOrderByColumns(adapter, input.table, input.orderBy);
799
+ const columns = formatColumns(input.selectColumns);
800
+ const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
801
+ const rankFunc = input.rankType.toUpperCase();
802
+ let sql = `
803
+ SELECT ${columns},
804
+ ${rankFunc}() OVER (${partition} ORDER BY ${input.orderBy}) as ${input.rankType}
805
+ FROM "${input.table}"
806
+ `;
807
+ if (input.whereClause) {
808
+ validateWhereClause(input.whereClause);
809
+ sql += ` WHERE ${input.whereClause}`;
810
+ }
811
+ sql += ` LIMIT ${input.limit}`;
812
+ const result = await adapter.executeReadQuery(sql);
813
+ return {
814
+ success: true,
815
+ rankType: input.rankType,
816
+ rowCount: result.rows?.length ?? 0,
817
+ rows: result.rows
818
+ };
819
+ } catch (error) {
820
+ return formatHandlerError(error);
821
+ }
822
+ }
823
+ };
824
+ }
825
+ function createLagLeadTool(adapter) {
826
+ return {
827
+ name: "sqlite_window_lag_lead",
828
+ description: "Access previous (LAG) or next (LEAD) row values. Useful for comparing consecutive rows.",
829
+ group: "stats",
830
+ inputSchema: LagLeadSchema,
831
+ outputSchema: WindowLagLeadOutputSchema,
832
+ annotations: readOnly("Window Lag/Lead"),
833
+ requiredScopes: ["read"],
834
+ handler: async (params, _context) => {
835
+ try {
836
+ const input = LagLeadSchema.parse(params);
837
+ const normalizedDirection = input.direction.toLowerCase();
838
+ if (!VALID_DIRECTIONS.includes(
839
+ normalizedDirection
840
+ )) {
841
+ return {
842
+ success: false,
843
+ error: `Invalid direction '${input.direction}'. Must be one of: ${VALID_DIRECTIONS.join(", ")}`,
844
+ code: "VALIDATION_ERROR",
845
+ category: "validation",
846
+ recoverable: false
847
+ };
848
+ }
849
+ await validateTableExists2(adapter, input.table);
850
+ await validateColumnInTable(adapter, input.table, input.column);
851
+ await validateOrderByColumns(adapter, input.table, input.orderBy);
852
+ const columns = formatColumns(input.selectColumns);
853
+ const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
854
+ const func = normalizedDirection.toUpperCase();
855
+ const defaultVal = input.defaultValue !== void 0 ? `, ${input.defaultValue}` : "";
856
+ let sql = `
857
+ SELECT ${columns},
858
+ ${func}("${input.column}", ${input.offset}${defaultVal}) OVER (${partition} ORDER BY ${input.orderBy}) as ${normalizedDirection}_value
859
+ FROM "${input.table}"
860
+ `;
861
+ if (input.whereClause) {
862
+ validateWhereClause(input.whereClause);
863
+ sql += ` WHERE ${input.whereClause}`;
864
+ }
865
+ sql += ` LIMIT ${input.limit}`;
866
+ const result = await adapter.executeReadQuery(sql);
867
+ return {
868
+ success: true,
869
+ direction: normalizedDirection,
870
+ offset: input.offset,
871
+ rowCount: result.rows?.length ?? 0,
872
+ rows: result.rows
873
+ };
874
+ } catch (error) {
875
+ return formatHandlerError(error);
876
+ }
877
+ }
878
+ };
879
+ }
880
+ function createRunningTotalTool(adapter) {
881
+ return {
882
+ name: "sqlite_window_running_total",
883
+ description: "Calculate running (cumulative) total. Useful for balance tracking, cumulative metrics.",
884
+ group: "stats",
885
+ inputSchema: RunningTotalSchema,
886
+ outputSchema: WindowRunningTotalOutputSchema,
887
+ annotations: readOnly("Window Running Total"),
888
+ requiredScopes: ["read"],
889
+ handler: async (params, _context) => {
890
+ try {
891
+ const input = RunningTotalSchema.parse(
892
+ resolveAliases(params, { valueColumn: "column" })
893
+ );
894
+ await validateTableExists2(adapter, input.table);
895
+ await validateColumnInTable(adapter, input.table, input.column);
896
+ await validateOrderByColumns(adapter, input.table, input.orderBy);
897
+ const columns = formatColumns(input.selectColumns);
898
+ const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
899
+ let sql = `
900
+ SELECT ${columns},
901
+ SUM("${input.column}") OVER (${partition} ORDER BY ${input.orderBy} ROWS UNBOUNDED PRECEDING) as running_total
902
+ FROM "${input.table}"
903
+ `;
904
+ if (input.whereClause) {
905
+ validateWhereClause(input.whereClause);
906
+ sql += ` WHERE ${input.whereClause}`;
907
+ }
908
+ sql += ` LIMIT ${input.limit}`;
909
+ const result = await adapter.executeReadQuery(sql);
910
+ return {
911
+ success: true,
912
+ valueColumn: input.column,
913
+ rowCount: result.rows?.length ?? 0,
914
+ rows: result.rows
915
+ };
916
+ } catch (error) {
917
+ return formatHandlerError(error);
918
+ }
919
+ }
920
+ };
921
+ }
922
+ function createMovingAverageTool(adapter) {
923
+ return {
924
+ name: "sqlite_window_moving_avg",
925
+ description: "Calculate moving (rolling) average. Useful for smoothing time series data.",
926
+ group: "stats",
927
+ inputSchema: MovingAverageSchema,
928
+ outputSchema: WindowMovingAvgOutputSchema,
929
+ annotations: readOnly("Window Moving Average"),
930
+ requiredScopes: ["read"],
931
+ handler: async (params, _context) => {
932
+ try {
933
+ const input = MovingAverageSchema.parse(
934
+ resolveAliases(params, { valueColumn: "column" })
935
+ );
936
+ if (input.windowSize === void 0) {
937
+ return {
938
+ success: false,
939
+ error: "'windowSize' is required",
940
+ code: "VALIDATION_ERROR",
941
+ category: "validation",
942
+ recoverable: false
943
+ };
944
+ }
945
+ await validateTableExists2(adapter, input.table);
946
+ await validateColumnInTable(adapter, input.table, input.column);
947
+ await validateOrderByColumns(adapter, input.table, input.orderBy);
948
+ const columns = formatColumns(input.selectColumns);
949
+ const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
950
+ const preceding = input.windowSize - 1;
951
+ let sql = `
952
+ SELECT ${columns},
953
+ AVG("${input.column}") OVER (${partition} ORDER BY ${input.orderBy} ROWS BETWEEN ${preceding} PRECEDING AND CURRENT ROW) as moving_avg
954
+ FROM "${input.table}"
955
+ `;
956
+ if (input.whereClause) {
957
+ validateWhereClause(input.whereClause);
958
+ sql += ` WHERE ${input.whereClause}`;
959
+ }
960
+ sql += ` LIMIT ${input.limit}`;
961
+ const result = await adapter.executeReadQuery(sql);
962
+ return {
963
+ success: true,
964
+ valueColumn: input.column,
965
+ windowSize: input.windowSize,
966
+ rowCount: result.rows?.length ?? 0,
967
+ rows: result.rows
968
+ };
969
+ } catch (error) {
970
+ return formatHandlerError(error);
971
+ }
972
+ }
973
+ };
974
+ }
975
+ function createNtileTool(adapter) {
976
+ return {
977
+ name: "sqlite_window_ntile",
978
+ description: "Divide rows into N buckets. E.g., 4 buckets = quartiles, 10 = deciles, 100 = percentiles.",
979
+ group: "stats",
980
+ inputSchema: NtileSchema,
981
+ outputSchema: WindowNtileOutputSchema,
982
+ annotations: readOnly("Window Ntile"),
983
+ requiredScopes: ["read"],
984
+ handler: async (params, _context) => {
985
+ try {
986
+ const input = NtileSchema.parse(params);
987
+ if (input.buckets === void 0) {
988
+ return {
989
+ success: false,
990
+ error: "'buckets' is required",
991
+ code: "VALIDATION_ERROR",
992
+ category: "validation",
993
+ recoverable: false
994
+ };
995
+ }
996
+ await validateTableExists2(adapter, input.table);
997
+ await validateOrderByColumns(adapter, input.table, input.orderBy);
998
+ const columns = formatColumns(input.selectColumns);
999
+ const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
1000
+ let sql = `
1001
+ SELECT ${columns},
1002
+ NTILE(${input.buckets}) OVER (${partition} ORDER BY ${input.orderBy}) as ntile
1003
+ FROM "${input.table}"
1004
+ `;
1005
+ if (input.whereClause) {
1006
+ validateWhereClause(input.whereClause);
1007
+ sql += ` WHERE ${input.whereClause}`;
1008
+ }
1009
+ sql += ` LIMIT ${input.limit}`;
1010
+ const result = await adapter.executeReadQuery(sql);
1011
+ return {
1012
+ success: true,
1013
+ buckets: input.buckets,
1014
+ rowCount: result.rows?.length ?? 0,
1015
+ rows: result.rows
1016
+ };
1017
+ } catch (error) {
1018
+ return formatHandlerError(error);
1019
+ }
1020
+ }
1021
+ };
1022
+ }
1023
+ var coerceNumber2 = (val) => typeof val === "string" ? isNaN(Number(val)) ? void 0 : Number(val) : val;
1024
+ var coerceBoolean = (val) => typeof val === "string" ? val === "true" ? true : val === "false" ? false : void 0 : val;
1025
+ function createEnumCoercer(validValues) {
1026
+ return (val) => typeof val === "string" && validValues.includes(val) ? val : typeof val === "string" ? void 0 : val;
1027
+ }
1028
+ var VALID_GEOMETRY_TYPES = [
1029
+ "POINT",
1030
+ "LINESTRING",
1031
+ "POLYGON",
1032
+ "MULTIPOINT",
1033
+ "MULTILINESTRING",
1034
+ "MULTIPOLYGON",
1035
+ "GEOMETRY"
1036
+ ];
1037
+ var VALID_ANALYSIS_TYPES = [
1038
+ "nearest_neighbor",
1039
+ "point_in_polygon",
1040
+ "distance_matrix",
1041
+ "spatial_extent"
1042
+ ];
1043
+ var VALID_INDEX_ACTIONS = ["create", "drop", "check"];
1044
+ var VALID_FORMATS = ["wkt", "geojson"];
1045
+ var VALID_OPERATIONS = [
1046
+ "buffer",
1047
+ "intersection",
1048
+ "union",
1049
+ "difference",
1050
+ "centroid",
1051
+ "envelope",
1052
+ "simplify"
1053
+ ];
1054
+ var coerceGeometryType = createEnumCoercer(VALID_GEOMETRY_TYPES);
1055
+ var LoadSpatialiteSchema = z.object({
1056
+ extensionPath: z.string().optional().describe("Custom path to mod_spatialite extension"),
1057
+ forceReload: z.preprocess(
1058
+ coerceBoolean,
1059
+ z.boolean().optional().default(false).describe("Force reload if already loaded")
1060
+ )
1061
+ });
1062
+ var CreateSpatialTableSchema = z.object({
1063
+ tableName: z.string().describe("Name of the spatial table to create"),
1064
+ geometryColumn: z.string().optional().default("geom").describe("Name of the geometry column"),
1065
+ geometryType: z.preprocess(
1066
+ coerceGeometryType,
1067
+ z.enum([
1068
+ "POINT",
1069
+ "LINESTRING",
1070
+ "POLYGON",
1071
+ "MULTIPOINT",
1072
+ "MULTILINESTRING",
1073
+ "MULTIPOLYGON",
1074
+ "GEOMETRY"
1075
+ ]).optional().default("POINT").describe("Type of geometry to store")
1076
+ ),
1077
+ srid: z.preprocess(
1078
+ coerceNumber2,
1079
+ z.number().optional().default(4326).describe("Spatial Reference System ID (4326 for WGS84)")
1080
+ ),
1081
+ additionalColumns: z.array(
1082
+ z.object({
1083
+ name: z.string(),
1084
+ type: z.string()
1085
+ })
1086
+ ).optional().default([]).describe("Additional non-spatial columns")
1087
+ });
1088
+ var SpatialQuerySchema = z.object({
1089
+ query: z.string().describe("Spatial SQL query using SpatiaLite functions"),
1090
+ params: z.array(z.unknown()).optional().default([]).describe("Query parameters")
1091
+ });
1092
+ var SpatialAnalysisSchema = z.object({
1093
+ analysisType: z.string().describe(
1094
+ "Type of spatial analysis: nearest_neighbor, point_in_polygon, distance_matrix, spatial_extent"
1095
+ ),
1096
+ sourceTable: z.string().describe("Source table for analysis"),
1097
+ targetTable: z.string().optional().describe(
1098
+ "Target table for operations. For point_in_polygon, this should contain POLYGON geometries while sourceTable contains POINTs"
1099
+ ),
1100
+ geometryColumn: z.string().optional().default("geom").describe("Geometry column name"),
1101
+ limit: z.preprocess(
1102
+ coerceNumber2,
1103
+ z.number().optional().default(100).describe("Limit results")
1104
+ ),
1105
+ excludeSelf: z.preprocess(
1106
+ coerceBoolean,
1107
+ z.boolean().optional().default(true).describe(
1108
+ "For nearest_neighbor: exclude self-matches when source and target tables are the same (default: true)"
1109
+ )
1110
+ ),
1111
+ includeGeometry: z.preprocess(
1112
+ coerceBoolean,
1113
+ z.boolean().optional().default(false).describe(
1114
+ "Include full WKT geometry in results (default: false to reduce payload size)"
1115
+ )
1116
+ )
1117
+ });
1118
+ var SpatialIndexSchema = z.object({
1119
+ tableName: z.string().describe("Name of the spatial table"),
1120
+ geometryColumn: z.string().optional().default("geom").describe("Geometry column name"),
1121
+ action: z.string().optional().default("create").describe("Action to perform: create, drop, check")
1122
+ });
1123
+ var GeometryTransformSchema = z.object({
1124
+ operation: z.string().describe(
1125
+ "Geometry operation: buffer, intersection, union, difference, centroid, envelope, simplify"
1126
+ ),
1127
+ geometry1: z.string().describe("First geometry (WKT format)"),
1128
+ geometry2: z.string().optional().describe("Second geometry for binary ops"),
1129
+ distance: z.preprocess(
1130
+ coerceNumber2,
1131
+ z.number().optional().default(1e-3).describe("Distance for buffer or simplify tolerance")
1132
+ ),
1133
+ srid: z.preprocess(
1134
+ coerceNumber2,
1135
+ z.number().optional().default(4326).describe("SRID for result")
1136
+ ),
1137
+ simplifyTolerance: z.preprocess(
1138
+ coerceNumber2,
1139
+ z.number().optional().describe(
1140
+ "For buffer operation: apply ST_Simplify to reduce vertices in output polygon. Recommended: 0.0001-0.001 for lat/lon"
1141
+ )
1142
+ )
1143
+ });
1144
+ var SpatialImportSchema = z.object({
1145
+ tableName: z.string().describe("Target table name"),
1146
+ format: z.string().describe("Input format: wkt or geojson"),
1147
+ data: z.string().describe("Geometry data (WKT string or GeoJSON)"),
1148
+ srid: z.preprocess(
1149
+ coerceNumber2,
1150
+ z.number().optional().default(4326).describe("SRID of input data")
1151
+ ),
1152
+ additionalData: z.record(z.string(), z.unknown()).optional().describe("Additional column values")
1153
+ });
1154
+
1155
+ // src/adapters/sqlite-native/tools/spatialite/loader.ts
1156
+ var SPATIALITE_PATHS = [
1157
+ process.env["SPATIALITE_PATH"],
1158
+ "mod_spatialite",
1159
+ "mod_spatialite.dll",
1160
+ "mod_spatialite.so",
1161
+ "/usr/lib/x86_64-linux-gnu/mod_spatialite.so",
1162
+ "/usr/local/lib/mod_spatialite.so",
1163
+ "/usr/local/lib/mod_spatialite.dylib"
1164
+ ].filter((p) => Boolean(p));
1165
+ var loadedDatabases = /* @__PURE__ */ new WeakSet();
1166
+ function tryLoadSpatialite(adapter, customPath) {
1167
+ const db = adapter.getDatabase();
1168
+ if (db === null) {
1169
+ return { success: false, error: "Database not connected" };
1170
+ }
1171
+ const paths = customPath ? [customPath, ...SPATIALITE_PATHS] : SPATIALITE_PATHS;
1172
+ const chosenPathForEnv = customPath ?? process.env["SPATIALITE_PATH"];
1173
+ if (process.platform === "win32" && chosenPathForEnv) {
1174
+ const looksLikeFsPath = chosenPathForEnv.includes("/") || chosenPathForEnv.includes("\\");
1175
+ if (looksLikeFsPath) {
1176
+ const extensionDir = chosenPathForEnv.replace(/[/\\][^/\\]+$/, "");
1177
+ const currentPath = process.env["PATH"] ?? "";
1178
+ if (!currentPath.includes(extensionDir)) {
1179
+ process.env["PATH"] = extensionDir + ";" + currentPath;
1180
+ }
1181
+ }
1182
+ }
1183
+ for (const candidatePath of paths) {
1184
+ try {
1185
+ db.loadExtension(candidatePath);
1186
+ db.exec("SELECT InitSpatialMetaData(1)");
1187
+ loadedDatabases.add(db);
1188
+ return { success: true, path: candidatePath };
1189
+ } catch {
1190
+ }
1191
+ }
1192
+ return {
1193
+ success: false,
1194
+ error: "SpatiaLite extension not found. Install mod_spatialite and set SPATIALITE_PATH environment variable."
1195
+ };
1196
+ }
1197
+ function isSpatialiteLoaded(adapter) {
1198
+ const db = adapter.getDatabase();
1199
+ if (db === null) return false;
1200
+ if (loadedDatabases.has(db)) return true;
1201
+ try {
1202
+ db.exec("SELECT spatialite_version()");
1203
+ db.exec("SELECT InitSpatialMetaData(1)");
1204
+ loadedDatabases.add(db);
1205
+ return true;
1206
+ } catch {
1207
+ return false;
1208
+ }
1209
+ }
1210
+ function ensureSpatialite(adapter) {
1211
+ if (!isSpatialiteLoaded(adapter)) {
1212
+ const result = tryLoadSpatialite(adapter);
1213
+ if (!result.success) {
1214
+ throw new DbMcpError(
1215
+ result.error ?? "Failed to load SpatiaLite",
1216
+ "SPATIALITE_LOAD_FAILED",
1217
+ "connection" /* CONNECTION */
1218
+ );
1219
+ }
1220
+ }
1221
+ }
1222
+
1223
+ // src/adapters/sqlite-native/tools/spatialite/tools.ts
1224
+ function createLoadSpatialiteTool(adapter) {
1225
+ return {
1226
+ name: "sqlite_spatialite_load",
1227
+ description: "Load SpatiaLite extension for geospatial capabilities. Required before using other spatial tools.",
1228
+ group: "geo",
1229
+ inputSchema: LoadSpatialiteSchema,
1230
+ outputSchema: SpatialiteLoadOutputSchema,
1231
+ requiredScopes: ["admin"],
1232
+ annotations: admin("SpatiaLite Load"),
1233
+ handler: (_params, _context) => {
1234
+ try {
1235
+ const input = LoadSpatialiteSchema.parse(_params);
1236
+ if (!input.forceReload && isSpatialiteLoaded(adapter)) {
1237
+ return Promise.resolve({
1238
+ success: true,
1239
+ message: "SpatiaLite already loaded",
1240
+ alreadyLoaded: true
1241
+ });
1242
+ }
1243
+ const result = tryLoadSpatialite(adapter, input.extensionPath);
1244
+ if (result.success) {
1245
+ return Promise.resolve({
1246
+ success: true,
1247
+ message: "SpatiaLite loaded successfully",
1248
+ extensionPath: result.path
1249
+ });
1250
+ }
1251
+ return Promise.resolve({
1252
+ success: false,
1253
+ error: result.error,
1254
+ code: "SPATIALITE_LOAD_FAILED",
1255
+ category: "internal",
1256
+ recoverable: false,
1257
+ searchedPaths: SPATIALITE_PATHS
1258
+ });
1259
+ } catch (error) {
1260
+ return Promise.resolve(formatHandlerError(error));
1261
+ }
1262
+ }
1263
+ };
1264
+ }
1265
+ function createSpatialTableTool(adapter) {
1266
+ return {
1267
+ name: "sqlite_spatialite_create_table",
1268
+ description: "Create a spatial table with geometry column using SpatiaLite.",
1269
+ group: "geo",
1270
+ inputSchema: CreateSpatialTableSchema,
1271
+ outputSchema: SpatialiteCreateTableOutputSchema,
1272
+ requiredScopes: ["write"],
1273
+ annotations: write("SpatiaLite Create Table"),
1274
+ handler: async (params, _context) => {
1275
+ try {
1276
+ const input = CreateSpatialTableSchema.parse(params);
1277
+ ensureSpatialite(adapter);
1278
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.tableName)) {
1279
+ return {
1280
+ success: false,
1281
+ error: `Invalid table name: '${input.tableName}'`,
1282
+ code: "VALIDATION_ERROR",
1283
+ category: "validation",
1284
+ recoverable: false
1285
+ };
1286
+ }
1287
+ const existsCheck = await adapter.executeReadQuery(
1288
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='${input.tableName}'`
1289
+ );
1290
+ const alreadyExists = existsCheck.rows != null && existsCheck.rows.length > 0;
1291
+ if (alreadyExists) {
1292
+ return {
1293
+ success: true,
1294
+ message: `Spatial table '${input.tableName}' already exists`,
1295
+ alreadyExists: true,
1296
+ tableName: input.tableName
1297
+ };
1298
+ }
1299
+ const columns = ["id INTEGER PRIMARY KEY AUTOINCREMENT"];
1300
+ for (const col of input.additionalColumns) {
1301
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(col.name)) {
1302
+ return {
1303
+ success: false,
1304
+ error: `Invalid column name: '${col.name}'`,
1305
+ code: "VALIDATION_ERROR",
1306
+ category: "validation",
1307
+ recoverable: false
1308
+ };
1309
+ }
1310
+ columns.push(`"${col.name}" ${col.type}`);
1311
+ }
1312
+ await adapter.executeWriteQuery(
1313
+ `CREATE TABLE "${input.tableName}" (${columns.join(", ")})`
1314
+ );
1315
+ const addResult = await adapter.executeReadQuery(
1316
+ `SELECT AddGeometryColumn('${input.tableName}', '${input.geometryColumn}', ${input.srid}, '${input.geometryType}', 'XY')`
1317
+ );
1318
+ const verifyResult = await adapter.executeReadQuery(
1319
+ `SELECT name FROM pragma_table_info('${input.tableName}') WHERE name = '${input.geometryColumn}'`
1320
+ );
1321
+ if (!verifyResult.rows || verifyResult.rows.length === 0) {
1322
+ return {
1323
+ success: false,
1324
+ error: `Failed to create geometry column '${input.geometryColumn}'. AddGeometryColumn returned: ${JSON.stringify(addResult.rows)}`,
1325
+ code: "SPATIALITE_CREATE_FAILED",
1326
+ category: "internal",
1327
+ recoverable: false
1328
+ };
1329
+ }
1330
+ return {
1331
+ success: true,
1332
+ message: `Spatial table '${input.tableName}' created`,
1333
+ tableName: input.tableName,
1334
+ geometryColumn: input.geometryColumn,
1335
+ geometryType: input.geometryType,
1336
+ srid: input.srid
1337
+ };
1338
+ } catch (error) {
1339
+ return formatHandlerError(error);
1340
+ }
1341
+ }
1342
+ };
1343
+ }
1344
+ function createSpatialQueryTool(adapter) {
1345
+ return {
1346
+ name: "sqlite_spatialite_query",
1347
+ description: "Execute spatial SQL queries using SpatiaLite functions (ST_Distance, ST_Within, etc.).",
1348
+ group: "geo",
1349
+ inputSchema: SpatialQuerySchema,
1350
+ outputSchema: SpatialiteQueryOutputSchema,
1351
+ requiredScopes: ["read"],
1352
+ annotations: readOnly("SpatiaLite Query"),
1353
+ handler: async (params, _context) => {
1354
+ try {
1355
+ const input = SpatialQuerySchema.parse(params);
1356
+ ensureSpatialite(adapter);
1357
+ const result = await adapter.executeReadQuery(input.query);
1358
+ return {
1359
+ success: true,
1360
+ rowCount: result.rows?.length ?? 0,
1361
+ rows: result.rows
1362
+ };
1363
+ } catch (error) {
1364
+ const msg = error instanceof Error ? error.message : String(error);
1365
+ if (msg.includes("does not return data")) {
1366
+ return {
1367
+ success: false,
1368
+ error: "This tool only supports SELECT queries. Use sqlite_write_query for INSERT/UPDATE/DELETE statements.",
1369
+ code: "QUERY_NOT_SELECT",
1370
+ category: "validation",
1371
+ recoverable: false
1372
+ };
1373
+ }
1374
+ return formatHandlerError(error);
1375
+ }
1376
+ }
1377
+ };
1378
+ }
1379
+ function createSpatialIndexTool(adapter) {
1380
+ return {
1381
+ name: "sqlite_spatialite_index",
1382
+ description: "Create, drop, or check spatial R-Tree index on geometry column.",
1383
+ group: "geo",
1384
+ inputSchema: SpatialIndexSchema,
1385
+ outputSchema: SpatialiteIndexOutputSchema,
1386
+ requiredScopes: ["write"],
1387
+ annotations: write("SpatiaLite Index"),
1388
+ handler: async (params, _context) => {
1389
+ try {
1390
+ const input = SpatialIndexSchema.parse(params);
1391
+ ensureSpatialite(adapter);
1392
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.tableName)) {
1393
+ return {
1394
+ success: false,
1395
+ error: `Invalid table name: '${input.tableName}'`,
1396
+ code: "VALIDATION_ERROR",
1397
+ category: "validation",
1398
+ recoverable: false
1399
+ };
1400
+ }
1401
+ if (!VALID_INDEX_ACTIONS.includes(input.action)) {
1402
+ return {
1403
+ success: false,
1404
+ error: `Invalid action: '${input.action}'. Must be one of: ${VALID_INDEX_ACTIONS.join(", ")}`,
1405
+ code: "VALIDATION_ERROR",
1406
+ category: "validation",
1407
+ recoverable: false
1408
+ };
1409
+ }
1410
+ const tableCheck = await adapter.executeReadQuery(
1411
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='${input.tableName}'`
1412
+ );
1413
+ if (!tableCheck.rows || tableCheck.rows.length === 0) {
1414
+ return {
1415
+ success: false,
1416
+ error: `Table '${input.tableName}' does not exist`,
1417
+ code: "TABLE_NOT_FOUND",
1418
+ category: "resource",
1419
+ suggestion: "Table not found. Run sqlite_list_tables to see available tables.",
1420
+ recoverable: false
1421
+ };
1422
+ }
1423
+ const indexExists = async () => {
1424
+ const idxCheck = await adapter.executeReadQuery(
1425
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='idx_${input.tableName}_${input.geometryColumn}'`
1426
+ );
1427
+ return (idxCheck.rows?.length ?? 0) > 0;
1428
+ };
1429
+ switch (input.action) {
1430
+ case "create": {
1431
+ if (await indexExists()) {
1432
+ return {
1433
+ success: true,
1434
+ message: `Spatial index already exists on ${input.tableName}.${input.geometryColumn}`,
1435
+ alreadyExists: true,
1436
+ action: "create"
1437
+ };
1438
+ }
1439
+ await adapter.executeReadQuery(
1440
+ `SELECT CreateSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
1441
+ );
1442
+ return {
1443
+ success: true,
1444
+ message: `Spatial index created on ${input.tableName}.${input.geometryColumn}`,
1445
+ action: "create"
1446
+ };
1447
+ }
1448
+ case "drop": {
1449
+ if (!await indexExists()) {
1450
+ return {
1451
+ success: true,
1452
+ message: `No spatial index exists on ${input.tableName}.${input.geometryColumn}`,
1453
+ alreadyDropped: true,
1454
+ action: "drop"
1455
+ };
1456
+ }
1457
+ await adapter.executeReadQuery(
1458
+ `SELECT DisableSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
1459
+ );
1460
+ return {
1461
+ success: true,
1462
+ message: `Spatial index dropped from ${input.tableName}.${input.geometryColumn}`,
1463
+ action: "drop"
1464
+ };
1465
+ }
1466
+ case "check": {
1467
+ const hasIndex = await indexExists();
1468
+ if (!hasIndex) {
1469
+ return {
1470
+ success: true,
1471
+ message: "No spatial index found",
1472
+ action: "check",
1473
+ indexed: false
1474
+ };
1475
+ }
1476
+ const checkResult = await adapter.executeReadQuery(
1477
+ `SELECT CheckSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
1478
+ );
1479
+ const checkValue = checkResult.rows?.[0];
1480
+ const rawResult = checkValue != null ? Object.values(checkValue)[0] : null;
1481
+ if (rawResult === 1) {
1482
+ return {
1483
+ success: true,
1484
+ message: "Spatial index is valid",
1485
+ action: "check",
1486
+ indexed: true,
1487
+ valid: true
1488
+ };
1489
+ } else if (rawResult === 0) {
1490
+ return {
1491
+ success: true,
1492
+ message: "Spatial index exists but is invalid (rebuild recommended)",
1493
+ action: "check",
1494
+ indexed: true,
1495
+ valid: false
1496
+ };
1497
+ } else {
1498
+ return {
1499
+ success: true,
1500
+ message: "Spatial index exists (validation inconclusive \u2014 common in SpatiaLite 5.x)",
1501
+ action: "check",
1502
+ indexed: true,
1503
+ valid: null
1504
+ };
1505
+ }
1506
+ }
1507
+ default:
1508
+ return {
1509
+ success: false,
1510
+ error: `Invalid action: '${input.action}'. Must be one of: ${VALID_INDEX_ACTIONS.join(", ")}`,
1511
+ code: "VALIDATION_ERROR",
1512
+ category: "validation",
1513
+ recoverable: false
1514
+ };
1515
+ }
1516
+ } catch (error) {
1517
+ return formatHandlerError(error);
1518
+ }
1519
+ }
1520
+ };
1521
+ }
1522
+
1523
+ // src/adapters/sqlite-native/tools/spatialite/analysis.ts
1524
+ function createSpatialAnalysisTool(adapter) {
1525
+ return {
1526
+ name: "sqlite_spatialite_analyze",
1527
+ description: "Perform spatial analysis: nearest neighbor, point in polygon, distance matrix. For point_in_polygon, sourceTable should contain POINTs and targetTable should contain POLYGONs.",
1528
+ group: "geo",
1529
+ inputSchema: SpatialAnalysisSchema,
1530
+ outputSchema: SpatialiteAnalyzeOutputSchema,
1531
+ requiredScopes: ["read"],
1532
+ annotations: readOnly("SpatiaLite Analyze"),
1533
+ handler: async (params, _context) => {
1534
+ try {
1535
+ const input = SpatialAnalysisSchema.parse(params);
1536
+ ensureSpatialite(adapter);
1537
+ if (!VALID_ANALYSIS_TYPES.includes(
1538
+ input.analysisType
1539
+ )) {
1540
+ return {
1541
+ success: false,
1542
+ error: `Invalid analysisType: '${input.analysisType}'. Must be one of: ${VALID_ANALYSIS_TYPES.join(", ")}`,
1543
+ code: "VALIDATION_ERROR",
1544
+ category: "validation",
1545
+ recoverable: false
1546
+ };
1547
+ }
1548
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.sourceTable)) {
1549
+ return {
1550
+ success: false,
1551
+ error: `Invalid source table name: '${input.sourceTable}'`,
1552
+ code: "VALIDATION_ERROR",
1553
+ category: "validation",
1554
+ recoverable: false
1555
+ };
1556
+ }
1557
+ if (input.targetTable && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.targetTable)) {
1558
+ return {
1559
+ success: false,
1560
+ error: `Invalid target table name: '${input.targetTable}'`,
1561
+ code: "VALIDATION_ERROR",
1562
+ category: "validation",
1563
+ recoverable: false
1564
+ };
1565
+ }
1566
+ let query;
1567
+ switch (input.analysisType) {
1568
+ case "spatial_extent":
1569
+ query = `SELECT
1570
+ MbrMinX(Extent("${input.geometryColumn}")) as min_x,
1571
+ MbrMinY(Extent("${input.geometryColumn}")) as min_y,
1572
+ MbrMaxX(Extent("${input.geometryColumn}")) as max_x,
1573
+ MbrMaxY(Extent("${input.geometryColumn}")) as max_y,
1574
+ COUNT(*) as feature_count
1575
+ FROM "${input.sourceTable}"`;
1576
+ break;
1577
+ case "nearest_neighbor": {
1578
+ if (!input.targetTable) {
1579
+ return {
1580
+ success: false,
1581
+ error: "Missing required parameter 'targetTable' for nearest neighbor analysis",
1582
+ code: "VALIDATION_ERROR",
1583
+ category: "validation",
1584
+ recoverable: false
1585
+ };
1586
+ }
1587
+ const sameTable = input.sourceTable === input.targetTable;
1588
+ const selfFilter = sameTable && input.excludeSelf ? "WHERE s.id != t.id" : "";
1589
+ const geomColumns = input.includeGeometry ? `, AsText(s."${input.geometryColumn}") as source_geom, AsText(t."${input.geometryColumn}") as target_geom` : "";
1590
+ query = `SELECT
1591
+ s.id as source_id, t.id as target_id,
1592
+ ST_Distance(s."${input.geometryColumn}", t."${input.geometryColumn}") as distance${geomColumns}
1593
+ FROM "${input.sourceTable}" s, "${input.targetTable}" t
1594
+ ${selfFilter}
1595
+ ORDER BY distance LIMIT ${input.limit}`;
1596
+ break;
1597
+ }
1598
+ case "point_in_polygon": {
1599
+ if (!input.targetTable) {
1600
+ return {
1601
+ success: false,
1602
+ error: "Missing required parameter 'targetTable' for point in polygon analysis",
1603
+ code: "VALIDATION_ERROR",
1604
+ category: "validation",
1605
+ recoverable: false
1606
+ };
1607
+ }
1608
+ const geomCols = input.includeGeometry ? `, AsText(s."${input.geometryColumn}") as source_geom, AsText(t."${input.geometryColumn}") as target_geom` : "";
1609
+ query = `SELECT
1610
+ s.id as source_id, t.id as target_id${geomCols}
1611
+ FROM "${input.sourceTable}" s, "${input.targetTable}" t
1612
+ WHERE ST_Within(s."${input.geometryColumn}", t."${input.geometryColumn}")
1613
+ LIMIT ${input.limit}`;
1614
+ break;
1615
+ }
1616
+ case "distance_matrix": {
1617
+ const dmTarget = input.targetTable ?? input.sourceTable;
1618
+ const dmSameTable = dmTarget === input.sourceTable;
1619
+ const dmFilter = dmSameTable ? "WHERE a.id < b.id" : "";
1620
+ query = `SELECT a.id as id1, b.id as id2,
1621
+ ST_Distance(a."${input.geometryColumn}", b."${input.geometryColumn}") as distance
1622
+ FROM "${input.sourceTable}" a, "${dmTarget}" b
1623
+ ${dmFilter}
1624
+ ORDER BY distance LIMIT ${input.limit}`;
1625
+ break;
1626
+ }
1627
+ default:
1628
+ query = "SELECT 1";
1629
+ }
1630
+ const result = await adapter.executeReadQuery(query);
1631
+ return {
1632
+ success: true,
1633
+ analysisType: input.analysisType,
1634
+ rowCount: result.rows?.length ?? 0,
1635
+ results: result.rows
1636
+ };
1637
+ } catch (error) {
1638
+ return formatHandlerError(error);
1639
+ }
1640
+ }
1641
+ };
1642
+ }
1643
+ function createGeometryTransformTool(adapter) {
1644
+ return {
1645
+ name: "sqlite_spatialite_transform",
1646
+ description: "Perform geometry operations: buffer, intersection, union, centroid, simplify.",
1647
+ group: "geo",
1648
+ inputSchema: GeometryTransformSchema,
1649
+ outputSchema: SpatialiteTransformOutputSchema,
1650
+ requiredScopes: ["read"],
1651
+ annotations: readOnly("SpatiaLite Transform"),
1652
+ handler: async (params, _context) => {
1653
+ try {
1654
+ const input = GeometryTransformSchema.parse(params);
1655
+ ensureSpatialite(adapter);
1656
+ if (!VALID_OPERATIONS.includes(input.operation)) {
1657
+ return {
1658
+ success: false,
1659
+ error: `Invalid operation: '${input.operation}'. Must be one of: ${VALID_OPERATIONS.join(", ")}`,
1660
+ code: "VALIDATION_ERROR",
1661
+ category: "validation",
1662
+ recoverable: false
1663
+ };
1664
+ }
1665
+ let query;
1666
+ switch (input.operation) {
1667
+ case "buffer": {
1668
+ const bufferGeom = `Buffer(GeomFromText('${input.geometry1}', ${input.srid}), ${input.distance})`;
1669
+ const defaultTolerance = Math.max(1e-4, input.distance * 0.01);
1670
+ const tolerance = input.simplifyTolerance ?? defaultTolerance;
1671
+ const finalGeom = tolerance > 0 ? `Simplify(${bufferGeom}, ${tolerance})` : bufferGeom;
1672
+ query = `SELECT AsText(${finalGeom}) as result`;
1673
+ break;
1674
+ }
1675
+ case "intersection":
1676
+ if (!input.geometry2) {
1677
+ return {
1678
+ success: false,
1679
+ error: "Second geometry required for intersection",
1680
+ code: "VALIDATION_ERROR",
1681
+ category: "validation",
1682
+ recoverable: false
1683
+ };
1684
+ }
1685
+ query = `SELECT AsText(Intersection(
1686
+ GeomFromText('${input.geometry1}', ${input.srid}),
1687
+ GeomFromText('${input.geometry2}', ${input.srid})
1688
+ )) as result`;
1689
+ break;
1690
+ case "union":
1691
+ if (!input.geometry2) {
1692
+ return {
1693
+ success: false,
1694
+ error: "Second geometry required for union",
1695
+ code: "VALIDATION_ERROR",
1696
+ category: "validation",
1697
+ recoverable: false
1698
+ };
1699
+ }
1700
+ query = `SELECT AsText(GUnion(
1701
+ GeomFromText('${input.geometry1}', ${input.srid}),
1702
+ GeomFromText('${input.geometry2}', ${input.srid})
1703
+ )) as result`;
1704
+ break;
1705
+ case "difference":
1706
+ if (!input.geometry2) {
1707
+ return {
1708
+ success: false,
1709
+ error: "Second geometry required for difference",
1710
+ code: "VALIDATION_ERROR",
1711
+ category: "validation",
1712
+ recoverable: false
1713
+ };
1714
+ }
1715
+ query = `SELECT AsText(Difference(
1716
+ GeomFromText('${input.geometry1}', ${input.srid}),
1717
+ GeomFromText('${input.geometry2}', ${input.srid})
1718
+ )) as result`;
1719
+ break;
1720
+ case "centroid":
1721
+ query = `SELECT AsText(Centroid(GeomFromText('${input.geometry1}', ${input.srid}))) as result`;
1722
+ break;
1723
+ case "envelope":
1724
+ query = `SELECT AsText(Envelope(GeomFromText('${input.geometry1}', ${input.srid}))) as result`;
1725
+ break;
1726
+ case "simplify":
1727
+ query = `SELECT AsText(Simplify(GeomFromText('${input.geometry1}', ${input.srid}), ${input.distance})) as result`;
1728
+ break;
1729
+ default:
1730
+ query = "SELECT 1";
1731
+ }
1732
+ const result = await adapter.executeReadQuery(query);
1733
+ const wktResult = result.rows?.[0]?.["result"];
1734
+ if (wktResult === null || wktResult === void 0) {
1735
+ return {
1736
+ success: false,
1737
+ error: `Invalid geometry: '${input.geometry1}' could not be parsed as WKT`,
1738
+ code: "VALIDATION_ERROR",
1739
+ category: "validation",
1740
+ recoverable: false
1741
+ };
1742
+ }
1743
+ return {
1744
+ success: true,
1745
+ operation: input.operation,
1746
+ result: wktResult
1747
+ };
1748
+ } catch (error) {
1749
+ return formatHandlerError(error);
1750
+ }
1751
+ }
1752
+ };
1753
+ }
1754
+ function createSpatialImportTool(adapter) {
1755
+ return {
1756
+ name: "sqlite_spatialite_import",
1757
+ description: "Import geometry data from WKT or GeoJSON into a spatial table.",
1758
+ group: "geo",
1759
+ inputSchema: SpatialImportSchema,
1760
+ outputSchema: SpatialiteImportOutputSchema,
1761
+ requiredScopes: ["write"],
1762
+ annotations: write("SpatiaLite Import"),
1763
+ handler: async (params, _context) => {
1764
+ try {
1765
+ const input = SpatialImportSchema.parse(params);
1766
+ ensureSpatialite(adapter);
1767
+ if (!VALID_FORMATS.includes(input.format)) {
1768
+ return {
1769
+ success: false,
1770
+ error: `Invalid format: '${input.format}'. Must be one of: ${VALID_FORMATS.join(", ")}`,
1771
+ code: "VALIDATION_ERROR",
1772
+ category: "validation",
1773
+ recoverable: false
1774
+ };
1775
+ }
1776
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.tableName)) {
1777
+ return {
1778
+ success: false,
1779
+ error: `Invalid table name: '${input.tableName}'`,
1780
+ code: "VALIDATION_ERROR",
1781
+ category: "validation",
1782
+ recoverable: false
1783
+ };
1784
+ }
1785
+ let wkt;
1786
+ if (input.format === "geojson") {
1787
+ try {
1788
+ JSON.parse(input.data);
1789
+ const columns2 = ["geom"];
1790
+ const values2 = [
1791
+ `SetSRID(GeomFromGeoJSON('${input.data}'), ${input.srid})`
1792
+ ];
1793
+ if (input.additionalData) {
1794
+ for (const [key, value] of Object.entries(input.additionalData)) {
1795
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
1796
+ return {
1797
+ success: false,
1798
+ error: `Invalid column name: '${key}'`,
1799
+ code: "VALIDATION_ERROR",
1800
+ category: "validation",
1801
+ recoverable: false
1802
+ };
1803
+ }
1804
+ columns2.push(`"${key}"`);
1805
+ values2.push(
1806
+ typeof value === "string" ? `'${value.replace(/'/g, "''")}'` : String(value)
1807
+ );
1808
+ }
1809
+ }
1810
+ const sql2 = `INSERT INTO "${input.tableName}" (${columns2.join(", ")}) VALUES (${values2.join(", ")})`;
1811
+ const insertResult2 = await adapter.executeWriteQuery(sql2);
1812
+ return {
1813
+ success: true,
1814
+ message: "GeoJSON geometry imported",
1815
+ rowsAffected: insertResult2.rowsAffected ?? 1
1816
+ };
1817
+ } catch (e) {
1818
+ return {
1819
+ success: false,
1820
+ error: `Invalid GeoJSON: ${e instanceof Error ? e.message : String(e)}`,
1821
+ code: "VALIDATION_ERROR",
1822
+ category: "validation",
1823
+ recoverable: false
1824
+ };
1825
+ }
1826
+ } else {
1827
+ wkt = input.data;
1828
+ }
1829
+ const wktCheck = await adapter.executeReadQuery(
1830
+ `SELECT GeomFromText('${wkt}', ${input.srid}) as geom`
1831
+ );
1832
+ const parsedGeom = wktCheck.rows?.[0]?.["geom"];
1833
+ if (parsedGeom === null || parsedGeom === void 0) {
1834
+ return {
1835
+ success: false,
1836
+ error: `Invalid WKT geometry: '${wkt}' could not be parsed`,
1837
+ code: "VALIDATION_ERROR",
1838
+ category: "validation",
1839
+ recoverable: false
1840
+ };
1841
+ }
1842
+ const columns = ["geom"];
1843
+ const values = [`GeomFromText('${wkt}', ${input.srid})`];
1844
+ if (input.additionalData) {
1845
+ for (const [key, value] of Object.entries(input.additionalData)) {
1846
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
1847
+ return {
1848
+ success: false,
1849
+ error: `Invalid column name: '${key}'`,
1850
+ code: "VALIDATION_ERROR",
1851
+ category: "validation",
1852
+ recoverable: false
1853
+ };
1854
+ }
1855
+ columns.push(`"${key}"`);
1856
+ values.push(
1857
+ typeof value === "string" ? `'${value.replace(/'/g, "''")}'` : String(value)
1858
+ );
1859
+ }
1860
+ }
1861
+ const sql = `INSERT INTO "${input.tableName}" (${columns.join(", ")}) VALUES (${values.join(", ")})`;
1862
+ const insertResult = await adapter.executeWriteQuery(sql);
1863
+ return {
1864
+ success: true,
1865
+ message: "WKT geometry imported",
1866
+ rowsAffected: insertResult.rowsAffected ?? 1
1867
+ };
1868
+ } catch (error) {
1869
+ return formatHandlerError(error);
1870
+ }
1871
+ }
1872
+ };
1873
+ }
1874
+
1875
+ // src/adapters/sqlite-native/tools/spatialite/index.ts
1876
+ function getSpatialiteTools(adapter) {
1877
+ return [
1878
+ createLoadSpatialiteTool(adapter),
1879
+ createSpatialTableTool(adapter),
1880
+ createSpatialQueryTool(adapter),
1881
+ createSpatialAnalysisTool(adapter),
1882
+ createSpatialIndexTool(adapter),
1883
+ createGeometryTransformTool(adapter),
1884
+ createSpatialImportTool(adapter)
1885
+ ];
1886
+ }
1887
+
1888
+ // src/adapters/sqlite-native/registration/index.ts
1889
+ function getNativeToolDefinitions(adapter) {
1890
+ const sharedAdapter = adapter;
1891
+ const tools = [
1892
+ ...getCoreTools(sharedAdapter),
1893
+ ...getJsonOperationTools(sharedAdapter),
1894
+ ...getJsonHelperTools(sharedAdapter),
1895
+ ...getTextTools(sharedAdapter),
1896
+ ...getFtsTools(sharedAdapter),
1897
+ ...getStatsTools(sharedAdapter),
1898
+ ...getVirtualTools(sharedAdapter),
1899
+ ...getVectorTools(sharedAdapter),
1900
+ ...getGeoTools(sharedAdapter),
1901
+ ...getAdminTools(sharedAdapter),
1902
+ ...getIntrospectionTools(sharedAdapter),
1903
+ ...getMigrationTools(sharedAdapter),
1904
+ ...getCodeModeTools(sharedAdapter),
1905
+ // Native-only tools
1906
+ ...getTransactionTools(adapter),
1907
+ ...getWindowTools(adapter),
1908
+ ...getSpatialiteTools(adapter)
1909
+ ];
1910
+ for (const tool of tools) {
1911
+ if (!tool.icons) {
1912
+ const icons = getToolGroupIcon(tool.group);
1913
+ if (icons) {
1914
+ tool.icons = icons;
1915
+ }
1916
+ }
1917
+ }
1918
+ return tools;
1919
+ }
1920
+ function findProjectRoot(startDir) {
1921
+ let dir = startDir;
1922
+ for (let i = 0; i < 10; i++) {
1923
+ if (fs.existsSync(path.join(dir, "package.json"))) return dir;
1924
+ const parent = path.dirname(dir);
1925
+ if (parent === dir) break;
1926
+ dir = parent;
1927
+ }
1928
+ return startDir;
1929
+ }
1930
+ var __moduleFilename = fileURLToPath(import.meta.url);
1931
+ var PROJECT_ROOT = findProjectRoot(path.dirname(__moduleFilename));
1932
+ var EXTENSIONS_DIR = path.join(PROJECT_ROOT, "extensions");
1933
+ function tryLoadExtension(db, name, envVar, candidates, log2) {
1934
+ for (const extPath of candidates) {
1935
+ try {
1936
+ db.loadExtension(extPath);
1937
+ log2.info(`Loaded ${name} extension from ${extPath}`, {
1938
+ code: "SQLITE_EXTENSION"
1939
+ });
1940
+ return;
1941
+ } catch {
1942
+ }
1943
+ }
1944
+ log2.warning(`${name} extension not available. Set ${envVar} env var.`, {
1945
+ code: "SQLITE_EXTENSION"
1946
+ });
1947
+ }
1948
+ function loadSpatialite(db, log2) {
1949
+ const spatialitePaths = [
1950
+ process.env["SPATIALITE_PATH"],
1951
+ // Absolute paths to local extensions
1952
+ path.join(
1953
+ EXTENSIONS_DIR,
1954
+ "mod_spatialite-5.1.0-win-amd64",
1955
+ "mod_spatialite"
1956
+ ),
1957
+ path.join(
1958
+ EXTENSIONS_DIR,
1959
+ "mod_spatialite-5.1.0-win-amd64",
1960
+ "mod_spatialite.dll"
1961
+ ),
1962
+ // System paths
1963
+ "mod_spatialite",
1964
+ "mod_spatialite.dll",
1965
+ "/usr/lib/x86_64-linux-gnu/mod_spatialite.so",
1966
+ "/usr/local/lib/mod_spatialite.so",
1967
+ "/usr/local/lib/mod_spatialite.dylib"
1968
+ ].filter((p) => Boolean(p));
1969
+ const envSpatialitePath = process.env["SPATIALITE_PATH"];
1970
+ if (envSpatialitePath && process.platform === "win32") {
1971
+ const spatialiteExtDir = path.dirname(envSpatialitePath);
1972
+ const currentPath = process.env["PATH"] ?? "";
1973
+ if (!currentPath.includes(spatialiteExtDir)) {
1974
+ process.env["PATH"] = spatialiteExtDir + ";" + currentPath;
1975
+ }
1976
+ }
1977
+ tryLoadExtension(db, "SpatiaLite", "SPATIALITE_PATH", spatialitePaths, log2);
1978
+ }
1979
+ function loadCsvExtension(db, log2) {
1980
+ const csvPaths = [
1981
+ process.env["CSV_EXTENSION_PATH"],
1982
+ // sqlite-xsv extension with absolute paths
1983
+ path.join(EXTENSIONS_DIR, "xsv0.dll"),
1984
+ path.join(EXTENSIONS_DIR, "xsv0"),
1985
+ // System paths
1986
+ "xsv0",
1987
+ "xsv0.dll",
1988
+ "csv",
1989
+ "csv.dll",
1990
+ "csv.so",
1991
+ "/usr/local/lib/csv.so",
1992
+ "/usr/local/lib/csv.dylib"
1993
+ ].filter((p) => Boolean(p));
1994
+ tryLoadExtension(db, "CSV", "CSV_EXTENSION_PATH", csvPaths, log2);
1995
+ }
1996
+
1997
+ // src/adapters/sqlite-native/transaction-methods.ts
1998
+ function beginTransaction(db) {
1999
+ db.exec("BEGIN TRANSACTION");
2000
+ }
2001
+ function commitTransaction(db) {
2002
+ db.exec("COMMIT");
2003
+ }
2004
+ function rollbackTransaction(db) {
2005
+ db.exec("ROLLBACK");
2006
+ }
2007
+ function savepoint(db, name) {
2008
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
2009
+ throw new ValidationError(
2010
+ "Invalid savepoint name. Must start with a letter/underscore and contain only alphanumeric chars.",
2011
+ "INVALID_SAVEPOINT_NAME"
2012
+ );
2013
+ }
2014
+ db.exec(`SAVEPOINT "${name}"`);
2015
+ }
2016
+ function releaseSavepoint(db, name) {
2017
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
2018
+ throw new Error(
2019
+ "Invalid savepoint name. Must start with a letter/underscore and contain only alphanumeric chars."
2020
+ );
2021
+ }
2022
+ db.exec(`RELEASE SAVEPOINT "${name}"`);
2023
+ }
2024
+ function rollbackToSavepoint(db, name) {
2025
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
2026
+ throw new Error(
2027
+ "Invalid savepoint name. Must start with a letter/underscore and contain only alphanumeric chars."
2028
+ );
2029
+ }
2030
+ db.exec(`ROLLBACK TO SAVEPOINT "${name}"`);
2031
+ }
2032
+
2033
+ // src/adapters/sqlite-native/native-query-executor.ts
2034
+ function nativeExecuteRead(db, sql, params, log2) {
2035
+ const start = Date.now();
2036
+ try {
2037
+ const stmt = db.prepare(sql);
2038
+ const normalizedParams = normalizeSqliteParams(params);
2039
+ const rows = normalizedParams ? stmt.all(...normalizedParams) : stmt.all();
2040
+ return Promise.resolve({
2041
+ rows,
2042
+ columns: stmt.columns().map((c) => ({ name: c.name, type: c.type ?? "unknown" })),
2043
+ executionTimeMs: Date.now() - start
2044
+ });
2045
+ } catch (error) {
2046
+ const message = error instanceof Error ? error.message : String(error);
2047
+ log2.error(`Query failed: ${message}`, {
2048
+ code: ERROR_CODES.DB.QUERY_FAILED.full
2049
+ });
2050
+ throw new QueryError(
2051
+ `Query execution failed: ${message}`,
2052
+ "DB_QUERY_FAILED",
2053
+ {
2054
+ sql,
2055
+ cause: error instanceof Error ? error : void 0
2056
+ }
2057
+ );
2058
+ }
2059
+ }
2060
+ function nativeExecuteWrite(db, sql, params, log2) {
2061
+ const start = Date.now();
2062
+ try {
2063
+ const stmt = db.prepare(sql);
2064
+ const normalizedParams = normalizeSqliteParams(params);
2065
+ const info = normalizedParams ? stmt.run(...normalizedParams) : stmt.run();
2066
+ return Promise.resolve({
2067
+ rows: [],
2068
+ rowsAffected: info.changes,
2069
+ executionTimeMs: Date.now() - start
2070
+ });
2071
+ } catch (error) {
2072
+ const message = error instanceof Error ? error.message : String(error);
2073
+ log2.error(`Write query failed: ${message}`, {
2074
+ code: ERROR_CODES.DB.QUERY_FAILED.full
2075
+ });
2076
+ throw new QueryError(`Write query failed: ${message}`, "DB_WRITE_FAILED", {
2077
+ sql,
2078
+ cause: error instanceof Error ? error : void 0
2079
+ });
2080
+ }
2081
+ }
2082
+
2083
+ // src/adapters/sqlite-native/native-sqlite-adapter.ts
2084
+ var log = logger.child("NATIVE_SQLITE");
2085
+ var NativeSqliteAdapter = class extends DatabaseAdapter {
2086
+ type = "sqlite";
2087
+ name = "Native SQLite Adapter (better-sqlite3)";
2088
+ version = "1.0.0";
2089
+ /**
2090
+ * Check if this adapter uses native (better-sqlite3) backend.
2091
+ * Returns true for native adapter.
2092
+ */
2093
+ isNativeBackend() {
2094
+ return true;
2095
+ }
2096
+ /**
2097
+ * Get the configured database file path.
2098
+ * Returns the user-configured path, not internal WASM virtual filesystem paths.
2099
+ */
2100
+ getConfiguredPath() {
2101
+ const config = this.config;
2102
+ return config?.filePath ?? config?.connectionString ?? ":memory:";
2103
+ }
2104
+ db = null;
2105
+ schemaManager = null;
2106
+ cachedToolDefinitions = null;
2107
+ /**
2108
+ * Connect to a SQLite database using better-sqlite3
2109
+ */
2110
+ connect(config) {
2111
+ if (config.type !== "sqlite") {
2112
+ throw new ConfigurationError(
2113
+ `Invalid database type: expected 'sqlite', got '${config.type}'`,
2114
+ "DB_TYPE_MISMATCH"
2115
+ );
2116
+ }
2117
+ const sqliteConfig = config;
2118
+ this.config = sqliteConfig;
2119
+ try {
2120
+ const filePath = sqliteConfig.filePath ?? sqliteConfig.connectionString ?? ":memory:";
2121
+ this.db = new Database(filePath, {
2122
+ readonly: false,
2123
+ fileMustExist: false
2124
+ });
2125
+ log.info(`Connected to SQLite database (native): ${filePath}`, {
2126
+ code: "SQLITE_CONNECT"
2127
+ });
2128
+ this.applyOptions(sqliteConfig.options);
2129
+ const db = this.db;
2130
+ autoEnableWal(
2131
+ { runPragma: (pragma) => db.pragma(pragma) },
2132
+ filePath,
2133
+ sqliteConfig.options,
2134
+ log
2135
+ );
2136
+ detectAndSetJsonbSupport(() => {
2137
+ const versionResult = db.prepare("SELECT sqlite_version()").get();
2138
+ return versionResult?.["sqlite_version()"] ?? "0.0.0";
2139
+ }, log);
2140
+ this.schemaManager = new SchemaManager(this);
2141
+ this.connected = true;
2142
+ } catch (error) {
2143
+ const message = error instanceof Error ? error.message : String(error);
2144
+ log.error(`Failed to connect to native SQLite: ${message}`, {
2145
+ code: ERROR_CODES.DB.CONNECT_FAILED.full
2146
+ });
2147
+ throw new ConnectionError(
2148
+ `Native SQLite connection failed: ${message}`,
2149
+ "DB_CONNECT_FAILED",
2150
+ {
2151
+ cause: error instanceof Error ? error : void 0
2152
+ }
2153
+ );
2154
+ }
2155
+ return Promise.resolve();
2156
+ }
2157
+ /**
2158
+ * Apply SQLite PRAGMA options and load extensions
2159
+ */
2160
+ applyOptions(options) {
2161
+ if (!this.db || !options) return;
2162
+ const db = this.db;
2163
+ applyCommonPragmas({ runPragma: (pragma) => db.pragma(pragma) }, options);
2164
+ if (options.spatialite) {
2165
+ loadSpatialite(db, log);
2166
+ }
2167
+ if (options.csv) {
2168
+ loadCsvExtension(db, log);
2169
+ }
2170
+ }
2171
+ /**
2172
+ * Disconnect from the database
2173
+ */
2174
+ disconnect() {
2175
+ if (this.db) {
2176
+ this.db.close();
2177
+ this.db = null;
2178
+ this.connected = false;
2179
+ log.info("Disconnected from native SQLite database", {
2180
+ code: "SQLITE_DISCONNECT"
2181
+ });
2182
+ }
2183
+ return Promise.resolve();
2184
+ }
2185
+ /**
2186
+ * Get health status
2187
+ */
2188
+ getHealth() {
2189
+ if (!this.db || !this.connected) {
2190
+ return Promise.resolve({
2191
+ connected: false,
2192
+ latencyMs: 0
2193
+ });
2194
+ }
2195
+ const start = Date.now();
2196
+ try {
2197
+ this.db.prepare("SELECT 1").get();
2198
+ return Promise.resolve({
2199
+ connected: true,
2200
+ latencyMs: Date.now() - start,
2201
+ details: {
2202
+ backend: "better-sqlite3",
2203
+ fts5: this.hasFts5(),
2204
+ spatialite: isSpatialiteLoaded(this)
2205
+ }
2206
+ });
2207
+ } catch {
2208
+ return Promise.resolve({
2209
+ connected: false,
2210
+ latencyMs: Date.now() - start
2211
+ });
2212
+ }
2213
+ }
2214
+ /**
2215
+ * Check if FTS5 is available via compile options.
2216
+ * Uses PRAGMA compile_options instead of creating a virtual table probe,
2217
+ * which can fail when SpatiaLite extensions are loaded.
2218
+ */
2219
+ hasFts5() {
2220
+ if (!this.db) return false;
2221
+ try {
2222
+ const rows = this.db.prepare("PRAGMA compile_options").all();
2223
+ return rows.some((r) => r.compile_options === "ENABLE_FTS5");
2224
+ } catch {
2225
+ return false;
2226
+ }
2227
+ }
2228
+ /**
2229
+ * Execute a read query
2230
+ */
2231
+ executeReadQuery(sql, params) {
2232
+ this.ensureConnected();
2233
+ return nativeExecuteRead(this.ensureDb(), sql, params, log);
2234
+ }
2235
+ /**
2236
+ * Execute a write query
2237
+ */
2238
+ executeWriteQuery(sql, params) {
2239
+ this.ensureConnected();
2240
+ const result = nativeExecuteWrite(this.ensureDb(), sql, params, log);
2241
+ if (isDDL(sql)) {
2242
+ this.clearSchemaCache();
2243
+ }
2244
+ return result;
2245
+ }
2246
+ /**
2247
+ * Execute any query
2248
+ */
2249
+ executeQuery(sql, params) {
2250
+ const trimmed = sql.trim().toUpperCase();
2251
+ if (trimmed.startsWith("SELECT") || trimmed.startsWith("PRAGMA") || trimmed.startsWith("EXPLAIN")) {
2252
+ return this.executeReadQuery(sql, params);
2253
+ }
2254
+ return this.executeWriteQuery(sql, params);
2255
+ }
2256
+ /**
2257
+ * Get schema information (cached via SchemaManager)
2258
+ */
2259
+ async getSchema() {
2260
+ this.ensureConnected();
2261
+ if (this.schemaManager) {
2262
+ return this.schemaManager.getSchema();
2263
+ }
2264
+ const tables = await this.listTables();
2265
+ return { tables, indexes: [] };
2266
+ }
2267
+ /**
2268
+ * List all tables (cached via SchemaManager)
2269
+ */
2270
+ async listTables() {
2271
+ this.ensureConnected();
2272
+ if (this.schemaManager) {
2273
+ return this.schemaManager.listTables();
2274
+ }
2275
+ const result = await this.executeReadQuery(
2276
+ `SELECT name, type FROM sqlite_master
2277
+ WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
2278
+ ORDER BY name`
2279
+ );
2280
+ const tables = [];
2281
+ for (const row of result.rows ?? []) {
2282
+ const name = row["name"];
2283
+ const type = row["type"];
2284
+ const tableInfo = await this.describeTable(name);
2285
+ tables.push({ ...tableInfo, type });
2286
+ }
2287
+ return tables;
2288
+ }
2289
+ /**
2290
+ * Describe a table (cached via SchemaManager)
2291
+ */
2292
+ async describeTable(tableName) {
2293
+ this.ensureConnected();
2294
+ if (this.schemaManager) {
2295
+ return this.schemaManager.describeTable(tableName);
2296
+ }
2297
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
2298
+ throw new DbMcpError(
2299
+ "Invalid table name",
2300
+ "SQLITE_INVALID_TABLE",
2301
+ "validation" /* VALIDATION */
2302
+ );
2303
+ }
2304
+ const result = await this.executeReadQuery(
2305
+ `PRAGMA table_info("${tableName}")`
2306
+ );
2307
+ if (!result.rows || result.rows.length === 0) {
2308
+ throw new DbMcpError(
2309
+ `Table '${tableName}' does not exist`,
2310
+ "SQLITE_TABLE_NOT_FOUND",
2311
+ "resource" /* RESOURCE */
2312
+ );
2313
+ }
2314
+ return {
2315
+ name: tableName,
2316
+ type: "table",
2317
+ columns: (result.rows ?? []).map((c) => ({
2318
+ name: c["name"],
2319
+ type: c["type"],
2320
+ nullable: c["notnull"] === 0,
2321
+ primaryKey: c["pk"] > 0
2322
+ }))
2323
+ };
2324
+ }
2325
+ /**
2326
+ * List schemas (SQLite only has one)
2327
+ */
2328
+ listSchemas() {
2329
+ return Promise.resolve(["main"]);
2330
+ }
2331
+ /**
2332
+ * Get all indexes in the database (cached via SchemaManager)
2333
+ * Required by sqlite_indexes resource
2334
+ */
2335
+ async getAllIndexes() {
2336
+ this.ensureConnected();
2337
+ if (this.schemaManager) {
2338
+ return this.schemaManager.getAllIndexes();
2339
+ }
2340
+ const db = this.ensureDb();
2341
+ const indexes = db.prepare(
2342
+ `SELECT name, tbl_name, sql
2343
+ FROM sqlite_master
2344
+ WHERE type = 'index' AND sql IS NOT NULL
2345
+ ORDER BY tbl_name, name`
2346
+ ).all();
2347
+ return indexes.map((idx) => {
2348
+ const indexInfo = db.prepare(`PRAGMA index_info("${idx.name}")`).all();
2349
+ return {
2350
+ name: idx.name,
2351
+ tableName: idx.tbl_name,
2352
+ columns: indexInfo.map((col) => col.name),
2353
+ unique: idx.sql?.toUpperCase().includes("UNIQUE") ?? false
2354
+ };
2355
+ });
2356
+ }
2357
+ /**
2358
+ * Clear the schema metadata cache.
2359
+ * Called automatically after DDL operations.
2360
+ */
2361
+ clearSchemaCache() {
2362
+ if (this.schemaManager) {
2363
+ this.schemaManager.clearCache();
2364
+ }
2365
+ }
2366
+ /**
2367
+ * Get capabilities
2368
+ */
2369
+ getCapabilities() {
2370
+ return {
2371
+ json: true,
2372
+ fullTextSearch: true,
2373
+ vector: true,
2374
+ geospatial: true,
2375
+ transactions: true,
2376
+ preparedStatements: true,
2377
+ connectionPooling: false,
2378
+ // better-sqlite3 is single-connection
2379
+ windowFunctions: true,
2380
+ spatialite: true
2381
+ };
2382
+ }
2383
+ /**
2384
+ * Get supported tool groups
2385
+ */
2386
+ getSupportedToolGroups() {
2387
+ return [
2388
+ "core",
2389
+ "json",
2390
+ "text",
2391
+ "stats",
2392
+ "vector",
2393
+ "admin",
2394
+ "geo",
2395
+ "introspection",
2396
+ "migration"
2397
+ ];
2398
+ }
2399
+ /**
2400
+ * Get all tool definitions (cached — immutable per adapter instance)
2401
+ */
2402
+ getToolDefinitions() {
2403
+ if (this.cachedToolDefinitions) return this.cachedToolDefinitions;
2404
+ this.cachedToolDefinitions = getNativeToolDefinitions(this);
2405
+ return this.cachedToolDefinitions;
2406
+ }
2407
+ /**
2408
+ * Get resource definitions
2409
+ */
2410
+ getResourceDefinitions() {
2411
+ return getResourceDefinitions(this);
2412
+ }
2413
+ /**
2414
+ * Get prompt definitions
2415
+ */
2416
+ getPromptDefinitions() {
2417
+ return getPromptDefinitions(this);
2418
+ }
2419
+ /**
2420
+ * Ensure database is connected
2421
+ */
2422
+ ensureConnected() {
2423
+ super.ensureConnected();
2424
+ if (!this.db) {
2425
+ throw new ConnectionError(
2426
+ "Not connected to database",
2427
+ "DB_NOT_CONNECTED"
2428
+ );
2429
+ }
2430
+ }
2431
+ /**
2432
+ * Ensure database is connected and return database instance
2433
+ */
2434
+ ensureDb() {
2435
+ this.ensureConnected();
2436
+ const db = this.db;
2437
+ if (!db) {
2438
+ throw new ConnectionError(
2439
+ "Not connected to database",
2440
+ "DB_NOT_CONNECTED"
2441
+ );
2442
+ }
2443
+ return db;
2444
+ }
2445
+ /**
2446
+ * Get the raw database instance (for tools)
2447
+ */
2448
+ getDatabase() {
2449
+ return this.ensureDb();
2450
+ }
2451
+ /**
2452
+ * Execute raw SQL and return results (for tools)
2453
+ */
2454
+ rawQuery(sql, params) {
2455
+ return this.executeQuery(sql, params);
2456
+ }
2457
+ /**
2458
+ * Begin a transaction
2459
+ */
2460
+ beginTransaction() {
2461
+ beginTransaction(this.ensureDb());
2462
+ }
2463
+ /**
2464
+ * Commit a transaction
2465
+ */
2466
+ commitTransaction() {
2467
+ commitTransaction(this.ensureDb());
2468
+ }
2469
+ /**
2470
+ * Rollback a transaction
2471
+ */
2472
+ rollbackTransaction() {
2473
+ rollbackTransaction(this.ensureDb());
2474
+ }
2475
+ /**
2476
+ * Create a savepoint
2477
+ */
2478
+ savepoint(name) {
2479
+ savepoint(this.ensureDb(), name);
2480
+ }
2481
+ /**
2482
+ * Release a savepoint
2483
+ */
2484
+ releaseSavepoint(name) {
2485
+ releaseSavepoint(this.ensureDb(), name);
2486
+ }
2487
+ /**
2488
+ * Rollback to a savepoint
2489
+ */
2490
+ rollbackToSavepoint(name) {
2491
+ rollbackToSavepoint(this.ensureDb(), name);
2492
+ }
2493
+ };
2494
+ function createNativeSqliteAdapter() {
2495
+ return new NativeSqliteAdapter();
2496
+ }
2497
+
2498
+ export { NativeSqliteAdapter, createNativeSqliteAdapter, getSpatialiteTools, getTransactionTools, getWindowTools };