sqlite-hub 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/bin/sqlite-hub-mcp.js +8 -0
- package/bin/sqlite-hub.js +555 -122
- package/docs/API.md +30 -0
- package/docs/CLI.md +22 -0
- package/docs/CLI_API_PARITY.md +61 -0
- package/docs/MCP.md +85 -0
- package/docs/changelog.md +13 -1
- package/docs/guidelines/AGENTS.md +32 -0
- package/docs/{DESIGN_GUIDELINES.md → guidelines/DESIGN.md} +10 -0
- package/docs/todo.md +1 -14
- package/frontend/js/api.js +49 -0
- package/frontend/js/app.js +202 -2
- package/frontend/js/components/connectionCard.js +3 -1
- package/frontend/js/components/modal.js +356 -15
- package/frontend/js/components/sidebar.js +41 -7
- package/frontend/js/components/topNav.js +2 -14
- package/frontend/js/router.js +10 -0
- package/frontend/js/store.js +483 -7
- package/frontend/js/utils/inputClear.js +36 -0
- package/frontend/js/utils/syntheticData.js +240 -0
- package/frontend/js/views/backups.js +52 -0
- package/frontend/js/views/data.js +16 -0
- package/frontend/js/views/logs.js +339 -0
- package/frontend/js/views/settings.js +266 -30
- package/frontend/js/views/structure.js +6 -0
- package/frontend/js/views/tableAdvisor.js +385 -0
- package/frontend/js/views/tableDesigner.js +6 -0
- package/frontend/styles/components.css +149 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +31 -0
- package/package.json +4 -2
- package/server/mcp/stdioServer.js +272 -0
- package/server/routes/data.js +45 -0
- package/server/routes/externalApi.js +285 -2
- package/server/routes/logs.js +127 -0
- package/server/routes/settings.js +47 -0
- package/server/server.js +3 -0
- package/server/services/databaseCommandService.js +284 -2
- package/server/services/mcpStatusService.js +140 -0
- package/server/services/mcpToolService.js +274 -0
- package/server/services/sqlite/dataBrowserService.js +36 -0
- package/server/services/sqlite/introspection.js +226 -22
- package/server/services/sqlite/syntheticDataGenerator.js +728 -0
- package/server/services/sqlite/tableAdvisor.js +790 -0
- package/server/services/storage/appStateStore.js +821 -169
|
@@ -29,6 +29,232 @@ function readStoreName(req) {
|
|
|
29
29
|
).trim();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function decodePathPart(value = "") {
|
|
33
|
+
try {
|
|
34
|
+
return decodeURIComponent(value);
|
|
35
|
+
} catch {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readPathParts(req) {
|
|
41
|
+
return String(req.path ?? "")
|
|
42
|
+
.split("/")
|
|
43
|
+
.map((part) => part.trim())
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.map(decodePathPart);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildExternalApiAccessDescriptor(req) {
|
|
49
|
+
const method = String(req.method ?? "GET").toUpperCase();
|
|
50
|
+
const parts = readPathParts(req);
|
|
51
|
+
|
|
52
|
+
if (parts[0] === "info") {
|
|
53
|
+
return {
|
|
54
|
+
action: "api.info.get",
|
|
55
|
+
targetType: "app",
|
|
56
|
+
targetName: "info",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (parts[0] === "query") {
|
|
61
|
+
return {
|
|
62
|
+
action: "api.query.execute",
|
|
63
|
+
databaseKey: readDatabaseId(req),
|
|
64
|
+
targetType: "query",
|
|
65
|
+
targetName: readStoreName(req) || "raw query",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (parts[0] !== "databases") {
|
|
70
|
+
return {
|
|
71
|
+
action: `api.${method.toLowerCase()}`,
|
|
72
|
+
targetType: "request",
|
|
73
|
+
targetName: req.path,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const databaseKey = parts[1] ?? "";
|
|
78
|
+
const collection = parts[2] ?? "";
|
|
79
|
+
const itemName = parts[3] ?? "";
|
|
80
|
+
const subAction = parts[4] ?? "";
|
|
81
|
+
|
|
82
|
+
if (!collection) {
|
|
83
|
+
return {
|
|
84
|
+
action: "api.database.get",
|
|
85
|
+
databaseKey,
|
|
86
|
+
targetType: "database",
|
|
87
|
+
targetName: databaseKey,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (collection === "tables") {
|
|
92
|
+
if (!itemName) {
|
|
93
|
+
return {
|
|
94
|
+
action: "api.tables.list",
|
|
95
|
+
databaseKey,
|
|
96
|
+
targetType: "database",
|
|
97
|
+
targetName: databaseKey,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (subAction === "row") {
|
|
102
|
+
return {
|
|
103
|
+
action: "api.table.row.export",
|
|
104
|
+
databaseKey,
|
|
105
|
+
targetType: "table",
|
|
106
|
+
targetName: itemName,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (subAction === "types") {
|
|
111
|
+
return {
|
|
112
|
+
action: "api.table.types.generate",
|
|
113
|
+
databaseKey,
|
|
114
|
+
targetType: "table",
|
|
115
|
+
targetName: itemName,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
action: "api.table.get",
|
|
121
|
+
databaseKey,
|
|
122
|
+
targetType: "table",
|
|
123
|
+
targetName: itemName,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (collection === "backups") {
|
|
128
|
+
return {
|
|
129
|
+
action: method === "GET" ? "api.backups.list" : "api.backup.create",
|
|
130
|
+
databaseKey,
|
|
131
|
+
targetType: "database",
|
|
132
|
+
targetName: databaseKey,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (collection === "queries") {
|
|
137
|
+
if (!itemName) {
|
|
138
|
+
return {
|
|
139
|
+
action: "api.queries.list",
|
|
140
|
+
databaseKey,
|
|
141
|
+
targetType: "database",
|
|
142
|
+
targetName: databaseKey,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (subAction === "execute") {
|
|
147
|
+
return {
|
|
148
|
+
action: "api.query.execute.saved",
|
|
149
|
+
databaseKey,
|
|
150
|
+
targetType: "query",
|
|
151
|
+
targetName: itemName,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (subAction === "export") {
|
|
156
|
+
return {
|
|
157
|
+
action: "api.query.export",
|
|
158
|
+
databaseKey,
|
|
159
|
+
targetType: "query",
|
|
160
|
+
targetName: itemName,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (subAction === "notes") {
|
|
165
|
+
return {
|
|
166
|
+
action: "api.query.notes.get",
|
|
167
|
+
databaseKey,
|
|
168
|
+
targetType: "query",
|
|
169
|
+
targetName: itemName,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
action: "api.query.get",
|
|
175
|
+
databaseKey,
|
|
176
|
+
targetType: "query",
|
|
177
|
+
targetName: itemName,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (collection === "documents") {
|
|
182
|
+
if (!itemName) {
|
|
183
|
+
return {
|
|
184
|
+
action: "api.documents.list",
|
|
185
|
+
databaseKey,
|
|
186
|
+
targetType: "database",
|
|
187
|
+
targetName: databaseKey,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (subAction === "export") {
|
|
192
|
+
return {
|
|
193
|
+
action: "api.document.export",
|
|
194
|
+
databaseKey,
|
|
195
|
+
targetType: "document",
|
|
196
|
+
targetName: itemName,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
action: "api.document.get",
|
|
202
|
+
databaseKey,
|
|
203
|
+
targetType: "document",
|
|
204
|
+
targetName: itemName,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
action: `api.${collection}.${method.toLowerCase()}`,
|
|
210
|
+
databaseKey,
|
|
211
|
+
targetType: collection,
|
|
212
|
+
targetName: itemName || databaseKey,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function createExternalApiAccessLogger(appStateStore) {
|
|
217
|
+
return (req, res, next) => {
|
|
218
|
+
const startedAtMs = Date.now();
|
|
219
|
+
const startedAt = new Date(startedAtMs).toISOString();
|
|
220
|
+
|
|
221
|
+
res.on("finish", () => {
|
|
222
|
+
if (!appStateStore?.recordAccessLog) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const descriptor = buildExternalApiAccessDescriptor(req);
|
|
227
|
+
const failed = res.statusCode >= 400;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
appStateStore.recordAccessLog({
|
|
231
|
+
source: "api",
|
|
232
|
+
action: descriptor.action,
|
|
233
|
+
databaseKey: descriptor.databaseKey,
|
|
234
|
+
targetType: descriptor.targetType,
|
|
235
|
+
targetName: descriptor.targetName,
|
|
236
|
+
status: failed ? "error" : "success",
|
|
237
|
+
startedAt,
|
|
238
|
+
durationMs: Date.now() - startedAtMs,
|
|
239
|
+
errorMessage: failed ? req.accessLogError || `HTTP ${res.statusCode}` : null,
|
|
240
|
+
metadata: {
|
|
241
|
+
method: req.method,
|
|
242
|
+
path: req.path,
|
|
243
|
+
route: req.route?.path ? String(req.route.path) : null,
|
|
244
|
+
statusCode: res.statusCode,
|
|
245
|
+
apiTokenId: req.apiToken?.id ?? null,
|
|
246
|
+
apiTokenName: req.apiToken?.name ?? null,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
} catch {
|
|
250
|
+
// Access logging must not change API behavior.
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
next();
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
32
258
|
function authenticateDatabaseRequest(req, tokenService, databaseId) {
|
|
33
259
|
const token = readBearerToken(req.get("authorization"));
|
|
34
260
|
|
|
@@ -38,12 +264,24 @@ function authenticateDatabaseRequest(req, tokenService, databaseId) {
|
|
|
38
264
|
});
|
|
39
265
|
}
|
|
40
266
|
|
|
41
|
-
|
|
267
|
+
const apiToken = tokenService.authenticate(databaseId, token);
|
|
268
|
+
|
|
269
|
+
req.apiToken = apiToken;
|
|
270
|
+
return apiToken;
|
|
42
271
|
}
|
|
43
272
|
|
|
44
|
-
function createExternalApiRouter({
|
|
273
|
+
function createExternalApiRouter({
|
|
274
|
+
databaseService,
|
|
275
|
+
tokenService,
|
|
276
|
+
appStateStore = null,
|
|
277
|
+
appInfoService = buildAppInfo,
|
|
278
|
+
}) {
|
|
45
279
|
const router = express.Router();
|
|
46
280
|
|
|
281
|
+
if (appStateStore?.recordAccessLog) {
|
|
282
|
+
router.use(createExternalApiAccessLogger(appStateStore));
|
|
283
|
+
}
|
|
284
|
+
|
|
47
285
|
router.get(
|
|
48
286
|
"/info",
|
|
49
287
|
route(async (req, res) => {
|
|
@@ -131,6 +369,46 @@ function createExternalApiRouter({ databaseService, tokenService, appInfoService
|
|
|
131
369
|
})
|
|
132
370
|
);
|
|
133
371
|
|
|
372
|
+
router.get(
|
|
373
|
+
"/databases/:databaseId/backups",
|
|
374
|
+
route((req, res) => {
|
|
375
|
+
const backups = databaseService.listBackups(req.params.databaseId);
|
|
376
|
+
|
|
377
|
+
res.json(
|
|
378
|
+
successResponse({
|
|
379
|
+
data: {
|
|
380
|
+
items: backups,
|
|
381
|
+
},
|
|
382
|
+
metadata: {
|
|
383
|
+
databaseId: req.params.databaseId,
|
|
384
|
+
total: backups.length,
|
|
385
|
+
},
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
router.post(
|
|
392
|
+
"/databases/:databaseId/backups",
|
|
393
|
+
route(async (req, res) => {
|
|
394
|
+
const backup = await databaseService.createBackup(req.params.databaseId, {
|
|
395
|
+
name: req.body?.name,
|
|
396
|
+
notes: req.body?.notes,
|
|
397
|
+
context: req.body?.context ?? "api",
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
res.json(
|
|
401
|
+
successResponse({
|
|
402
|
+
message: "Backup created and verified.",
|
|
403
|
+
data: backup,
|
|
404
|
+
metadata: {
|
|
405
|
+
databaseId: req.params.databaseId,
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
|
|
134
412
|
router.post(
|
|
135
413
|
"/databases/:databaseId/tables/:tableName/row",
|
|
136
414
|
route((req, res) => {
|
|
@@ -336,6 +614,11 @@ function createExternalApiRouter({ databaseService, tokenService, appInfoService
|
|
|
336
614
|
})
|
|
337
615
|
);
|
|
338
616
|
|
|
617
|
+
router.use((error, req, res, next) => {
|
|
618
|
+
req.accessLogError = error?.message ?? null;
|
|
619
|
+
next(error);
|
|
620
|
+
});
|
|
621
|
+
|
|
339
622
|
return router;
|
|
340
623
|
}
|
|
341
624
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const { DatabaseRequiredError, route, successResponse } = require("../utils/errors");
|
|
3
|
+
|
|
4
|
+
const RANGE_MS = {
|
|
5
|
+
"1h": 60 * 60 * 1000,
|
|
6
|
+
"24h": 24 * 60 * 60 * 1000,
|
|
7
|
+
"7d": 7 * 24 * 60 * 60 * 1000,
|
|
8
|
+
"30d": 30 * 24 * 60 * 60 * 1000,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function normalizeOption(value, allowedValues, fallback = "all") {
|
|
12
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
13
|
+
return allowedValues.includes(normalized) ? normalized : fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseLimit(value) {
|
|
17
|
+
const numeric = Number(value);
|
|
18
|
+
return Number.isInteger(numeric) && numeric > 0 ? Math.min(numeric, 200) : 100;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseOffset(value) {
|
|
22
|
+
const numeric = Number(value);
|
|
23
|
+
return Number.isInteger(numeric) && numeric >= 0 ? numeric : 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeTimestamp(value) {
|
|
27
|
+
const normalized = String(value ?? "").trim();
|
|
28
|
+
|
|
29
|
+
if (!normalized) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const timestamp = new Date(normalized).getTime();
|
|
34
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveTimeWindow(query = {}, now = new Date()) {
|
|
38
|
+
const explicitFrom = normalizeTimestamp(query.from);
|
|
39
|
+
const explicitTo = normalizeTimestamp(query.to);
|
|
40
|
+
const range = normalizeOption(query.range, ["1h", "24h", "7d", "30d", "all"], "all");
|
|
41
|
+
|
|
42
|
+
if (explicitFrom || explicitTo) {
|
|
43
|
+
return {
|
|
44
|
+
range,
|
|
45
|
+
from: explicitFrom,
|
|
46
|
+
to: explicitTo,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (range === "all") {
|
|
51
|
+
return {
|
|
52
|
+
range,
|
|
53
|
+
from: null,
|
|
54
|
+
to: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
range,
|
|
60
|
+
from: new Date(now.getTime() - RANGE_MS[range]).toISOString(),
|
|
61
|
+
to: now.toISOString(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveActiveDatabase(connectionManager) {
|
|
66
|
+
const activeDatabase = connectionManager.getActiveConnection() ?? null;
|
|
67
|
+
|
|
68
|
+
if (!activeDatabase?.id) {
|
|
69
|
+
throw new DatabaseRequiredError();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return activeDatabase;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createLogsRouter({ appStateStore, connectionManager, now = () => new Date() }) {
|
|
76
|
+
const router = express.Router();
|
|
77
|
+
|
|
78
|
+
router.get(
|
|
79
|
+
"/",
|
|
80
|
+
route((req, res) => {
|
|
81
|
+
const timeWindow = resolveTimeWindow(req.query, now());
|
|
82
|
+
const activeDatabase = resolveActiveDatabase(connectionManager);
|
|
83
|
+
const kind = normalizeOption(req.query.kind, ["all", "query", "access"], "all");
|
|
84
|
+
const actor = normalizeOption(req.query.actor, ["all", "user", "cli", "api", "mcp"], "all");
|
|
85
|
+
const status = normalizeOption(req.query.status, ["all", "success", "error"], "all");
|
|
86
|
+
const queryType = normalizeOption(
|
|
87
|
+
req.query.queryType,
|
|
88
|
+
["all", "select", "insert", "update", "delete", "pragma", "create", "alter", "drop", "other"],
|
|
89
|
+
"all"
|
|
90
|
+
);
|
|
91
|
+
const destructive = normalizeOption(req.query.destructive, ["all", "yes", "no"], "all");
|
|
92
|
+
const search = String(req.query.search ?? "").trim();
|
|
93
|
+
const result = appStateStore.listActivityLogs({
|
|
94
|
+
kind,
|
|
95
|
+
actor: actor === "all" ? null : actor,
|
|
96
|
+
status: status === "all" ? null : status,
|
|
97
|
+
databaseKey: activeDatabase.id,
|
|
98
|
+
queryType: queryType === "all" ? null : queryType,
|
|
99
|
+
destructive,
|
|
100
|
+
from: timeWindow.from,
|
|
101
|
+
to: timeWindow.to,
|
|
102
|
+
search,
|
|
103
|
+
limit: parseLimit(req.query.limit),
|
|
104
|
+
offset: parseOffset(req.query.offset),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
res.json(
|
|
108
|
+
successResponse({
|
|
109
|
+
data: result,
|
|
110
|
+
metadata: {
|
|
111
|
+
...result.filters,
|
|
112
|
+
range: timeWindow.range,
|
|
113
|
+
activeDatabase,
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return router;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
createLogsRouter,
|
|
125
|
+
resolveActiveDatabase,
|
|
126
|
+
resolveTimeWindow,
|
|
127
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const express = require("express");
|
|
2
|
+
const path = require("node:path");
|
|
2
3
|
const { AppError, DatabaseRequiredError, route, successResponse } = require("../utils/errors");
|
|
3
4
|
const {
|
|
4
5
|
checkLatestAppVersion,
|
|
@@ -7,6 +8,8 @@ const {
|
|
|
7
8
|
readSettingsMetadata,
|
|
8
9
|
readSqliteVersion,
|
|
9
10
|
} = require("../services/appInfoService");
|
|
11
|
+
const { McpStatusService } = require("../services/mcpStatusService");
|
|
12
|
+
const { MCP_TOOL_DEFINITIONS } = require("../services/mcpToolService");
|
|
10
13
|
|
|
11
14
|
function getActiveTokenContext({ connectionManager, tokenService }) {
|
|
12
15
|
const activeDatabase = connectionManager?.getActiveConnection?.() ?? null;
|
|
@@ -34,6 +37,37 @@ function buildSettingsMetadata(context) {
|
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
function buildMcpCodexConfig() {
|
|
41
|
+
const serverPath = path.resolve(__dirname, "../../bin/sqlite-hub-mcp.js");
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
"[mcp_servers.sqlitehub]",
|
|
45
|
+
'command = "node"',
|
|
46
|
+
`args = [${JSON.stringify(serverPath)}]`,
|
|
47
|
+
"startup_timeout_sec = 10",
|
|
48
|
+
"tool_timeout_sec = 60",
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildMcpSettingsStatus(appStateStore) {
|
|
53
|
+
const statusService = new McpStatusService({
|
|
54
|
+
appStateStore,
|
|
55
|
+
exposedTools: MCP_TOOL_DEFINITIONS,
|
|
56
|
+
transport: "stdio",
|
|
57
|
+
});
|
|
58
|
+
const status = statusService.getStatus();
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
...status,
|
|
62
|
+
toolDetails: MCP_TOOL_DEFINITIONS.map((tool) => ({
|
|
63
|
+
name: tool.name,
|
|
64
|
+
description: tool.description,
|
|
65
|
+
})),
|
|
66
|
+
command: `node ${path.resolve(__dirname, "../../bin/sqlite-hub-mcp.js")}`,
|
|
67
|
+
codexConfig: buildMcpCodexConfig(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
37
71
|
function createSettingsRouter({ appStateStore, connectionManager, tokenService, versionCheckService }) {
|
|
38
72
|
const router = express.Router();
|
|
39
73
|
const context = { connectionManager, tokenService };
|
|
@@ -89,6 +123,17 @@ function createSettingsRouter({ appStateStore, connectionManager, tokenService,
|
|
|
89
123
|
})
|
|
90
124
|
);
|
|
91
125
|
|
|
126
|
+
router.get(
|
|
127
|
+
"/mcp",
|
|
128
|
+
route((req, res) => {
|
|
129
|
+
res.json(
|
|
130
|
+
successResponse({
|
|
131
|
+
data: buildMcpSettingsStatus(appStateStore),
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
})
|
|
135
|
+
);
|
|
136
|
+
|
|
92
137
|
router.post(
|
|
93
138
|
"/api-tokens",
|
|
94
139
|
route((req, res) => {
|
|
@@ -126,6 +171,8 @@ function createSettingsRouter({ appStateStore, connectionManager, tokenService,
|
|
|
126
171
|
|
|
127
172
|
module.exports = {
|
|
128
173
|
createSettingsRouter,
|
|
174
|
+
buildMcpCodexConfig,
|
|
175
|
+
buildMcpSettingsStatus,
|
|
129
176
|
buildSettingsMetadata,
|
|
130
177
|
checkLatestAppVersion,
|
|
131
178
|
compareSemver,
|
package/server/server.js
CHANGED
|
@@ -36,6 +36,7 @@ const { createSettingsRouter } = require("./routes/settings");
|
|
|
36
36
|
const { createExportRouter } = require("./routes/export");
|
|
37
37
|
const { createDocumentsRouter } = require("./routes/documents");
|
|
38
38
|
const { createExternalApiRouter } = require("./routes/externalApi");
|
|
39
|
+
const { createLogsRouter } = require("./routes/logs");
|
|
39
40
|
|
|
40
41
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
41
42
|
const FRONTEND_ROOT = path.join(PACKAGE_ROOT, "frontend");
|
|
@@ -133,11 +134,13 @@ app.use(
|
|
|
133
134
|
);
|
|
134
135
|
app.use("/api/export", createExportRouter({ exportService }));
|
|
135
136
|
app.use("/api/documents", createDocumentsRouter({ appStateStore, connectionManager }));
|
|
137
|
+
app.use("/api/logs", createLogsRouter({ appStateStore, connectionManager }));
|
|
136
138
|
app.use(
|
|
137
139
|
"/api/v1",
|
|
138
140
|
createExternalApiRouter({
|
|
139
141
|
databaseService: databaseCommandService,
|
|
140
142
|
tokenService: apiTokenService,
|
|
143
|
+
appStateStore,
|
|
141
144
|
})
|
|
142
145
|
);
|
|
143
146
|
|