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 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@latest
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
- | `mcp__db__run_query` | run one statement |
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@latest
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
- | `mcp__db__run_query` | 执行一条语句 |
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 |
@@ -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>`) reflects edits
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 `run_query`, no database needed |
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.6.10 # package.json + src/host.js, both
76
- git commit -am "chore: release 0.6.10"
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.6.10 && git push origin v0.6.10
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@latest # fresh install
11
- dsh plugin add dsh-db-mcp@0.6.10 # pin a version (skips dist-tag and cache games)
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
- Two things to know about upgrades:
22
-
23
- - pnpm caches a package's metadata. A version published minutes ago may still be
24
- invisible to `@latest`; naming the version sidesteps it.
25
- - DSH applies a supply-chain cooldown to freshly published releases. Pinning is
26
- also the way around that.
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; re-apply with `plugin_manager set_bundle` |
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
- | `run_query` | `sql`, `datasource?`, `max_rows?` | `{ columns, rows, rowCount, truncated, elapsedMs }` |
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":"mcp__db__run_query","datasource":"pg-demo","ok":true,"ms":41,"rows":3,"sql":"select id, name from customers order by id"}
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.6.10",
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 run_query(profile: dict, connection, sql: str, max_rows: int) -> dict:
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 = run_query(profile, connection, sql, limit)
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
- "Query the databases the user registered in the DSH Database panel. "
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
- "run_query. Prefer these tools over guessing table names."
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 run_query(sql: str, datasource: Optional[str] = None, max_rows: Optional[int] = None) -> dict:
205
- """Run one SQL statement and return columns plus rows.
206
-
207
- Read-only datasources accept SELECT/WITH/SHOW/EXPLAIN statements only.
208
- Results are capped by `max_rows` (default 200); `truncated` says whether more
209
- rows existed. Writes (INSERT/UPDATE/DELETE/DDL) are committed and report
210
- `affectedRows` instead of a result set.
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.run_query(profile, connection, sql, limit)
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.6.10'
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'