dsh-db-mcp 0.6.10 → 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 +7 -2
- package/README.zh.md +6 -2
- package/docs/development.md +36 -6
- package/docs/guide.md +28 -10
- package/docs/reference.md +2 -2
- package/package.json +1 -1
- package/server/engines.py +2 -2
- package/server/main.py +13 -10
- package/src/host.js +1 -1
package/README.md
CHANGED
|
@@ -28,9 +28,14 @@ twice: import the connections DBeaver already knows, then just ask.
|
|
|
28
28
|
## Run
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
dsh plugin add dsh-db-mcp
|
|
31
|
+
dsh plugin --profile desktop add dsh-db-mcp # desktop app
|
|
32
|
+
dsh plugin --profile web add dsh-db-mcp # web UI
|
|
32
33
|
```
|
|
33
34
|
|
|
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`.
|
|
38
|
+
|
|
34
39
|
Then:
|
|
35
40
|
|
|
36
41
|
1. Sidebar → **Database**.
|
|
@@ -52,7 +57,7 @@ Prefer to start from scratch? **New data source** takes one connection string
|
|
|
52
57
|
| `mcp__db__list_tables` | tables and views of a source |
|
|
53
58
|
| `mcp__db__describe_table` | columns, types, nullability, primary key |
|
|
54
59
|
| `mcp__db__sample_rows` | a few rows of a table |
|
|
55
|
-
| `
|
|
60
|
+
| `mcp__db__execute_sql` | execute one statement — a query, or a write when that is allowed |
|
|
56
61
|
| `mcp__db__explain_query` | query plan |
|
|
57
62
|
| `mcp__db__get_ddl` | `CREATE TABLE` statement |
|
|
58
63
|
| `mcp__db__test_connection` | real login, `select 1`, server/user/schema |
|
package/README.zh.md
CHANGED
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
## 运行
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
|
-
dsh plugin add dsh-db-mcp
|
|
21
|
+
dsh plugin --profile desktop add dsh-db-mcp # 桌面端
|
|
22
|
+
dsh plugin --profile web add dsh-db-mcp # Web 端
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
`--profile` 指定装进哪个 profile(随 DSH 提供的是 `desktop` 与 `web`,其它名字首次使用时自动创建)。卸载用
|
|
26
|
+
`dsh plugin --profile desktop remove dsh-db-mcp`。
|
|
27
|
+
|
|
24
28
|
然后:
|
|
25
29
|
|
|
26
30
|
1. 侧栏 → **数据库**。
|
|
@@ -39,7 +43,7 @@ dsh plugin add dsh-db-mcp@latest
|
|
|
39
43
|
| `mcp__db__list_tables` | 某个源里的表与视图 |
|
|
40
44
|
| `mcp__db__describe_table` | 列、类型、可空、主键 |
|
|
41
45
|
| `mcp__db__sample_rows` | 取几行样本 |
|
|
42
|
-
| `
|
|
46
|
+
| `mcp__db__execute_sql` | 执行一条语句——查询,或在允许写入时执行写入 |
|
|
43
47
|
| `mcp__db__explain_query` | 执行计划 |
|
|
44
48
|
| `mcp__db__get_ddl` | `CREATE TABLE` 语句 |
|
|
45
49
|
| `mcp__db__test_connection` | 真实登录 + `select 1`,回报服务器/用户/schema |
|
package/docs/development.md
CHANGED
|
@@ -38,8 +38,8 @@ The Cordis Loader imports a plugin entry **once per process**, so editing
|
|
|
38
38
|
button) replaces it. Tool registrations stay in place — disposing and re-registering
|
|
39
39
|
them makes the agent's tool catalog go stale and report `unknown tool`.
|
|
40
40
|
|
|
41
|
-
A profile that links this directory (`dsh plugin add <path>`)
|
|
42
|
-
immediately; one installed from npm needs a release.
|
|
41
|
+
A profile that links this directory (`dsh plugin --profile desktop add <path>`)
|
|
42
|
+
reflects edits immediately; one installed from npm needs a release.
|
|
43
43
|
|
|
44
44
|
## Tests
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ npm run test:engines # server/engines.py against a stub DB-API connection
|
|
|
52
52
|
|
|
53
53
|
| Suite | Covers |
|
|
54
54
|
| ----- | ------ |
|
|
55
|
-
| `dev/engines.test.mjs` + `engines.test.py` | commit/rollback and `affectedRows` contract of `
|
|
55
|
+
| `dev/engines.test.mjs` + `engines.test.py` | commit/rollback and `affectedRows` contract of `execute_sql`, no database needed |
|
|
56
56
|
| `dev/selftest.mjs` | mounts `src/host.js` on a throwaway port: registry, validation, groups, bulk, schema route, DBeaver scan, probes |
|
|
57
57
|
| `dev/shims.test.mjs` | mounts `index.js`, checks both routes, `/reload`, tool publication, and that `package.json` has no BOM |
|
|
58
58
|
| `dev/parse.test.mjs` | the connection-string parser (DSN, JDBC, TNS, SID, paths) |
|
|
@@ -72,10 +72,10 @@ The package publishes from GitHub Actions with
|
|
|
72
72
|
publish credential, so no `NPM_TOKEN` secret exists.
|
|
73
73
|
|
|
74
74
|
```sh
|
|
75
|
-
node dev/bump.mjs 0.
|
|
76
|
-
git commit -am "chore: release 0.
|
|
75
|
+
node dev/bump.mjs 0.7.0 # package.json + src/host.js, both
|
|
76
|
+
git commit -am "chore: release 0.7.0"
|
|
77
77
|
git push origin main
|
|
78
|
-
git tag v0.
|
|
78
|
+
git tag v0.7.0 && git push origin v0.7.0
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
The workflow refuses to publish when the tag and `package.json` disagree, runs
|
|
@@ -86,6 +86,36 @@ a trusted publisher exists.
|
|
|
86
86
|
Keep `src/host.js`'s `PLUGIN_VERSION` in step with `package.json` — the panel and
|
|
87
87
|
`/health` read it, and a mismatch ships a package that reports the previous version.
|
|
88
88
|
|
|
89
|
+
Installing that release into a profile (the profile name is required, the verb is
|
|
90
|
+
pnpm's):
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
dsh plugin --profile desktop add dsh-db-mcp@0.7.0
|
|
94
|
+
dsh plugin --profile desktop remove dsh-db-mcp
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Expect pnpm to print `Added 1 entry to minimumReleaseAgeExclude …` when the version
|
|
98
|
+
is younger than its 1440-minute cooldown: that line is pnpm recording a per-version
|
|
99
|
+
exception so the explicit pin may install. See
|
|
100
|
+
[guide.md](guide.md#install-and-upgrade) for the policy knobs.
|
|
101
|
+
|
|
102
|
+
## When git says "not a git repository"
|
|
103
|
+
|
|
104
|
+
Three times on this machine `git status` answered `fatal: not a git repository` while
|
|
105
|
+
`.git/objects` still held every commit: `HEAD`, `config` and `index` had vanished,
|
|
106
|
+
and git refuses to run at all without `HEAD` (even with an explicit `--git-dir`).
|
|
107
|
+
The history was never lost, so it can be rebuilt from the refs:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
node dev/git-repair.mjs --check # report what is missing
|
|
111
|
+
node dev/git-repair.mjs # write HEAD + config, then git reset to rebuild the index
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The remote URL comes from `package.json`'s `repository`, and the branch is the one
|
|
115
|
+
with the newest ref, so the script stays machine independent. It is a repair, not a
|
|
116
|
+
backup: if `.git/objects` itself is missing, restore from a bundle instead
|
|
117
|
+
(`git bundle create backup.bundle --all` in a healthy checkout, then `git clone`).
|
|
118
|
+
|
|
89
119
|
## Adding an engine
|
|
90
120
|
|
|
91
121
|
1. `server/engines.py`: extend `connect()` and the dialect tables (`_quote`,
|
package/docs/guide.md
CHANGED
|
@@ -6,24 +6,42 @@ file formats see [reference.md](reference.md); for build and release see
|
|
|
6
6
|
|
|
7
7
|
## Install and upgrade
|
|
8
8
|
|
|
9
|
+
`dsh plugin` forwards its arguments to pnpm inside the profile directory, so it
|
|
10
|
+
needs `--profile` before the pnpm verb:
|
|
11
|
+
|
|
9
12
|
```sh
|
|
10
|
-
dsh plugin add dsh-db-mcp
|
|
11
|
-
dsh plugin add dsh-db-mcp
|
|
13
|
+
dsh plugin --profile desktop add dsh-db-mcp # fresh install, desktop app
|
|
14
|
+
dsh plugin --profile web add dsh-db-mcp # the web UI's profile
|
|
15
|
+
dsh plugin --profile desktop add dsh-db-mcp@0.7.0 # pin a version
|
|
16
|
+
dsh plugin --profile desktop remove dsh-db-mcp # uninstall
|
|
17
|
+
dsh plugin --profile desktop why dsh-db-mcp # which version is installed
|
|
12
18
|
```
|
|
13
19
|
|
|
20
|
+
`desktop` and `web` ship with DSH; any other name is initialized on first use
|
|
21
|
+
(`--profile scratch add dsh-db-mcp`). Check what a profile has with
|
|
22
|
+
`dsh plugin --profile desktop list dsh-db-mcp`.
|
|
23
|
+
|
|
14
24
|
From a source checkout, install the directory instead — the profile then links it,
|
|
15
25
|
so edits are live:
|
|
16
26
|
|
|
17
27
|
```sh
|
|
18
|
-
dsh plugin add C:\path\to\dsh-db-mcp
|
|
28
|
+
dsh plugin --profile desktop add C:\path\to\dsh-db-mcp
|
|
19
29
|
```
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- pnpm caches a package's metadata. A version published minutes ago may
|
|
24
|
-
invisible to
|
|
25
|
-
-
|
|
26
|
-
|
|
31
|
+
Three things to know about upgrades:
|
|
32
|
+
|
|
33
|
+
- pnpm caches a package's metadata. A version published minutes ago may be
|
|
34
|
+
invisible to the dist-tag; naming the version sidesteps it.
|
|
35
|
+
- pnpm holds new releases behind a cooldown (`minimumReleaseAge`, 1440 minutes by
|
|
36
|
+
default) so a freshly published — possibly compromised — version is not resolved
|
|
37
|
+
automatically. Asking for an exact version makes pnpm record that version in
|
|
38
|
+
`minimumReleaseAgeExclude` inside the profile's `pnpm-workspace.yaml`; that is the
|
|
39
|
+
`Added 1 entry to minimumReleaseAgeExclude …` line you will see. Nothing is broken:
|
|
40
|
+
the exclusion is per version and only lets *that* pin through. To be asked instead,
|
|
41
|
+
set `minimumReleaseAgeStrict: true` in the profile's `pnpm-workspace.yaml`; to drop
|
|
42
|
+
stale entries, set `minimumReleaseAgeExcludePrune: true`.
|
|
43
|
+
- Upgrading needs either an exact version or a removed lock entry: `add` will not
|
|
44
|
+
move a dependency that still satisfies its existing range.
|
|
27
45
|
|
|
28
46
|
If the sidebar entry disappears after a restart, see [Troubleshooting](#troubleshooting).
|
|
29
47
|
|
|
@@ -213,7 +231,7 @@ oracle://system:dbmcp-demo@127.0.0.1:1521/FREEPDB1
|
|
|
213
231
|
|
|
214
232
|
| Symptom | Cause | Fix |
|
|
215
233
|
| ------- | ----- | --- |
|
|
216
|
-
| Sidebar entry gone, plugin card says *not installed* | `package.json` carries a UTF-8 BOM, so the host's `JSON.parse` fails and the bundle is skipped silently | rewrite the file without a BOM
|
|
234
|
+
| Sidebar entry gone, plugin card says *not installed* | `package.json` carries a UTF-8 BOM, so the host's `JSON.parse` fails and the bundle is skipped silently | rewrite the file without a BOM (any editor's "UTF-8 without BOM"), then toggle the plugin off and on in the **Plugins** panel |
|
|
217
235
|
| Panel says *Host interface disconnected* | the Host half is not mounted (plugin disabled, or bundle failed to load) | check the plugin card, then reload the page |
|
|
218
236
|
| Edited `src/host.js` but nothing changed | the Cordis entry is imported once per process | `POST /plugins/db-mcp/api/reload`, or restart DSH after changing `index.js` |
|
|
219
237
|
| Tools missing from the agent | the Python server never started | *Runtime* → tools count and error; then **Restart MCP** |
|
package/docs/reference.md
CHANGED
|
@@ -56,7 +56,7 @@ Row limits come from the server: `DB_MCP_MAX_ROWS` (default 200) per call, cappe
|
|
|
56
56
|
| `list_tables` | `datasource?`, `schema?` | `{ datasource, tables: [{ schema, name, type }] }` |
|
|
57
57
|
| `describe_table` | `table`, `datasource?`, `schema?` | `{ columns: [{ name, type, nullable, default, primaryKey }] }` |
|
|
58
58
|
| `sample_rows` | `table`, `datasource?`, `limit=20`, `schema?` | `{ columns, rows, rowCount, truncated }` |
|
|
59
|
-
| `
|
|
59
|
+
| `execute_sql` | `sql`, `datasource?`, `max_rows?` | `{ columns, rows, rowCount, truncated, elapsedMs }`, or `{ affectedRows }` for a write |
|
|
60
60
|
| `explain_query` | `sql`, `datasource?` | `{ plan }` (engine-specific text) |
|
|
61
61
|
| `get_ddl` | `table`, `datasource?`, `schema?` | `{ ddl, source: 'sqlite_master' \| 'columns' }` |
|
|
62
62
|
| `test_connection` | `datasource?` or `kind`+`config`+`password?`, `read_only?` | `{ ok, latencyMs, message, detail: { stage, server, database, user, schema } }` |
|
|
@@ -109,7 +109,7 @@ and is read only by the Host, never sent to the page.
|
|
|
109
109
|
512 KiB:
|
|
110
110
|
|
|
111
111
|
```json
|
|
112
|
-
{"at":"2026-11-08T06:20:28.000Z","tool":"
|
|
112
|
+
{"at":"2026-11-08T06:20:28.000Z","tool":"mcp__db__execute_sql","datasource":"pg-demo","ok":true,"ms":41,"rows":3,"sql":"select id, name from customers order by id"}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
### Data source fields
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "DSH plugin: import the connections DBeaver already knows, then let the agent query SQLite, PostgreSQL, MySQL and Oracle through eight MCP tools. No DSH files to edit, no data source to retype.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
package/server/engines.py
CHANGED
|
@@ -549,7 +549,7 @@ def table_ddl(profile: dict, connection, table: str, schema: str | None = None)
|
|
|
549
549
|
# queries
|
|
550
550
|
|
|
551
551
|
|
|
552
|
-
def
|
|
552
|
+
def execute_sql(profile: dict, connection, sql: str, max_rows: int) -> dict:
|
|
553
553
|
ensure_readable(sql, profile)
|
|
554
554
|
kind = profile.get("kind")
|
|
555
555
|
started = time.perf_counter()
|
|
@@ -613,7 +613,7 @@ def connection_autocommit(kind: str) -> bool:
|
|
|
613
613
|
|
|
614
614
|
def sample_rows(profile: dict, connection, table: str, limit: int, schema: str | None) -> dict:
|
|
615
615
|
sql = f"SELECT * FROM {qualified(profile, table)}{_limit_clause(profile.get('kind'), limit)}"
|
|
616
|
-
result =
|
|
616
|
+
result = execute_sql(profile, connection, sql, limit)
|
|
617
617
|
result["table"] = table
|
|
618
618
|
result["schema"] = schema or (profile.get("config") or {}).get("schema") or None
|
|
619
619
|
return result
|
package/server/main.py
CHANGED
|
@@ -27,9 +27,11 @@ HARD_MAX_ROWS = int(os.environ.get("DB_MCP_MAX_ROWS_HARD") or 2000)
|
|
|
27
27
|
mcp = FastMCP(
|
|
28
28
|
"db-mcp",
|
|
29
29
|
instructions=(
|
|
30
|
-
"
|
|
30
|
+
"Work with the databases the user registered in the DSH Database panel. "
|
|
31
31
|
"Start with list_datasources, then list_tables, describe_table and finally "
|
|
32
|
-
"
|
|
32
|
+
"execute_sql. execute_sql runs any single statement: SELECT and EXPLAIN when "
|
|
33
|
+
"the source is read-only, INSERT/UPDATE/DELETE/DDL only when writes are "
|
|
34
|
+
"enabled. Prefer these tools over guessing table names."
|
|
33
35
|
),
|
|
34
36
|
)
|
|
35
37
|
|
|
@@ -201,19 +203,20 @@ def sample_rows(
|
|
|
201
203
|
|
|
202
204
|
|
|
203
205
|
@mcp.tool()
|
|
204
|
-
def
|
|
205
|
-
"""
|
|
206
|
-
|
|
207
|
-
Read-only datasources accept SELECT/WITH/SHOW/EXPLAIN statements only
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
`affectedRows` instead of a
|
|
206
|
+
def execute_sql(sql: str, datasource: Optional[str] = None, max_rows: Optional[int] = None) -> dict:
|
|
207
|
+
"""Execute one SQL statement — a query or, when writes are enabled, a change.
|
|
208
|
+
|
|
209
|
+
Read-only datasources accept SELECT/WITH/SHOW/EXPLAIN statements only; every
|
|
210
|
+
other statement is refused before it reaches the database. Results are capped by
|
|
211
|
+
`max_rows` (default 200) and `truncated` says whether more rows existed. Writes
|
|
212
|
+
(INSERT/UPDATE/DELETE/DDL) are committed and report `affectedRows` instead of a
|
|
213
|
+
result set. One statement per call: no scripts, no `;`-separated batches.
|
|
211
214
|
"""
|
|
212
215
|
try:
|
|
213
216
|
limit = _clamp_rows(max_rows)
|
|
214
217
|
profile, (connection, close) = _open(datasource)
|
|
215
218
|
try:
|
|
216
|
-
result = engines.
|
|
219
|
+
result = engines.execute_sql(profile, connection, sql, limit)
|
|
217
220
|
finally:
|
|
218
221
|
close()
|
|
219
222
|
return {"ok": True, "datasource": _source(profile), **result}
|
package/src/host.js
CHANGED
|
@@ -33,7 +33,7 @@ const { createBridge } = await import(new URL(`./tools.js?v=${GENERATION}`, impo
|
|
|
33
33
|
|
|
34
34
|
const API_PREFIX = '/plugins/db-mcp/api'
|
|
35
35
|
const PLUGIN_NAME = 'dsh-db-mcp'
|
|
36
|
-
const PLUGIN_VERSION = '0.
|
|
36
|
+
const PLUGIN_VERSION = '0.7.0'
|
|
37
37
|
const SCHEMA_VERSION = 1
|
|
38
38
|
const REGISTRY_FILE = 'datasources.json'
|
|
39
39
|
const SECRETS_FILE = 'secrets.json'
|