ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

mcp-server-mysql 项目级数据库接入实战:SSH 隧道、.mcp.json 配置与 Claude Code Hooks 完整指南

mcp-server-mysql 项目级数据库接入实战:SSH 隧道、.mcp.json 配置与 Claude Code Hooks 完整指南
  • MCP 服务
  • 数据库客户端
  • AI 应用

【免费下载链接】mcp-server-mysql

A Model Context Protocol server that provides read-only access to MySQL databases. This server enables LLMs to inspect database schemas and execute read-only queries.

项目地址:https://gitcode.com/gh_mirrors/mc/mcp-server-mysql
点击查看免费下载

本文基于 mcp-server-mysql 仓库自带的 PROJECT_SETUP_GUIDE.md,完整讲解如何为每个独立项目搭建"SSH 隧道脚本 + MCP 配置 + 自动 Hooks"三位一体的远程 MySQL 接入方案:读完你将掌握为多个项目分别建立唯一本地端口隧道、编写带密码输入钩子的.mcp.json、并让 Claude Code 启动/退出时自动启停隧道的完整流程,同时结合仓库源码弄清每个ALLOW_*环境变量与MYSQL_DISABLE_READ_ONLY_TRANSACTIONS在底层是如何生效的。

一、总览:项目级数据库访问的三大组件

该指南面向"一个项目连接一个远程数据库"的多项目场景。每个项目要打通 Claude Code 到 MySQL 的链路,需要三个组件:

  1. SSH Tunnel Scripts(SSH 隧道脚本):建立到远程数据库的安全通道,把远端3306端口映射到本地唯一端口(如3307);
  2. MCP Configuration(MCP 配置):在项目目录的.mcp.json中声明 MCP 服务器,通过环境变量把数据库凭证注入 mcp-server-mysql 进程;
  3. Hooks(自动钩子):在 Claude Code 启动时自动拉起隧道、退出时自动关闭隧道,避免手动管理。

整条链路为:Claude Code(stdio)→node dist/index.js启动的 MCP 服务器 →127.0.0.1:33XX(本地隧道端口)→ SSH 隧道 → 远端 MySQL3306。

示例项目配置表

原文档给出了多项目并行时的典型端口规划(端口必须全局唯一,避免冲突):

项目端口SSH 服务器数据库位置
project-a3307server1.example.com:1022db_project_a/path/to/project-a
project-b3308server1.example.com:1022db_project_b/path/to/project-b
project-c3309server2.example.com:1022db_project_c/path/to/project-c
project-d3310server1.example.com:1022db_project_d/path/to/project-d

二、前置条件

  1. 已构建的 MCP MySQL 服务器(mcp-server-mysql):
    • 需要支持MYSQL_DISABLE_READ_ONLY_TRANSACTIONS(指南中用于允许CREATE TABLE等 DDL 操作,后文会结合源码说明其底层行为);
    • 通过npm run build或pnpm build构建。对应 package.json 中的build脚本为tsc && shx chmod +x dist/*.js,产物入口是dist/index.js(main字段亦指向它),这也是指南.mcp.json中启动命令node dist/index.js的来源;
  2. SSH 访问权限:
    • 已配置 SSH 密钥(~/.ssh/id_rsa);
    • 已配置 known hosts;
  3. 本地 MySQL 客户端(用于独立验证连接),安装示例:brew install mysql-client。

三、步骤一:创建 SSH 隧道脚本

在项目目录下创建两个脚本:启动脚本和停止脚本。

start-tunnel-[project].sh

#!/bin/bash # SSH Tunnel for [PROJECT] project LOCAL_PORT=33XX # Use unique port (3307, 3308, 3309, etc.) REMOTE_SERVER="your.server.com" # Your SSH server SSH_PORT=1022 # SSH port (often 1022 or 22) SSH_USER="your_ssh_user" # SSH username echo "🔗 Starting SSH tunnel for [PROJECT] project..." echo "📊 Local port: $LOCAL_PORT" echo "🌐 Remote server: $REMOTE_SERVER:$SSH_PORT" echo "👤 User: $SSH_USER" # Check if port is already in use if lsof -Pi :$LOCAL_PORT -sTCP:LISTEN -t >/dev/null ; then echo "⚠️ Port $LOCAL_PORT already in use - tunnel may already be running" exit 0 fi # Create the SSH tunnel ssh -f -N -L $LOCAL_PORT:localhost:3306 -p $SSH_PORT $SSH_USER@$REMOTE_SERVER if [ $? -eq 0 ]; then sleep 2 if lsof -Pi :$LOCAL_PORT -sTCP:LISTEN -t >/dev/null ; then echo "✅ [PROJECT] SSH tunnel created successfully on port $LOCAL_PORT" else echo "❌ Tunnel creation failed" exit 1 fi else echo "❌ Failed to create SSH tunnel" exit 1 fi

要点:

  • ssh -f -N -L $LOCAL_PORT:localhost:3306:-f让 ssh 建好隧道后转入后台,-N表示不执行远端命令,-L把本地端口转发到隧道对端的localhost:3306(即 SSH 服务器本机的 MySQL);
  • 启动前先lsof -Pi :$LOCAL_PORT -sTCP:LISTEN -t检测端口占用,若已监听则视为隧道已存在,直接退出(幂等设计,方便 Hooks 重复触发);
  • 建隧道后sleep 2再复查监听状态,失败则exit 1。

stop-tunnel-[project].sh

#!/bin/bash # Stop SSH Tunnel for [PROJECT] project LOCAL_PORT=33XX # Same port as in start script echo "🛑 Stopping SSH tunnel for [PROJECT] project (port $LOCAL_PORT)..." # Find and kill the tunnel process PID=$(lsof -ti:$LOCAL_PORT) if [ -n "$PID" ]; then kill $PID echo "✅ [PROJECT] SSH tunnel stopped (PID: $PID)" else echo "ℹ️ No tunnel found running on port $LOCAL_PORT" fi

停止脚本的口径是"谁在监听本地端口就杀掉谁",用lsof -ti:$LOCAL_PORT取 PID 后kill。

赋予执行权限

chmod +x start-tunnel-*.sh stop-tunnel-*.sh

仓库内置脚本:生产级的隧道管理参考

mcp-server-mysql 仓库的 scripts/ 目录自带了一对功能等价、但更贴近生产环境的隧道脚本,可作为指南模板的"增强版"参考:

  • scripts/start-tunnel-rds.sh:面向"经跳板机(Bastion)访问 RDS"的更复杂拓扑,核心转发命令为ssh -i "$SSH_KEY" -f -N -L "$LOCAL_PORT:$RDS_ENDPOINT:$RDS_PORT" -p "$BASTION_PORT" "$BASTION_USER_HOST"(见 第 63 行),即在跳板机上把流量转发到 VPC 内的RDS_ENDPOINT:RDS_PORT,而不是跳板机本机的 3306;
  • 该脚本开头用set -euo pipefail并强制校验BASTION_HOST、RDS_ENDPOINT、SSH_KEY三个必填项(第 19-47 行),所有参数均可通过环境变量覆盖(LOCAL_PORT、BASTION_USER等),比指南模板中硬编码变量更便于多项目复用;
  • scripts/stop-tunnel-rds.sh:除按端口杀进程外,还会在kill后复查监听状态,未释放时提示kill -9 $(lsof -ti :$LOCAL_PORT)手动兜底(第 19-31 行);
  • scripts/tunnel-config.example:示例配置文件,约定把LOCAL_PORT、BASTION_*、RDS_*、SSH_KEY写入tunnel-config.sh后source再启动隧道,隧道建立后在 MCP 配置中使用MYSQL_HOST=127.0.0.1+MYSQL_PORT=<LOCAL_PORT>。

如果你的数据库就在 SSH 服务器本机,直接用指南模板;如果是经跳板机访问 VPC/RDS 实例,建议对照仓库内脚本改造。

四、步骤二:创建 MCP 配置(.mcp.json)

在项目目录创建.mcp.json,指南给出的完整模板如下:

{ "hooks": { "claude_start": { "command": "./start-tunnel-[project].sh", "background": true, "description": "Start SSH tunnel for [PROJECT] database" }, "claude_stop": { "command": "./stop-tunnel-[project].sh", "background": false, "description": "Stop SSH tunnel for [PROJECT] database" } }, "inputs": [ { "type": "promptString", "id": "mysql-password-[project]", "description": "MySQL Password for [PROJECT] Database", "password": true, "default": "your_password_here" } ], "servers": { "[project]-mysql-server": { "type": "stdio", "command": "bash", "args": ["-c", "cd /path/to/mcp-server-mysql && node dist/index.js"], "env": { "MYSQL_HOST": "127.0.0.1", "MYSQL_PORT": "33XX", "MYSQL_USER": "your_db_user", "MYSQL_PASS": "${input:mysql-password-[project]}", "MYSQL_DB": "your_database_name", "ALLOW_INSERT_OPERATION": "true", "ALLOW_UPDATE_OPERATION": "true", "ALLOW_DELETE_OPERATION": "true", "ALLOW_DDL_OPERATION": "true", "MYSQL_DISABLE_READ_ONLY_TRANSACTIONS": "true" } } } }

各部分作用:

  • hooks:claude_start在 Claude Code 启动时以后台方式("background": true)执行启动脚本;claude_stop在退出时前台执行停止脚本。这样隧道生命周期与 Claude 会话绑定,无需人工干预;
  • inputs:声明一个promptString类型的密码输入,"password": true表示以密文提示输入;服务器env中用${input:mysql-password-[project]}占位符引用,避免明文散落在服务器配置块里(但default值仍会写在文件中,安全注意事项见第八节);
  • servers:声明一个stdio类型的 MCP 服务器。command+args用bash -c "cd /path/to/mcp-server-mysql && node dist/index.js"的方式切到已构建的仓库目录再启动服务器——这解释了前置条件中"必须先npm run build"的原因:启动的是编译产物dist/index.js;
  • env:即 mcp-server-mysql 的数据库连接与权限开关,MYSQL_HOST/MYSQL_PORT指向隧道本地端,MYSQL_USER/MYSQL_PASS/MYSQL_DB为数据库凭证。

源码印证:这些环境变量在服务器内部如何生效

结合仓库源码,可以确认上述env参数的真实行为,也解释了指南为何强调写操作参数:

1. 写操作开关是严格字符串匹配。src/config/index.ts 中:

// Write operation flags (global defaults) export const ALLOW_INSERT_OPERATION = process.env.ALLOW_INSERT_OPERATION === "true"; export const ALLOW_UPDATE_OPERATION = process.env.ALLOW_UPDATE_OPERATION === "true"; export const ALLOW_DELETE_OPERATION = process.env.ALLOW_DELETE_OPERATION === "true"; export const ALLOW_DDL_OPERATION = process.env.ALLOW_DDL_OPERATION === "true"; // Transaction mode control export const MYSQL_DISABLE_READ_ONLY_TRANSACTIONS = process.env.MYSQL_DISABLE_READ_ONLY_TRANSACTIONS === "true";

即只有值恰好为字符串"true"才生效,默认全部为false——这与 README.md 中"write operations opt-in via env flags"的描述一致。入口文件 index.ts 会据此计算isReadOnly,并写进mysql_query工具的readOnlyHint/destructiveHint注解,供 LLM 客户端理解该工具是否具有破坏性。

2.MYSQL_DISABLE_READ_ONLY_TRANSACTIONS控制会话级只读事务。这是指南特别点名"必须支持"的参数,原因在于 mcp-server-mysql 的只读查询路径 src/db/index.ts:

connection = await pool.getConnection(); // Set read-only mode (unless disabled via environment variable) if (!MYSQL_DISABLE_READ_ONLY_TRANSACTIONS) { await connection.query("SET SESSION TRANSACTION READ ONLY"); } else { log("info", "Read-only transactions disabled via MYSQL_DISABLE_READ_ONLY_TRANSACTIONS=true"); } await connection.beginTransaction();

若不禁用,只读查询会先执行SET SESSION TRANSACTION READ ONLY再开事务,事务结束回滚后恢复READ WRITE。指南的验证流程包含CREATE TABLE/INSERT/DROP TABLE(见第六节),这些写操作需要关闭该会话级保护,故模板中显式设置MYSQL_DISABLE_READ_ONLY_TRANSACTIONS: "true"。

3. 写操作还有第二道闸门:按 schema 的权限检查。src/db/permissions.ts 中每个isXxxAllowedForSchema(schema)函数先查SCHEMA_*_PERMISSIONS的按库覆盖项,未命中才回落到全局ALLOW_*_OPERATION开关;执行层在 src/db/index.ts 先用getQueryTypes解析出语句类型,再逐一校验,例如:

if (isDDLOperation && !isDDLAllowedForSchema(schema)) { log("error", `DDL operations are not allowed for schema '${schema || "default"}'...`); ... } // For write operations that are allowed, use executeWriteQuery if ( (isInsertOperation && isInsertAllowedForSchema(schema)) || (isUpdateOperation && isUpdateAllowedForSchema(schema)) || (isDeleteOperation && isDeleteAllowedForSchema(schema)) || (isDDLOperation && isDDLAllowedForSchema(schema)) ) { return executeWriteQuery(sql); }

从源码结构看,即使四个ALLOW_*全开,也可以再用SCHEMA_INSERT_PERMISSIONS、SCHEMA_DDL_PERMISSIONS等变量做按库细粒度控制,这是指南模板未展开、但同一仓库支持的进阶能力。

4. 连接池与默认值。连接参数在 src/config/index.ts 的mcpConfig.mysql中组装:MYSQL_HOST缺省127.0.0.1、MYSQL_PORT缺省3306、MYSQL_USER缺省root、密码缺省空串,连接池固定connectionLimit: 10,并可由MYSQL_CONNECT_TIMEOUT调整超时(默认 10000ms)。指南中所有必填env项均能在此找到对应解析逻辑。

5. 启动即做连接自检。服务器入口 index.ts 在连接 stdio 传输前会先getPool()并取一个连接做测试,失败即safeExit(1);第 561-567 行 中非 remote 模式则使用StdioServerTransport启动。因此"隧道没起来导致服务器起不来/连不上"会直接体现在 MCP 服务器的报错里,这也是第九节排障顺序"先查隧道、再查 MCP"的依据。

五、步骤三:端口映射规划

为每个项目选择唯一本地端口,避免多个隧道互相抢占:

端口区间用途
3307第一个项目
3308第二个项目
3309第三个项目
3310第四个项目
3311+后续项目

六、项目文件结构

按指南约定,每个项目的目录应长这样:

/path/to/your/project/ ├── .mcp.json # MCP 配置 ├── start-tunnel-[project].sh # 启动 SSH 隧道脚本 ├── stop-tunnel-[project].sh # 停止 SSH 隧道脚本 └── ... (your project files)

七、两个完整配置示例

示例 1:部署在 example.com 上的项目(如 project-a/b/d)

完整.mcp.json,隧道端口3311:

{ "hooks": { "claude_start": { "command": "./start-tunnel-myproject.sh", "background": true, "description": "Start SSH tunnel for myproject database" }, "claude_stop": { "command": "./stop-tunnel-myproject.sh", "background": false, "description": "Stop SSH tunnel for myproject database" } }, "inputs": [ { "type": "promptString", "id": "mysql-password-myproject", "description": "MySQL Password for myproject Database", "password": true, "default": "my_secure_password" } ], "servers": { "myproject-mysql-server": { "type": "stdio", "command": "bash", "args": ["-c", "cd /path/to/mcp-server-mysql && node dist/index.js"], "env": { "MYSQL_HOST": "127.0.0.1", "MYSQL_PORT": "3311", "MYSQL_USER": "db_myproject", "MYSQL_PASS": "${input:mysql-password-myproject}", "MYSQL_DB": "db_myproject", "ALLOW_INSERT_OPERATION": "true", "ALLOW_UPDATE_OPERATION": "true", "ALLOW_DELETE_OPERATION": "true", "ALLOW_DDL_OPERATION": "true", "MYSQL_DISABLE_READ_ONLY_TRANSACTIONS": "true" } } } }

示例 2:部署在另一台服务器上的项目(如 project-c)

只需修改启动脚本中的目标地址与 SSH 端口:

# start-tunnel-myproject.sh LOCAL_PORT=3312 REMOTE_SERVER="server2.example.com" SSH_PORT=22 # Standard SSH port SSH_USER="myuser"

其余(停止脚本端口、.mcp.json中的MYSQL_PORT与MYSQL_DB)与示例 1 同构替换即可。

八、测试你的配置

1. 手动测试 SSH 隧道

# 手动启动隧道 ./start-tunnel-[project].sh # 检查端口是否在监听 lsof -i :33XX # 停止隧道 ./stop-tunnel-[project].sh

2. 测试数据库连接

# 通过隧道测试连接 mysql -h 127.0.0.1 -P 33XX -u db_user -p"password" -D database_name -e "SELECT 'Connection OK' as status;"

此步骤绕过 MCP 直接验证"隧道 + 凭证 + 数据库名"三要素,能隔离问题来源。

3. 测试 Claude 集成

# 进入项目目录 cd /path/to/your/project # 启动 Claude(应自动拉起隧道) claude # 查看 MCP 服务器状态 /mcp # 退出 Claude(应自动停止隧道) # 按两次 Ctrl+C

4. 测试 MCP 服务器操作

进入 Claude 后,通过mysql_query工具执行一套 DDL/DML 往返验证(这也是指南要求MYSQL_DISABLE_READ_ONLY_TRANSACTIONS的原因):

CREATE TABLE test_table ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) ); INSERT INTO test_table (name) VALUES ('Test'); SELECT * FROM test_table; DROP TABLE test_table;

从源码看,CREATE/ALTER/DROP/TRUNCATE会被识别为 DDL(见 src/db/index.ts),INSERT识别为写操作,分别对应第四节的isDDLAllowedForSchema/isInsertAllowedForSchema检查;全部放行后走executeWriteQuery。

九、故障排查

1. SSH 隧道启动失败

  • 检查 SSH 密钥:确认~/.ssh/id_rsa存在且权限正确(600);
  • 手动测试 SSH:ssh -p 1022 user@server.com;
  • 检查端口占用:lsof -i :33XX。

2. 数据库连接失败

  • 确认隧道在运行:lsof -i :33XX;
  • 核对凭证:直接用 mysql 客户端测试;
  • 确认数据库存在:SHOW DATABASES;。

3. MCP 服务器无法连接

  • 确认已构建:ls /path/to/mcp-server-mysql/dist/;
  • Node.js 版本:node --version(指南建议 18+ 或 20+;仓库 README.md 要求 Node.js v20+);
  • 查看日志:claude --debug。

4. Hooks 不生效

  • 检查脚本权限:ls -la *.sh(必须可执行);
  • 核对脚本路径:.mcp.json中用相对路径(./script.sh);
  • 手动运行脚本:直接执行 start/stop 脚本定位问题。

5. 端口冲突

  • 杀掉占用进程:kill $(lsof -ti:33XX);
  • 更换端口:同步修改两个脚本和.mcp.json三处。

常用调试命令

# 查看所有 SSH 隧道 ps aux | grep ssh | grep -E "3307|3308|3309|3310" # 查看所有监听端口 lsof -i -P | grep LISTEN | grep -E "3307|3308|3309|3310" # 直接测试 MCP 服务器 cd /path/to/mcp-server-mysql MYSQL_HOST=127.0.0.1 MYSQL_PORT=33XX MYSQL_USER=user MYSQL_PASS=pass MYSQL_DB=db node dist/index.js # 查看 Claude 日志 claude --debug 2>&1 | grep -i mcp

其中"直接测试 MCP 服务器"一条与源码行为吻合:服务器启动时会立即尝试数据库连接并打印配置摘要(index.ts 中的MySQL Configuration日志、第 374-379 行 的连接测试),连接失败会直接safeExit(1),方便在无 Claude 环境下复现问题。

十、安全注意事项

  1. 密码存储:.mcp.json中inputs的default密码是明文可见的。指南建议:
    • 改用环境变量注入;
    • 去掉default,每次手动输入;
    • 结合密码管理器。
  2. SSH 密钥:
    • 权限设为 600;
    • 设置密钥口令以加强保护;
    • 定期轮换。
  3. 端口安全:本地端口(3307–3310+)仅在 localhost 上监听,不对外暴露。

十一、新项目快速上线清单

每接入一个新项目,按顺序完成:

  • 选择唯一本地端口(3311、3312 等)
  • 创建start-tunnel-[project].sh,填入正确的服务器信息
  • 创建stop-tunnel-[project].sh,端口与启动脚本一致
  • 赋予执行权限:chmod +x *.sh
  • 创建.mcp.json:
    • start/stop 脚本的 Hooks
    • 密码输入项
    • MCP 服务器配置
  • 手动测试 SSH 隧道
  • 测试数据库连接
  • 用claude命令实测
  • 确认自动启停隧道生效

十二、小结与延伸阅读

这套"隧道脚本 +.mcp.json+ Hooks"模式把远程 MySQL 的接入成本压缩为"每项目三个文件",并借 mcp-server-mysql 的ALLOW_*权限开关与MYSQL_DISABLE_READ_ONLY_TRANSACTIONS事务控制实现最小权限的写操作。进一步可结合仓库文档深入:docs/CONFIGURATION.md(全部环境变量,含SCHEMA_*_PERMISSIONS按库权限、SSL/mTLS、多库模式)、docs/INSTALLATION.md(安装方式)、docs/TROUBLESHOOTING.md(故障排查)、README-MULTI-DB.md(多数据库模式),以及仓库自带的 scripts/start-tunnel-rds.sh、scripts/stop-tunnel-rds.sh、scripts/tunnel-config.example 作为跳板机场景的脚本范本。

  • MCP 服务
  • 数据库客户端
  • AI 应用

【免费下载链接】mcp-server-mysql

A Model Context Protocol server that provides read-only access to MySQL databases. This server enables LLMs to inspect database schemas and execute read-only queries.

项目地址:https://gitcode.com/gh_mirrors/mc/mcp-server-mysql
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进