dsh-db-mcp 0.6.4
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/LICENSE +202 -0
- package/README.md +326 -0
- package/client.js +2939 -0
- package/cordis.patch.yml +6 -0
- package/docs/requirements.md +960 -0
- package/icon.svg +8 -0
- package/index.js +119 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +63 -0
- package/server/engines.py +617 -0
- package/server/main.py +260 -0
- package/server/requirements.txt +10 -0
- package/server/store.py +98 -0
- package/src/host.js +1507 -0
- package/src/mcp.js +264 -0
- package/src/tools.js +372 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# dsh-db-mcp
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
|
|
5
|
+
DSH plugin that adds a **Database** entry to the sidebar with a data-source
|
|
6
|
+
management panel, backed by a Python MCP server that lets the agent query those
|
|
7
|
+
databases through tools (not through the page).
|
|
8
|
+
|
|
9
|
+
- One panel for every engine: SQLite, PostgreSQL, MySQL, Oracle.
|
|
10
|
+
- One connection string fills the whole form; passwords never leave the machine
|
|
11
|
+
and never come back to the page.
|
|
12
|
+
- Import the connections DBeaver already has, including its encrypted passwords.
|
|
13
|
+
- Read-only by default, with two independent write gates and an audit log.
|
|
14
|
+
- The agent gets seven MCP tools, not a SQL editor in the UI.
|
|
15
|
+
|
|
16
|
+
Requirements and staged plan: [`docs/requirements.md`](docs/requirements.md).
|
|
17
|
+
|
|
18
|
+
## What exists today (P7)
|
|
19
|
+
|
|
20
|
+
- **Host half** (`index.js` 鈫?`src/host.js`): a Cordis plugin serving
|
|
21
|
+
`/plugins/db-mcp/api` from the DSH web server.
|
|
22
|
+
| Route | Purpose |
|
|
23
|
+
| ----- | ------- |
|
|
24
|
+
| `GET /health` | plugin version, Host facts, Python discovery, runtime paths, registry state, MCP status, settings |
|
|
25
|
+
| `GET /datasources` | registry, credentials projected away |
|
|
26
|
+
| `GET /schema?datasource=…&table=…[&schema=…]` | read-only structure: tables, or the columns of one table |
|
|
27
|
+
| `GET /dbeaver/scan` | connections already configured in DBeaver |
|
|
28
|
+
| `GET /mcp/status` | Python server phase, tool list, last error, stderr tail |
|
|
29
|
+
| `GET /settings` 路 `POST /settings` | global write switch and audit switch |
|
|
30
|
+
| `GET /audit?limit=N` | the tail of the tool-call audit log |
|
|
31
|
+
| `GET /python/env` | interpreter, venv path, installed driver versions |
|
|
32
|
+
| `POST /python/setup` | create the venv and `pip install -r server/requirements.txt` |
|
|
33
|
+
| `POST /datasources/save` | create or update a source (`id` is immutable) |
|
|
34
|
+
| `POST /datasources/delete` | remove a source and its stored secret |
|
|
35
|
+
| `POST /datasources/test` | real login probe: SQLite over `node:sqlite`, the rest through the Python drivers |
|
|
36
|
+
| `POST /dbeaver/import` | copy selected DBeaver connections into the registry |
|
|
37
|
+
| `POST /mcp/start` 路 `POST /mcp/restart` | start or restart the Python MCP server |
|
|
38
|
+
| `POST /mcp/call` | dev helper: call one MCP tool through the panel |
|
|
39
|
+
| `POST /reload` | dev helper: re-import `src/host.js` without restarting DSH |
|
|
40
|
+
- **Client half** (`client.js`): sidebar entry (`db-mcp`, order 25) plus the main
|
|
41
|
+
panel with the source list, the minimal create/edit form (one connection string),
|
|
42
|
+
test/delete/default actions, a read-only *structure* browser, the DBeaver import
|
|
43
|
+
flow, a *Restart MCP* button, the runtime diagnostics card (tool count, global
|
|
44
|
+
write switch, venv and package versions with a one-click install) and an
|
|
45
|
+
*audit log* card. Copy is localized (zh/en) through the client `locale` service.
|
|
46
|
+
- **Python server** (`server/`): `main.py` (FastMCP, seven tools), `engines.py`
|
|
47
|
+
(per-engine connect/introspect/query), `store.py` (reads the registry).
|
|
48
|
+
The plugin spawns it over stdio and publishes its tools as `mcp__db__*` on the
|
|
49
|
+
host `tools` service — it does not go through a `dsh-mcp-client` Loader row.
|
|
50
|
+
|
|
51
|
+
### Tools the agent gets
|
|
52
|
+
|
|
53
|
+
| Tool | Purpose |
|
|
54
|
+
| ---- | ------- |
|
|
55
|
+
| `mcp__db__list_datasources` | the registered sources (id, kind, target, read-only) |
|
|
56
|
+
| `mcp__db__list_tables` | tables/views of one source |
|
|
57
|
+
| `mcp__db__describe_table` | columns, types, nullability, primary key |
|
|
58
|
+
| `mcp__db__sample_rows` | a few rows of one table |
|
|
59
|
+
| `mcp__db__run_query` | one statement; read-only sources refuse writes |
|
|
60
|
+
| `mcp__db__explain_query` | query plan |
|
|
61
|
+
| `mcp__db__get_ddl` | `CREATE TABLE` statement (SQLite: as created; others: rebuilt from the catalog) |
|
|
62
|
+
| `mcp__db__test_connection` | log in for real, run `select 1`, report server/user/schema |
|
|
63
|
+
|
|
64
|
+
`datasource` is optional everywhere: omitted, the default source is used.
|
|
65
|
+
|
|
66
|
+
### Python environment
|
|
67
|
+
|
|
68
|
+
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:
|
|
69
|
+
|
|
70
|
+
python -m venv C:\Users\<you>\.dsh\db-mcp\.venv
|
|
71
|
+
C:\Users\<you>\.dsh\db-mcp\.venv\Scripts\python.exe -m pip install -r server\requirements.txt
|
|
72
|
+
|
|
73
|
+
`mcp` must stay on 1.x (2.x renamed `FastMCP` to `MCPServer`).
|
|
74
|
+
|
|
75
|
+
## Does the Test button really log in?
|
|
76
|
+
|
|
77
|
+
Yes. For PostgreSQL, MySQL and Oracle the panel's **Test** action calls
|
|
78
|
+
`mcp__db__test_connection` over the Python MCP server, so the probe is a real
|
|
79
|
+
authenticated login plus `select 1` (Oracle uses `select 1 from dual`). The
|
|
80
|
+
panel row then shows the server banner, the connected user and database, and how
|
|
81
|
+
long the login took, for example:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Oracle AI Database 26ai Free Release 23.26.3.0.0 路 DEMO@FREEPDB1 路 select 1 from dual ok
|
|
85
|
+
PostgreSQL 15.19 路 demo@demo 路 select version() ok
|
|
86
|
+
MySQL 8.0.46 路 demo@%@demo 路 select version() ok
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A wrong password or an unreachable listener now surfaces the driver's own
|
|
90
|
+
message (`ORA-01017`, `DPY-6005`, `password authentication failed` — instead of
|
|
91
|
+
"port reachable". Testing an unsaved form works too: `test_connection` accepts
|
|
92
|
+
`kind` + `config` + `password` without writing the registry first. When the
|
|
93
|
+
Python server cannot start, the probe degrades to a TCP check and says so.
|
|
94
|
+
|
|
95
|
+
## Filling the form from one string
|
|
96
|
+
|
|
97
|
+
The create/edit form is deliberately as small as DBeaver's connection dialog:
|
|
98
|
+
**connection string**, **user**, **password** (with a *save password* box), and
|
|
99
|
+
that is it. Pasting a JDBC URL, a DSN, an Oracle TNS descriptor or a `.db` path
|
|
100
|
+
fills type, host, port, database/service, name and identifier; the derived values
|
|
101
|
+
only show up under **More settings**, together with schema, SID, SSL mode,
|
|
102
|
+
charset and the read-only switch.
|
|
103
|
+
|
|
104
|
+
Accepted shapes (see `dev/parse.test.mjs` for the full matrix):
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
postgresql://alice:secret@db.example.com:6543/orders?sslmode=require
|
|
108
|
+
jdbc:postgresql://localhost:5432/appdb
|
|
109
|
+
mysql://root:pw@127.0.0.1:3306/shop?charset=utf8mb4
|
|
110
|
+
jdbc:mariadb://10.0.0.5:3307/legacy
|
|
111
|
+
oracle://system:oracle@localhost:1521/XEPDB1
|
|
112
|
+
jdbc:oracle:thin:@//ora:1521/ORCLPDB1 # service name
|
|
113
|
+
jdbc:oracle:thin:@ora:1521:ORCL # SID
|
|
114
|
+
(DESCRIPTION=(ADDRESS=(HOST=tns-host)(PORT=1522))(CONNECT_DATA=(SERVICE_NAME=svc1)))
|
|
115
|
+
jdbc:sqlite:C:\data\app.db
|
|
116
|
+
sqlite:///C:/data/chinook.db
|
|
117
|
+
C:\data\app.db / /var/lib/app.sqlite3
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
A password found inside the URL is sent once to `POST /datasources/save` and
|
|
121
|
+
then only stored in `secrets.json`; it is never echoed back.
|
|
122
|
+
|
|
123
|
+
Writes are `POST` only (the same-origin check compares `Origin` with `Host`).
|
|
124
|
+
Passwords never enter `datasources.json`; they live in `secrets.json` and are
|
|
125
|
+
never returned to the page (`hasSecret` is the only signal).
|
|
126
|
+
|
|
127
|
+
## DBeaver import
|
|
128
|
+
|
|
129
|
+
`GET /dbeaver/scan` looks for `data-sources.json` under
|
|
130
|
+
`%APPDATA%\DBeaverData\workspace*\General\.dbeaver\` (plus the `General` and
|
|
131
|
+
Linux/macOS layouts). When the sibling `credentials-config.json` exists it is
|
|
132
|
+
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
|
|
133
|
+
imports the connection with an empty password, which the UI shows as
|
|
134
|
+
`no password`.
|
|
135
|
+
|
|
136
|
+
Connections created through the DBeaver driver dialog carry only a JDBC URL
|
|
137
|
+
(`configurationType: URL`) — no `database` field at all and no `user`. The
|
|
138
|
+
importer therefore parses `jdbc:mysql://host:port/db`,
|
|
139
|
+
`jdbc:postgresql://…` and `jdbc:oracle:thin:@…` for whatever the form fields
|
|
140
|
+
omit, and takes the user from the decrypted credentials when it is stored
|
|
141
|
+
there. The imported source is named after the DBeaver connection name (slugged
|
|
142
|
+
into an id), keeps `readOnly: true`, and gets the saved password copied into
|
|
143
|
+
`secrets.json`. Anything still missing is reported in `warnings`.
|
|
144
|
+
|
|
145
|
+
A connection that is already configured is not offered twice: every source
|
|
146
|
+
is identified by kind + host + port + database/service/SID + user (SQLite by
|
|
147
|
+
file path), and a DBeaver entry that matches one of them comes back with
|
|
148
|
+
`alreadyImported: true`, `duplicateId` and `duplicateName`. The panel disables
|
|
149
|
+
its checkbox and says *already added as …*; `POST /dbeaver/import` reports such
|
|
150
|
+
ids under `skipped` instead of creating a second copy.
|
|
151
|
+
|
|
152
|
+
The panel navigates with a tab bar (`数据源列表 / 新建数据源 / 从 DBeaver 导入`): the trailing step
|
|
153
|
+
is plain text, every earlier step is a link back, and no page needs a separate
|
|
154
|
+
"back to list" button.
|
|
155
|
+
|
|
156
|
+
## Test databases in Docker
|
|
157
|
+
|
|
158
|
+
Nothing has to be installed on the host; every engine below is a throwaway
|
|
159
|
+
container with a demo account (`demo` / `dbmcp-demo`).
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
# PostgreSQL — smallest and fastest to start, the recommended first target
|
|
163
|
+
docker run -d --name dbmcp-pg -p 5432:5432 \
|
|
164
|
+
-e POSTGRES_USER=demo -e POSTGRES_PASSWORD=dbmcp-demo -e POSTGRES_DB=demo \
|
|
165
|
+
postgres:15-alpine
|
|
166
|
+
|
|
167
|
+
# MySQL
|
|
168
|
+
docker run -d --name dbmcp-mysql -p 3306:3306 \
|
|
169
|
+
-e MYSQL_USER=demo -e MYSQL_PASSWORD=dbmcp-demo -e MYSQL_DATABASE=demo \
|
|
170
|
+
-e MYSQL_ROOT_PASSWORD=dbmcp-root mysql:8.0-debian
|
|
171
|
+
|
|
172
|
+
# Oracle Free — several GB, a minute or two to become healthy
|
|
173
|
+
docker run -d --name dbmcp-oracle -p 1521:1521 -e ORACLE_PASSWORD=dbmcp-demo \
|
|
174
|
+
gvenzl/oracle-free:23-slim
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
On Windows, 1521 often falls inside a Hyper-V reserved port block and `docker run`
|
|
178
|
+
fails with *"An attempt was made to access a socket in a way forbidden by its
|
|
179
|
+
access permissions"*. Check `netsh interface ipv4 show excludedportrange
|
|
180
|
+
protocol=tcp` and publish another host port instead, e.g. `-p 11521:1521`; the
|
|
181
|
+
datasource then points at port 11521.
|
|
182
|
+
|
|
183
|
+
Then create a source in the panel with the matching connection string, e.g.
|
|
184
|
+
`postgresql://demo:dbmcp-demo@127.0.0.1:5432/demo`,
|
|
185
|
+
`mysql://demo:dbmcp-demo@127.0.0.1:3306/demo` or
|
|
186
|
+
`oracle://system:dbmcp-demo@127.0.0.1:1521/FREEPDB1`.
|
|
187
|
+
|
|
188
|
+
## Install into a profile
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
plugin_manager install_bundle target=<absolute path to this directory>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The bundle patch (`cordis.patch.yml`) inserts one Loader row with id `db-mcp`.
|
|
195
|
+
The profile links this directory, so file edits are live; the Host module is
|
|
196
|
+
imported once per process, hence `POST /plugins/db-mcp/api/reload` (or an app
|
|
197
|
+
restart) after changing `src/host.js`.
|
|
198
|
+
|
|
199
|
+
## Panel layout
|
|
200
|
+
|
|
201
|
+
The page follows the same chrome as the other first-party panels:
|
|
202
|
+
|
|
203
|
+
- a masthead with **‹ 返回会话** (closes the panel through `ctx.layout.selectPanel(null)`),
|
|
204
|
+
the `数据库` title, and the connection/version pills;
|
|
205
|
+
- a tab row — **数据源列表 / 新建数据源 / 从 DBeaver 导入 / 运行时诊断 / 审计日志** — with
|
|
206
|
+
the active tab underlined;
|
|
207
|
+
|
|
208
|
+
Picking **新建数据源** or **从 DBeaver 导入** swaps only the card below the tabs — the
|
|
209
|
+
masthead and the tabs stay put. Editing reuses the **新建数据源** tab; the structure
|
|
210
|
+
browser is a sub-view of **数据源列表** with its own `← 数据源` link. There is no second
|
|
211
|
+
action bar: **刷新** and **重启 MCP** sit in the source-list card header, and the list
|
|
212
|
+
itself owns the group tools.
|
|
213
|
+
|
|
214
|
+
## Schema overview
|
|
215
|
+
The **Structure** button on a source row opens a read-only browser: the tables
|
|
216
|
+
(or views) of that source on the left, and the columns of the selected table on
|
|
217
|
+
the right — name, type, nullability, default value and primary-key marker. It is
|
|
218
|
+
served by `GET /schema`, which only forwards `list_tables` / `describe_table` to
|
|
219
|
+
the MCP server, so the page itself can never run SQL. Nothing is cached: each
|
|
220
|
+
open reads the live catalog.
|
|
221
|
+
|
|
222
|
+
## Groups and bulk actions
|
|
223
|
+
|
|
224
|
+
Every source carries an optional **group** label (`group` in the registry,
|
|
225
|
+
editable under *More settings*). Groups are user-owned: type a name in the
|
|
226
|
+
**新建分组** box to create one, rename or delete it from the group header, and drag
|
|
227
|
+
a source row onto a group to move it there (drag it onto **未分组** to clear the
|
|
228
|
+
label). The list renders one section per group, sorted by name with the ungrouped
|
|
229
|
+
sources last; while no group exists the list keeps its flat look.
|
|
230
|
+
|
|
231
|
+
Each group header carries a select-all box, so sources inside a group take part in
|
|
232
|
+
bulk work exactly like loose ones. Tick any row (or click its text) to open the
|
|
233
|
+
bulk bar: test all selected sources, flip them to read-only or writable, move them
|
|
234
|
+
into a group, or delete them (two-step confirmation). Bulk moves go through
|
|
235
|
+
`POST /datasources/bulk` with `{ action: 'test' | 'readOnly' | 'group' | 'delete', ids: [...], … }`,
|
|
236
|
+
while group CRUD uses `GET /groups`, `POST /groups/save` and `POST /groups/delete`.
|
|
237
|
+
|
|
238
|
+
## Releasing
|
|
239
|
+
|
|
240
|
+
The package is published from GitHub Actions with [npm trusted publishing](https://docs.npmjs.com/trusted-publishers):
|
|
241
|
+
`.github/workflows/publish.yml` trades the workflow's OIDC token for a short-lived
|
|
242
|
+
publish credential, so no `NPM_TOKEN` secret exists anywhere.
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
node dev/bump.mjs 0.6.5 # package.json + src/host.js
|
|
246
|
+
git commit -am "chore: release 0.6.5"
|
|
247
|
+
git tag v0.6.5 && git push origin main --tags
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The workflow refuses to publish when the tag and `package.json` disagree, runs the
|
|
251
|
+
portable suites (`npm run test:ci`), publishes with `--provenance`, then waits for the
|
|
252
|
+
version to appear in the registry. Local `npm publish` still works for the very first
|
|
253
|
+
release, before a trusted publisher exists for the package.
|
|
254
|
+
|
|
255
|
+
## Development
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
node dev/selftest.mjs # mounts src/host.js on a throwaway port, 42 checks
|
|
259
|
+
node dev/shims.test.mjs # mounts index.js, checks routes, /reload, tool publication
|
|
260
|
+
node dev/parse.test.mjs # 24 checks: connection-string parser (DSN/JDBC/TNS/paths)
|
|
261
|
+
node dev/render.test.mjs # 37 checks: components render with a stub React
|
|
262
|
+
node dev/i18n.test.mjs # 12 checks: en/zh parity, no dead keys, no stray copy
|
|
263
|
+
node dev/p5.test.mjs # 15 checks: settings, audit log, python environment
|
|
264
|
+
node dev/mcp.test.mjs # 19 checks: real Python server over stdio, real SQLite + Oracle logins
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`npm test` runs everything except `mcp.test.mjs`, which needs the venv and the
|
|
268
|
+
Oracle container. `node dev/bump.mjs 0.6.2` updates both version strings.
|
|
269
|
+
|
|
270
|
+
## Safety switches
|
|
271
|
+
|
|
272
|
+
Two independent gates decide whether a statement may write:
|
|
273
|
+
|
|
274
|
+
1. **Global switch** — `allowWrite` in `<runtime>/settings.json`, toggled from the
|
|
275
|
+
diagnostics card. Off by default; while off every write statement is refused
|
|
276
|
+
with `user_error` and a hint pointing at the switch.
|
|
277
|
+
2. **Per-source flag** — `readOnly` on the data source, on by default. A source
|
|
278
|
+
stays read-only even when the global switch is on until you clear its flag.
|
|
279
|
+
|
|
280
|
+
The Python server reads the global flag from `DB_MCP_ALLOW_WRITE` at start-up; the
|
|
281
|
+
host restarts it whenever the switch flips. Read-only is also enforced by the
|
|
282
|
+
driver itself where the engine allows it (`mode=ro` for SQLite,
|
|
283
|
+
`default_transaction_read_only` for PostgreSQL, `SET SESSION TRANSACTION READ ONLY`
|
|
284
|
+
for MySQL).
|
|
285
|
+
|
|
286
|
+
## Audit log
|
|
287
|
+
|
|
288
|
+
Every MCP tool call appends one JSON line to `<runtime>/logs/audit.jsonl` (0600):
|
|
289
|
+
time, tool, source, duration, row count, the SQL truncated to 2000 characters and
|
|
290
|
+
the error when it failed. Result rows are never logged. The file rotates to
|
|
291
|
+
`audit.1.jsonl` at 512 KiB and logging can be turned off with `auditLog: false` in
|
|
292
|
+
`settings.json`. The panel shows the last dozen entries.
|
|
293
|
+
|
|
294
|
+
`dev/shims.test.mjs` also asserts that `package.json` has no UTF-8 BOM: the host
|
|
295
|
+
parses it with `JSON.parse`, which rejects a BOM, and the plugin then shows up as
|
|
296
|
+
`availability: missing` instead of loading. Editors that "add BOM" will break the
|
|
297
|
+
bundle silently — if the sidebar entry disappears after a restart, check this
|
|
298
|
+
first.
|
|
299
|
+
|
|
300
|
+
## Stage map
|
|
301
|
+
|
|
302
|
+
| Stage | Content |
|
|
303
|
+
| ----- | ------- |
|
|
304
|
+
| P1 | installable skeleton: sidebar entry, empty panel, Host health API (done) |
|
|
305
|
+
| P2 | data-source CRUD, connection test, DBeaver import (done) |
|
|
306
|
+
| P3 | Python MCP server, tools published as `mcp__db__*` (done) |
|
|
307
|
+
| P4 | PostgreSQL / MySQL / Oracle dialects (done) |
|
|
308
|
+
| P5 | diagnostics, one-click venv, reload, audit log, global write switch (done) |
|
|
309
|
+
| P6 | i18n polish and docs (done) |
|
|
310
|
+
| P7 | read-only schema overview (done) |
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
Apache License 2.0 — see [LICENSE](LICENSE).
|
|
315
|
+
|
|
316
|
+
## Runtime layout
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
$DSH_HOME/db-mcp/
|
|
320
|
+
datasources.json # profiles, never passwords, 0600
|
|
321
|
+
secrets.json # per-source password, 0600, Host-only
|
|
322
|
+
settings.json # global write switch + audit switch, 0600
|
|
323
|
+
logs/mcp.log # masked log
|
|
324
|
+
logs/audit.jsonl # one line per MCP tool call, rotates at 512 KiB
|
|
325
|
+
.venv/ # plugin-local venv: mcp, psycopg, PyMySQL, oracledb
|
|
326
|
+
```
|