@rmwxxwmr/mcp-database-service 0.1.2
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 +21 -0
- package/README.md +236 -0
- package/README.zh-CN.md +236 -0
- package/config/databases.example.json +48 -0
- package/dist/config/configSummary.d.ts +4 -0
- package/dist/config/configSummary.js +117 -0
- package/dist/config/configSummary.js.map +1 -0
- package/dist/config/configTypes.d.ts +81 -0
- package/dist/config/configTypes.js +2 -0
- package/dist/config/configTypes.js.map +1 -0
- package/dist/config/configValidation.d.ts +6 -0
- package/dist/config/configValidation.js +142 -0
- package/dist/config/configValidation.js.map +1 -0
- package/dist/config/loadConfig.d.ts +8 -0
- package/dist/config/loadConfig.js +85 -0
- package/dist/config/loadConfig.js.map +1 -0
- package/dist/core/errors.d.ts +15 -0
- package/dist/core/errors.js +28 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/logger.d.ts +7 -0
- package/dist/core/logger.js +68 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/resultTypes.d.ts +49 -0
- package/dist/core/resultTypes.js +2 -0
- package/dist/core/resultTypes.js.map +1 -0
- package/dist/db/clientFactory.d.ts +9 -0
- package/dist/db/clientFactory.js +27 -0
- package/dist/db/clientFactory.js.map +1 -0
- package/dist/db/readonlyGuard.d.ts +14 -0
- package/dist/db/readonlyGuard.js +253 -0
- package/dist/db/readonlyGuard.js.map +1 -0
- package/dist/db/redis/redisClient.d.ts +16 -0
- package/dist/db/redis/redisClient.js +106 -0
- package/dist/db/redis/redisClient.js.map +1 -0
- package/dist/db/sql/baseSqlAdapter.d.ts +52 -0
- package/dist/db/sql/baseSqlAdapter.js +310 -0
- package/dist/db/sql/baseSqlAdapter.js.map +1 -0
- package/dist/db/sql/mysqlClient.d.ts +33 -0
- package/dist/db/sql/mysqlClient.js +150 -0
- package/dist/db/sql/mysqlClient.js.map +1 -0
- package/dist/db/sql/openGaussClient.d.ts +9 -0
- package/dist/db/sql/openGaussClient.js +11 -0
- package/dist/db/sql/openGaussClient.js.map +1 -0
- package/dist/db/sql/oracleClient.d.ts +33 -0
- package/dist/db/sql/oracleClient.js +203 -0
- package/dist/db/sql/oracleClient.js.map +1 -0
- package/dist/db/sql/postgresClient.d.ts +33 -0
- package/dist/db/sql/postgresClient.js +160 -0
- package/dist/db/sql/postgresClient.js.map +1 -0
- package/dist/db/types.d.ts +24 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -0
- package/dist/server/createServer.d.ts +46 -0
- package/dist/server/createServer.js +441 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/toolRegistry.d.ts +36 -0
- package/dist/server/toolRegistry.js +927 -0
- package/dist/server/toolRegistry.js.map +1 -0
- package/dist/utils/normalize.d.ts +6 -0
- package/dist/utils/normalize.js +29 -0
- package/dist/utils/normalize.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rmwxxwmr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
English | [简体中文](./README.zh-CN.md)
|
|
2
|
+
|
|
3
|
+
# MCP Database Service
|
|
4
|
+
|
|
5
|
+
A multi-database MCP server for Model Context Protocol (MCP), written in TypeScript.
|
|
6
|
+
|
|
7
|
+
It supports MySQL, PostgreSQL, Oracle, openGauss, and Redis, with lazy short-lived connections, read-oriented database tools, SQL plan analysis, config reload, and guarded write execution.
|
|
8
|
+
|
|
9
|
+
This project is intended for AI agents and MCP clients that need safe database discovery, querying, performance analysis, and controlled write operations.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Install from npm:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
npm install -g @rmwxxwmr/mcp-database-service
|
|
17
|
+
mcp-database-service --config ./config/databases.example.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run from source:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install
|
|
24
|
+
npm run build
|
|
25
|
+
node dist/index.js --config ./config/databases.example.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Support Matrix
|
|
29
|
+
|
|
30
|
+
| Database | Query Tools | Metadata Tools | `explain_query` | `analyze_query` | Write Support |
|
|
31
|
+
| --- | --- | --- | --- | --- | --- |
|
|
32
|
+
| MySQL | Yes | Yes | Yes | Yes | Yes |
|
|
33
|
+
| PostgreSQL | Yes | Yes | Yes | Yes | Yes |
|
|
34
|
+
| openGauss | Yes | Yes | Yes | Yes | Yes |
|
|
35
|
+
| Oracle | Yes | Yes | Yes | No | Yes |
|
|
36
|
+
| Redis | Yes | Limited to Redis tools | No | No | No |
|
|
37
|
+
|
|
38
|
+
Notes:
|
|
39
|
+
- show_create_table currently supports MySQL and Oracle. PostgreSQL and openGauss currently return NOT_SUPPORTED.
|
|
40
|
+
- Operational tools such as `show_variables`, `find_long_running_queries`, `find_blocking_sessions`, and `show_locks` depend on the visibility and privileges of the configured database account.
|
|
41
|
+
|
|
42
|
+
## Supported Databases
|
|
43
|
+
- MySQL
|
|
44
|
+
- Oracle
|
|
45
|
+
- PostgreSQL
|
|
46
|
+
- openGauss (via PostgreSQL protocol compatibility)
|
|
47
|
+
- Redis
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
- Multiple named database targets in a single config file
|
|
51
|
+
- Optional file logging with configurable output directory
|
|
52
|
+
- Manual config reload without restarting the MCP server
|
|
53
|
+
- Automatic config reload when the JSON file changes on disk
|
|
54
|
+
- Strict read-only enforcement for query tools
|
|
55
|
+
- Optional write execution with explicit MCP confirmation for writable targets
|
|
56
|
+
- Lazy connections with guaranteed cleanup after each request
|
|
57
|
+
- Metadata discovery tools for SQL databases
|
|
58
|
+
- Dedicated read tools for Redis
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
Provide the config path by one of these methods:
|
|
62
|
+
|
|
63
|
+
1. `node dist/index.js --config ./config/databases.json`
|
|
64
|
+
2. Set `MCP_DATABASE_CONFIG=/absolute/path/to/databases.json`
|
|
65
|
+
|
|
66
|
+
The configuration file must be a JSON object with a required top-level `databases` array. Optional top-level sections such as `logging` and `query` may also be provided. Example:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"logging": {
|
|
71
|
+
"enabled": true,
|
|
72
|
+
"directory": "./logs"
|
|
73
|
+
},
|
|
74
|
+
"query": {
|
|
75
|
+
"timeoutMs": 5000
|
|
76
|
+
},
|
|
77
|
+
"databases": [
|
|
78
|
+
{
|
|
79
|
+
"key": "main-mysql",
|
|
80
|
+
"type": "mysql",
|
|
81
|
+
"readonly": true,
|
|
82
|
+
"connection": {
|
|
83
|
+
"host": "127.0.0.1",
|
|
84
|
+
"port": 3306,
|
|
85
|
+
"databaseName": "app_db",
|
|
86
|
+
"user": "root",
|
|
87
|
+
"password": "secret",
|
|
88
|
+
"connectTimeoutMs": 5000
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`logging.enabled` defaults to `false`. When enabled, logs are written to the system temporary directory by default. You can override that with `logging.directory`, and relative paths are resolved relative to the config file location. In the example above, logs are written under `./logs`.
|
|
96
|
+
|
|
97
|
+
`query.timeoutMs` is optional. When set, the server applies a query timeout to database operations. In the example above, queries time out after `5000` milliseconds.
|
|
98
|
+
|
|
99
|
+
Oracle supports both Thin and Thick mode. Thick mode uses the same `oracledb` package, but requires Oracle Instant Client on the host machine. Example:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"key": "oracle-thick-example",
|
|
104
|
+
"type": "oracle",
|
|
105
|
+
"readonly": true,
|
|
106
|
+
"connection": {
|
|
107
|
+
"host": "127.0.0.1",
|
|
108
|
+
"port": 1521,
|
|
109
|
+
"serviceName": "XEPDB1",
|
|
110
|
+
"user": "system",
|
|
111
|
+
"password": "secret",
|
|
112
|
+
"clientMode": "thick",
|
|
113
|
+
"clientLibDir": "C:\\oracle\\instantclient_19_25"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Available MCP Tools
|
|
119
|
+
- `show_loaded_config`: show the current in-memory config path, load time, and all configured database targets
|
|
120
|
+
- `reload_config`: reload the JSON config file currently in use and atomically replace the in-memory configuration on success
|
|
121
|
+
- `list_databases`: list all configured target keys and logical database names without opening database connections
|
|
122
|
+
- `ping_database`: test connectivity for one configured target
|
|
123
|
+
- `list_schemas`: list schemas for one SQL target
|
|
124
|
+
- `list_tables`: list tables/views under one SQL schema or the default schema
|
|
125
|
+
- `list_views`: list views under one SQL schema or the default schema
|
|
126
|
+
- `describe_table`: inspect columns before writing joins, reports, or optimization SQL
|
|
127
|
+
- `show_create_table`: inspect exact database-side DDL when the current database supports it
|
|
128
|
+
- `search_tables`: search tables or views by partial name
|
|
129
|
+
- `search_columns`: search columns by partial name across a schema
|
|
130
|
+
- `list_indexes`: inspect table indexes for performance analysis
|
|
131
|
+
- `get_table_statistics`: inspect approximate row counts, storage metrics, and database-specific table statistics
|
|
132
|
+
- `show_variables`: inspect database runtime configuration variables
|
|
133
|
+
- `find_long_running_queries`: inspect currently running sessions above a duration threshold
|
|
134
|
+
- `find_blocking_sessions`: inspect current blocking relationships between sessions
|
|
135
|
+
- `show_locks`: inspect current lock rows exposed by the database
|
|
136
|
+
- `execute_query`: run one read-only SQL query; pass the original query SQL, not write SQL
|
|
137
|
+
- `explain_query`: get the static execution plan for one read-only SQL query; pass the original query SQL, not `EXPLAIN ...`
|
|
138
|
+
- `analyze_query`: get runtime analysis for one read-only SQL query; pass the original query SQL, not `EXPLAIN ANALYZE ...`
|
|
139
|
+
- `execute_statement`: run one non-query SQL statement on a writable target after explicit manual confirmation
|
|
140
|
+
- `redis_get`: read one Redis string key
|
|
141
|
+
- `redis_hgetall`: read one Redis hash key
|
|
142
|
+
- `redis_scan`: cursor-scan Redis keys safely with an optional pattern
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm install
|
|
148
|
+
npm run build
|
|
149
|
+
node dist/index.js --config ./config/databases.example.json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Global Installation
|
|
153
|
+
|
|
154
|
+
This project exposes a CLI command named `mcp-database-service` through the package `bin` field.
|
|
155
|
+
|
|
156
|
+
Recommended options:
|
|
157
|
+
|
|
158
|
+
1. Install from npm:
|
|
159
|
+
|
|
160
|
+
```powershell
|
|
161
|
+
npm install -g @rmwxxwmr/mcp-database-service
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
2. Or install from the local source tree with the helper script:
|
|
165
|
+
|
|
166
|
+
```powershell
|
|
167
|
+
pwsh -File .\scripts\install-global.ps1
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or on Linux/macOS:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
sh ./scripts/install-global.sh
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The helper scripts install dependencies, build the project, create a tarball with `npm pack`, install that tarball globally with `npm install -g <tarball>`, and then delete the temporary tarball. They do not use `npm link`.
|
|
177
|
+
|
|
178
|
+
3. Or install the packed tarball manually:
|
|
179
|
+
|
|
180
|
+
```powershell
|
|
181
|
+
npm pack
|
|
182
|
+
npm install -g .\mcp-database-service-0.1.0.tgz
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The package only publishes runtime files through the `files` field, so packaged installation includes `dist` and the runtime README/config example, not the whole source tree.
|
|
186
|
+
|
|
187
|
+
After installation, the command can be used like this:
|
|
188
|
+
|
|
189
|
+
```powershell
|
|
190
|
+
mcp-database-service --config .\config\databases.example.json
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Example MCP server configuration:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"mcpServers": {
|
|
198
|
+
"database": {
|
|
199
|
+
"command": "mcp-database-service",
|
|
200
|
+
"args": [
|
|
201
|
+
"--config",
|
|
202
|
+
"C:\\path\\to\\databases.json"
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Config Reload
|
|
210
|
+
|
|
211
|
+
- The server loads the JSON config file at startup and keeps a validated in-memory snapshot.
|
|
212
|
+
- The server also watches the same JSON file and automatically reloads it after on-disk changes are detected.
|
|
213
|
+
- Automatic reload is debounced to avoid reloading half-written files too aggressively.
|
|
214
|
+
- You can still use `reload_config` to force a manual reload without restarting the process.
|
|
215
|
+
- Reload is atomic: if the new file is invalid, the old in-memory configuration remains active.
|
|
216
|
+
- `show_loaded_config` can be used to inspect the current config path, load time, and configured database targets.
|
|
217
|
+
- `show_loaded_config` also includes the current logging status, resolved log directory, and configured query timeout.
|
|
218
|
+
- `show_loaded_config` also includes a sanitized connection summary for each target, such as host, port, databaseName or serviceName, user name, and Oracle client mode, but it never exposes passwords.
|
|
219
|
+
|
|
220
|
+
## Oracle Notes
|
|
221
|
+
- Thin mode is the default when `clientMode` is omitted.
|
|
222
|
+
- Thick mode requires `clientMode: "thick"` and a valid `clientLibDir`.
|
|
223
|
+
- All Oracle targets in one process must use the same client mode. Thick mode targets must also share the same `clientLibDir`.
|
|
224
|
+
- `analyze_query` is currently not supported for Oracle and will return `NOT_SUPPORTED`.
|
|
225
|
+
|
|
226
|
+
## Write Statements
|
|
227
|
+
- `execute_query` remains read-only and blocks non-query SQL.
|
|
228
|
+
- `execute_statement` is intended for writable SQL targets only.
|
|
229
|
+
- `execute_statement` requires `readonly: false` on the target database config.
|
|
230
|
+
- Before executing a non-query SQL statement, the server asks the MCP client for explicit user confirmation through MCP elicitation when the client supports it.
|
|
231
|
+
- If the MCP client does not support elicitation, `execute_statement` automatically falls back to a two-step confirmation flow: the first call returns confirmation details and a `confirmationId`, and the second call must resend the same SQL with `confirmationId` and `confirmExecution: true` after the user confirms.
|
|
232
|
+
- `execute_statement` confirmation includes SQL type, target object, SQL preview, parameter preview, and risk hints for dangerous statements.
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
[English](./README.md) | 简体中文
|
|
2
|
+
|
|
3
|
+
# MCP Database Service
|
|
4
|
+
|
|
5
|
+
一个基于 TypeScript 实现的多数据库 MCP 服务,面向 Model Context Protocol (MCP)。
|
|
6
|
+
|
|
7
|
+
它支持 MySQL、PostgreSQL、Oracle、openGauss 和 Redis,提供懒连接、读为主的数据库工具、SQL 执行计划分析、配置热刷新,以及带保护机制的写操作执行。
|
|
8
|
+
|
|
9
|
+
这个项目适合需要安全地做数据库发现、查询、性能分析和受控写操作的 AI agent 与 MCP client。
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
从 npm 安装:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
npm install -g @rmwxxwmr/mcp-database-service
|
|
17
|
+
mcp-database-service --config ./config/databases.example.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
从源码运行:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install
|
|
24
|
+
npm run build
|
|
25
|
+
node dist/index.js --config ./config/databases.example.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 支持矩阵
|
|
29
|
+
|
|
30
|
+
| 数据库 | 查询工具 | 元数据工具 | `explain_query` | `analyze_query` | 写操作支持 |
|
|
31
|
+
| --- | --- | --- | --- | --- | --- |
|
|
32
|
+
| MySQL | 是 | 是 | 是 | 是 | 是 |
|
|
33
|
+
| PostgreSQL | 是 | 是 | 是 | 是 | 是 |
|
|
34
|
+
| openGauss | 是 | 是 | 是 | 是 | 是 |
|
|
35
|
+
| Oracle | 是 | 是 | 是 | 否 | 是 |
|
|
36
|
+
| Redis | 是 | 仅 Redis 专用工具 | 否 | 否 | 否 |
|
|
37
|
+
|
|
38
|
+
补充说明:
|
|
39
|
+
- show_create_table 当前支持 MySQL 和 Oracle;PostgreSQL 与 openGauss 目前会返回 NOT_SUPPORTED。
|
|
40
|
+
- `show_variables`、`find_long_running_queries`、`find_blocking_sessions`、`show_locks` 这类运行态排障工具依赖数据库账号可见性和权限,结果可能为空或受限。
|
|
41
|
+
|
|
42
|
+
## 支持的数据库
|
|
43
|
+
- MySQL
|
|
44
|
+
- Oracle
|
|
45
|
+
- PostgreSQL
|
|
46
|
+
- openGauss(通过 PostgreSQL 协议兼容)
|
|
47
|
+
- Redis
|
|
48
|
+
|
|
49
|
+
## 特性
|
|
50
|
+
- 单个配置文件中支持多个数据库目标
|
|
51
|
+
- 支持可选的文件日志,可自定义输出目录
|
|
52
|
+
- 无需重启服务即可手动刷新配置
|
|
53
|
+
- JSON 配置文件变更后自动热刷新
|
|
54
|
+
- 对查询工具进行严格只读限制
|
|
55
|
+
- 可写目标支持显式确认后的写操作
|
|
56
|
+
- 每次请求使用懒连接,并在完成后保证关闭
|
|
57
|
+
- 为 SQL 数据库提供元数据发现工具
|
|
58
|
+
- 为 Redis 提供专用只读工具
|
|
59
|
+
|
|
60
|
+
## 配置
|
|
61
|
+
可以通过以下任一方式传入配置文件路径:
|
|
62
|
+
|
|
63
|
+
1. `node dist/index.js --config ./config/databases.json`
|
|
64
|
+
2. 设置 `MCP_DATABASE_CONFIG=/absolute/path/to/databases.json`
|
|
65
|
+
|
|
66
|
+
配置文件必须是一个 JSON 对象,其中顶层 `databases` 数组是必填项;`logging`、`query` 等顶层配置项是可选的。示例:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"logging": {
|
|
71
|
+
"enabled": true,
|
|
72
|
+
"directory": "./logs"
|
|
73
|
+
},
|
|
74
|
+
"query": {
|
|
75
|
+
"timeoutMs": 5000
|
|
76
|
+
},
|
|
77
|
+
"databases": [
|
|
78
|
+
{
|
|
79
|
+
"key": "main-mysql",
|
|
80
|
+
"type": "mysql",
|
|
81
|
+
"readonly": true,
|
|
82
|
+
"connection": {
|
|
83
|
+
"host": "127.0.0.1",
|
|
84
|
+
"port": 3306,
|
|
85
|
+
"databaseName": "app_db",
|
|
86
|
+
"user": "root",
|
|
87
|
+
"password": "secret",
|
|
88
|
+
"connectTimeoutMs": 5000
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`logging.enabled` 默认是 `false`。开启后,日志默认写入系统临时目录;你也可以通过 `logging.directory` 自定义目录,若填相对路径,则相对于配置文件所在目录解析。上面的示例会把日志写到 `./logs`。
|
|
96
|
+
|
|
97
|
+
`query.timeoutMs` 是可选项。配置后,服务会为数据库操作应用查询超时。上面的示例会在 `5000` 毫秒后超时。
|
|
98
|
+
|
|
99
|
+
Oracle 同时支持 Thin 和 Thick 模式。Thick 模式仍然使用同一个 `oracledb` 包,但要求宿主机安装 Oracle Instant Client。示例:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"key": "oracle-thick-example",
|
|
104
|
+
"type": "oracle",
|
|
105
|
+
"readonly": true,
|
|
106
|
+
"connection": {
|
|
107
|
+
"host": "127.0.0.1",
|
|
108
|
+
"port": 1521,
|
|
109
|
+
"serviceName": "XEPDB1",
|
|
110
|
+
"user": "system",
|
|
111
|
+
"password": "secret",
|
|
112
|
+
"clientMode": "thick",
|
|
113
|
+
"clientLibDir": "C:\\oracle\\instantclient_19_25"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 可用 MCP Tools
|
|
119
|
+
- `show_loaded_config`:查看当前内存中的配置路径、加载时间和所有已配置数据库目标
|
|
120
|
+
- `reload_config`:重新读取当前 JSON 配置文件,并在成功时原子替换内存配置
|
|
121
|
+
- `list_databases`:列出所有已配置目标的 key 和逻辑数据库名,不会打开数据库连接
|
|
122
|
+
- `ping_database`:测试某个已配置目标的连通性
|
|
123
|
+
- `list_schemas`:列出某个 SQL 目标下的 schema
|
|
124
|
+
- `list_tables`:列出某个 SQL schema 或默认 schema 下的表 / 视图
|
|
125
|
+
- `list_views`:列出某个 SQL schema 或默认 schema 下的视图
|
|
126
|
+
- `describe_table`:查看列信息,适合写 join、报表或优化 SQL 前使用
|
|
127
|
+
- `show_create_table`:在当前数据库支持时查看数据库侧的 DDL 定义
|
|
128
|
+
- `search_tables`:按部分名称搜索表或视图
|
|
129
|
+
- `search_columns`:按部分名称搜索 schema 下的列
|
|
130
|
+
- `list_indexes`:查看表索引,适合做性能分析
|
|
131
|
+
- `get_table_statistics`:查看近似行数、存储信息和数据库特定统计信息
|
|
132
|
+
- `show_variables`:查看数据库运行时参数
|
|
133
|
+
- `find_long_running_queries`:查看超过阈值的当前长时间运行会话
|
|
134
|
+
- `find_blocking_sessions`:查看当前阻塞关系
|
|
135
|
+
- `show_locks`:查看数据库当前可见的锁信息
|
|
136
|
+
- `execute_query`:执行只读 SQL 查询;传原始查询 SQL,不要传写 SQL
|
|
137
|
+
- `explain_query`:查看只读 SQL 的静态执行计划;传原始查询 SQL,不要传 `EXPLAIN ...`
|
|
138
|
+
- `analyze_query`:查看只读 SQL 的运行时分析;传原始查询 SQL,不要传 `EXPLAIN ANALYZE ...`
|
|
139
|
+
- `execute_statement`:在可写目标上执行非查询 SQL,但必须先经过显式确认
|
|
140
|
+
- `redis_get`:读取一个 Redis 字符串 key
|
|
141
|
+
- `redis_hgetall`:读取一个 Redis hash key
|
|
142
|
+
- `redis_scan`:按游标安全扫描 Redis key,可选 pattern
|
|
143
|
+
|
|
144
|
+
## 开发
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm install
|
|
148
|
+
npm run build
|
|
149
|
+
node dist/index.js --config ./config/databases.example.json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 全局安装
|
|
153
|
+
|
|
154
|
+
本项目通过 `bin` 字段暴露了一个 CLI 命令:`mcp-database-service`。
|
|
155
|
+
|
|
156
|
+
推荐方式:
|
|
157
|
+
|
|
158
|
+
1. 直接从 npm 安装:
|
|
159
|
+
|
|
160
|
+
```powershell
|
|
161
|
+
npm install -g @rmwxxwmr/mcp-database-service
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
2. 或从本地源码树通过辅助脚本安装:
|
|
165
|
+
|
|
166
|
+
```powershell
|
|
167
|
+
pwsh -File .\scripts\install-global.ps1
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Linux / macOS 可使用:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
sh ./scripts/install-global.sh
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
这些脚本会安装依赖、构建项目、通过 `npm pack` 生成 tarball、再用 `npm install -g <tarball>` 全局安装,最后删除临时 tarball。它们不会使用 `npm link`。
|
|
177
|
+
|
|
178
|
+
3. 或手动安装打包产物:
|
|
179
|
+
|
|
180
|
+
```powershell
|
|
181
|
+
npm pack
|
|
182
|
+
npm install -g .\mcp-database-service-0.1.0.tgz
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
由于 `files` 字段只发布运行时文件,所以打包安装时会包含 `dist`、运行时 README 和配置示例,而不是整个源码目录。
|
|
186
|
+
|
|
187
|
+
安装后可以这样启动:
|
|
188
|
+
|
|
189
|
+
```powershell
|
|
190
|
+
mcp-database-service --config .\config\databases.example.json
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
MCP 服务配置示例:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"mcpServers": {
|
|
198
|
+
"database": {
|
|
199
|
+
"command": "mcp-database-service",
|
|
200
|
+
"args": [
|
|
201
|
+
"--config",
|
|
202
|
+
"C:\\path\\to\\databases.json"
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 配置刷新
|
|
210
|
+
|
|
211
|
+
- 服务启动时会加载 JSON 配置文件,并保留一份校验通过的内存快照
|
|
212
|
+
- 服务也会监听同一个 JSON 文件,在磁盘变更后自动热刷新
|
|
213
|
+
- 自动刷新有防抖,避免在文件半写入状态下频繁重载
|
|
214
|
+
- 你仍然可以使用 `reload_config` 手动刷新,而无需重启进程
|
|
215
|
+
- 刷新是原子的:如果新文件无效,旧的内存配置会继续保持生效
|
|
216
|
+
- 可以用 `show_loaded_config` 查看当前配置路径、加载时间和已配置数据库目标
|
|
217
|
+
- `show_loaded_config` 也会返回当前日志开关状态、解析后的日志目录以及配置的查询超时
|
|
218
|
+
- `show_loaded_config` 还会返回脱敏后的连接摘要,例如 host、port、databaseName 或 serviceName、用户名以及 Oracle client mode,但不会暴露密码
|
|
219
|
+
|
|
220
|
+
## Oracle 说明
|
|
221
|
+
- 未配置 `clientMode` 时,默认使用 Thin 模式
|
|
222
|
+
- Thick 模式要求 `clientMode: "thick"`,并提供有效的 `clientLibDir`
|
|
223
|
+
- 同一进程中的所有 Oracle 目标必须使用相同的 client mode;如果使用 Thick,还必须共用同一个 `clientLibDir`
|
|
224
|
+
- Oracle 目前不支持 `analyze_query`,会返回 `NOT_SUPPORTED`
|
|
225
|
+
|
|
226
|
+
## 写操作说明
|
|
227
|
+
- `execute_query` 始终是只读的,会拦截非查询 SQL
|
|
228
|
+
- `execute_statement` 仅用于可写 SQL 目标
|
|
229
|
+
- `execute_statement` 要求目标配置为 `readonly: false`
|
|
230
|
+
- 如果 MCP client 支持交互确认,服务会在执行非查询 SQL 前通过 MCP elicitation 请求用户确认
|
|
231
|
+
- 如果 MCP client 不支持 elicitation,`execute_statement` 会自动退回到二段式确认流程:第一次调用返回确认详情和 `confirmationId`,用户确认后,第二次必须带上 `confirmationId` 和 `confirmExecution: true`,并重复同一条 SQL 才会真正执行
|
|
232
|
+
- `execute_statement` 的确认信息会包含 SQL 类型、目标对象、SQL 预览、参数预览,以及高风险语句的风险提示
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"logging": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"directory": "./logs"
|
|
5
|
+
},
|
|
6
|
+
"query": {
|
|
7
|
+
"timeoutMs": 5000
|
|
8
|
+
},
|
|
9
|
+
"databases": [
|
|
10
|
+
{
|
|
11
|
+
"key": "main-mysql",
|
|
12
|
+
"type": "mysql",
|
|
13
|
+
"readonly": true,
|
|
14
|
+
"connection": {
|
|
15
|
+
"host": "127.0.0.1",
|
|
16
|
+
"port": 3306,
|
|
17
|
+
"databaseName": "app_db",
|
|
18
|
+
"user": "root",
|
|
19
|
+
"password": "secret",
|
|
20
|
+
"connectTimeoutMs": 5000
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "cache-redis",
|
|
25
|
+
"type": "redis",
|
|
26
|
+
"readonly": true,
|
|
27
|
+
"connection": {
|
|
28
|
+
"url": "redis://127.0.0.1:6379/0",
|
|
29
|
+
"connectTimeoutMs": 5000
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"key": "oracle-thick-example",
|
|
34
|
+
"type": "oracle",
|
|
35
|
+
"readonly": true,
|
|
36
|
+
"connection": {
|
|
37
|
+
"host": "127.0.0.1",
|
|
38
|
+
"port": 1521,
|
|
39
|
+
"serviceName": "XEPDB1",
|
|
40
|
+
"user": "system",
|
|
41
|
+
"password": "secret",
|
|
42
|
+
"connectTimeoutMs": 5000,
|
|
43
|
+
"clientMode": "thick",
|
|
44
|
+
"clientLibDir": "D:\\Work\\Navicat\\instantclient_19_25"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatabaseConfig, LoadedConfig } from "./configTypes.js";
|
|
2
|
+
export declare function summarizeLoadedConfig(config: LoadedConfig): Record<string, unknown>;
|
|
3
|
+
export declare function summarizeDatabaseConfig(database: DatabaseConfig): Record<string, unknown>;
|
|
4
|
+
export declare function summarizeDatabaseListItem(database: DatabaseConfig): Record<string, unknown>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { URL } from "node:url";
|
|
2
|
+
export function summarizeLoadedConfig(config) {
|
|
3
|
+
return {
|
|
4
|
+
configPath: config.configPath,
|
|
5
|
+
loadedAt: config.loadedAt,
|
|
6
|
+
databaseCount: config.databases.length,
|
|
7
|
+
logging: {
|
|
8
|
+
enabled: config.logging.enabled,
|
|
9
|
+
directory: config.logging.directory
|
|
10
|
+
},
|
|
11
|
+
query: {
|
|
12
|
+
timeoutMs: config.query.timeoutMs
|
|
13
|
+
},
|
|
14
|
+
items: config.databases.map((database) => summarizeDatabaseConfig(database))
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function summarizeDatabaseConfig(database) {
|
|
18
|
+
return {
|
|
19
|
+
key: database.key,
|
|
20
|
+
databaseName: summarizeLogicalDatabaseName(database),
|
|
21
|
+
type: database.type,
|
|
22
|
+
readonly: database.readonly,
|
|
23
|
+
connection: summarizeConnection(database)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function summarizeDatabaseListItem(database) {
|
|
27
|
+
return {
|
|
28
|
+
key: database.key,
|
|
29
|
+
databaseName: summarizeLogicalDatabaseName(database),
|
|
30
|
+
type: database.type,
|
|
31
|
+
readonly: database.readonly
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function summarizeConnection(database) {
|
|
35
|
+
switch (database.type) {
|
|
36
|
+
case "mysql":
|
|
37
|
+
return summarizeMysqlConnection(database);
|
|
38
|
+
case "postgresql":
|
|
39
|
+
case "opengauss":
|
|
40
|
+
return summarizePostgresConnection(database);
|
|
41
|
+
case "oracle":
|
|
42
|
+
return summarizeOracleConnection(database);
|
|
43
|
+
case "redis":
|
|
44
|
+
return summarizeRedisConnection(database);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function summarizeMysqlConnection(database) {
|
|
48
|
+
return {
|
|
49
|
+
host: database.connection.host,
|
|
50
|
+
port: database.connection.port ?? 3306,
|
|
51
|
+
databaseName: database.connection.databaseName,
|
|
52
|
+
user: database.connection.user,
|
|
53
|
+
connectTimeoutMs: database.connection.connectTimeoutMs ?? null,
|
|
54
|
+
sslEnabled: database.connection.ssl === true || typeof database.connection.ssl === "object"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function summarizePostgresConnection(database) {
|
|
58
|
+
return {
|
|
59
|
+
host: database.connection.host,
|
|
60
|
+
port: database.connection.port ?? 5432,
|
|
61
|
+
databaseName: database.connection.databaseName,
|
|
62
|
+
user: database.connection.user,
|
|
63
|
+
connectTimeoutMs: database.connection.connectTimeoutMs ?? null,
|
|
64
|
+
sslEnabled: database.connection.ssl === true || typeof database.connection.ssl === "object"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function summarizeOracleConnection(database) {
|
|
68
|
+
return {
|
|
69
|
+
host: database.connection.host,
|
|
70
|
+
port: database.connection.port ?? 1521,
|
|
71
|
+
serviceName: database.connection.serviceName ?? null,
|
|
72
|
+
sid: database.connection.sid ?? null,
|
|
73
|
+
user: database.connection.user,
|
|
74
|
+
connectTimeoutMs: database.connection.connectTimeoutMs ?? null,
|
|
75
|
+
clientMode: database.connection.clientMode ?? (database.connection.clientLibDir ? "thick" : "thin"),
|
|
76
|
+
clientLibDir: database.connection.clientLibDir ?? null
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function summarizeRedisConnection(database) {
|
|
80
|
+
return {
|
|
81
|
+
url: database.connection.url ? sanitizeRedisUrl(database.connection.url) : null,
|
|
82
|
+
host: database.connection.host ?? null,
|
|
83
|
+
port: database.connection.port ?? null,
|
|
84
|
+
databaseName: database.connection.databaseName ?? null,
|
|
85
|
+
username: database.connection.username ?? null,
|
|
86
|
+
connectTimeoutMs: database.connection.connectTimeoutMs ?? null
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function summarizeLogicalDatabaseName(database) {
|
|
90
|
+
switch (database.type) {
|
|
91
|
+
case "mysql":
|
|
92
|
+
return database.connection.databaseName;
|
|
93
|
+
case "postgresql":
|
|
94
|
+
case "opengauss":
|
|
95
|
+
return database.connection.databaseName;
|
|
96
|
+
case "oracle":
|
|
97
|
+
return database.connection.serviceName ?? database.connection.sid ?? null;
|
|
98
|
+
case "redis":
|
|
99
|
+
return database.connection.databaseName ?? null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function sanitizeRedisUrl(rawUrl) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = new URL(rawUrl);
|
|
105
|
+
if (parsed.password) {
|
|
106
|
+
parsed.password = "***";
|
|
107
|
+
}
|
|
108
|
+
if (parsed.username) {
|
|
109
|
+
parsed.username = parsed.username;
|
|
110
|
+
}
|
|
111
|
+
return parsed.toString();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return rawUrl.replace(/\/\/([^:@/]+):([^@/]+)@/, "//$1:***@");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=configSummary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configSummary.js","sourceRoot":"","sources":["../../src/config/configSummary.ts"],"names":[],"mappings":"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"}
|