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 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 stdio MCP server for agents such as Codex. 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.
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, point Codex at the script directly:
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 server uses stdio transport. It 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.
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
@@ -1,6 +1,7 @@
1
1
  # v2.0.0
2
2
 
3
3
  - mcp
4
+ - mcp http endpoint starts with sqlite hub
4
5
 
5
6
  # v1.5.0
6
7
 
@@ -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
- Local_Command
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>Local stdio MCP is allowed because it runs on the same machine as SQLite Hub and does not expose network credentials.</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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlite-hub",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "SQLite-only local management app backend and SPA shell",
5
5
  "license": "MIT",
6
6
  "main": "server/server.js",
@@ -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 buildMcpCodexConfig() {
41
- const serverPath = path.resolve(__dirname, "../../bin/sqlite-hub-mcp.js");
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 ${path.resolve(__dirname, "../../bin/sqlite-hub-mcp.js")}`,
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,