sqlite-hub 2.0.0 → 2.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/README.md +1 -1
- package/docs/MCP.md +28 -4
- package/docs/changelog.md +1 -0
- package/frontend/js/views/settings.js +2 -2
- package/package.json +1 -1
- package/server/mcp/httpRouter.js +95 -0
- package/server/routes/settings.js +29 -7
- package/server/server.js +15 -0
package/README.md
CHANGED
|
@@ -269,7 +269,7 @@ SQLite Hub also provides a local JSON API for app info, database metadata, table
|
|
|
269
269
|
|
|
270
270
|
## MCP
|
|
271
271
|
|
|
272
|
-
SQLite Hub includes a local
|
|
272
|
+
SQLite Hub includes a local MCP server for agents such as Codex. When SQLite Hub is running, the MCP endpoint is available at `/mcp`; a stdio fallback is also available through `sqlite-hub-mcp`. It exposes the shared API/CLI service layer as guarded tools for schema inspection, read-only queries, query-plan explanation, backups, type generation, documents, and chart creation. See the [MCP documentation](./docs/MCP.md) for setup, tool names, and security boundaries.
|
|
273
273
|
|
|
274
274
|
## Changelog
|
|
275
275
|
|
package/docs/MCP.md
CHANGED
|
@@ -4,7 +4,29 @@ SQLite Hub ships a local MCP server so agents can inspect and automate the same
|
|
|
4
4
|
|
|
5
5
|
The MCP server uses the shared SQLite Hub service layer. API, CLI, and MCP calls all go through the same database registry, query execution, type generation, backup, document, and chart logic.
|
|
6
6
|
|
|
7
|
-
## Start
|
|
7
|
+
## Start With SQLite Hub
|
|
8
|
+
|
|
9
|
+
When SQLite Hub is running, the MCP server is available on the same local web server:
|
|
10
|
+
|
|
11
|
+
```toml
|
|
12
|
+
[mcp_servers.sqlitehub]
|
|
13
|
+
url = "http://127.0.0.1:4173/mcp"
|
|
14
|
+
startup_timeout_sec = 10
|
|
15
|
+
tool_timeout_sec = 60
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If SQLite Hub is started with a custom port, use that port in the URL:
|
|
19
|
+
|
|
20
|
+
```toml
|
|
21
|
+
[mcp_servers.sqlitehub]
|
|
22
|
+
url = "http://127.0.0.1:PORT/mcp"
|
|
23
|
+
startup_timeout_sec = 10
|
|
24
|
+
tool_timeout_sec = 60
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The Settings `MCP` tab shows a copyable Codex config with the active host and port.
|
|
28
|
+
|
|
29
|
+
## Stdio Fallback
|
|
8
30
|
|
|
9
31
|
When SQLite Hub is installed from npm, use:
|
|
10
32
|
|
|
@@ -12,7 +34,7 @@ When SQLite Hub is installed from npm, use:
|
|
|
12
34
|
sqlite-hub-mcp
|
|
13
35
|
```
|
|
14
36
|
|
|
15
|
-
For a local checkout,
|
|
37
|
+
For a local checkout, Codex can also spawn the stdio server directly:
|
|
16
38
|
|
|
17
39
|
```toml
|
|
18
40
|
[mcp_servers.sqlitehub]
|
|
@@ -22,7 +44,7 @@ startup_timeout_sec = 10
|
|
|
22
44
|
tool_timeout_sec = 60
|
|
23
45
|
```
|
|
24
46
|
|
|
25
|
-
The
|
|
47
|
+
The stdio fallback is intended for local agents running on the same machine as SQLite Hub. It does not expose a network listener and does not require API tokens for local stdio use.
|
|
26
48
|
|
|
27
49
|
## Tools
|
|
28
50
|
|
|
@@ -64,11 +86,13 @@ These statements are blocked in `run_readonly_query`:
|
|
|
64
86
|
|
|
65
87
|
Backups are always created through SQLite Hub's managed backup service. Chart creation stores chart metadata only. The MCP server does not write arbitrary local files.
|
|
66
88
|
|
|
89
|
+
The HTTP MCP endpoint runs on SQLite Hub's existing loopback server. It is local-only and uses the same localhost request guard as the internal API. API tokens are not exposed through MCP tool responses.
|
|
90
|
+
|
|
67
91
|
## Settings Status
|
|
68
92
|
|
|
69
93
|
The Settings view has an `MCP` tab. It shows whether the MCP server is running, whether an agent is connected, active client count, the last connection time, last tool call, transport, exposed tools, and a copyable Codex config example.
|
|
70
94
|
|
|
71
|
-
For stdio, connection state is tracked from MCP `initialize` and tool calls. When the MCP process exits cleanly, SQLite Hub marks the session as disconnected.
|
|
95
|
+
For HTTP, connection state is tracked from MCP `initialize` and tool calls against `/mcp`. For stdio, connection state is tracked from MCP `initialize` and tool calls in the spawned process. When the stdio MCP process exits cleanly, SQLite Hub marks the session as disconnected.
|
|
72
96
|
|
|
73
97
|
## Example Prompts
|
|
74
98
|
|
package/docs/changelog.md
CHANGED
|
@@ -240,7 +240,7 @@ function renderMcpSection(settings) {
|
|
|
240
240
|
<div class="space-y-4 p-6">
|
|
241
241
|
<div>
|
|
242
242
|
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
243
|
-
|
|
243
|
+
MCP_ENDPOINT
|
|
244
244
|
</div>
|
|
245
245
|
<div class="mt-3 border border-outline-variant/10 bg-surface-container-high px-4 py-3 font-mono text-sm text-primary-container">
|
|
246
246
|
${escapeHtml(command)}
|
|
@@ -265,7 +265,7 @@ function renderMcpSection(settings) {
|
|
|
265
265
|
<div>Read-only queries are limited to SELECT, PRAGMA, and EXPLAIN.</div>
|
|
266
266
|
<div>Mutating SQL statements are blocked server-side before execution.</div>
|
|
267
267
|
<div>Destructive actions must use existing SQLite Hub safety mechanisms such as verified backups.</div>
|
|
268
|
-
<div>
|
|
268
|
+
<div>The local HTTP MCP endpoint is bound to SQLite Hub's loopback server and does not expose API tokens.</div>
|
|
269
269
|
</div>
|
|
270
270
|
</section>
|
|
271
271
|
</div>
|
package/package.json
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const {
|
|
3
|
+
createJsonRpcError,
|
|
4
|
+
handleMcpRequest,
|
|
5
|
+
MCP_PROTOCOL_VERSION,
|
|
6
|
+
} = require("./stdioServer");
|
|
7
|
+
|
|
8
|
+
function isJsonRpcBatch(message) {
|
|
9
|
+
return Array.isArray(message);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function handleHttpMcpMessage(message, services) {
|
|
13
|
+
try {
|
|
14
|
+
return await handleMcpRequest(message, services);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
services.statusService?.markError?.(error);
|
|
17
|
+
return createJsonRpcError(message?.id, error);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function handleHttpMcpBatch(messages, services) {
|
|
22
|
+
const responses = await Promise.all(
|
|
23
|
+
messages.map((message) => handleHttpMcpMessage(message, services))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return responses.filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createMcpHttpRouter({ services }) {
|
|
30
|
+
if (!services?.toolService || !services?.statusService) {
|
|
31
|
+
throw new Error("MCP HTTP router requires MCP services.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const router = express.Router();
|
|
35
|
+
|
|
36
|
+
services.statusService.markServerRunning();
|
|
37
|
+
|
|
38
|
+
router.get("/", (req, res) => {
|
|
39
|
+
res
|
|
40
|
+
.set({
|
|
41
|
+
Allow: "POST, OPTIONS",
|
|
42
|
+
"MCP-Protocol-Version": MCP_PROTOCOL_VERSION,
|
|
43
|
+
})
|
|
44
|
+
.status(405)
|
|
45
|
+
.json({
|
|
46
|
+
error: "SQLite Hub MCP uses Streamable HTTP POST requests.",
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
router.options("/", (req, res) => {
|
|
51
|
+
res
|
|
52
|
+
.set({
|
|
53
|
+
Allow: "POST, OPTIONS",
|
|
54
|
+
"MCP-Protocol-Version": MCP_PROTOCOL_VERSION,
|
|
55
|
+
})
|
|
56
|
+
.status(204)
|
|
57
|
+
.end();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
router.post("/", async (req, res) => {
|
|
61
|
+
const message = req.body;
|
|
62
|
+
|
|
63
|
+
res.set({
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
"MCP-Protocol-Version": MCP_PROTOCOL_VERSION,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (isJsonRpcBatch(message)) {
|
|
69
|
+
const responses = await handleHttpMcpBatch(message, services);
|
|
70
|
+
|
|
71
|
+
if (!responses.length) {
|
|
72
|
+
res.status(202).end();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
res.json(responses);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const response = await handleHttpMcpMessage(message, services);
|
|
81
|
+
|
|
82
|
+
if (!response) {
|
|
83
|
+
res.status(202).end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
res.json(response);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return router;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
createMcpHttpRouter,
|
|
95
|
+
};
|
|
@@ -37,8 +37,22 @@ function buildSettingsMetadata(context) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function
|
|
41
|
-
const
|
|
40
|
+
function buildMcpHttpUrl(req) {
|
|
41
|
+
const host = String(req?.get?.("host") ?? "127.0.0.1:4173").trim() || "127.0.0.1:4173";
|
|
42
|
+
return `http://${host}/mcp`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildMcpCodexConfig({ url = null, commandPath = null } = {}) {
|
|
46
|
+
if (url) {
|
|
47
|
+
return [
|
|
48
|
+
"[mcp_servers.sqlitehub]",
|
|
49
|
+
`url = ${JSON.stringify(url)}`,
|
|
50
|
+
"startup_timeout_sec = 10",
|
|
51
|
+
"tool_timeout_sec = 60",
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const serverPath = commandPath ?? path.resolve(__dirname, "../../bin/sqlite-hub-mcp.js");
|
|
42
56
|
|
|
43
57
|
return [
|
|
44
58
|
"[mcp_servers.sqlitehub]",
|
|
@@ -49,11 +63,13 @@ function buildMcpCodexConfig() {
|
|
|
49
63
|
].join("\n");
|
|
50
64
|
}
|
|
51
65
|
|
|
52
|
-
function buildMcpSettingsStatus(appStateStore) {
|
|
66
|
+
function buildMcpSettingsStatus(appStateStore, options = {}) {
|
|
67
|
+
const httpUrl = options.httpUrl ?? null;
|
|
68
|
+
const stdioCommandPath = path.resolve(__dirname, "../../bin/sqlite-hub-mcp.js");
|
|
53
69
|
const statusService = new McpStatusService({
|
|
54
70
|
appStateStore,
|
|
55
71
|
exposedTools: MCP_TOOL_DEFINITIONS,
|
|
56
|
-
transport: "stdio",
|
|
72
|
+
transport: httpUrl ? "http" : "stdio",
|
|
57
73
|
});
|
|
58
74
|
const status = statusService.getStatus();
|
|
59
75
|
|
|
@@ -63,8 +79,11 @@ function buildMcpSettingsStatus(appStateStore) {
|
|
|
63
79
|
name: tool.name,
|
|
64
80
|
description: tool.description,
|
|
65
81
|
})),
|
|
66
|
-
command: `node ${
|
|
67
|
-
codexConfig: buildMcpCodexConfig(),
|
|
82
|
+
command: httpUrl ?? `node ${stdioCommandPath}`,
|
|
83
|
+
codexConfig: buildMcpCodexConfig({ url: httpUrl, commandPath: stdioCommandPath }),
|
|
84
|
+
httpUrl,
|
|
85
|
+
stdioCommand: `node ${stdioCommandPath}`,
|
|
86
|
+
stdioCodexConfig: buildMcpCodexConfig({ commandPath: stdioCommandPath }),
|
|
68
87
|
};
|
|
69
88
|
}
|
|
70
89
|
|
|
@@ -128,7 +147,9 @@ function createSettingsRouter({ appStateStore, connectionManager, tokenService,
|
|
|
128
147
|
route((req, res) => {
|
|
129
148
|
res.json(
|
|
130
149
|
successResponse({
|
|
131
|
-
data: buildMcpSettingsStatus(appStateStore
|
|
150
|
+
data: buildMcpSettingsStatus(appStateStore, {
|
|
151
|
+
httpUrl: buildMcpHttpUrl(req),
|
|
152
|
+
}),
|
|
132
153
|
})
|
|
133
154
|
);
|
|
134
155
|
})
|
|
@@ -172,6 +193,7 @@ function createSettingsRouter({ appStateStore, connectionManager, tokenService,
|
|
|
172
193
|
module.exports = {
|
|
173
194
|
createSettingsRouter,
|
|
174
195
|
buildMcpCodexConfig,
|
|
196
|
+
buildMcpHttpUrl,
|
|
175
197
|
buildMcpSettingsStatus,
|
|
176
198
|
buildSettingsMetadata,
|
|
177
199
|
checkLatestAppVersion,
|
package/server/server.js
CHANGED
|
@@ -23,6 +23,8 @@ const { TableDesignerService } = require("./services/sqlite/tableDesignerService
|
|
|
23
23
|
const { MediaTaggingService } = require("./services/sqlite/mediaTaggingService");
|
|
24
24
|
const { ApiTokenService } = require("./services/apiTokenService");
|
|
25
25
|
const { DatabaseCommandService } = require("./services/databaseCommandService");
|
|
26
|
+
const { createMcpServices } = require("./mcp/stdioServer");
|
|
27
|
+
const { createMcpHttpRouter } = require("./mcp/httpRouter");
|
|
26
28
|
const { createConnectionsRouter } = require("./routes/connections");
|
|
27
29
|
const { createBackupsRouter } = require("./routes/backups");
|
|
28
30
|
const { createOverviewRouter } = require("./routes/overview");
|
|
@@ -71,6 +73,7 @@ const tableDesignerService = new TableDesignerService({ connectionManager });
|
|
|
71
73
|
const mediaTaggingService = new MediaTaggingService({ connectionManager, appStateStore });
|
|
72
74
|
const apiTokenService = new ApiTokenService({ appStateStore });
|
|
73
75
|
const databaseCommandService = new DatabaseCommandService({ appStateStore });
|
|
76
|
+
const mcpServices = createMcpServices({ appStateStore, transport: "http" });
|
|
74
77
|
|
|
75
78
|
connectionManager.initialize();
|
|
76
79
|
|
|
@@ -91,6 +94,16 @@ app.use(
|
|
|
91
94
|
legacyHeaders: false,
|
|
92
95
|
})
|
|
93
96
|
);
|
|
97
|
+
app.use("/mcp", localRequestSecurity);
|
|
98
|
+
app.use(
|
|
99
|
+
"/mcp",
|
|
100
|
+
rateLimit({
|
|
101
|
+
windowMs: 60 * 1000,
|
|
102
|
+
max: 300,
|
|
103
|
+
standardHeaders: true,
|
|
104
|
+
legacyHeaders: false,
|
|
105
|
+
})
|
|
106
|
+
);
|
|
94
107
|
app.use(express.json({ limit: "10mb" }));
|
|
95
108
|
app.use(express.urlencoded({ extended: false }));
|
|
96
109
|
|
|
@@ -143,6 +156,7 @@ app.use(
|
|
|
143
156
|
appStateStore,
|
|
144
157
|
})
|
|
145
158
|
);
|
|
159
|
+
app.use("/mcp", createMcpHttpRouter({ services: mcpServices }));
|
|
146
160
|
|
|
147
161
|
// auth: public favicon response; it exposes no application data.
|
|
148
162
|
app.get("/favicon.ico", (req, res) => {
|
|
@@ -254,6 +268,7 @@ module.exports = {
|
|
|
254
268
|
apiTokenService,
|
|
255
269
|
connectionManager,
|
|
256
270
|
databaseCommandService,
|
|
271
|
+
mcpServices,
|
|
257
272
|
DEFAULT_HOST,
|
|
258
273
|
DEFAULT_PORT,
|
|
259
274
|
parsePortArgument,
|