dsh-db-mcp 0.6.10 → 0.7.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
@@ -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 |
@@ -57,7 +61,7 @@ dsh plugin add dsh-db-mcp@latest
57
61
 
58
62
  | 文档 | 内容 |
59
63
  | ---- | ---- |
60
- | [docs/guide.md](docs/guide.md) | 面板操作、连接串、分组、Docker 测试库、排错(英文) |
64
+ | [docs/guide.zh.md](docs/guide.zh.md) | 面板操作、连接串、分组、Docker 测试库、排错(中文) |
61
65
  | [docs/reference.md](docs/reference.md) | HTTP 接口、工具契约、注册表与设置文件、插件配置(英文) |
62
66
  | [docs/development.md](docs/development.md) | 目录结构、热重载、测试套件、发布流程(英文) |
63
67
  | [docs/design-notes.md](docs/design-notes.md) | 设计日志(P1 → P7)与决策记录(英文) |
@@ -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
@@ -1,3 +1,5 @@
1
+ English | [中文](guide.zh.md)
2
+
1
3
  # Guide
2
4
 
3
5
  Everything the panel can do, in the order you meet it. For the wire protocol and
@@ -6,24 +8,42 @@ file formats see [reference.md](reference.md); for build and release see
6
8
 
7
9
  ## Install and upgrade
8
10
 
11
+ `dsh plugin` forwards its arguments to pnpm inside the profile directory, so it
12
+ needs `--profile` before the pnpm verb:
13
+
9
14
  ```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)
15
+ dsh plugin --profile desktop add dsh-db-mcp # fresh install, desktop app
16
+ dsh plugin --profile web add dsh-db-mcp # the web UI's profile
17
+ dsh plugin --profile desktop add dsh-db-mcp@0.7.0 # pin a version
18
+ dsh plugin --profile desktop remove dsh-db-mcp # uninstall
19
+ dsh plugin --profile desktop why dsh-db-mcp # which version is installed
12
20
  ```
13
21
 
22
+ `desktop` and `web` ship with DSH; any other name is initialized on first use
23
+ (`--profile scratch add dsh-db-mcp`). Check what a profile has with
24
+ `dsh plugin --profile desktop list dsh-db-mcp`.
25
+
14
26
  From a source checkout, install the directory instead — the profile then links it,
15
27
  so edits are live:
16
28
 
17
29
  ```sh
18
- dsh plugin add C:\path\to\dsh-db-mcp
30
+ dsh plugin --profile desktop add C:\path\to\dsh-db-mcp
19
31
  ```
20
32
 
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.
33
+ Three things to know about upgrades:
34
+
35
+ - pnpm caches a package's metadata. A version published minutes ago may be
36
+ invisible to the dist-tag; naming the version sidesteps it.
37
+ - pnpm holds new releases behind a cooldown (`minimumReleaseAge`, 1440 minutes by
38
+ default) so a freshly published — possibly compromised — version is not resolved
39
+ automatically. Asking for an exact version makes pnpm record that version in
40
+ `minimumReleaseAgeExclude` inside the profile's `pnpm-workspace.yaml`; that is the
41
+ `Added 1 entry to minimumReleaseAgeExclude …` line you will see. Nothing is broken:
42
+ the exclusion is per version and only lets *that* pin through. To be asked instead,
43
+ set `minimumReleaseAgeStrict: true` in the profile's `pnpm-workspace.yaml`; to drop
44
+ stale entries, set `minimumReleaseAgeExcludePrune: true`.
45
+ - Upgrading needs either an exact version or a removed lock entry: `add` will not
46
+ move a dependency that still satisfies its existing range.
27
47
 
28
48
  If the sidebar entry disappears after a restart, see [Troubleshooting](#troubleshooting).
29
49
 
@@ -213,7 +233,7 @@ oracle://system:dbmcp-demo@127.0.0.1:1521/FREEPDB1
213
233
 
214
234
  | Symptom | Cause | Fix |
215
235
  | ------- | ----- | --- |
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` |
236
+ | 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
237
  | 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
238
  | 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
239
  | Tools missing from the agent | the Python server never started | *Runtime* → tools count and error; then **Restart MCP** |
@@ -0,0 +1,178 @@
1
+ [English](guide.md) | 中文
2
+
3
+ # 使用指南
4
+
5
+ 面板能做的事,按你会遇到的顺序排列。协议与文件格式见 [reference.md](reference.md)(英文);构建与发布见 [development.md](development.md)(英文)。
6
+
7
+ ## 安装与升级
8
+
9
+ `dsh plugin` 会把参数原样转给 profile 目录里的 pnpm,所以 `--profile` 必须写在 pnpm 动词之前:
10
+
11
+ ```sh
12
+ dsh plugin --profile desktop add dsh-db-mcp # 全新安装,桌面端
13
+ dsh plugin --profile web add dsh-db-mcp # Web 界面的 profile
14
+ dsh plugin --profile desktop add dsh-db-mcp@0.7.0 # 锁定某个版本
15
+ dsh plugin --profile desktop remove dsh-db-mcp # 卸载
16
+ dsh plugin --profile desktop why dsh-db-mcp # 当前装的是哪个版本
17
+ ```
18
+
19
+ `desktop` 与 `web` 随 DSH 自带;其它名字会在首次使用时自动创建(`--profile scratch add dsh-db-mcp`)。查看某个 profile 装了什么:`dsh plugin --profile desktop list dsh-db-mcp`。
20
+
21
+ 从源码目录安装则传目录路径,profile 会以链接方式接入,改代码即时生效:
22
+
23
+ ```sh
24
+ dsh plugin --profile desktop add C:\path\to\dsh-db-mcp
25
+ ```
26
+
27
+ 升级有三点要注意:
28
+
29
+ - pnpm 会缓存包的元数据。刚发布几分钟的版本可能还不在 dist-tag 里;直接写出版本号可以绕过。
30
+ - pnpm 对新版本有冷却期(`minimumReleaseAge`,默认 1440 分钟),避免刚发布、可能已被投毒的新版本被自动解析。点名具体版本时,pnpm 会把该版本记进 profile 的 `pnpm-workspace.yaml` 里的 `minimumReleaseAgeExclude`,这就是你看到的 `Added 1 entry to minimumReleaseAgeExclude …`。这不是错误:豁免只针对那一个版本,只放行这一次 pin。想改成先询问,在 profile 的 `pnpm-workspace.yaml` 里设 `minimumReleaseAgeStrict: true`;想清理过期条目,设 `minimumReleaseAgeExcludePrune: true`。
31
+ - 升级要么写精确版本,要么先删掉 lock 条目:只要依赖还满足原有范围,`add` 不会动它。
32
+
33
+ 如果重启后侧栏入口消失,见 [排错](#排错)。
34
+
35
+ ## 面板
36
+
37
+ 侧栏 → **数据库**。页头有 **‹ 返回会话**、标题,以及连接状态和版本两个药丸。下面五个标签:
38
+
39
+ | 标签 | 作用 |
40
+ | ---- | ---- |
41
+ | 数据源列表 | 注册表:按分组标签分行的列表、批量操作、刷新、重启 MCP |
42
+ | 新建数据源 | 一条连接串、用户、口令;编辑也复用这个表单 |
43
+ | 从 DBeaver 导入 | 扫描、勾选、导入 |
44
+ | 运行时诊断 | Host 信息、工具数量、全局写开关、venv 与驱动版本 |
45
+ | 审计日志 | 最近十几次工具调用,按你的时区显示 |
46
+
47
+ **新建数据源** 与 **从 DBeaver 导入** 只替换标签下方那张卡片——页头和标签保持不动,之前所在的位置始终只隔一个标签。
48
+
49
+ ## 新增数据源
50
+
51
+ 表单和 DBeaver 的连接对话框一样精简:**连接串**、**用户**、**口令**(带 *保存密码* 勾选)。粘贴连接串会自动填出类型、主机、端口、库名/服务名、显示名称与标识符;推导出来的值只出现在 **更多设置** 里,那里还有模式、SID、SSL 模式、字符集和只读开关。
52
+
53
+ 可接受的写法——DSN、JDBC URL、Oracle TNS 描述符、JDBC thin 描述符,或者干脆一个数据库文件路径:
54
+
55
+ ```
56
+ postgresql://alice:secret@db.example.com:6543/orders?sslmode=require
57
+ jdbc:postgresql://localhost:5432/appdb
58
+ mysql://root:pw@127.0.0.1:3306/shop?charset=utf8mb4
59
+ jdbc:mariadb://10.0.0.5:3307/legacy
60
+ oracle://system:oracle@localhost:1521/XEPDB1
61
+ jdbc:oracle:thin:@//ora:1521/ORCLPDB1 # 服务名
62
+ jdbc:oracle:thin:@ora:1521:ORCL # SID
63
+ (DESCRIPTION=(ADDRESS=(HOST=tns-host)(PORT=1522))(CONNECT_DATA=(SERVICE_NAME=svc1)))
64
+ jdbc:sqlite:C:\data\app.db
65
+ sqlite:///C:/data/chinook.db
66
+ C:\data\app.db / /var/lib/app.sqlite3
67
+ ```
68
+
69
+ 连接串里带的密码只发送一次,之后仅保存,永远不会回显。`id` 创建后不可修改——要改的是 *显示名称*(双击行上的名字)。
70
+
71
+ ## 从 DBeaver 导入
72
+
73
+ 导入器会在 `%APPDATA%\DBeaverData\workspace*\General\.dbeaver\` 下找 `data-sources.json`,同时兼容 `General` 布局与 Linux/macOS 布局。同目录存在 `credentials-config.json` 时,会用 DBeaver 内置密钥解密(AES-128-CBC、零 IV、前 16 字节前缀——参考 [Tatsh/dbeaver-creds](https://github.com/Tatsh/dbeaver-creds))。勾选的连接被复制进注册表,口令写入 `secrets.json`。
74
+
75
+ 两个细节让真实的 DBeaver 工作区可用:
76
+
77
+ - 通过驱动对话框创建的连接常常只有一条 JDBC URL(`configurationType: URL`),既没有 `database` 字段也没有 `user`。导入器会从 URL 里解析表单缺失的部分,用户名则取自解密后的凭据。
78
+ - 已经配置过的连接不会重复出现。数据源按 类型 + 主机 + 端口 + 库名/服务名/SID + 用户 识别(SQLite 用文件路径);命中时返回 `alreadyImported`,复选框被禁用,行上写着 *已作为 … 添加*。
79
+
80
+ 从未保存过口令的工作区也没问题:连接会以空口令导入,行上显示 **无密码**。补填一次就会一直保留。
81
+
82
+ ## 测试连接
83
+
84
+ **测试** 执行的是真实登录:SQLite 通过 `node:sqlite` 以只读方式打开,其它引擎走 Python 驱动,完成认证并执行 `select 1`(Oracle 是 `select 1 from dual`)。之后行上会显示版本横幅、用户、数据库,以及登录耗时:
85
+
86
+ ```
87
+ Oracle AI Database 26ai Free Release 23.26.3.0.0 · DEMO@FREEPDB1 · select 1 from dual ok
88
+ PostgreSQL 15.19 · demo@demo · select version() ok
89
+ MySQL 8.0.46 · demo@%@demo · select version() ok
90
+ ```
91
+
92
+ 口令错误现在会直接显示驱动自己的报错(`ORA-01017`、`DPY-6005`、`password authentication failed`),而不是笼统的「端口可达」。还没保存的表单也能测试。Python 服务起不来时,探测会退化成 TCP 检查,并明确说明这一点。
93
+
94
+ ## 分组与批量操作
95
+
96
+ 每个数据源都可以带一个可选的 **分组** 标签,在 *更多设置* 里编辑。
97
+
98
+ - **新建** —— 在 **新建分组** 里输入名字。
99
+ - **重命名** —— 双击组名;**删除** 只解除该组下成员的标签。
100
+ - **移动** —— 把数据源行拖到某个分组上,拖到 **未分组** 即清除标签。
101
+ - **重命名数据源** —— 双击它的名字;`id` 永远不变。
102
+ - **选择** —— 每个组头都有全选框,分组内的数据源和散落的一样能参与批量操作。
103
+
104
+ 选中之后,批量条可以测试、切换只读、移入分组,或删除(两步确认)。
105
+
106
+ ## 结构浏览
107
+
108
+ 数据源行上的 **结构** 会打开只读视图:左边是表和视图,右边是所选表的列(名称、类型、可空、默认值、主键标记)。它由 `GET /schema` 提供,该接口只把 `list_tables` / `describe_table` 转发给 MCP 服务——页面无法执行 SQL,也不做缓存。
109
+
110
+ ## 两道安全闸门
111
+
112
+ 两条独立的闸门决定一条语句能不能写入:
113
+
114
+ 1. **全局开关** —— `<runtime>/settings.json` 里的 `allowWrite`,在 *运行时诊断* 标签切换。默认关闭;关闭期间所有写入都会以 `user_error` 拒绝,并提示去打开开关。
115
+ 2. **单源开关** —— 数据源上的 `readOnly`,默认开启。点行上的 *只读* / *读写* 药丸可一键切换;该源允许写入时药丸变成琥珀色。
116
+
117
+ Python 服务在启动时读取全局开关,开关一动 Host 就重启它。在引擎允许的地方,只读还会在驱动层再拦一次(SQLite 用 `mode=ro`,PostgreSQL 用 `default_transaction_read_only`,MySQL 用 `SET SESSION TRANSACTION READ ONLY`)。
118
+
119
+ ## 审计日志
120
+
121
+ 每次 MCP 工具调用都会往 `<runtime>/logs/audit.jsonl`(0600)追加一行 JSON:时间、工具、数据源、耗时、行数、截断到 2000 字符的 SQL,以及失败时的错误。结果行本身从不记录。文件到 512 KiB 会轮转为 `audit.1.jsonl`,日志可用 `auditLog: false` 关闭。
122
+
123
+ 面板按 **你本机的时钟** 显示最近十几次记录;文件里保存的是 UTC,以便跨机器比较。每条记录的时间戳在悬停时会显示完整的本地时间。
124
+
125
+ ## 运行时目录
126
+
127
+ ```
128
+ $DSH_HOME/db-mcp/
129
+ datasources.json # 数据源与分组,从不含口令,0600
130
+ secrets.json # 每个数据源的口令,0600,仅 Host 可读
131
+ settings.json # 全局写开关 + 审计开关,0600
132
+ logs/mcp.log # 脱敏日志
133
+ logs/audit.jsonl # 每次 MCP 工具调用一行,512 KiB 轮转
134
+ .venv/ # 插件内 venv:mcp、psycopg、PyMySQL、oracledb
135
+ ```
136
+
137
+ ## 用 Docker 起测试库
138
+
139
+ 本机什么都不用装;下面每个引擎都是一个用完即弃的容器,带演示账号(`demo` / `dbmcp-demo`)。
140
+
141
+ ```sh
142
+ # PostgreSQL —— 镜像最小、启动最快,推荐先试它
143
+ docker run -d --name dbmcp-pg -p 5432:5432 \
144
+ -e POSTGRES_USER=demo -e POSTGRES_PASSWORD=dbmcp-demo -e POSTGRES_DB=demo \
145
+ postgres:15-alpine
146
+
147
+ # MySQL
148
+ docker run -d --name dbmcp-mysql -p 3306:3306 \
149
+ -e MYSQL_USER=demo -e MYSQL_PASSWORD=dbmcp-demo -e MYSQL_DATABASE=demo \
150
+ -e MYSQL_ROOT_PASSWORD=dbmcp-root mysql:8.0-debian
151
+
152
+ # Oracle Free —— 几个 GB,需要一两分钟才健康
153
+ docker run -d --name dbmcp-oracle -p 1521:1521 -e ORACLE_PASSWORD=dbmcp-demo \
154
+ gvenzl/oracle-free:23-slim
155
+ ```
156
+
157
+ 在 Windows 上,1521 常常落在 Hyper-V 的保留端口段里,`docker run` 会报 *"An attempt was made to access a socket in a way forbidden by its access permissions"*。用 `netsh interface ipv4 show excludedportrange protocol=tcp` 确认,然后映射到别的宿主端口,例如 `-p 11521:1521`,再把数据源指向 11521。
158
+
159
+ 然后用对应的连接串导入或新建数据源:
160
+
161
+ ```
162
+ postgresql://demo:dbmcp-demo@127.0.0.1:5432/demo
163
+ mysql://demo:dbmcp-demo@127.0.0.1:3306/demo
164
+ oracle://system:dbmcp-demo@127.0.0.1:1521/FREEPDB1
165
+ ```
166
+
167
+ ## 排错
168
+
169
+ | 现象 | 原因 | 处理 |
170
+ | ---- | ---- | ---- |
171
+ | 侧栏入口消失,插件卡片显示 *未安装* | `package.json` 带 UTF-8 BOM,Host 的 `JSON.parse` 失败,整个 bundle 被静默跳过 | 用编辑器另存为「UTF-8 无 BOM」,再到 **插件** 面板把插件关掉再打开 |
172
+ | 面板显示 *Host 接口未连接* | Host 半边没挂载(插件被禁用,或 bundle 加载失败) | 检查插件卡片,然后刷新页面 |
173
+ | 改了 `src/host.js` 但没有变化 | Cordis 入口在每个进程里只导入一次 | `POST /plugins/db-mcp/api/reload`;改过 `index.js` 则需重启 DSH |
174
+ | 智能体看不到工具 | Python 服务没起来 | *运行时诊断* 里看工具数与错误,然后点 **重启 MCP** |
175
+ | 重启后立刻报 `unknown tool "mcp__db__…"` | 工具注册随旧进程被释放 | 重启一次 DSH,之后的重启都用 **重启 MCP** |
176
+ | Oracle 登录报 `DPY-6005` | 监听不可达,或主机/端口不对 | `docker ps` 确认容器,再点 *测试* |
177
+ | 首次写入报 `ORA-01950` | Oracle 用户没有表空间配额 | `alter user <用户> quota unlimited on users` |
178
+ | 导入行标红:*incomplete host/database* | 该 DBeaver 条目既没有字段也没有可解析的 URL | 先用 DBeaver 打开一次这个连接,或手工新建数据源 |
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.1",
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.1'
37
37
  const SCHEMA_VERSION = 1
38
38
  const REGISTRY_FILE = 'datasources.json'
39
39
  const SECRETS_FILE = 'secrets.json'