sqlite-hub 1.5.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 +5 -1
- package/bin/sqlite-hub-mcp.js +8 -0
- package/docs/MCP.md +109 -0
- package/docs/changelog.md +5 -0
- package/docs/todo.md +0 -1
- package/frontend/js/api.js +4 -0
- package/frontend/js/app.js +10 -0
- package/frontend/js/store.js +35 -7
- package/frontend/js/views/settings.js +189 -3
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +23 -0
- package/package.json +4 -2
- package/server/mcp/httpRouter.js +95 -0
- package/server/mcp/stdioServer.js +272 -0
- package/server/routes/settings.js +69 -0
- package/server/server.js +15 -0
- package/server/services/databaseCommandService.js +239 -2
- package/server/services/mcpStatusService.js +140 -0
- package/server/services/mcpToolService.js +274 -0
- package/server/services/storage/appStateStore.js +48 -0
package/README.md
CHANGED
|
@@ -265,7 +265,11 @@ rows as JSON, generating schema types, and working with Markdown documents. See
|
|
|
265
265
|
|
|
266
266
|
## API
|
|
267
267
|
|
|
268
|
-
SQLite Hub also provides a local JSON API for app info, database metadata, tables, saved queries, exports, documents, and schema type generation. `/api/v1/info` returns the same app/version status as `sqlite-hub --info`; database data is protected by database-specific API
|
|
268
|
+
SQLite Hub also provides a local JSON API for app info, database metadata, tables, saved queries, exports, documents, and schema type generation. `/api/v1/info` returns the same app/version status as `sqlite-hub --info`; database data is protected by database-specific API tokens created in Settings. See the [API documentation](./docs/API.md) for authentication, endpoints, and examples.
|
|
269
|
+
|
|
270
|
+
## MCP
|
|
271
|
+
|
|
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.
|
|
269
273
|
|
|
270
274
|
## Changelog
|
|
271
275
|
|
package/docs/MCP.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# SQLite Hub MCP
|
|
2
|
+
|
|
3
|
+
SQLite Hub ships a local MCP server so agents can inspect and automate the same imported SQLite databases that the UI, CLI, and local API use.
|
|
4
|
+
|
|
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
|
+
|
|
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
|
|
30
|
+
|
|
31
|
+
When SQLite Hub is installed from npm, use:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
sqlite-hub-mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For a local checkout, Codex can also spawn the stdio server directly:
|
|
38
|
+
|
|
39
|
+
```toml
|
|
40
|
+
[mcp_servers.sqlitehub]
|
|
41
|
+
command = "node"
|
|
42
|
+
args = ["/absolute/path/to/sqlite-hub/bin/sqlite-hub-mcp.js"]
|
|
43
|
+
startup_timeout_sec = 10
|
|
44
|
+
tool_timeout_sec = 60
|
|
45
|
+
```
|
|
46
|
+
|
|
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.
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
Read-only and safe tools:
|
|
52
|
+
|
|
53
|
+
- `list_connections`: list imported SQLite Hub database ids and labels.
|
|
54
|
+
- `get_database_overview`: inspect database health, SQLite metadata, table counts, and schema-map statistics.
|
|
55
|
+
- `list_tables`: list database tables.
|
|
56
|
+
- `describe_table`: inspect columns, indexes, foreign keys, triggers, and row counts for one table.
|
|
57
|
+
- `get_schema`: return tables, views, indexes, triggers, and raw schema entries.
|
|
58
|
+
- `get_indexes`: return all indexes or indexes for one table.
|
|
59
|
+
- `get_foreign_keys`: return all foreign keys or foreign keys for one table.
|
|
60
|
+
- `run_readonly_query`: execute a read-only `SELECT`, `PRAGMA`, or `EXPLAIN` query.
|
|
61
|
+
- `explain_query_plan`: run `EXPLAIN QUERY PLAN` and return structured plan rows plus index hints when a table scan appears.
|
|
62
|
+
- `read_documents`: read database-scoped Markdown documents.
|
|
63
|
+
|
|
64
|
+
Controlled write tools:
|
|
65
|
+
|
|
66
|
+
- `create_backup`: create a verified backup through SQLite Hub's existing backup mechanism.
|
|
67
|
+
- `generate_types`: generate TypeScript, Rust, Kotlin, or Swift types from one table or all tables.
|
|
68
|
+
- `create_chart_from_query`: create a saved chart from a read-only `SELECT` query. It writes chart metadata to SQLite Hub but does not export files.
|
|
69
|
+
|
|
70
|
+
## Security
|
|
71
|
+
|
|
72
|
+
`run_readonly_query` validates SQL server-side before execution. Only `SELECT`, `PRAGMA`, and `EXPLAIN` statements that return rows are allowed.
|
|
73
|
+
|
|
74
|
+
These statements are blocked in `run_readonly_query`:
|
|
75
|
+
|
|
76
|
+
- `INSERT`
|
|
77
|
+
- `UPDATE`
|
|
78
|
+
- `DELETE`
|
|
79
|
+
- `DROP`
|
|
80
|
+
- `ALTER`
|
|
81
|
+
- `CREATE`
|
|
82
|
+
- `ATTACH`
|
|
83
|
+
- `DETACH`
|
|
84
|
+
- `VACUUM`
|
|
85
|
+
- other non-reader or mutating statements
|
|
86
|
+
|
|
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.
|
|
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
|
+
|
|
91
|
+
## Settings Status
|
|
92
|
+
|
|
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.
|
|
94
|
+
|
|
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.
|
|
96
|
+
|
|
97
|
+
## Example Prompts
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
Use SQLite Hub MCP to inspect my current database schema and suggest missing indexes.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
Use SQLite Hub MCP to create a backup before generating TypeScript types.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
Use SQLite Hub MCP to explain the query plan for this SQL query.
|
|
109
|
+
```
|
package/docs/changelog.md
CHANGED
package/docs/todo.md
CHANGED
package/frontend/js/api.js
CHANGED
|
@@ -558,6 +558,10 @@ export function checkAppVersion() {
|
|
|
558
558
|
return request("/api/settings/version-check");
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
export function getSettingsMcpStatus() {
|
|
562
|
+
return request("/api/settings/mcp");
|
|
563
|
+
}
|
|
564
|
+
|
|
561
565
|
export function createApiToken(name) {
|
|
562
566
|
return request("/api/settings/api-tokens", {
|
|
563
567
|
method: "POST",
|
package/frontend/js/app.js
CHANGED
|
@@ -2577,6 +2577,16 @@ async function handleAction(actionNode) {
|
|
|
2577
2577
|
}
|
|
2578
2578
|
return;
|
|
2579
2579
|
}
|
|
2580
|
+
case 'copy-mcp-config': {
|
|
2581
|
+
const configNode = document.querySelector('[data-mcp-config]');
|
|
2582
|
+
const config = configNode instanceof HTMLTextAreaElement ? configNode.value : '';
|
|
2583
|
+
|
|
2584
|
+
if (config && navigator.clipboard?.writeText) {
|
|
2585
|
+
await navigator.clipboard.writeText(config);
|
|
2586
|
+
showToast('MCP config copied.', 'success');
|
|
2587
|
+
}
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2580
2590
|
case 'open-row-editor-url':
|
|
2581
2591
|
openRowEditorUrl(actionNode);
|
|
2582
2592
|
return;
|
package/frontend/js/store.js
CHANGED
|
@@ -332,6 +332,9 @@ const state = {
|
|
|
332
332
|
versionCheck: null,
|
|
333
333
|
versionCheckLoading: false,
|
|
334
334
|
versionCheckError: null,
|
|
335
|
+
mcpStatus: null,
|
|
336
|
+
mcpStatusLoading: false,
|
|
337
|
+
mcpStatusError: null,
|
|
335
338
|
},
|
|
336
339
|
overview: {
|
|
337
340
|
data: null,
|
|
@@ -1568,6 +1571,22 @@ async function refreshSettingsState() {
|
|
|
1568
1571
|
}
|
|
1569
1572
|
}
|
|
1570
1573
|
|
|
1574
|
+
async function refreshSettingsMcpStatus() {
|
|
1575
|
+
state.settings.mcpStatusLoading = true;
|
|
1576
|
+
state.settings.mcpStatusError = null;
|
|
1577
|
+
emitChange();
|
|
1578
|
+
|
|
1579
|
+
try {
|
|
1580
|
+
const response = await api.getSettingsMcpStatus();
|
|
1581
|
+
state.settings.mcpStatus = response.data ?? null;
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
state.settings.mcpStatusError = normalizeError(error);
|
|
1584
|
+
} finally {
|
|
1585
|
+
state.settings.mcpStatusLoading = false;
|
|
1586
|
+
emitChange();
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1571
1590
|
function buildLogRequestOptions({ append = false } = {}) {
|
|
1572
1591
|
const filters = normalizeLogFilters(state.logs.filters);
|
|
1573
1592
|
|
|
@@ -1674,7 +1693,8 @@ export async function createSettingsApiToken(name) {
|
|
|
1674
1693
|
}
|
|
1675
1694
|
|
|
1676
1695
|
export function setSettingsSection(section) {
|
|
1677
|
-
const normalizedSection =
|
|
1696
|
+
const normalizedSection =
|
|
1697
|
+
section === 'api-tokens' ? 'api-tokens' : section === 'mcp' ? 'mcp' : 'information';
|
|
1678
1698
|
|
|
1679
1699
|
if (state.settings.section === normalizedSection) {
|
|
1680
1700
|
return;
|
|
@@ -1726,7 +1746,9 @@ export async function loadMoreLogs() {
|
|
|
1726
1746
|
await loadLogs({ append: true });
|
|
1727
1747
|
}
|
|
1728
1748
|
|
|
1729
|
-
export async function checkSettingsAppVersion() {
|
|
1749
|
+
export async function checkSettingsAppVersion(options = {}) {
|
|
1750
|
+
const silent = Boolean(options.silent);
|
|
1751
|
+
|
|
1730
1752
|
state.settings.versionCheckLoading = true;
|
|
1731
1753
|
state.settings.versionCheckError = null;
|
|
1732
1754
|
emitChange();
|
|
@@ -1736,14 +1758,18 @@ export async function checkSettingsAppVersion() {
|
|
|
1736
1758
|
const result = response.data ?? null;
|
|
1737
1759
|
|
|
1738
1760
|
state.settings.versionCheck = result;
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1761
|
+
if (!silent) {
|
|
1762
|
+
pushToast(
|
|
1763
|
+
result?.updateAvailable ? `SQLite Hub v${result.latestVersion} is available.` : 'SQLite Hub is up to date.',
|
|
1764
|
+
'success',
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1743
1767
|
return result;
|
|
1744
1768
|
} catch (error) {
|
|
1745
1769
|
state.settings.versionCheckError = normalizeError(error);
|
|
1746
|
-
|
|
1770
|
+
if (!silent) {
|
|
1771
|
+
pushToast('Version check failed.', 'alert');
|
|
1772
|
+
}
|
|
1747
1773
|
return null;
|
|
1748
1774
|
} finally {
|
|
1749
1775
|
state.settings.versionCheckLoading = false;
|
|
@@ -2964,6 +2990,8 @@ async function loadRouteData(route, options = {}) {
|
|
|
2964
2990
|
return;
|
|
2965
2991
|
case 'settings':
|
|
2966
2992
|
await refreshSettingsState();
|
|
2993
|
+
await refreshSettingsMcpStatus();
|
|
2994
|
+
await checkSettingsAppVersion({ silent: true });
|
|
2967
2995
|
return;
|
|
2968
2996
|
default:
|
|
2969
2997
|
}
|
|
@@ -6,6 +6,7 @@ function renderSettingsNavigation(activeSection) {
|
|
|
6
6
|
const items = [
|
|
7
7
|
{ id: 'information', icon: 'info', label: 'Information' },
|
|
8
8
|
{ id: 'api-tokens', icon: 'key', label: 'API Tokens' },
|
|
9
|
+
{ id: 'mcp', icon: 'hub', label: 'MCP' },
|
|
9
10
|
];
|
|
10
11
|
|
|
11
12
|
return `
|
|
@@ -90,6 +91,187 @@ function renderVersionCheckStatus(settings) {
|
|
|
90
91
|
`;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
function renderMcpTimestamp(value) {
|
|
95
|
+
return value ? formatCompactDateTime(value) : 'Never';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function renderMcpStatusSummary(status) {
|
|
99
|
+
if (status?.error) {
|
|
100
|
+
return {
|
|
101
|
+
label: 'Server Error',
|
|
102
|
+
icon: 'error',
|
|
103
|
+
badgeClass: 'status-badge border-error/30 bg-error-container/20 text-error',
|
|
104
|
+
message: status.error,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (status?.connected) {
|
|
109
|
+
return {
|
|
110
|
+
label: 'Agent Connected',
|
|
111
|
+
icon: 'check_circle',
|
|
112
|
+
badgeClass: 'status-badge status-badge--success',
|
|
113
|
+
message: `Last tool call: ${status.lastToolName || 'none'} at ${renderMcpTimestamp(status.lastToolCallAt)}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (status?.enabled) {
|
|
118
|
+
return {
|
|
119
|
+
label: 'Waiting For Agent',
|
|
120
|
+
icon: 'radio_button_unchecked',
|
|
121
|
+
badgeClass: 'status-badge',
|
|
122
|
+
message: status.lastConnectedAt
|
|
123
|
+
? `Last agent connection: ${renderMcpTimestamp(status.lastConnectedAt)}`
|
|
124
|
+
: 'No MCP agent connected yet.',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
label: 'Stopped',
|
|
130
|
+
icon: 'block',
|
|
131
|
+
badgeClass: 'status-badge',
|
|
132
|
+
message: 'No MCP agent connected yet.',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderMcpMetric(label, value) {
|
|
137
|
+
return `
|
|
138
|
+
<div class="border border-outline-variant/10 bg-surface-container-high px-4 py-3">
|
|
139
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
140
|
+
${escapeHtml(label)}
|
|
141
|
+
</div>
|
|
142
|
+
<div class="mt-2 break-words font-mono text-sm text-on-surface">
|
|
143
|
+
${escapeHtml(value)}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function renderMcpSection(settings) {
|
|
150
|
+
if (settings.mcpStatusLoading && !settings.mcpStatus) {
|
|
151
|
+
return `
|
|
152
|
+
<div class="flex min-h-[240px] items-center justify-center border border-outline-variant/10 bg-surface-container-low">
|
|
153
|
+
<div class="text-center text-on-surface-variant/40">
|
|
154
|
+
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
155
|
+
<p class="font-mono text-[10px] uppercase tracking-[0.22em]">LOADING_MCP_STATUS</p>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (settings.mcpStatusError) {
|
|
162
|
+
return `
|
|
163
|
+
<div class="border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
|
|
164
|
+
<div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
|
|
165
|
+
${escapeHtml(settings.mcpStatusError.code ?? 'MCP_STATUS_FAILED')}
|
|
166
|
+
</div>
|
|
167
|
+
<div class="mt-2">${escapeHtml(settings.mcpStatusError.message ?? 'MCP status could not be loaded.')}</div>
|
|
168
|
+
</div>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const status = settings.mcpStatus ?? {};
|
|
173
|
+
const summary = renderMcpStatusSummary(status);
|
|
174
|
+
const tools = status.toolDetails?.length
|
|
175
|
+
? status.toolDetails
|
|
176
|
+
: (status.exposedTools ?? []).map(name => ({ name, description: '' }));
|
|
177
|
+
const codexConfig = status.codexConfig ?? '';
|
|
178
|
+
const command = status.command ?? 'sqlite-hub-mcp';
|
|
179
|
+
|
|
180
|
+
return `
|
|
181
|
+
<div class="space-y-6">
|
|
182
|
+
<section class="shell-section overflow-hidden">
|
|
183
|
+
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
184
|
+
<span class="text-[10px] font-bold uppercase tracking-[0.25em]">MCP Status</span>
|
|
185
|
+
<span class="material-symbols-outlined text-xs text-primary-container">hub</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="space-y-5 p-6">
|
|
188
|
+
<div class="flex flex-col gap-3 border border-outline-variant/10 bg-surface-container-high px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
|
|
189
|
+
<div class="min-w-0">
|
|
190
|
+
<div class="flex items-center gap-3">
|
|
191
|
+
<span class="material-symbols-outlined text-base text-primary-container">${summary.icon}</span>
|
|
192
|
+
<span class="${summary.badgeClass}">${escapeHtml(summary.label)}</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="mt-2 text-sm leading-6 text-on-surface-variant">
|
|
195
|
+
${escapeHtml(summary.message)}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-on-surface-variant/60">
|
|
199
|
+
${escapeHtml(status.transport ?? 'unknown')}
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="grid grid-cols-1 gap-3 md:grid-cols-3">
|
|
203
|
+
${renderMcpMetric('MCP Server', status.serverRunning ? 'Running' : 'Stopped')}
|
|
204
|
+
${renderMcpMetric('Agent connected', status.connected ? 'Yes' : 'No')}
|
|
205
|
+
${renderMcpMetric('Active clients', String(status.activeClientCount ?? 0))}
|
|
206
|
+
${renderMcpMetric('Last connected', renderMcpTimestamp(status.lastConnectedAt))}
|
|
207
|
+
${renderMcpMetric('Last disconnected', renderMcpTimestamp(status.lastDisconnectedAt))}
|
|
208
|
+
${renderMcpMetric('Last tool call', status.lastToolCallAt ? renderMcpTimestamp(status.lastToolCallAt) : 'Never')}
|
|
209
|
+
${renderMcpMetric('Last tool name', status.lastToolName ?? 'None')}
|
|
210
|
+
${renderMcpMetric('Transport', status.transport ?? 'unknown')}
|
|
211
|
+
${renderMcpMetric('Exposed tools', String(status.exposedTools?.length ?? tools.length))}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</section>
|
|
215
|
+
|
|
216
|
+
<section class="shell-section overflow-hidden">
|
|
217
|
+
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
218
|
+
<span class="text-[10px] font-bold uppercase tracking-[0.25em]">Tools</span>
|
|
219
|
+
<span class="material-symbols-outlined text-xs text-primary-container">construction</span>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="grid grid-cols-1 gap-3 p-6 md:grid-cols-2">
|
|
222
|
+
${tools
|
|
223
|
+
.map(
|
|
224
|
+
tool => `
|
|
225
|
+
<div class="border border-outline-variant/10 bg-surface-container-high px-4 py-3">
|
|
226
|
+
<div class="font-mono text-xs font-bold text-primary-container">${escapeHtml(tool.name)}</div>
|
|
227
|
+
<div class="mt-2 text-sm leading-6 text-on-surface-variant">${escapeHtml(tool.description || 'SQLite Hub MCP tool.')}</div>
|
|
228
|
+
</div>
|
|
229
|
+
`,
|
|
230
|
+
)
|
|
231
|
+
.join('')}
|
|
232
|
+
</div>
|
|
233
|
+
</section>
|
|
234
|
+
|
|
235
|
+
<section class="shell-section overflow-hidden">
|
|
236
|
+
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
237
|
+
<span class="text-[10px] font-bold uppercase tracking-[0.25em]">Codex Setup</span>
|
|
238
|
+
<span class="material-symbols-outlined text-xs text-primary-container">terminal</span>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="space-y-4 p-6">
|
|
241
|
+
<div>
|
|
242
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
243
|
+
MCP_ENDPOINT
|
|
244
|
+
</div>
|
|
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
|
+
${escapeHtml(command)}
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="grid grid-cols-1 gap-3 md:grid-cols-[minmax(0,1fr)_auto]">
|
|
250
|
+
<textarea class="settings-mcp-config-input custom-scrollbar control-input font-mono text-xs leading-6" readonly data-mcp-config>${escapeHtml(codexConfig)}</textarea>
|
|
251
|
+
<button class="standard-button self-start" data-action="copy-mcp-config" type="button">
|
|
252
|
+
<span class="material-symbols-outlined text-sm">content_copy</span>
|
|
253
|
+
Copy Config
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</section>
|
|
258
|
+
|
|
259
|
+
<section class="shell-section overflow-hidden">
|
|
260
|
+
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
261
|
+
<span class="text-[10px] font-bold uppercase tracking-[0.25em]">Security</span>
|
|
262
|
+
<span class="material-symbols-outlined text-xs text-primary-container">shield</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="space-y-2 p-6 text-sm leading-6 text-on-surface-variant">
|
|
265
|
+
<div>Read-only queries are limited to SELECT, PRAGMA, and EXPLAIN.</div>
|
|
266
|
+
<div>Mutating SQL statements are blocked server-side before execution.</div>
|
|
267
|
+
<div>Destructive actions must use existing SQLite Hub safety mechanisms such as verified backups.</div>
|
|
268
|
+
<div>The local HTTP MCP endpoint is bound to SQLite Hub's loopback server and does not expose API tokens.</div>
|
|
269
|
+
</div>
|
|
270
|
+
</section>
|
|
271
|
+
</div>
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
|
|
93
275
|
function renderApiTokenRow(token, tokenSaving) {
|
|
94
276
|
const callCount = Number(token.callCount ?? 0);
|
|
95
277
|
const createdAt = token.createdAt ? formatCompactDateTime(token.createdAt) : 'Unknown';
|
|
@@ -188,7 +370,8 @@ function renderSettingsContent(state) {
|
|
|
188
370
|
`;
|
|
189
371
|
}
|
|
190
372
|
|
|
191
|
-
const activeSection =
|
|
373
|
+
const activeSection =
|
|
374
|
+
state.settings.section === 'api-tokens' ? 'api-tokens' : state.settings.section === 'mcp' ? 'mcp' : 'information';
|
|
192
375
|
const appVersion = escapeHtml(state.settings.appVersion ?? '0.0.0');
|
|
193
376
|
const sqliteVersion = escapeHtml(state.settings.sqliteVersion ?? 'unknown');
|
|
194
377
|
const versionCheckLoading = Boolean(state.settings.versionCheckLoading);
|
|
@@ -377,11 +560,12 @@ function renderSettingsContent(state) {
|
|
|
377
560
|
</div>
|
|
378
561
|
</section>
|
|
379
562
|
`;
|
|
563
|
+
const mcpSection = renderMcpSection(state.settings);
|
|
380
564
|
|
|
381
565
|
return `
|
|
382
566
|
<div class="space-y-6">
|
|
383
567
|
${renderSettingsNavigation(activeSection)}
|
|
384
|
-
${activeSection === 'api-tokens' ? apiTokensSection : informationSection}
|
|
568
|
+
${activeSection === 'api-tokens' ? apiTokensSection : activeSection === 'mcp' ? mcpSection : informationSection}
|
|
385
569
|
</div>
|
|
386
570
|
`;
|
|
387
571
|
}
|
|
@@ -396,7 +580,9 @@ export function renderSettingsView(state) {
|
|
|
396
580
|
subtitle:
|
|
397
581
|
state.settings.section === 'api-tokens'
|
|
398
582
|
? 'Security // Database API access'
|
|
399
|
-
:
|
|
583
|
+
: state.settings.section === 'mcp'
|
|
584
|
+
? 'Agents // Local MCP access'
|
|
585
|
+
: 'Application // Build + Credits',
|
|
400
586
|
})}
|
|
401
587
|
${renderSettingsContent(state)}
|
|
402
588
|
</div>
|