db-mcp 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +860 -0
  3. package/dist/adapters/DatabaseAdapter.d.ts +141 -0
  4. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -0
  5. package/dist/adapters/DatabaseAdapter.js +131 -0
  6. package/dist/adapters/DatabaseAdapter.js.map +1 -0
  7. package/dist/adapters/sqlite/SchemaManager.d.ts +58 -0
  8. package/dist/adapters/sqlite/SchemaManager.d.ts.map +1 -0
  9. package/dist/adapters/sqlite/SchemaManager.js +187 -0
  10. package/dist/adapters/sqlite/SchemaManager.js.map +1 -0
  11. package/dist/adapters/sqlite/SqliteAdapter.d.ts +161 -0
  12. package/dist/adapters/sqlite/SqliteAdapter.d.ts.map +1 -0
  13. package/dist/adapters/sqlite/SqliteAdapter.js +741 -0
  14. package/dist/adapters/sqlite/SqliteAdapter.js.map +1 -0
  15. package/dist/adapters/sqlite/index.d.ts +9 -0
  16. package/dist/adapters/sqlite/index.d.ts.map +1 -0
  17. package/dist/adapters/sqlite/index.js +8 -0
  18. package/dist/adapters/sqlite/index.js.map +1 -0
  19. package/dist/adapters/sqlite/json-utils.d.ts +100 -0
  20. package/dist/adapters/sqlite/json-utils.d.ts.map +1 -0
  21. package/dist/adapters/sqlite/json-utils.js +274 -0
  22. package/dist/adapters/sqlite/json-utils.js.map +1 -0
  23. package/dist/adapters/sqlite/output-schemas.d.ts +1187 -0
  24. package/dist/adapters/sqlite/output-schemas.d.ts.map +1 -0
  25. package/dist/adapters/sqlite/output-schemas.js +1337 -0
  26. package/dist/adapters/sqlite/output-schemas.js.map +1 -0
  27. package/dist/adapters/sqlite/prompts.d.ts +13 -0
  28. package/dist/adapters/sqlite/prompts.d.ts.map +1 -0
  29. package/dist/adapters/sqlite/prompts.js +605 -0
  30. package/dist/adapters/sqlite/prompts.js.map +1 -0
  31. package/dist/adapters/sqlite/resources.d.ts +13 -0
  32. package/dist/adapters/sqlite/resources.d.ts.map +1 -0
  33. package/dist/adapters/sqlite/resources.js +251 -0
  34. package/dist/adapters/sqlite/resources.js.map +1 -0
  35. package/dist/adapters/sqlite/tools/admin.d.ts +14 -0
  36. package/dist/adapters/sqlite/tools/admin.d.ts.map +1 -0
  37. package/dist/adapters/sqlite/tools/admin.js +788 -0
  38. package/dist/adapters/sqlite/tools/admin.js.map +1 -0
  39. package/dist/adapters/sqlite/tools/core.d.ts +25 -0
  40. package/dist/adapters/sqlite/tools/core.d.ts.map +1 -0
  41. package/dist/adapters/sqlite/tools/core.js +359 -0
  42. package/dist/adapters/sqlite/tools/core.js.map +1 -0
  43. package/dist/adapters/sqlite/tools/fts.d.ts +13 -0
  44. package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -0
  45. package/dist/adapters/sqlite/tools/fts.js +347 -0
  46. package/dist/adapters/sqlite/tools/fts.js.map +1 -0
  47. package/dist/adapters/sqlite/tools/geo.d.ts +14 -0
  48. package/dist/adapters/sqlite/tools/geo.d.ts.map +1 -0
  49. package/dist/adapters/sqlite/tools/geo.js +252 -0
  50. package/dist/adapters/sqlite/tools/geo.js.map +1 -0
  51. package/dist/adapters/sqlite/tools/index.d.ts +30 -0
  52. package/dist/adapters/sqlite/tools/index.d.ts.map +1 -0
  53. package/dist/adapters/sqlite/tools/index.js +61 -0
  54. package/dist/adapters/sqlite/tools/index.js.map +1 -0
  55. package/dist/adapters/sqlite/tools/json-helpers.d.ts +14 -0
  56. package/dist/adapters/sqlite/tools/json-helpers.d.ts.map +1 -0
  57. package/dist/adapters/sqlite/tools/json-helpers.js +477 -0
  58. package/dist/adapters/sqlite/tools/json-helpers.js.map +1 -0
  59. package/dist/adapters/sqlite/tools/json-operations.d.ts +14 -0
  60. package/dist/adapters/sqlite/tools/json-operations.d.ts.map +1 -0
  61. package/dist/adapters/sqlite/tools/json-operations.js +839 -0
  62. package/dist/adapters/sqlite/tools/json-operations.js.map +1 -0
  63. package/dist/adapters/sqlite/tools/stats.d.ts +15 -0
  64. package/dist/adapters/sqlite/tools/stats.d.ts.map +1 -0
  65. package/dist/adapters/sqlite/tools/stats.js +1219 -0
  66. package/dist/adapters/sqlite/tools/stats.js.map +1 -0
  67. package/dist/adapters/sqlite/tools/text.d.ts +14 -0
  68. package/dist/adapters/sqlite/tools/text.d.ts.map +1 -0
  69. package/dist/adapters/sqlite/tools/text.js +1141 -0
  70. package/dist/adapters/sqlite/tools/text.js.map +1 -0
  71. package/dist/adapters/sqlite/tools/vector.d.ts +14 -0
  72. package/dist/adapters/sqlite/tools/vector.d.ts.map +1 -0
  73. package/dist/adapters/sqlite/tools/vector.js +613 -0
  74. package/dist/adapters/sqlite/tools/vector.js.map +1 -0
  75. package/dist/adapters/sqlite/tools/virtual.d.ts +13 -0
  76. package/dist/adapters/sqlite/tools/virtual.d.ts.map +1 -0
  77. package/dist/adapters/sqlite/tools/virtual.js +930 -0
  78. package/dist/adapters/sqlite/tools/virtual.js.map +1 -0
  79. package/dist/adapters/sqlite/types.d.ts +207 -0
  80. package/dist/adapters/sqlite/types.d.ts.map +1 -0
  81. package/dist/adapters/sqlite/types.js +186 -0
  82. package/dist/adapters/sqlite/types.js.map +1 -0
  83. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts +163 -0
  84. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts.map +1 -0
  85. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js +748 -0
  86. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js.map +1 -0
  87. package/dist/adapters/sqlite-native/index.d.ts +11 -0
  88. package/dist/adapters/sqlite-native/index.d.ts.map +1 -0
  89. package/dist/adapters/sqlite-native/index.js +11 -0
  90. package/dist/adapters/sqlite-native/index.js.map +1 -0
  91. package/dist/adapters/sqlite-native/tools/spatialite.d.ts +19 -0
  92. package/dist/adapters/sqlite-native/tools/spatialite.d.ts.map +1 -0
  93. package/dist/adapters/sqlite-native/tools/spatialite.js +628 -0
  94. package/dist/adapters/sqlite-native/tools/spatialite.js.map +1 -0
  95. package/dist/adapters/sqlite-native/tools/transactions.d.ts +12 -0
  96. package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +1 -0
  97. package/dist/adapters/sqlite-native/tools/transactions.js +255 -0
  98. package/dist/adapters/sqlite-native/tools/transactions.js.map +1 -0
  99. package/dist/adapters/sqlite-native/tools/window.d.ts +12 -0
  100. package/dist/adapters/sqlite-native/tools/window.d.ts.map +1 -0
  101. package/dist/adapters/sqlite-native/tools/window.js +370 -0
  102. package/dist/adapters/sqlite-native/tools/window.js.map +1 -0
  103. package/dist/auth/AuthorizationServerDiscovery.d.ts +90 -0
  104. package/dist/auth/AuthorizationServerDiscovery.d.ts.map +1 -0
  105. package/dist/auth/AuthorizationServerDiscovery.js +204 -0
  106. package/dist/auth/AuthorizationServerDiscovery.js.map +1 -0
  107. package/dist/auth/OAuthResourceServer.d.ts +65 -0
  108. package/dist/auth/OAuthResourceServer.d.ts.map +1 -0
  109. package/dist/auth/OAuthResourceServer.js +121 -0
  110. package/dist/auth/OAuthResourceServer.js.map +1 -0
  111. package/dist/auth/TokenValidator.d.ts +60 -0
  112. package/dist/auth/TokenValidator.d.ts.map +1 -0
  113. package/dist/auth/TokenValidator.js +235 -0
  114. package/dist/auth/TokenValidator.js.map +1 -0
  115. package/dist/auth/errors.d.ts +74 -0
  116. package/dist/auth/errors.d.ts.map +1 -0
  117. package/dist/auth/errors.js +133 -0
  118. package/dist/auth/errors.js.map +1 -0
  119. package/dist/auth/index.d.ts +13 -0
  120. package/dist/auth/index.d.ts.map +1 -0
  121. package/dist/auth/index.js +15 -0
  122. package/dist/auth/index.js.map +1 -0
  123. package/dist/auth/middleware.d.ts +81 -0
  124. package/dist/auth/middleware.d.ts.map +1 -0
  125. package/dist/auth/middleware.js +291 -0
  126. package/dist/auth/middleware.js.map +1 -0
  127. package/dist/auth/scopes.d.ts +136 -0
  128. package/dist/auth/scopes.d.ts.map +1 -0
  129. package/dist/auth/scopes.js +349 -0
  130. package/dist/auth/scopes.js.map +1 -0
  131. package/dist/auth/types.d.ts +257 -0
  132. package/dist/auth/types.d.ts.map +1 -0
  133. package/dist/auth/types.js +8 -0
  134. package/dist/auth/types.js.map +1 -0
  135. package/dist/cli.d.ts +8 -0
  136. package/dist/cli.d.ts.map +1 -0
  137. package/dist/cli.js +236 -0
  138. package/dist/cli.js.map +1 -0
  139. package/dist/constants/ServerInstructions.d.ts +45 -0
  140. package/dist/constants/ServerInstructions.d.ts.map +1 -0
  141. package/dist/constants/ServerInstructions.js +356 -0
  142. package/dist/constants/ServerInstructions.js.map +1 -0
  143. package/dist/filtering/ToolConstants.d.ts +34 -0
  144. package/dist/filtering/ToolConstants.d.ts.map +1 -0
  145. package/dist/filtering/ToolConstants.js +174 -0
  146. package/dist/filtering/ToolConstants.js.map +1 -0
  147. package/dist/filtering/ToolFilter.d.ts +82 -0
  148. package/dist/filtering/ToolFilter.d.ts.map +1 -0
  149. package/dist/filtering/ToolFilter.js +296 -0
  150. package/dist/filtering/ToolFilter.js.map +1 -0
  151. package/dist/index.d.ts +13 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +17 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/server/McpServer.d.ts +61 -0
  156. package/dist/server/McpServer.d.ts.map +1 -0
  157. package/dist/server/McpServer.js +270 -0
  158. package/dist/server/McpServer.js.map +1 -0
  159. package/dist/transports/http.d.ts +134 -0
  160. package/dist/transports/http.d.ts.map +1 -0
  161. package/dist/transports/http.js +516 -0
  162. package/dist/transports/http.js.map +1 -0
  163. package/dist/transports/index.d.ts +5 -0
  164. package/dist/transports/index.d.ts.map +1 -0
  165. package/dist/transports/index.js +5 -0
  166. package/dist/transports/index.js.map +1 -0
  167. package/dist/types/index.d.ts +380 -0
  168. package/dist/types/index.d.ts.map +1 -0
  169. package/dist/types/index.js +68 -0
  170. package/dist/types/index.js.map +1 -0
  171. package/dist/utils/annotations.d.ts +44 -0
  172. package/dist/utils/annotations.d.ts.map +1 -0
  173. package/dist/utils/annotations.js +77 -0
  174. package/dist/utils/annotations.js.map +1 -0
  175. package/dist/utils/errors.d.ts +155 -0
  176. package/dist/utils/errors.d.ts.map +1 -0
  177. package/dist/utils/errors.js +329 -0
  178. package/dist/utils/errors.js.map +1 -0
  179. package/dist/utils/identifiers.d.ts +121 -0
  180. package/dist/utils/identifiers.d.ts.map +1 -0
  181. package/dist/utils/identifiers.js +319 -0
  182. package/dist/utils/identifiers.js.map +1 -0
  183. package/dist/utils/index.d.ts +7 -0
  184. package/dist/utils/index.d.ts.map +1 -0
  185. package/dist/utils/index.js +7 -0
  186. package/dist/utils/index.js.map +1 -0
  187. package/dist/utils/insightsManager.d.ts +39 -0
  188. package/dist/utils/insightsManager.d.ts.map +1 -0
  189. package/dist/utils/insightsManager.js +63 -0
  190. package/dist/utils/insightsManager.js.map +1 -0
  191. package/dist/utils/logger.d.ts +189 -0
  192. package/dist/utils/logger.d.ts.map +1 -0
  193. package/dist/utils/logger.js +394 -0
  194. package/dist/utils/logger.js.map +1 -0
  195. package/dist/utils/progress-utils.d.ts +54 -0
  196. package/dist/utils/progress-utils.d.ts.map +1 -0
  197. package/dist/utils/progress-utils.js +74 -0
  198. package/dist/utils/progress-utils.js.map +1 -0
  199. package/dist/utils/resourceAnnotations.d.ts +36 -0
  200. package/dist/utils/resourceAnnotations.d.ts.map +1 -0
  201. package/dist/utils/resourceAnnotations.js +57 -0
  202. package/dist/utils/resourceAnnotations.js.map +1 -0
  203. package/dist/utils/where-clause.d.ts +41 -0
  204. package/dist/utils/where-clause.d.ts.map +1 -0
  205. package/dist/utils/where-clause.js +116 -0
  206. package/dist/utils/where-clause.js.map +1 -0
  207. package/package.json +83 -0
  208. package/server.json +53 -0
@@ -0,0 +1,788 @@
1
+ /**
2
+ * SQLite Admin Tools
3
+ *
4
+ * Database administration operations:
5
+ * backup, restore, analyze, optimize, integrity check, PRAGMA operations.
6
+ * 13 tools total.
7
+ */
8
+ import { z } from "zod";
9
+ import { admin, readOnly } from "../../../utils/annotations.js";
10
+ import { sanitizeIdentifier } from "../../../utils/index.js";
11
+ import { insightsManager } from "../../../utils/insightsManager.js";
12
+ import { buildProgressContext, sendProgress, } from "../../../utils/progress-utils.js";
13
+ import { BackupOutputSchema, AnalyzeOutputSchema, OptimizeOutputSchema, IntegrityCheckOutputSchema, RestoreOutputSchema, VerifyBackupOutputSchema, IndexStatsOutputSchema, PragmaCompileOptionsOutputSchema, PragmaDatabaseListOutputSchema, PragmaOptimizeOutputSchema, PragmaSettingsOutputSchema, PragmaTableInfoOutputSchema, } from "../output-schemas.js";
14
+ // Admin schemas
15
+ const BackupSchema = z.object({
16
+ targetPath: z.string().describe("Path for backup file"),
17
+ });
18
+ const AnalyzeSchema = z.object({
19
+ table: z
20
+ .string()
21
+ .optional()
22
+ .describe("Specific table to analyze (default: all)"),
23
+ });
24
+ const IntegrityCheckSchema = z.object({
25
+ maxErrors: z
26
+ .number()
27
+ .optional()
28
+ .default(100)
29
+ .describe("Maximum errors to report"),
30
+ });
31
+ const OptimizeSchema = z.object({
32
+ table: z.string().optional().describe("Specific table to optimize"),
33
+ reindex: z.boolean().optional().default(false),
34
+ analyze: z.boolean().optional().default(true),
35
+ });
36
+ const RestoreSchema = z.object({
37
+ sourcePath: z.string().describe("Path to backup file to restore from"),
38
+ });
39
+ const VerifyBackupSchema = z.object({
40
+ backupPath: z.string().describe("Path to backup file to verify"),
41
+ });
42
+ const IndexStatsSchema = z.object({
43
+ table: z
44
+ .string()
45
+ .optional()
46
+ .describe("Filter indexes by table name (default: all tables)"),
47
+ excludeSystemIndexes: z
48
+ .boolean()
49
+ .optional()
50
+ .default(true)
51
+ .describe("Exclude SpatiaLite system indexes (default: true). Set to false to include all indexes."),
52
+ });
53
+ const PragmaOptimizeSchema = z.object({
54
+ mask: z
55
+ .number()
56
+ .optional()
57
+ .describe("Optional optimization mask (default: 0xfffe)"),
58
+ });
59
+ const PragmaSettingsSchema = z.object({
60
+ pragma: z
61
+ .string()
62
+ .describe("PRAGMA name (e.g., 'cache_size', 'journal_mode')"),
63
+ value: z
64
+ .union([z.string(), z.number()])
65
+ .optional()
66
+ .describe("Value to set (omit to only read)"),
67
+ });
68
+ const PragmaTableInfoSchema = z.object({
69
+ table: z.string().describe("Table name to get column information for"),
70
+ });
71
+ /**
72
+ * Get all admin tools
73
+ */
74
+ export function getAdminTools(adapter) {
75
+ return [
76
+ createBackupTool(adapter),
77
+ createAnalyzeTool(adapter),
78
+ createIntegrityCheckTool(adapter),
79
+ createOptimizeTool(adapter),
80
+ createRestoreTool(adapter),
81
+ createVerifyBackupTool(adapter),
82
+ createIndexStatsTool(adapter),
83
+ createPragmaCompileOptionsTool(adapter),
84
+ createPragmaDatabaseListTool(adapter),
85
+ createPragmaOptimizeTool(adapter),
86
+ createPragmaSettingsTool(adapter),
87
+ createPragmaTableInfoTool(adapter),
88
+ createAppendInsightTool(),
89
+ ];
90
+ }
91
+ /**
92
+ * Backup database
93
+ */
94
+ function createBackupTool(adapter) {
95
+ return {
96
+ name: "sqlite_backup",
97
+ description: "Create a backup of the database to a file.",
98
+ group: "admin",
99
+ inputSchema: BackupSchema,
100
+ outputSchema: BackupOutputSchema,
101
+ requiredScopes: ["admin"],
102
+ annotations: admin("Database Backup"),
103
+ handler: async (params, _context) => {
104
+ const input = BackupSchema.parse(params);
105
+ // Use VACUUM INTO to create backup
106
+ const escapedPath = input.targetPath.replace(/'/g, "''");
107
+ const sql = `VACUUM INTO '${escapedPath}'`;
108
+ const start = Date.now();
109
+ try {
110
+ await adapter.executeQuery(sql);
111
+ const duration = Date.now() - start;
112
+ // In WASM mode, backup goes to virtual filesystem (ephemeral)
113
+ const isWasm = !adapter.isNativeBackend();
114
+ return {
115
+ success: true,
116
+ message: isWasm
117
+ ? `Database backed up to WASM virtual filesystem (ephemeral)`
118
+ : `Database backed up to '${input.targetPath}'`,
119
+ path: input.targetPath,
120
+ durationMs: duration,
121
+ wasmLimitation: isWasm ? true : undefined,
122
+ note: isWasm
123
+ ? "In WASM mode, backups are stored in an ephemeral virtual filesystem and will NOT persist after the session ends. Use native SQLite for persistent backups."
124
+ : undefined,
125
+ };
126
+ }
127
+ catch (error) {
128
+ // Detect WASM file system limitation (only in WASM mode)
129
+ const errMsg = error instanceof Error ? error.message : String(error);
130
+ if (!adapter.isNativeBackend() &&
131
+ (errMsg.includes("unable to open database") ||
132
+ errMsg.includes("not supported"))) {
133
+ return {
134
+ success: false,
135
+ message: "Backup not available: file system access is not supported in WASM mode.",
136
+ wasmLimitation: true,
137
+ path: input.targetPath,
138
+ };
139
+ }
140
+ throw error;
141
+ }
142
+ },
143
+ };
144
+ }
145
+ /**
146
+ * Analyze tables for query optimization
147
+ */
148
+ function createAnalyzeTool(adapter) {
149
+ return {
150
+ name: "sqlite_analyze",
151
+ description: "Analyze table statistics to improve query performance.",
152
+ group: "admin",
153
+ inputSchema: AnalyzeSchema,
154
+ outputSchema: AnalyzeOutputSchema,
155
+ requiredScopes: ["admin"],
156
+ annotations: admin("Analyze Tables"),
157
+ handler: async (params, _context) => {
158
+ const input = AnalyzeSchema.parse(params);
159
+ let sql;
160
+ if (input.table) {
161
+ const table = sanitizeIdentifier(input.table);
162
+ sql = `ANALYZE ${table}`;
163
+ }
164
+ else {
165
+ sql = "ANALYZE";
166
+ }
167
+ const start = Date.now();
168
+ await adapter.executeQuery(sql);
169
+ const duration = Date.now() - start;
170
+ return {
171
+ success: true,
172
+ message: input.table
173
+ ? `Table '${input.table}' analyzed`
174
+ : "All tables analyzed",
175
+ durationMs: duration,
176
+ };
177
+ },
178
+ };
179
+ }
180
+ /**
181
+ * Check database integrity
182
+ */
183
+ function createIntegrityCheckTool(adapter) {
184
+ return {
185
+ name: "sqlite_integrity_check",
186
+ description: "Check database integrity for corruption or errors.",
187
+ group: "admin",
188
+ inputSchema: IntegrityCheckSchema,
189
+ outputSchema: IntegrityCheckOutputSchema,
190
+ requiredScopes: ["admin"],
191
+ annotations: readOnly("Integrity Check"),
192
+ handler: async (params, _context) => {
193
+ const input = IntegrityCheckSchema.parse(params);
194
+ const sql = `PRAGMA integrity_check(${input.maxErrors})`;
195
+ const result = await adapter.executeReadQuery(sql);
196
+ const messages = (result.rows ?? []).map((r) => r["integrity_check"]);
197
+ const isOk = messages.length === 1 && messages[0] === "ok";
198
+ return {
199
+ success: true,
200
+ integrity: isOk ? "ok" : "errors_found",
201
+ errorCount: isOk ? 0 : messages.length,
202
+ messages: isOk ? undefined : messages,
203
+ };
204
+ },
205
+ };
206
+ }
207
+ /**
208
+ * Optimize database
209
+ */
210
+ function createOptimizeTool(adapter) {
211
+ return {
212
+ name: "sqlite_optimize",
213
+ description: "Optimize database by reindexing and/or analyzing.",
214
+ group: "admin",
215
+ inputSchema: OptimizeSchema,
216
+ outputSchema: OptimizeOutputSchema,
217
+ requiredScopes: ["admin"],
218
+ annotations: admin("Optimize Database"),
219
+ handler: async (params, context) => {
220
+ const input = OptimizeSchema.parse(params);
221
+ const progress = buildProgressContext(context);
222
+ // Calculate total steps for progress tracking
223
+ const totalSteps = 1 + (input.reindex ? 1 : 0) + (input.analyze ? 1 : 0) + 1; // start + ops + complete
224
+ let step = 0;
225
+ const operations = [];
226
+ const start = Date.now();
227
+ // Phase 1: Starting
228
+ await sendProgress(progress, ++step, totalSteps, "Starting optimization...");
229
+ // Reindex if requested
230
+ if (input.reindex) {
231
+ await sendProgress(progress, ++step, totalSteps, "Reindexing...");
232
+ if (input.table) {
233
+ const table = sanitizeIdentifier(input.table);
234
+ await adapter.executeQuery(`REINDEX ${table}`);
235
+ operations.push(`reindexed ${input.table}`);
236
+ }
237
+ else {
238
+ await adapter.executeQuery("REINDEX");
239
+ operations.push("reindexed all");
240
+ }
241
+ }
242
+ // Analyze if requested
243
+ if (input.analyze) {
244
+ await sendProgress(progress, ++step, totalSteps, "Analyzing...");
245
+ if (input.table) {
246
+ const table = sanitizeIdentifier(input.table);
247
+ await adapter.executeQuery(`ANALYZE ${table}`);
248
+ operations.push(`analyzed ${input.table}`);
249
+ }
250
+ else {
251
+ await adapter.executeQuery("ANALYZE");
252
+ operations.push("analyzed all");
253
+ }
254
+ }
255
+ const duration = Date.now() - start;
256
+ // Phase N: Complete
257
+ await sendProgress(progress, totalSteps, totalSteps, "Optimization complete");
258
+ return {
259
+ success: true,
260
+ message: `Optimization complete: ${operations.length > 0 ? operations.join(", ") : "no operations performed"}`,
261
+ operations,
262
+ durationMs: duration,
263
+ };
264
+ },
265
+ };
266
+ }
267
+ /**
268
+ * Restore database from backup
269
+ */
270
+ function createRestoreTool(adapter) {
271
+ return {
272
+ name: "sqlite_restore",
273
+ description: "Restore database from a backup file. WARNING: This replaces the current database.",
274
+ group: "admin",
275
+ inputSchema: RestoreSchema,
276
+ outputSchema: RestoreOutputSchema,
277
+ requiredScopes: ["admin"],
278
+ annotations: admin("Restore Database"),
279
+ handler: async (params, context) => {
280
+ const input = RestoreSchema.parse(params);
281
+ const progress = buildProgressContext(context);
282
+ const start = Date.now();
283
+ // Phase 1: Preparing restore
284
+ await sendProgress(progress, 1, 5, "Preparing restore...");
285
+ const escapedPath = input.sourcePath.replace(/'/g, "''");
286
+ // Verify current database is valid before overwriting
287
+ await adapter.executeReadQuery("PRAGMA integrity_check(1)");
288
+ // Phase 2: Attach backup database
289
+ await sendProgress(progress, 2, 5, "Attaching backup database...");
290
+ try {
291
+ await adapter.executeWriteQuery(`ATTACH DATABASE '${escapedPath}' AS backup_source`, undefined, true);
292
+ }
293
+ catch (error) {
294
+ // Detect WASM file system limitation (only in WASM mode)
295
+ const errMsg = error instanceof Error ? error.message : String(error);
296
+ if (!adapter.isNativeBackend() &&
297
+ (errMsg.includes("unable to open database") ||
298
+ errMsg.includes("not supported"))) {
299
+ return {
300
+ success: false,
301
+ message: "Restore not available: file system access is not supported in WASM mode.",
302
+ wasmLimitation: true,
303
+ sourcePath: input.sourcePath,
304
+ };
305
+ }
306
+ throw error;
307
+ }
308
+ try {
309
+ // Phase 3: Drop virtual tables first (prevents shadow table errors)
310
+ await sendProgress(progress, 3, 5, "Cleaning up virtual tables...");
311
+ // Get list of virtual tables in main database
312
+ // Note: In WASM mode, this query may fail if the database contains
313
+ // virtual tables using unavailable modules (FTS5, R-Tree, etc.)
314
+ try {
315
+ const virtualTablesResult = await adapter.executeReadQuery(`SELECT name FROM sqlite_master
316
+ WHERE type='table'
317
+ AND sql LIKE 'CREATE VIRTUAL TABLE%'
318
+ AND name NOT LIKE 'sqlite_%'`);
319
+ // Drop virtual tables first - this also drops their shadow tables
320
+ for (const row of virtualTablesResult.rows ?? []) {
321
+ const tableName = row["name"];
322
+ const quotedName = `"${tableName.replace(/"/g, '""')}"`;
323
+ await adapter
324
+ .executeWriteQuery(`DROP TABLE IF EXISTS main.${quotedName}`, undefined, true)
325
+ .catch(() => {
326
+ // Ignore errors - table may already be gone or module unavailable
327
+ });
328
+ }
329
+ }
330
+ catch {
331
+ // WASM mode may fail to query/drop virtual tables due to missing modules
332
+ // Continue with restore - virtual tables will remain as-is
333
+ }
334
+ // Phase 4: Copy tables from backup
335
+ await sendProgress(progress, 4, 5, "Restoring tables from backup...");
336
+ await adapter.executeWriteQuery("PRAGMA foreign_keys = OFF", undefined, true);
337
+ // Get list of regular tables from backup (excluding shadow tables and virtual tables)
338
+ // FTS5 shadow tables: _data, _idx, _content, _docsize, _config
339
+ // R-Tree shadow tables: _node, _rowid, _parent
340
+ const tablesResult = await adapter.executeReadQuery(`SELECT name, sql FROM backup_source.sqlite_master
341
+ WHERE type='table'
342
+ AND name NOT LIKE 'sqlite_%'
343
+ AND sql NOT LIKE 'CREATE VIRTUAL TABLE%'
344
+ AND name NOT LIKE '%_data'
345
+ AND name NOT LIKE '%_idx'
346
+ AND name NOT LIKE '%_content'
347
+ AND name NOT LIKE '%_docsize'
348
+ AND name NOT LIKE '%_config'
349
+ AND name NOT LIKE '%_node'
350
+ AND name NOT LIKE '%_rowid'
351
+ AND name NOT LIKE '%_parent'
352
+ ORDER BY name`);
353
+ // Get list of virtual tables that will be skipped
354
+ const backupVirtualTables = await adapter.executeReadQuery(`SELECT name, sql FROM backup_source.sqlite_master
355
+ WHERE type='table'
356
+ AND sql LIKE 'CREATE VIRTUAL TABLE%'
357
+ AND name NOT LIKE 'sqlite_%'`);
358
+ // Track skipped virtual tables upfront
359
+ const skippedTables = [];
360
+ // In Native mode, attempt to recreate virtual tables
361
+ // In WASM mode, skip them since modules like FTS5/R-Tree aren't available
362
+ if (adapter.isNativeBackend()) {
363
+ // Native mode: try to recreate virtual tables
364
+ for (const row of backupVirtualTables.rows ?? []) {
365
+ const tableName = row["name"];
366
+ const createSql = row["sql"];
367
+ const quotedName = `"${tableName.replace(/"/g, '""')}"`;
368
+ try {
369
+ // Drop existing virtual table first
370
+ await adapter
371
+ .executeWriteQuery(`DROP TABLE IF EXISTS main.${quotedName}`, undefined, true)
372
+ .catch(() => {
373
+ // Ignore drop errors - table may already exist or not
374
+ });
375
+ // Recreate the virtual table
376
+ await adapter.executeWriteQuery(createSql, undefined, true);
377
+ }
378
+ catch (error) {
379
+ // If recreation fails, add to skipped list
380
+ const moduleMatch = /USING\s+(\w+)/i.exec(createSql);
381
+ const moduleName = moduleMatch?.[1] ?? "unknown";
382
+ const errMsg = error instanceof Error ? error.message : String(error);
383
+ skippedTables.push(`${tableName} (${moduleName}: ${errMsg.substring(0, 50)})`);
384
+ }
385
+ }
386
+ }
387
+ else {
388
+ // WASM mode: skip all virtual tables
389
+ for (const row of backupVirtualTables.rows ?? []) {
390
+ const tableName = row["name"];
391
+ const createSql = row["sql"];
392
+ // Extract module name for better error message
393
+ const moduleMatch = /USING\s+(\w+)/i.exec(createSql);
394
+ const moduleName = moduleMatch?.[1] ?? "unknown";
395
+ skippedTables.push(`${tableName} (${moduleName} module unavailable in WASM)`);
396
+ }
397
+ }
398
+ // Drop existing tables and copy from backup
399
+ for (const row of tablesResult.rows ?? []) {
400
+ const tableName = row["name"];
401
+ const createSql = row["sql"];
402
+ const quotedName = `"${tableName.replace(/"/g, '""')}"`;
403
+ // Skip if no CREATE statement (shouldn't happen for regular tables)
404
+ if (!createSql)
405
+ continue;
406
+ // Drop existing table
407
+ await adapter.executeWriteQuery(`DROP TABLE IF EXISTS main.${quotedName}`, undefined, true);
408
+ // Create the table in main
409
+ await adapter.executeWriteQuery(createSql, undefined, true);
410
+ // Copy data
411
+ await adapter.executeWriteQuery(`INSERT INTO main.${quotedName} SELECT * FROM backup_source.${quotedName}`, undefined, true);
412
+ }
413
+ // Re-enable foreign key constraints
414
+ await adapter.executeWriteQuery("PRAGMA foreign_keys = ON", undefined, true);
415
+ const duration = Date.now() - start;
416
+ // Phase 5: Complete
417
+ await sendProgress(progress, 5, 5, "Restore complete");
418
+ return {
419
+ success: true,
420
+ message: skippedTables.length > 0
421
+ ? `Database restored from '${input.sourcePath}' with ${skippedTables.length} virtual table(s) skipped`
422
+ : `Database restored from '${input.sourcePath}'`,
423
+ sourcePath: input.sourcePath,
424
+ durationMs: duration,
425
+ skippedTables: skippedTables.length > 0 ? skippedTables : undefined,
426
+ note: skippedTables.length > 0
427
+ ? "Some virtual tables could not be restored because their modules are not available in this environment (e.g., FTS5, R-Tree in WASM mode)."
428
+ : undefined,
429
+ };
430
+ }
431
+ finally {
432
+ // Always detach backup and re-enable FK constraints
433
+ await adapter
434
+ .executeWriteQuery("PRAGMA foreign_keys = ON", undefined, true)
435
+ .catch(() => {
436
+ // Ignore errors
437
+ });
438
+ await adapter
439
+ .executeWriteQuery("DETACH DATABASE backup_source", undefined, true)
440
+ .catch(() => {
441
+ // Ignore detach errors - backup may not have been attached
442
+ });
443
+ }
444
+ },
445
+ };
446
+ }
447
+ /**
448
+ * Verify backup file integrity
449
+ */
450
+ function createVerifyBackupTool(adapter) {
451
+ return {
452
+ name: "sqlite_verify_backup",
453
+ description: "Verify a backup file's integrity without restoring it.",
454
+ group: "admin",
455
+ inputSchema: VerifyBackupSchema,
456
+ outputSchema: VerifyBackupOutputSchema,
457
+ requiredScopes: ["read"],
458
+ annotations: readOnly("Verify Backup"),
459
+ handler: async (params, _context) => {
460
+ const input = VerifyBackupSchema.parse(params);
461
+ const escapedPath = input.backupPath.replace(/'/g, "''");
462
+ // Attach backup database temporarily - may fail in WASM
463
+ try {
464
+ await adapter.executeQuery(`ATTACH DATABASE '${escapedPath}' AS backup_verify`);
465
+ }
466
+ catch (error) {
467
+ // Detect WASM file system limitation (only in WASM mode)
468
+ const errMsg = error instanceof Error ? error.message : String(error);
469
+ if (!adapter.isNativeBackend() &&
470
+ (errMsg.includes("unable to open database") ||
471
+ errMsg.includes("not supported"))) {
472
+ return {
473
+ success: false,
474
+ message: "Verify backup not available: file system access is not supported in WASM mode.",
475
+ wasmLimitation: true,
476
+ backupPath: input.backupPath,
477
+ };
478
+ }
479
+ throw error;
480
+ }
481
+ try {
482
+ // Get page info
483
+ const pageCountResult = await adapter.executeReadQuery("PRAGMA backup_verify.page_count");
484
+ const pageSizeResult = await adapter.executeReadQuery("PRAGMA backup_verify.page_size");
485
+ const pageCount = pageCountResult.rows?.[0]?.["page_count"] ?? 0;
486
+ const pageSize = pageSizeResult.rows?.[0]?.["page_size"] ?? 0;
487
+ // Run integrity check on backup
488
+ const integrityResult = await adapter.executeReadQuery("PRAGMA backup_verify.integrity_check(10)");
489
+ const messages = (integrityResult.rows ?? []).map((r) => r["integrity_check"]);
490
+ const isOk = messages.length === 1 && messages[0] === "ok";
491
+ return {
492
+ success: true,
493
+ valid: isOk,
494
+ pageCount,
495
+ pageSize,
496
+ integrity: isOk ? "ok" : "errors_found",
497
+ messages: isOk ? undefined : messages,
498
+ };
499
+ }
500
+ finally {
501
+ // Always detach
502
+ await adapter.executeQuery("DETACH DATABASE backup_verify");
503
+ }
504
+ },
505
+ };
506
+ }
507
+ /**
508
+ * Get index statistics
509
+ */
510
+ function createIndexStatsTool(adapter) {
511
+ return {
512
+ name: "sqlite_index_stats",
513
+ description: "Get detailed statistics for database indexes.",
514
+ group: "admin",
515
+ inputSchema: IndexStatsSchema,
516
+ outputSchema: IndexStatsOutputSchema,
517
+ requiredScopes: ["read"],
518
+ annotations: readOnly("Index Statistics"),
519
+ handler: async (params, _context) => {
520
+ const input = IndexStatsSchema.parse(params);
521
+ // Query for indexes
522
+ let sql = `
523
+ SELECT name, tbl_name as "table", sql
524
+ FROM sqlite_master
525
+ WHERE type = 'index' AND sql IS NOT NULL
526
+ `;
527
+ if (input.table) {
528
+ // Validate table name using centralized utility
529
+ sanitizeIdentifier(input.table);
530
+ sql += ` AND tbl_name = '${input.table}'`;
531
+ }
532
+ sql += " ORDER BY tbl_name, name";
533
+ const result = await adapter.executeReadQuery(sql);
534
+ const indexes = [];
535
+ for (const row of result.rows ?? []) {
536
+ const indexName = row["name"];
537
+ const tableName = row["table"];
538
+ const sqlDef = row["sql"] ?? "";
539
+ // Filter out SpatiaLite system indexes if requested (default: true)
540
+ if (input.excludeSystemIndexes) {
541
+ // Import isSpatialiteSystemIndex from core
542
+ const { isSpatialiteSystemIndex } = await import("./core.js");
543
+ if (isSpatialiteSystemIndex(indexName)) {
544
+ continue;
545
+ }
546
+ }
547
+ // Check if unique from CREATE statement
548
+ const unique = sqlDef.toUpperCase().includes("UNIQUE");
549
+ const partial = sqlDef.toUpperCase().includes("WHERE");
550
+ // Get column info
551
+ const indexInfoResult = await adapter.executeReadQuery(`PRAGMA index_info("${indexName}")`);
552
+ const columns = (indexInfoResult.rows ?? []).map((col) => ({
553
+ name: col["name"],
554
+ seqno: col["seqno"],
555
+ }));
556
+ indexes.push({
557
+ name: indexName,
558
+ table: tableName,
559
+ unique,
560
+ partial,
561
+ columns,
562
+ });
563
+ }
564
+ return {
565
+ success: true,
566
+ indexes,
567
+ };
568
+ },
569
+ };
570
+ }
571
+ const PragmaCompileOptionsSchema = z.object({
572
+ filter: z
573
+ .string()
574
+ .optional()
575
+ .describe("Optional filter pattern (case-insensitive substring match) to limit returned options"),
576
+ });
577
+ /**
578
+ * Get SQLite compile options
579
+ */
580
+ function createPragmaCompileOptionsTool(adapter) {
581
+ return {
582
+ name: "sqlite_pragma_compile_options",
583
+ description: "Get the compile-time options used to build SQLite. Use the filter parameter to reduce output (~50+ options by default).",
584
+ group: "admin",
585
+ inputSchema: PragmaCompileOptionsSchema,
586
+ outputSchema: PragmaCompileOptionsOutputSchema,
587
+ requiredScopes: ["read"],
588
+ annotations: readOnly("Compile Options"),
589
+ handler: async (params, _context) => {
590
+ const input = PragmaCompileOptionsSchema.parse(params);
591
+ const result = await adapter.executeReadQuery("PRAGMA compile_options");
592
+ let options = (result.rows ?? []).map((r) => r["compile_options"]);
593
+ // Apply filter if provided
594
+ if (input.filter) {
595
+ const filterLower = input.filter.toLowerCase();
596
+ options = options.filter((opt) => opt.toLowerCase().includes(filterLower));
597
+ }
598
+ return {
599
+ success: true,
600
+ options,
601
+ };
602
+ },
603
+ };
604
+ }
605
+ /**
606
+ * List attached databases
607
+ */
608
+ function createPragmaDatabaseListTool(adapter) {
609
+ return {
610
+ name: "sqlite_pragma_database_list",
611
+ description: "List all attached databases.",
612
+ group: "admin",
613
+ inputSchema: z.object({}),
614
+ outputSchema: PragmaDatabaseListOutputSchema,
615
+ requiredScopes: ["read"],
616
+ annotations: readOnly("Database List"),
617
+ handler: async (_params, _context) => {
618
+ const result = await adapter.executeReadQuery("PRAGMA database_list");
619
+ const databases = (result.rows ?? []).map((r) => ({
620
+ seq: r["seq"],
621
+ name: r["name"],
622
+ file: r["file"],
623
+ }));
624
+ // Get the user's configured path
625
+ const configuredPath = adapter.getConfiguredPath();
626
+ // Check if internal path differs from configured path (common in WASM mode)
627
+ const mainDb = databases.find((db) => db.name === "main");
628
+ const internalPathDiffers = Boolean(mainDb?.file && mainDb.file !== configuredPath);
629
+ return {
630
+ success: true,
631
+ databases,
632
+ configuredPath,
633
+ note: internalPathDiffers
634
+ ? "Internal file paths shown above are WASM virtual filesystem paths. The configuredPath shows the original database location."
635
+ : undefined,
636
+ };
637
+ },
638
+ };
639
+ }
640
+ /**
641
+ * Run PRAGMA optimize
642
+ */
643
+ function createPragmaOptimizeTool(adapter) {
644
+ return {
645
+ name: "sqlite_pragma_optimize",
646
+ description: "Run PRAGMA optimize to improve query performance based on usage patterns.",
647
+ group: "admin",
648
+ inputSchema: PragmaOptimizeSchema,
649
+ outputSchema: PragmaOptimizeOutputSchema,
650
+ requiredScopes: ["admin"],
651
+ annotations: admin("PRAGMA Optimize"),
652
+ handler: async (params, _context) => {
653
+ const input = PragmaOptimizeSchema.parse(params);
654
+ const start = Date.now();
655
+ const sql = input.mask !== undefined
656
+ ? `PRAGMA optimize(${input.mask})`
657
+ : "PRAGMA optimize";
658
+ await adapter.executeQuery(sql);
659
+ const duration = Date.now() - start;
660
+ return {
661
+ success: true,
662
+ message: "Database optimized",
663
+ durationMs: duration,
664
+ };
665
+ },
666
+ };
667
+ }
668
+ /**
669
+ * Get or set PRAGMA values
670
+ */
671
+ function createPragmaSettingsTool(adapter) {
672
+ return {
673
+ name: "sqlite_pragma_settings",
674
+ description: "Get or set a PRAGMA value.",
675
+ group: "admin",
676
+ inputSchema: PragmaSettingsSchema,
677
+ outputSchema: PragmaSettingsOutputSchema,
678
+ requiredScopes: ["admin"],
679
+ annotations: admin("PRAGMA Settings"),
680
+ handler: async (params, _context) => {
681
+ const input = PragmaSettingsSchema.parse(params);
682
+ // Validate pragma name (alphanumeric + underscore only)
683
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input.pragma)) {
684
+ throw new Error("Invalid PRAGMA name");
685
+ }
686
+ if (input.value !== undefined) {
687
+ // Get old value first
688
+ const oldResult = await adapter.executeReadQuery(`PRAGMA ${input.pragma}`);
689
+ const oldValue = oldResult.rows?.[0]?.[input.pragma];
690
+ // Set new value
691
+ await adapter.executeQuery(`PRAGMA ${input.pragma} = ${input.value}`);
692
+ // Verify new value
693
+ const newResult = await adapter.executeReadQuery(`PRAGMA ${input.pragma}`);
694
+ const newValue = newResult.rows?.[0]?.[input.pragma];
695
+ return {
696
+ success: true,
697
+ pragma: input.pragma,
698
+ value: newValue,
699
+ oldValue,
700
+ newValue,
701
+ };
702
+ }
703
+ else {
704
+ // Just read value
705
+ const result = await adapter.executeReadQuery(`PRAGMA ${input.pragma}`);
706
+ const value = result.rows?.[0]?.[input.pragma];
707
+ return {
708
+ success: true,
709
+ pragma: input.pragma,
710
+ value,
711
+ };
712
+ }
713
+ },
714
+ };
715
+ }
716
+ /**
717
+ * Get table column information
718
+ */
719
+ function createPragmaTableInfoTool(adapter) {
720
+ return {
721
+ name: "sqlite_pragma_table_info",
722
+ description: "Get detailed column information for a table.",
723
+ group: "admin",
724
+ inputSchema: PragmaTableInfoSchema,
725
+ outputSchema: PragmaTableInfoOutputSchema,
726
+ requiredScopes: ["read"],
727
+ annotations: readOnly("Table Info"),
728
+ handler: async (params, _context) => {
729
+ const input = PragmaTableInfoSchema.parse(params);
730
+ // Validate and quote table name
731
+ const table = sanitizeIdentifier(input.table);
732
+ const result = await adapter.executeReadQuery(`PRAGMA table_info(${table})`);
733
+ const columns = (result.rows ?? []).map((r) => ({
734
+ cid: r["cid"],
735
+ name: r["name"],
736
+ type: r["type"],
737
+ notNull: r["notnull"] === 1,
738
+ defaultValue: r["dflt_value"],
739
+ pk: r["pk"],
740
+ }));
741
+ return {
742
+ success: true,
743
+ table: input.table,
744
+ columns,
745
+ };
746
+ },
747
+ };
748
+ }
749
+ /**
750
+ * Schema for append_insight input
751
+ */
752
+ const AppendInsightSchema = z.object({
753
+ insight: z
754
+ .string()
755
+ .describe("Business insight discovered from data analysis"),
756
+ });
757
+ /**
758
+ * Output schema for append_insight
759
+ */
760
+ const AppendInsightOutputSchema = z.object({
761
+ success: z.boolean(),
762
+ message: z.string(),
763
+ insightCount: z.number(),
764
+ });
765
+ /**
766
+ * Append a business insight to the memo resource
767
+ */
768
+ function createAppendInsightTool() {
769
+ return {
770
+ name: "sqlite_append_insight",
771
+ description: "Add a business insight to the memo://insights resource. Use this to capture key findings during data analysis.",
772
+ group: "admin",
773
+ inputSchema: AppendInsightSchema,
774
+ outputSchema: AppendInsightOutputSchema,
775
+ requiredScopes: ["write"],
776
+ annotations: admin("Append Insight"),
777
+ handler: (params, _context) => {
778
+ const input = AppendInsightSchema.parse(params);
779
+ insightsManager.append(input.insight);
780
+ return Promise.resolve({
781
+ success: true,
782
+ message: "Insight added to memo",
783
+ insightCount: insightsManager.count(),
784
+ });
785
+ },
786
+ };
787
+ }
788
+ //# sourceMappingURL=admin.js.map