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.
- package/LICENSE +21 -0
- package/README.md +860 -0
- package/dist/adapters/DatabaseAdapter.d.ts +141 -0
- package/dist/adapters/DatabaseAdapter.d.ts.map +1 -0
- package/dist/adapters/DatabaseAdapter.js +131 -0
- package/dist/adapters/DatabaseAdapter.js.map +1 -0
- package/dist/adapters/sqlite/SchemaManager.d.ts +58 -0
- package/dist/adapters/sqlite/SchemaManager.d.ts.map +1 -0
- package/dist/adapters/sqlite/SchemaManager.js +187 -0
- package/dist/adapters/sqlite/SchemaManager.js.map +1 -0
- package/dist/adapters/sqlite/SqliteAdapter.d.ts +161 -0
- package/dist/adapters/sqlite/SqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/sqlite/SqliteAdapter.js +741 -0
- package/dist/adapters/sqlite/SqliteAdapter.js.map +1 -0
- package/dist/adapters/sqlite/index.d.ts +9 -0
- package/dist/adapters/sqlite/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/index.js +8 -0
- package/dist/adapters/sqlite/index.js.map +1 -0
- package/dist/adapters/sqlite/json-utils.d.ts +100 -0
- package/dist/adapters/sqlite/json-utils.d.ts.map +1 -0
- package/dist/adapters/sqlite/json-utils.js +274 -0
- package/dist/adapters/sqlite/json-utils.js.map +1 -0
- package/dist/adapters/sqlite/output-schemas.d.ts +1187 -0
- package/dist/adapters/sqlite/output-schemas.d.ts.map +1 -0
- package/dist/adapters/sqlite/output-schemas.js +1337 -0
- package/dist/adapters/sqlite/output-schemas.js.map +1 -0
- package/dist/adapters/sqlite/prompts.d.ts +13 -0
- package/dist/adapters/sqlite/prompts.d.ts.map +1 -0
- package/dist/adapters/sqlite/prompts.js +605 -0
- package/dist/adapters/sqlite/prompts.js.map +1 -0
- package/dist/adapters/sqlite/resources.d.ts +13 -0
- package/dist/adapters/sqlite/resources.d.ts.map +1 -0
- package/dist/adapters/sqlite/resources.js +251 -0
- package/dist/adapters/sqlite/resources.js.map +1 -0
- package/dist/adapters/sqlite/tools/admin.d.ts +14 -0
- package/dist/adapters/sqlite/tools/admin.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin.js +788 -0
- package/dist/adapters/sqlite/tools/admin.js.map +1 -0
- package/dist/adapters/sqlite/tools/core.d.ts +25 -0
- package/dist/adapters/sqlite/tools/core.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core.js +359 -0
- package/dist/adapters/sqlite/tools/core.js.map +1 -0
- package/dist/adapters/sqlite/tools/fts.d.ts +13 -0
- package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/fts.js +347 -0
- package/dist/adapters/sqlite/tools/fts.js.map +1 -0
- package/dist/adapters/sqlite/tools/geo.d.ts +14 -0
- package/dist/adapters/sqlite/tools/geo.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/geo.js +252 -0
- package/dist/adapters/sqlite/tools/geo.js.map +1 -0
- package/dist/adapters/sqlite/tools/index.d.ts +30 -0
- package/dist/adapters/sqlite/tools/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/index.js +61 -0
- package/dist/adapters/sqlite/tools/index.js.map +1 -0
- package/dist/adapters/sqlite/tools/json-helpers.d.ts +14 -0
- package/dist/adapters/sqlite/tools/json-helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-helpers.js +477 -0
- package/dist/adapters/sqlite/tools/json-helpers.js.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations.d.ts +14 -0
- package/dist/adapters/sqlite/tools/json-operations.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations.js +839 -0
- package/dist/adapters/sqlite/tools/json-operations.js.map +1 -0
- package/dist/adapters/sqlite/tools/stats.d.ts +15 -0
- package/dist/adapters/sqlite/tools/stats.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats.js +1219 -0
- package/dist/adapters/sqlite/tools/stats.js.map +1 -0
- package/dist/adapters/sqlite/tools/text.d.ts +14 -0
- package/dist/adapters/sqlite/tools/text.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text.js +1141 -0
- package/dist/adapters/sqlite/tools/text.js.map +1 -0
- package/dist/adapters/sqlite/tools/vector.d.ts +14 -0
- package/dist/adapters/sqlite/tools/vector.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector.js +613 -0
- package/dist/adapters/sqlite/tools/vector.js.map +1 -0
- package/dist/adapters/sqlite/tools/virtual.d.ts +13 -0
- package/dist/adapters/sqlite/tools/virtual.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual.js +930 -0
- package/dist/adapters/sqlite/tools/virtual.js.map +1 -0
- package/dist/adapters/sqlite/types.d.ts +207 -0
- package/dist/adapters/sqlite/types.d.ts.map +1 -0
- package/dist/adapters/sqlite/types.js +186 -0
- package/dist/adapters/sqlite/types.js.map +1 -0
- package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts +163 -0
- package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/NativeSqliteAdapter.js +748 -0
- package/dist/adapters/sqlite-native/NativeSqliteAdapter.js.map +1 -0
- package/dist/adapters/sqlite-native/index.d.ts +11 -0
- package/dist/adapters/sqlite-native/index.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/index.js +11 -0
- package/dist/adapters/sqlite-native/index.js.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite.d.ts +19 -0
- package/dist/adapters/sqlite-native/tools/spatialite.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite.js +628 -0
- package/dist/adapters/sqlite-native/tools/spatialite.js.map +1 -0
- package/dist/adapters/sqlite-native/tools/transactions.d.ts +12 -0
- package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/transactions.js +255 -0
- package/dist/adapters/sqlite-native/tools/transactions.js.map +1 -0
- package/dist/adapters/sqlite-native/tools/window.d.ts +12 -0
- package/dist/adapters/sqlite-native/tools/window.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window.js +370 -0
- package/dist/adapters/sqlite-native/tools/window.js.map +1 -0
- package/dist/auth/AuthorizationServerDiscovery.d.ts +90 -0
- package/dist/auth/AuthorizationServerDiscovery.d.ts.map +1 -0
- package/dist/auth/AuthorizationServerDiscovery.js +204 -0
- package/dist/auth/AuthorizationServerDiscovery.js.map +1 -0
- package/dist/auth/OAuthResourceServer.d.ts +65 -0
- package/dist/auth/OAuthResourceServer.d.ts.map +1 -0
- package/dist/auth/OAuthResourceServer.js +121 -0
- package/dist/auth/OAuthResourceServer.js.map +1 -0
- package/dist/auth/TokenValidator.d.ts +60 -0
- package/dist/auth/TokenValidator.d.ts.map +1 -0
- package/dist/auth/TokenValidator.js +235 -0
- package/dist/auth/TokenValidator.js.map +1 -0
- package/dist/auth/errors.d.ts +74 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/errors.js +133 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +15 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +81 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +291 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/scopes.d.ts +136 -0
- package/dist/auth/scopes.d.ts.map +1 -0
- package/dist/auth/scopes.js +349 -0
- package/dist/auth/scopes.js.map +1 -0
- package/dist/auth/types.d.ts +257 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +8 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +236 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants/ServerInstructions.d.ts +45 -0
- package/dist/constants/ServerInstructions.d.ts.map +1 -0
- package/dist/constants/ServerInstructions.js +356 -0
- package/dist/constants/ServerInstructions.js.map +1 -0
- package/dist/filtering/ToolConstants.d.ts +34 -0
- package/dist/filtering/ToolConstants.d.ts.map +1 -0
- package/dist/filtering/ToolConstants.js +174 -0
- package/dist/filtering/ToolConstants.js.map +1 -0
- package/dist/filtering/ToolFilter.d.ts +82 -0
- package/dist/filtering/ToolFilter.d.ts.map +1 -0
- package/dist/filtering/ToolFilter.js +296 -0
- package/dist/filtering/ToolFilter.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/server/McpServer.d.ts +61 -0
- package/dist/server/McpServer.d.ts.map +1 -0
- package/dist/server/McpServer.js +270 -0
- package/dist/server/McpServer.js.map +1 -0
- package/dist/transports/http.d.ts +134 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +516 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/index.d.ts +5 -0
- package/dist/transports/index.d.ts.map +1 -0
- package/dist/transports/index.js +5 -0
- package/dist/transports/index.js.map +1 -0
- package/dist/types/index.d.ts +380 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +68 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/annotations.d.ts +44 -0
- package/dist/utils/annotations.d.ts.map +1 -0
- package/dist/utils/annotations.js +77 -0
- package/dist/utils/annotations.js.map +1 -0
- package/dist/utils/errors.d.ts +155 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +329 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/identifiers.d.ts +121 -0
- package/dist/utils/identifiers.d.ts.map +1 -0
- package/dist/utils/identifiers.js +319 -0
- package/dist/utils/identifiers.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/insightsManager.d.ts +39 -0
- package/dist/utils/insightsManager.d.ts.map +1 -0
- package/dist/utils/insightsManager.js +63 -0
- package/dist/utils/insightsManager.js.map +1 -0
- package/dist/utils/logger.d.ts +189 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +394 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/progress-utils.d.ts +54 -0
- package/dist/utils/progress-utils.d.ts.map +1 -0
- package/dist/utils/progress-utils.js +74 -0
- package/dist/utils/progress-utils.js.map +1 -0
- package/dist/utils/resourceAnnotations.d.ts +36 -0
- package/dist/utils/resourceAnnotations.d.ts.map +1 -0
- package/dist/utils/resourceAnnotations.js +57 -0
- package/dist/utils/resourceAnnotations.js.map +1 -0
- package/dist/utils/where-clause.d.ts +41 -0
- package/dist/utils/where-clause.d.ts.map +1 -0
- package/dist/utils/where-clause.js +116 -0
- package/dist/utils/where-clause.js.map +1 -0
- package/package.json +83 -0
- 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
|