关于 MySQL (read-only, switchable)
面向需要查询 MySQL 的 AI 助手,提供只读 MCP 服务,可在会话中切换数据库、服务器和凭据而无需重启客户端。连接切换经验证后才生效。
主要功能
- 01只读 SQL 查询
- 02会话中切换数据库与服务器
- 03管理命名连接配置
- 04查看表、索引与外键
- 05采样行并检查表结构
- 06查询结果最多返回 100 行
适用场景
- 01在对话中比较多个数据库
- 02检查表结构、索引及外键
- 03读取样本数据并运行只读查询
MCP 相关
作为 MCP 服务向客户端提供 MySQL 连接管理与只读查询工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @shibbirweb/mcp-mysql-read-onlyMYSQL_HOSTHostname of the MySQL server. Defaults to 127.0.0.1MYSQL_PORTPort of the MySQL server. Defaults to 3306MYSQL_USERMySQL user. Prefer an account holding only SELECT grantsMYSQL_PASSWORD密钥Password for MYSQL_USER. Empty when the account has noneMYSQL_DATABASEDatabase to open at startup. Can be changed later without restartingMYSQL_PROFILES密钥JSON object of named connections to switch between, keyed by profile nameMYSQL_DEFAULT_PROFILEWhich MYSQL_PROFILES entry to start onMYSQL_CONNECT_TIMEOUT_MSConnection timeout in milliseconds. Defaults to 10000MYSQL_QUERY_TIMEOUT_MSServer-side query timeout in milliseconds. Defaults to 30000本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
12收录的工具列表 · 更新于 2 周前
此工具无需参数。
详细介绍
mcp-mysql-read-only
一个 MCP 服务,为 AI 助手提供 MySQL 只读访问权限,并允许它在对话过程中更改数据库、服务和凭据,无需重启客户端。
大多数 MySQL MCP 服务在启动时会从环境变量读取连接信息。要将其指向其他数据库,就必须编辑配置文件并重启助手,这会导致对话丢失。本服务将连接作为运行时状态保存,因此切换连接只需再调用一次工具。
可通过 npm 使用 npx 运行,也可完全在 Docker 中运行,无需在机器上安装任何内容。
flowchart LR
A["AI assistant<br/>Claude Desktop / Claude Code"]
B["mcp-mysql-read-only<br/>one process, whole session"]
C[("app_dev")]
D[("staging")]
E[("analytics")]
F[("any server<br/>reached with connect")]
A <-->|"MCP over stdio"| B
B -.->|"pooled per target"| C
B -.->|"pooled per target"| D
B -.->|"pooled per target"| E
B -.->|"opened at runtime"| F
服务会在整个会话期间持续运行,因此当前连接只是其中的状态。切换时会选择另一个连接池,而不是重新连接;切换回来时则会复用已预热的连接池。
快速开始
有两种运行方式。npm 的设置更简单;Docker 则会将服务置于容器内运行。
npm
MYSQL_HOST=127.0.0.1 \
MYSQL_USER=readonly \
MYSQL_PASSWORD=secret \
MYSQL_DATABASE=my_database \
npx -y @shibbirweb/mcp-mysql-read-only
需要 Node 22 或更高版本。没有容器阻隔,因此 127.0.0.1 的含义就是你所预期的那样。
Docker
docker run -i --rm \
--add-host host.docker.internal:host-gateway \
-e MYSQL_HOST=host.docker.internal \
-e MYSQL_USER=readonly \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DATABASE=my_database \
shibbirweb/mcp-mysql-read-only
要访问与 Docker 运行在同一台机器上的 MySQL,请使用 host.docker.internal。在容器内,localhost 指的是容器本身。
两种方式中,容器的隔离性更强:服务仅使用镜像和环境提供的内容运行。通过 npm 运行时,服务会直接在你的机器上以当前用户的权限运行。两者都遵守相同的只读保证。
Claude Desktop
添加到 claude_desktop_config.json:
{
"mcpServers": {
"mysql": {
"command": "npx",
"args": ["-y", "@shibbirweb/mcp-mysql-read-only"],
"env": {
"MYSQL_HOST": "127.0.0.1",
"MYSQL_USER": "readonly",
"MYSQL_PASSWORD": "secret",
"MYSQL_DATABASE": "my_database"
}
}
}
}
或者在 Docker 中运行同一个服务:
{
"mcpServers": {
"mysql": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"--add-host", "host.docker.internal:host-gateway",
"-e", "MYSQL_HOST=host.docker.internal",
"-e", "MYSQL_USER=readonly",
"-e", "MYSQL_PASSWORD=secret",
"-e", "MYSQL_DATABASE=my_database",
"shibbirweb/mcp-mysql-read-only"
]
}
}
}
Claude Code
在项目根目录的 .mcp.json 中使用相同的配置格式:
{
"mcpServers": {
"mysql": {
"command": "npx",
"args": ["-y", "@shibbirweb/mcp-mysql-read-only"],
"env": {
"MYSQL_HOST": "127.0.0.1",
"MYSQL_USER": "readonly",
"MYSQL_PASSWORD": "secret",
"MYSQL_DATABASE": "my_database"
}
}
}
}
Claude Desktop 部分中的任一种配置格式也适用于此处。
重启客户端一次即可。之后更改数据库时,无需再重启客户端。
这些文件中的凭据以纯文本形式保存在磁盘上。建议使用仅授予
SELECT权限的 MySQL 账户,并将该文件排除在版本控制之外。请参阅安全性。
切换连接
直接提出请求即可。这些请求会映射到连接工具:
“切换到暂存数据库” “
analytics中有哪些表?” “以 reporting_user 身份连接到 10.0.0.5 上的 MySQL”
| 需求 | 是否需要重启? |
|---|---|
| 同一服务上的另一个数据库 | 否 |
| 另一个命名配置文件 | 否 |
| 不同的服务或凭据 | 否 |
在 MYSQL_PROFILES 中新增永久配置文件 | 是,仅需一次 |
sequenceDiagram
autonumber
actor You
participant A as Assistant
participant S as MCP server
participant M as MySQL
You->>A: "how many users in staging?"
A->>S: use_connection(staging)
S->>M: open + SELECT 1
M-->>S: ok
Note over S: verified, so the switch is committed
S-->>A: Switched to staging
A->>S: run_query(SELECT COUNT(*) ...)
S->>M: SELECT COUNT(*) ...
M-->>S: 4821
A-->>You: 4821 users in staging
You->>A: "and in production?"
Note over A,S: same session, no restart
A->>S: use_connection(production)
如果切换未能通过验证,则不会提交切换,之前的连接会保持活动状态,会话也会继续正常工作:
sequenceDiagram
participant S as MCP server
participant M as MySQL
S->>M: open "no_such_db" + SELECT 1
M-->>S: Unknown database
Note over S: active connection left untouched
S-->>S: Error: Unknown database 'no_such_db'
命名配置
使用 MYSQL_PROFILES 这个 JSON 对象预先定义多个连接:
{
"local": { "host": "host.docker.internal", "user": "root", "password": "", "database": "app_dev" },
"staging": { "host": "db.staging.internal", "user": "readonly", "password": "secret", "database": "app" },
"reports": { "host": "db.staging.internal", "user": "readonly", "password": "secret", "database": "analytics" }
}
作为单个环境变量传入:
docker run -i --rm \
-e MYSQL_PROFILES='{"local":{"host":"host.docker.internal","user":"root","password":"","database":"app_dev"}}' \
-e MYSQL_DEFAULT_PROFILE=local \
shibbirweb/mcp-mysql-read-only
host 默认为 host.docker.internal,port 默认为 3306,password 默认为空。user 和 database 为必填项;缺少其中任一项的配置会被跳过并发出警告,不会导致服务停止。
连接配置中没有的服务器
connect 工具可在运行时接收主机、用户、密码和数据库,并在本次会话的剩余时间内以别名保存该连接。所有信息都不会写入磁盘,也无需重启,因此只为临时查看某个数据库时,无须修改 MYSQL_PROFILES。
工具
连接
| 工具 | 用途 |
|---|---|
current_connection | 查看当前使用的服务器和数据库 |
list_connections | 查看可用配置,* 表示当前使用的配置 |
list_databases | 查看已连接服务器上的数据库 |
use_database | 切换当前服务器上的数据库 |
use_connection | 切换到指定名称的配置,可选覆盖 database |
connect | 使用明确的凭据连接任意服务器,可选指定 alias |
读取
| 工具 | 用途 |
|---|---|
list_tables | 查看当前数据库中的表 |
describe_table | 查看单个表的列和类型 |
get_table_indexes | 查看单个表的索引 |
get_foreign_keys | 查看单个表的外键关系 |
get_table_sample | 获取最多 50 行示例数据 |
run_query | 执行一条只读语句 |
所有读取工具也都接受可选的 database 参数,该参数仅对本次调用生效,不会更改当前连接。适合在无需来回切换的情况下比较两个数据库。
use_database、use_connection 和 connect 都会先建立连接并运行 SELECT 1,然后才确认切换,因此无效的数据库名称或无法访问的主机都会立即报错。切换失败时,之前的连接仍保持活动状态。
配置
| 变量 | 默认值 | 用途 |
|---|---|---|
MYSQL_PROFILES | 无 | 命名配置的 JSON 对象 |
MYSQL_DEFAULT_PROFILE | 无 | 启动时使用的配置 |
MYSQL_HOST | host.docker.internal | 单连接回退配置 |
MYSQL_PORT | 3306 | " |
MYSQL_USER | 无 | " |
MYSQL_PASSWORD | 空 | " |
MYSQL_DATABASE | 无 | " |
MYSQL_QUERY_TIMEOUT_MS | 30000 | 语句超时 |
MYSQL_CONNECT_TIMEOUT_MS | 10000 | 连接超时 |
设置 MYSQL_USER 和 MYSQL_DATABASE 后,会注册名为 default 的配置。这些配置项都不是必需的:即使完全没有配置,服务仍会启动,工具会提示你调用 connect。
启动时使用的配置:若 MYSQL_DEFAULT_PROFILE 指向一个有效配置,则使用该配置;否则使用 default;再否则使用第一个已定义的配置。
安全
通过两个相互独立的层确保只读,因此其中一层存在漏洞也不会自动导致写入。
flowchart TD
Q["run_query"] --> V{"SQL validator"}
V -->|"DELETE, DROP, stacked statements,<br/>write behind a CTE, INTO OUTFILE"| R1["rejected, no connection used"]
V -->|"reads only"| D{"mysql2 driver"}
D -->|"multipleStatements: false"| R2["a second statement<br/>cannot even be sent"]
D --> M{"MySQL session"}
M -->|"SET SESSION TRANSACTION READ ONLY"| R3["writes rejected by the server<br/>with error 1792"]
M -->|"read"| OK["rows returned"]
style R1 fill:#fde,stroke:#b55
style R2 fill:#fde,stroke:#b55
style R3 fill:#fde,stroke:#b55
style OK fill:#dfd,stroke:#5b5
SQL 验证器。 语句只能以 SELECT、WITH、SHOW、DESCRIBE、DESC 和 EXPLAIN 开头。在检查任何关键字之前,字符串字面量、反引号标识符以及 --、# 和 /* */ 注释都会被清空,因此字面量中隐藏的关键字或分号不会被误认为 SQL。系统会拒绝多条语句拼接执行。由于 WITH 和 EXPLAIN ANALYZE 可在看似无害的首词之后包含写入操作,因此也会扫描它们的主体以检查写入关键字。INTO OUTFILE、INTO DUMPFILE、LOAD DATA、SLEEP() 和 BENCHMARK() 均会被拦截。作为工具参数传入的表名和数据库名必须匹配 ^[A-Za-z0-9_$]+$,以防它们跳出被插入的标识符。
MySQL 会话。 每个连接池连接都会运行 SET SESSION TRANSACTION READ ONLY 和 SET SESSION MAX_EXECUTION_TIME。启用自动提交后,每条语句都是独立的只读事务,因此即使以某种方式绕过验证器,服务器也会以错误 1792 拒绝写入。驱动程序使用 multipleStatements: false 运行,因此无法夹带第二条语句。
这不是什么
这是一个防护机制,不是权限系统。 它会阻止助手通过此服务写入数据,但无法阻止持有相同凭据的人通过其他客户端写入数据。
请将它指向一个只读 MySQL 用户。 这才是真正的保护措施:
CREATE USER 'readonly'@'%' IDENTIFIED BY 'a strong password';
GRANT SELECT ON your_database.* TO 'readonly'@'%';
这样,即使此服务存在错误,也无法写入任何内容。
还有一些值得了解的限制:
SLEEP()和BENCHMARK()会被直接拦截,以粗略防止会话挂起。WITH查询中,名称恰好为update或delete的列会被拒绝。请用反引号括起来。- 工具输出中的结果最多截断为 100 行。查询本身不受限制,因此读取大型表时请添加
LIMIT。
已知行为
并行工具调用。 当前连接是进程状态中的单一数据。如果客户端在一个批次中发出多个工具调用,这些调用会并发处理,因此与 run_query 一起批量发送的 use_database 不保证先执行。顺序调用则会按预期工作。如果读取操作必须固定到特定数据库,请传入每次调用的 database 参数,而不要依赖同一批次中执行的切换操作。
关闭。 服务会在收到 SIGINT/SIGTERM 时退出,而不是在 stdin 关闭时退出。打开的连接池套接字会让事件循环保持运行;stdin 到达 EOF 仅表示没有缓冲更多请求。若将其视为关闭信号,就会在调用仍在进行时关闭连接池。任何直接驱动此服务的脚本都应持续读取,直到收到预期响应,然后再终止进程。
开发
所有操作都在 Docker 中运行,因此只需克隆项目并安装 Docker:
git clone https://github.com/shibbirweb/mcp-mysql-read-only.git
cd mcp-mysql-read-only
./scripts/test-in-docker.sh
此命令会启动一个临时 MySQL 容器,构建测试镜像,对该容器运行完整测试套件,然后清理所有内容。不会触及你自己的 MySQL。
本地安装 Node 22 后:
npm ci
npm run build
npm run test:unit # no database needed
npm test # integration tests need MySQL, see below
集成测试会读取 TEST_MYSQL_HOST、TEST_MYSQL_PORT、TEST_MYSQL_USER、TEST_MYSQL_PASSWORD。测试会创建并删除两个临时数据库(mcp_test、mcp_test_alt),因此请将其指向可丢弃的服务。无法连接 MySQL 时,测试会跳过,而不是失败。
项目结构
src/
index.ts Entry point
ApplicationFactory.ts Composition root: the only file that wires things together
types/ Interfaces and type aliases, one file per concern
errors/ Named error classes
domain/ ConnectionTarget, ConnectionProfile (immutable value objects)
config/ Reading configuration from the environment
connections/ Target factory, profile registry, connection manager
database/ Pool manager, read-only session initializer, query executor
validation/ SQL skeletonizer, validators, rules/
formatting/ Response and row rendering
tools/ BaseTool, DatabaseScopedTool, connection/, reading/
server/ McpMySqlServer
依赖关系由外向内,且没有任何类会自行构造协作者:所有依赖都由 ApplicationFactory 注入,因此每个部分都可以在没有数据库或运行环境的情况下进行单元测试。
开发者文档介绍了每个类采用当前构建方式的原因,以及各处使用的设计模式,详见 wiki(源文件位于 docs/wiki/)。
贡献
Pull request 请提交到 master。CI 会针对 MySQL 8.0 和 8.4 运行完整测试套件,并构建适用于 amd64 和 arm64 的镜像。请确保更改有测试覆盖;如果行为发生变化,请更新 docs/wiki/。
本文档还有一个副本 README.dockerhub.md,发布工作流会将其作为 Docker Hub 的描述发布。Docker Hub 既不支持渲染 mermaid,也不支持相对链接,因此该副本使用 ASCII 图示和绝对 URL。如果你在此处更改面向用户的行为,也请同步更改该副本。
更新日志
发布历史(包括哪些版本发布到了 npm,哪些版本仅发布到了 Docker Hub)见 CHANGELOG.md。
隐私
除你指定的 MySQL 外,此服务不会向任何地方发送数据:没有遥测、没有分析、不写入磁盘,也不会在退出后保留任何内容。会离开你设备的是助手读取的内容,因为查询结果会成为对话内容。PRIVACY.md 详细说明了这两方面。
许可证
MIT © Md. Shibbir Ahmed
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。