dsh-db-mcp 0.6.9 → 0.7.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 +69 -347
- package/README.zh.md +42 -223
- package/docs/{requirements.md → design-notes.md} +5 -0
- package/docs/development.md +129 -0
- package/docs/guide.md +241 -0
- package/docs/reference.md +146 -0
- package/package.json +2 -2
- package/server/engines.py +2 -2
- package/server/main.py +13 -10
- package/src/host.js +1 -1
package/README.md
CHANGED
|
@@ -1,370 +1,92 @@
|
|
|
1
1
|
# dsh-db-mcp
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/dsh-db-mcp)
|
|
4
5
|
|
|
5
6
|
English | [中文](README.zh.md)
|
|
6
7
|
|
|
7
|
-
DSH plugin that
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
A [DSH](https://github.com/deepseek-ai/deepseek-harness) plugin that hands your
|
|
9
|
+
databases to the agent. It adds a **Database** panel to the sidebar and publishes
|
|
10
|
+
every registered source as `mcp__db__*` tools.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
and never come back to the page.
|
|
14
|
-
- Import the connections DBeaver already has, including its encrypted passwords.
|
|
15
|
-
- Read-only by default, with two independent write gates and an audit log.
|
|
16
|
-
- The agent gets eight MCP tools, not a SQL editor in the UI.
|
|
12
|
+
**Zero configuration.** There is nothing to edit in DSH and no connection to type
|
|
13
|
+
twice: import the connections DBeaver already knows, then just ask.
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
## Zero configuration, in three senses
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
- **No DSH files.** The bundle patch inserts one Loader row. The plugin starts its
|
|
18
|
+
own Python MCP server over stdio and registers the tools on the host `tools`
|
|
19
|
+
service — no `dsh-mcp-client` row, no JSON to hand-edit, no env var to export.
|
|
20
|
+
- **No data source to fill in.** The importer reads `data-sources.json` from the
|
|
21
|
+
DBeaver workspace and decrypts `credentials-config.json` with DBeaver's own key,
|
|
22
|
+
so host, port, database, user and saved password arrive together. Ticking a box
|
|
23
|
+
is the whole setup step.
|
|
24
|
+
- **No Python to prepare.** The panel finds an interpreter, creates the venv and
|
|
25
|
+
installs the four driver packages from **Runtime → Install / repair
|
|
26
|
+
dependencies**.
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
`/plugins/db-mcp/api` from the DSH web server.
|
|
24
|
-
|
|
25
|
-
| Route | Purpose |
|
|
26
|
-
| ----- | ------- |
|
|
27
|
-
| `GET /health` | plugin version, Host facts, Python discovery, runtime paths, registry state, MCP status, settings |
|
|
28
|
-
| `GET /datasources` | registry, credentials projected away |
|
|
29
|
-
| `GET /schema?datasource=…&table=…[&schema=…]` | read-only structure: tables, or the columns of one table |
|
|
30
|
-
| `GET /groups` | declared groups plus every group label in use |
|
|
31
|
-
| `GET /dbeaver/scan` | connections already configured in DBeaver |
|
|
32
|
-
| `GET /mcp/status` | Python server phase, tool list, last error, stderr tail |
|
|
33
|
-
| `GET /settings` · `POST /settings` | global write switch and audit switch |
|
|
34
|
-
| `GET /audit?limit=N` | the tail of the tool-call audit log |
|
|
35
|
-
| `GET /python/env` | interpreter, venv path, installed driver versions |
|
|
36
|
-
| `POST /python/setup` | create the venv and `pip install -r server/requirements.txt` |
|
|
37
|
-
| `POST /datasources/save` | create or update a source (`id` is immutable) |
|
|
38
|
-
| `POST /datasources/delete` | remove a source and its stored secret |
|
|
39
|
-
| `POST /datasources/test` | real login probe: SQLite over `node:sqlite`, the rest through the Python drivers |
|
|
40
|
-
| `POST /datasources/bulk` | test / read-only / group / delete a set of sources |
|
|
41
|
-
| `POST /groups/save` · `POST /groups/delete` | create, rename or delete a group |
|
|
42
|
-
| `POST /dbeaver/import` | copy selected DBeaver connections into the registry |
|
|
43
|
-
| `POST /mcp/start` · `POST /mcp/restart` | start or restart the Python MCP server |
|
|
44
|
-
| `POST /mcp/call` | dev helper: call one MCP tool through the panel |
|
|
45
|
-
| `POST /reload` | dev helper: re-import `src/host.js` without restarting DSH |
|
|
46
|
-
|
|
47
|
-
- **Client half** (`client.js`): sidebar entry (`db-mcp`, order 25) plus the main
|
|
48
|
-
panel with the source list, the minimal create/edit form (one connection string),
|
|
49
|
-
test/delete/default actions, a read-only *structure* browser, the DBeaver import
|
|
50
|
-
flow, a *Restart MCP* button, the runtime diagnostics card (tool count, global
|
|
51
|
-
write switch, venv and package versions with a one-click install) and an
|
|
52
|
-
*audit log* card. Copy is localized (zh/en) through the client `locale` service.
|
|
53
|
-
- **Python server** (`server/`): `main.py` (FastMCP, eight tools), `engines.py`
|
|
54
|
-
(per-engine connect/introspect/query), `store.py` (reads the registry).
|
|
55
|
-
The plugin spawns it over stdio and publishes its tools as `mcp__db__*` on the
|
|
56
|
-
host `tools` service — it does not go through a `dsh-mcp-client` Loader row.
|
|
57
|
-
|
|
58
|
-
### Tools the agent gets
|
|
59
|
-
|
|
60
|
-
| Tool | Purpose |
|
|
61
|
-
| ---- | ------- |
|
|
62
|
-
| `mcp__db__list_datasources` | the registered sources (id, kind, target, read-only) |
|
|
63
|
-
| `mcp__db__list_tables` | tables/views of one source |
|
|
64
|
-
| `mcp__db__describe_table` | columns, types, nullability, primary key |
|
|
65
|
-
| `mcp__db__sample_rows` | a few rows of one table |
|
|
66
|
-
| `mcp__db__run_query` | one statement; read-only sources refuse writes |
|
|
67
|
-
| `mcp__db__explain_query` | query plan |
|
|
68
|
-
| `mcp__db__get_ddl` | `CREATE TABLE` statement (SQLite: as created; others: rebuilt from the catalog) |
|
|
69
|
-
| `mcp__db__test_connection` | log in for real, run `select 1`, report server/user/schema |
|
|
70
|
-
|
|
71
|
-
`datasource` is optional everywhere: omitted, the default source is used.
|
|
72
|
-
|
|
73
|
-
### Python environment
|
|
74
|
-
|
|
75
|
-
Interpreter discovery order: plugin config `pythonPath` → `<DSH_HOME>/db-mcp/.venv/Scripts/python.exe` → that venv on POSIX → `python` → `python3`. Dependencies live in the runtime venv, not in the package:
|
|
76
|
-
|
|
77
|
-
python -m venv C:\Users\<you>\.dsh\db-mcp\.venv
|
|
78
|
-
C:\Users\<you>\.dsh\db-mcp\.venv\Scripts\python.exe -m pip install -r server\requirements.txt
|
|
79
|
-
|
|
80
|
-
`mcp` must stay on 1.x (2.x renamed `FastMCP` to `MCPServer`).
|
|
81
|
-
|
|
82
|
-
## Does the Test button really log in?
|
|
83
|
-
|
|
84
|
-
Yes. For PostgreSQL, MySQL and Oracle the panel's **Test** action calls
|
|
85
|
-
`mcp__db__test_connection` over the Python MCP server, so the probe is a real
|
|
86
|
-
authenticated login plus `select 1` (Oracle uses `select 1 from dual`). The
|
|
87
|
-
panel row then shows the server banner, the connected user and database, and how
|
|
88
|
-
long the login took, for example:
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
Oracle AI Database 26ai Free Release 23.26.3.0.0 · DEMO@FREEPDB1 · select 1 from dual ok
|
|
92
|
-
PostgreSQL 15.19 · demo@demo · select version() ok
|
|
93
|
-
MySQL 8.0.46 · demo@%@demo · select version() ok
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
A wrong password or an unreachable listener now surfaces the driver's own
|
|
97
|
-
message (`ORA-01017`, `DPY-6005`, `password authentication failed` — instead of
|
|
98
|
-
"port reachable"). Testing an unsaved form works too: `test_connection` accepts
|
|
99
|
-
`kind` + `config` + `password` without writing the registry first. When the
|
|
100
|
-
Python server cannot start, the probe degrades to a TCP check and says so.
|
|
101
|
-
|
|
102
|
-
## Filling the form from one string
|
|
103
|
-
|
|
104
|
-
The create/edit form is deliberately as small as DBeaver's connection dialog:
|
|
105
|
-
**connection string**, **user**, **password** (with a *save password* box), and
|
|
106
|
-
that is it. Pasting a JDBC URL, a DSN, an Oracle TNS descriptor or a `.db` path
|
|
107
|
-
fills type, host, port, database/service, name and identifier; the derived values
|
|
108
|
-
only show up under **More settings**, together with schema, SID, SSL mode,
|
|
109
|
-
charset and the read-only switch.
|
|
110
|
-
|
|
111
|
-
Accepted shapes (see `dev/parse.test.mjs` for the full matrix):
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
postgresql://alice:secret@db.example.com:6543/orders?sslmode=require
|
|
115
|
-
jdbc:postgresql://localhost:5432/appdb
|
|
116
|
-
mysql://root:pw@127.0.0.1:3306/shop?charset=utf8mb4
|
|
117
|
-
jdbc:mariadb://10.0.0.5:3307/legacy
|
|
118
|
-
oracle://system:oracle@localhost:1521/XEPDB1
|
|
119
|
-
jdbc:oracle:thin:@//ora:1521/ORCLPDB1 # service name
|
|
120
|
-
jdbc:oracle:thin:@ora:1521:ORCL # SID
|
|
121
|
-
(DESCRIPTION=(ADDRESS=(HOST=tns-host)(PORT=1522))(CONNECT_DATA=(SERVICE_NAME=svc1)))
|
|
122
|
-
jdbc:sqlite:C:\data\app.db
|
|
123
|
-
sqlite:///C:/data/chinook.db
|
|
124
|
-
C:\data\app.db / /var/lib/app.sqlite3
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
A password found inside the URL is sent once to `POST /datasources/save` and
|
|
128
|
-
then only stored in `secrets.json`; it is never echoed back.
|
|
129
|
-
|
|
130
|
-
Writes are `POST` only (the same-origin check compares `Origin` with `Host`).
|
|
131
|
-
Passwords never enter `datasources.json`; they live in `secrets.json` and are
|
|
132
|
-
never returned to the page (`hasSecret` is the only signal).
|
|
133
|
-
|
|
134
|
-
## DBeaver import
|
|
135
|
-
|
|
136
|
-
`GET /dbeaver/scan` looks for `data-sources.json` under
|
|
137
|
-
`%APPDATA%\DBeaverData\workspace*\General\.dbeaver\` (plus the `General` and
|
|
138
|
-
Linux/macOS layouts). When the sibling `credentials-config.json` exists it is
|
|
139
|
-
decrypted with DBeaver's built-in key (AES-128-CBC, zero IV, 16-byte prefix — see `Tatsh/dbeaver-creds`). A workspace that never saved passwords simply
|
|
140
|
-
imports the connection with an empty password, which the UI shows as
|
|
141
|
-
`no password`.
|
|
142
|
-
|
|
143
|
-
Connections created through the DBeaver driver dialog carry only a JDBC URL
|
|
144
|
-
(`configurationType: URL`) — no `database` field at all and no `user`. The
|
|
145
|
-
importer therefore parses `jdbc:mysql://host:port/db`,
|
|
146
|
-
`jdbc:postgresql://…` and `jdbc:oracle:thin:@…` for whatever the form fields
|
|
147
|
-
omit, and takes the user from the decrypted credentials when it is stored
|
|
148
|
-
there. The imported source is named after the DBeaver connection name (slugged
|
|
149
|
-
into an id), keeps `readOnly: true`, and gets the saved password copied into
|
|
150
|
-
`secrets.json`. Anything still missing is reported in `warnings`.
|
|
151
|
-
|
|
152
|
-
A connection that is already configured is not offered twice: every source
|
|
153
|
-
is identified by kind + host + port + database/service/SID + user (SQLite by
|
|
154
|
-
file path), and a DBeaver entry that matches one of them comes back with
|
|
155
|
-
`alreadyImported: true`, `duplicateId` and `duplicateName`. The panel disables
|
|
156
|
-
its checkbox and says *already added as …*; `POST /dbeaver/import` reports such
|
|
157
|
-
ids under `skipped` instead of creating a second copy.
|
|
158
|
-
|
|
159
|
-
The panel navigates with a tab bar (*Data source list / New data source /
|
|
160
|
-
Import from DBeaver / Runtime / Audit log*): the active step is plain text, every
|
|
161
|
-
earlier step is a link back, and no page needs a separate "back to list" button.
|
|
162
|
-
|
|
163
|
-
## Test databases in Docker
|
|
164
|
-
|
|
165
|
-
Nothing has to be installed on the host; every engine below is a throwaway
|
|
166
|
-
container with a demo account (`demo` / `dbmcp-demo`).
|
|
28
|
+
## Run
|
|
167
29
|
|
|
30
|
+
```sh
|
|
31
|
+
dsh plugin --profile desktop add dsh-db-mcp # desktop app
|
|
32
|
+
dsh plugin --profile web add dsh-db-mcp # web UI
|
|
168
33
|
```
|
|
169
|
-
# PostgreSQL — smallest and fastest to start, the recommended first target
|
|
170
|
-
docker run -d --name dbmcp-pg -p 5432:5432 \
|
|
171
|
-
-e POSTGRES_USER=demo -e POSTGRES_PASSWORD=dbmcp-demo -e POSTGRES_DB=demo \
|
|
172
|
-
postgres:15-alpine
|
|
173
|
-
|
|
174
|
-
# MySQL
|
|
175
|
-
docker run -d --name dbmcp-mysql -p 3306:3306 \
|
|
176
|
-
-e MYSQL_USER=demo -e MYSQL_PASSWORD=dbmcp-demo -e MYSQL_DATABASE=demo \
|
|
177
|
-
-e MYSQL_ROOT_PASSWORD=dbmcp-root mysql:8.0-debian
|
|
178
34
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```
|
|
35
|
+
`--profile` picks the profile to install into (`desktop` and `web` ship with DSH;
|
|
36
|
+
any other name is created on first use). Remove with
|
|
37
|
+
`dsh plugin --profile desktop remove dsh-db-mcp`.
|
|
183
38
|
|
|
184
|
-
|
|
185
|
-
fails with *"An attempt was made to access a socket in a way forbidden by its
|
|
186
|
-
access permissions"*. Check `netsh interface ipv4 show excludedportrange
|
|
187
|
-
protocol=tcp` and publish another host port instead, e.g. `-p 11521:1521`; the
|
|
188
|
-
datasource then points at port 11521.
|
|
39
|
+
Then:
|
|
189
40
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`oracle://system:dbmcp-demo@127.0.0.1:1521/FREEPDB1`.
|
|
41
|
+
1. Sidebar → **Database**.
|
|
42
|
+
2. **Import from DBeaver** → tick the connections → **Import selected**.
|
|
43
|
+
3. Ask the agent something like *"which tables does my Chinook database have?"*
|
|
194
44
|
|
|
195
|
-
|
|
45
|
+
A connection whose password was never saved shows **no password**; open the source
|
|
46
|
+
once and type it — it goes to `secrets.json` and never comes back to the page.
|
|
196
47
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
48
|
+
Prefer to start from scratch? **New data source** takes one connection string
|
|
49
|
+
(`postgresql://user:pw@host:5432/db`, a JDBC URL, an Oracle TNS descriptor or a
|
|
50
|
+
`.db` path) and fills the form from it.
|
|
200
51
|
|
|
201
|
-
|
|
52
|
+
## What the agent gets
|
|
202
53
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
## Groups and bulk actions
|
|
239
|
-
|
|
240
|
-
Every source carries an optional **group** label (`group` in the registry,
|
|
241
|
-
editable under *More settings*). Groups are user-owned: type a name in the
|
|
242
|
-
**New group** box to create one, **double-click a group name to rename it** (or
|
|
243
|
-
the delete button to drop the group), and drag a source row onto a group to move
|
|
244
|
-
it there (drag it onto **Ungrouped** to clear the label). Double-clicking a
|
|
245
|
-
source name renames just that source — its id never changes. The list renders one
|
|
246
|
-
section per group, sorted by name with the ungrouped sources last; while no group
|
|
247
|
-
exists the list keeps its flat look.
|
|
248
|
-
|
|
249
|
-
Each group header carries a select-all box, so sources inside a group take part
|
|
250
|
-
in bulk work exactly like loose ones. Tick any row to open the bulk bar: test all
|
|
251
|
-
selected sources, flip them to read-only or writable, move them into a group, or
|
|
252
|
-
delete them (two-step confirmation). Bulk moves go through
|
|
253
|
-
`POST /datasources/bulk` with
|
|
254
|
-
`{ action: 'test' | 'readOnly' | 'group' | 'delete', ids: [...], … }`, while group
|
|
255
|
-
CRUD uses `GET /groups`, `POST /groups/save` and `POST /groups/delete`.
|
|
256
|
-
|
|
257
|
-
## Releasing
|
|
258
|
-
|
|
259
|
-
The package is published from GitHub Actions with [npm trusted publishing](https://docs.npmjs.com/trusted-publishers):
|
|
260
|
-
`.github/workflows/publish.yml` trades the workflow's OIDC token for a short-lived
|
|
261
|
-
publish credential, so no `NPM_TOKEN` secret exists anywhere.
|
|
262
|
-
|
|
263
|
-
```bash
|
|
264
|
-
node dev/bump.mjs 0.6.5 # package.json + src/host.js
|
|
265
|
-
git commit -am "chore: release 0.6.5"
|
|
266
|
-
git tag v0.6.5 && git push origin main --tags
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
The workflow refuses to publish when the tag and `package.json` disagree, runs the
|
|
270
|
-
portable suites (`npm run test:ci`), publishes with `--provenance`, then waits for the
|
|
271
|
-
version to appear in the registry. Local `npm publish` still works for the very first
|
|
272
|
-
release, before a trusted publisher exists for the package.
|
|
273
|
-
|
|
274
|
-
## Development
|
|
275
|
-
|
|
276
|
-
```
|
|
277
|
-
node dev/engines.test.mjs # 11 checks: engines.py commit/rollback + affectedRows, stub connection
|
|
278
|
-
node dev/selftest.mjs # mounts src/host.js on a throwaway port, 46 checks
|
|
279
|
-
node dev/shims.test.mjs # mounts index.js, checks routes, /reload, tool publication
|
|
280
|
-
node dev/parse.test.mjs # 24 checks: connection-string parser (DSN/JDBC/TNS/paths)
|
|
281
|
-
node dev/render.test.mjs # 40 checks: components and the result renderer, stub React
|
|
282
|
-
node dev/i18n.test.mjs # 12 checks: en/zh parity, no dead keys, no stray copy
|
|
283
|
-
node dev/p5.test.mjs # 15 checks: settings, audit log, python environment
|
|
284
|
-
node dev/mcp.test.mjs # 19 checks: real Python server over stdio, real SQLite + Oracle logins
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
`npm test` runs everything except `mcp.test.mjs`, which needs the venv and the
|
|
288
|
-
Oracle container. `dev/engines.test.mjs` needs a Python interpreter (the plugin
|
|
289
|
-
venv, else `python` on PATH) but no database: it drives `server/engines.py`
|
|
290
|
-
against a stub DB-API connection, covering the commit/rollback and
|
|
291
|
-
`affectedRows` contract of `run_query`. `node dev/bump.mjs 0.6.6` updates both
|
|
292
|
-
version strings.
|
|
293
|
-
|
|
294
|
-
## Safety switches
|
|
295
|
-
|
|
296
|
-
Two independent gates decide whether a statement may write:
|
|
297
|
-
|
|
298
|
-
1. **Global switch** — `allowWrite` in `<runtime>/settings.json`, toggled from the
|
|
299
|
-
diagnostics card. Off by default; while off every write statement is refused
|
|
300
|
-
with `user_error` and a hint pointing at the switch.
|
|
301
|
-
2. **Per-source flag** — `readOnly` on the data source, on by default. A source
|
|
302
|
-
stays read-only even when the global switch is on until you clear its flag.
|
|
303
|
-
Click the *read-only* / *writable* pill on a source row to flip it in one
|
|
304
|
-
click; the pill turns amber while that source accepts writes, and the notice
|
|
305
|
-
reminds you when the global switch is still off.
|
|
306
|
-
|
|
307
|
-
The Python server reads the global flag from `DB_MCP_ALLOW_WRITE` at start-up; the
|
|
308
|
-
host restarts it whenever the switch flips. Read-only is also enforced by the
|
|
309
|
-
driver itself where the engine allows it (`mode=ro` for SQLite,
|
|
310
|
-
`default_transaction_read_only` for PostgreSQL, `SET SESSION TRANSACTION READ ONLY`
|
|
311
|
-
for MySQL).
|
|
312
|
-
|
|
313
|
-
### What the plugin runs, and with what
|
|
314
|
-
|
|
315
|
-
- The Python server is started with a fixed interpreter path, a fixed script and a
|
|
316
|
-
fixed argument list — never through a shell, so no string is ever re-parsed by
|
|
317
|
-
`cmd.exe` or `/bin/sh`.
|
|
318
|
-
- The child does **not** inherit the host environment. It gets a short allowlist
|
|
319
|
-
(`PATH`, `TEMP`, `HOME`, `PYTHON*`, and similar) plus the three `DB_MCP_*` flags
|
|
320
|
-
the plugin sets. Tokens or cloud credentials in the DSH process are not passed
|
|
321
|
-
down.
|
|
322
|
-
- Connections to PostgreSQL, MySQL and Oracle are opened by that child; only SQLite
|
|
323
|
-
is read in-process. The raw TCP probe is a fallback used when the child cannot
|
|
324
|
-
start, and it never sends credentials.
|
|
325
|
-
- Passwords live in `secrets.json` (mode `0600`) and are never returned by any HTTP
|
|
326
|
-
route; the panel only learns `hasSecret`.
|
|
327
|
-
|
|
328
|
-
## Audit log
|
|
329
|
-
|
|
330
|
-
Every MCP tool call appends one JSON line to `<runtime>/logs/audit.jsonl` (0600):
|
|
331
|
-
time, tool, source, duration, row count, the SQL truncated to 2000 characters and
|
|
332
|
-
the error when it failed. Result rows are never logged. The file rotates to
|
|
333
|
-
`audit.1.jsonl` at 512 KiB and logging can be turned off with `auditLog: false` in
|
|
334
|
-
`settings.json`. The panel shows the last dozen entries on **your own clock** —
|
|
335
|
-
the file keeps UTC so it stays comparable across machines, the card converts each
|
|
336
|
-
stamp to the viewer's time zone and prints the offset it used.
|
|
337
|
-
|
|
338
|
-
`dev/shims.test.mjs` also asserts that `package.json` has no UTF-8 BOM: the host
|
|
339
|
-
parses it with `JSON.parse`, which rejects a BOM, and the plugin then shows up as
|
|
340
|
-
`availability: missing` instead of loading. Editors that "add BOM" will break the
|
|
341
|
-
bundle silently — if the sidebar entry disappears after a restart, check this
|
|
342
|
-
first.
|
|
343
|
-
|
|
344
|
-
## Stage map
|
|
345
|
-
|
|
346
|
-
| Stage | Content |
|
|
347
|
-
| ----- | ------- |
|
|
348
|
-
| P1 | installable skeleton: sidebar entry, empty panel, Host health API (done) |
|
|
349
|
-
| P2 | data-source CRUD, connection test, DBeaver import (done) |
|
|
350
|
-
| P3 | Python MCP server, tools published as `mcp__db__*` (done) |
|
|
351
|
-
| P4 | PostgreSQL / MySQL / Oracle dialects (done) |
|
|
352
|
-
| P5 | diagnostics, one-click venv, reload, audit log, global write switch (done) |
|
|
353
|
-
| P6 | i18n polish and docs (done) |
|
|
354
|
-
| P7 | read-only schema overview, groups, bulk actions (done) |
|
|
54
|
+
| Tool | Purpose |
|
|
55
|
+
| ---- | ------- |
|
|
56
|
+
| `mcp__db__list_datasources` | registered sources: id, kind, target, read-only |
|
|
57
|
+
| `mcp__db__list_tables` | tables and views of a source |
|
|
58
|
+
| `mcp__db__describe_table` | columns, types, nullability, primary key |
|
|
59
|
+
| `mcp__db__sample_rows` | a few rows of a table |
|
|
60
|
+
| `mcp__db__execute_sql` | execute one statement — a query, or a write when that is allowed |
|
|
61
|
+
| `mcp__db__explain_query` | query plan |
|
|
62
|
+
| `mcp__db__get_ddl` | `CREATE TABLE` statement |
|
|
63
|
+
| `mcp__db__test_connection` | real login, `select 1`, server/user/schema |
|
|
64
|
+
|
|
65
|
+
`datasource` is optional on all of them: omitted, the default source is used.
|
|
66
|
+
Queries run **in the conversation**; the panel is for managing connections, not a
|
|
67
|
+
SQL console.
|
|
68
|
+
|
|
69
|
+
## Safety
|
|
70
|
+
|
|
71
|
+
- **Read-only by default**, with two independent gates: a global write switch and a
|
|
72
|
+
per-source flag. Both must be open before a statement may write.
|
|
73
|
+
- **Passwords stay local.** They live in `secrets.json` (mode `0600`); no HTTP route
|
|
74
|
+
ever returns one, and the Python child receives a short environment allowlist
|
|
75
|
+
rather than the whole DSH environment.
|
|
76
|
+
- **Every tool call is audited** to `logs/audit.jsonl` — tool, source, duration, row
|
|
77
|
+
count, SQL. Result rows are never logged.
|
|
78
|
+
- **Nothing runs from the panel except catalog reads.** The structure browser only
|
|
79
|
+
forwards `list_tables` and `describe_table`.
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
| Document | Contents |
|
|
84
|
+
| -------- | -------- |
|
|
85
|
+
| [docs/guide.md](docs/guide.md) | Panel walkthrough, connection strings, groups, Docker test databases, troubleshooting |
|
|
86
|
+
| [docs/reference.md](docs/reference.md) | HTTP routes, tool contracts, registry and settings files, plugin config |
|
|
87
|
+
| [docs/development.md](docs/development.md) | Repository layout, hot reload, test suites, release flow |
|
|
88
|
+
| [docs/design-notes.md](docs/design-notes.md) | Historical design log (P1 → P7) and the decisions behind them |
|
|
355
89
|
|
|
356
90
|
## License
|
|
357
91
|
|
|
358
92
|
Apache License 2.0 — see [LICENSE](LICENSE).
|
|
359
|
-
|
|
360
|
-
## Runtime layout
|
|
361
|
-
|
|
362
|
-
```
|
|
363
|
-
$DSH_HOME/db-mcp/
|
|
364
|
-
datasources.json # profiles, never passwords, 0600
|
|
365
|
-
secrets.json # per-source password, 0600, Host-only
|
|
366
|
-
settings.json # global write switch + audit switch, 0600
|
|
367
|
-
logs/mcp.log # masked log
|
|
368
|
-
logs/audit.jsonl # one line per MCP tool call, rotates at 512 KiB
|
|
369
|
-
.venv/ # plugin-local venv: mcp, psycopg, PyMySQL, oracledb
|
|
370
|
-
```
|