☰
实战:写一个Mysql MCP Server,把本地数据库接进 AI 工具链
2026/10/8 6:04:31 网站建设 项目流程

1. 为什么要把 MySQL 接进 AI 工具链

先说清楚我们在解决什么问题。你平时用 Cline、Claude Code 或者 CC Switch 这类 AI 编程助手时,模型对数据库的理解基本靠"猜"——它会根据你项目里的 ORM 定义、SQL 迁移文件去推断表结构,然后给你写查询语句。问题是,真实数据库里往往有历史遗留字段、命名不规范的列、甚至和代码里对不上的索引。模型猜错了,你复制粘贴执行报错,来回折腾。

MCP Server 就是来解决这个断层的。MCP(Model Context Protocol)是 Anthropic 提出的一套开放协议,定义了大模型和外部工具之间的标准连接方式。你可以把它理解成 AI 世界的 USB-C 接口:不管你要接的是数据库、文件系统还是第三方 API,只要双方都遵循 MCP 规范,就能即插即用。整个架构只有两个角色,MCP Client 跑在 AI 助手这一侧负责和模型交互,MCP Server 是你写的独立进程,暴露具体的工具能力,两者通过 JSON-RPC over stdio 通信。用 stdio 而不是 HTTP 的好处是零网络配置、不开放端口,本地开发场景下特别省心。

这篇要带你从零写一个可运行的 MySQL MCP Server,然后把它接进 Cline MCP 或 CC Switch 这类支持 MCP 的工具里,让模型能直接DESCRIBE表结构、执行SELECT查询、拿到真实数据。适合谁看?有 Node.js 基础、想让 AI 助手真正"看见"本地库的后端和全栈同学。全程给可复制的配置片段、工具注册代码和一次真实查询的验证步骤,跟着做就能跑通。

技术栈选型上我用 Node.js 18+ 配 TypeScript,用tsx直接运行不用编译,MCP 部分用@modelcontextprotocol/sdk的McpServer高级 API,数据库驱动用mysql2的连接池,输入校验用zod。项目用 ESM 模块("type": "module"),启动命令就是npx tsx src/server.ts。

2. TaoToken 前置准备与 API Key 获取

在动手写 Server 之前,得先把 AI 工具这一侧的"大脑"接好。我用的方案是通过 TaoToken 统一管理模型调用,这样 Cline、CC Switch、Claude Code 这些工具可以共用一套 Key 和 Base URL,切换模型不用改一堆配置。

TaoToken 在这里扮演的是模型接入网关的角色,它把不同厂商的模型能力收敛成统一的 OpenAI 兼容接口。你需要先拿到 API Key,然后配置到各个 AI 工具里。具体操作路径是这样:打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册登录后,进入控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 创建一个新的 Key,复制保存好。

拿到 Key 之后,不同工具的配置方式略有差异,但核心三件套是一样的:Base URL、API Key、Model ID。Base URL 统一填https://taotoken.net/api(注意这个地址不加 UTM 参数,是纯 API 端点)。Model ID 根据你要用的模型填,比如claude-sonnet-4-5或者gpt-4o这类。如果你不确定有哪些模型可用,可以先去模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 试一下,确认 Key 能正常调用再往下走。

这里有个坑要提醒:Cline 和 CC Switch 对 Base URL 的格式要求不完全一样。Cline 通常要求填到/v1结尾,也就是https://taotoken.net/api/v1;而 CC Switch 和 Claude Code 这类走 Anthropic 协议的工具,Base URL 填https://taotoken.net/api就行,不需要/v1。填错了会报 404 或者model not found,这个后面排障章节会细说。

如果你打算长期跑编码任务或者做 Agent 自动化,建议看一下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它针对高频调用场景做了额度优化,比按量付费划算。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各工具的详细配置示例,遇到不确定的地方可以对照查。

3. 可复制的 MySQL MCP Server 配置与工具注册

现在进入正题,开始写 Server。先建项目目录,初始化package.json:

mkdir mysql-mcp-server && cd mysql-mcp-server npm init -y npm install @modelcontextprotocol/sdk mysql2 zod npm install -D typescript tsx @types/node

然后在package.json里加上"type": "module",并配置启动脚本:

{ "name": "mysql-mcp-server", "version": "1.0.0", "type": "module", "scripts": { "start": "tsx src/server.ts" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "mysql2": "^3.11.0", "zod": "^3.23.0" }, "devDependencies": { "typescript": "^5.6.0", "tsx": "^4.19.0", "@types/node": "^22.0.0" } }

项目结构规划成这样:

mysql-mcp-server/ ├── package.json ├── tsconfig.json ├── connections.json # 数据库连接配置,记得加 .gitignore ├── src/ │ ├── db.ts # 连接池管理、查询执行、安全校验 │ └── server.ts # MCP Server 主体,注册工具 └── README.md

connections.json里配置多个数据库连接,每个连接独立一个池:

{ "main": { "host": "127.0.0.1", "port": 3306, "user": "readonly_user", "password": "your_password", "database": "main_db" }, "analytics": { "host": "10.0.0.5", "port": 3306, "user": "readonly_user", "password": "your_password", "database": "analytics_db" } }

接下来写src/db.ts,核心是连接池管理和安全校验。安全这块必须做足,因为模型生成的 SQL 不可控,白名单、LIMIT 上限、敏感列检测、频率限制一个都不能少:

import mysql from 'mysql2/promise'; import fs from 'fs'; import path from 'path'; type ConnConfig = { host: string; port: number; user: string; password: string; database: string; }; const pools = new Map<string, mysql.Pool>(); const rateMap = new Map<string, number[]>(); function loadConnections(): Record<string, ConnConfig> { const file = path.resolve(process.cwd(), 'connections.json'); return JSON.parse(fs.readFileSync(file, 'utf-8')); } export function getPool(name: string): mysql.Pool { if (pools.has(name)) return pools.get(name)!; const configs = loadConnections(); const cfg = configs[name]; if (!cfg) throw new Error(`Unknown connection: ${name}`); const pool = mysql.createPool({ ...cfg, waitForConnections: true, connectionLimit: 5, namedPlaceholders: true, }); pools.set(name, pool); return pool; } const SENSITIVE = /(password|secret|token|api_key)/i; export function validateSql(sql: string): string { const trimmed = sql.trim(); if (!/^(SELECT|SHOW|DESCRIBE)\s+/i.test(trimmed)) { throw new Error('Only SELECT/SHOW/DESCRIBE allowed'); } if (trimmed.includes(';') && trimmed.indexOf(';') < trimmed.length - 1) { throw new Error('Multiple statements not allowed'); } if (SENSITIVE.test(trimmed)) { throw new Error('Query contains sensitive column'); } if (!/\bLIMIT\b/i.test(trimmed)) { return trimmed + ' LIMIT 500'; } return trimmed.replace(/LIMIT\s+(\d+)/i, (_, n) => `LIMIT ${Math.min(parseInt(n, 10), 500)}` ); } export function checkRate(sql: string): void { const fingerprint = sql.replace(/\d+/g, '?'); const now = Date.now(); const arr = (rateMap.get(fingerprint) || []).filter(t => now - t < 60000); if (arr.length >= 10) { throw new Error('Rate limit exceeded (max 10 queries per minute)'); } arr.push(now); rateMap.set(fingerprint, arr); } export async function runQuery(connName: string, sql: string) { const safeSql = validateSql(sql); checkRate(safeSql); const pool = getPool(connName); const [rows] = await pool.query(safeSql); const json = JSON.stringify(rows); if (json.length > 100 * 1024) { const arr = rows as any[]; return { truncated: true, rows: arr.slice(0, 100), message: '结果集过大(超过 100KB),请添加更严格的 WHERE 条件或减小 LIMIT', }; } return { truncated: false, rows }; }

然后是src/server.ts,注册四个工具:list_connections、list_tables、describe_table、query。每个工具的description要写清楚,帮模型判断什么时候用:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { getPool, runQuery } from './db.js'; const server = new McpServer({ name: 'mysql-mcp', version: '1.0.0' }); server.tool( 'list_connections', '列出所有已配置的数据库连接名称,用于选择要查询的库', {}, async () => { const fs = await import('fs'); const path = await import('path'); const cfg = JSON.parse( fs.readFileSync(path.resolve(process.cwd(), 'connections.json'), 'utf-8') ); return { content: [{ type: 'text', text: Object.keys(cfg).join('\n') }], }; } ); server.tool( 'list_tables', '列出指定连接下的所有表名', { connection: z.string().describe('连接名称,如 main') }, async ({ connection }) => { const pool = getPool(connection); const [rows] = await pool.query('SHOW TABLES'); return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }], }; } ); server.tool( 'describe_table', '查看指定表的结构,包括字段名、类型、是否可空、键信息', { connection: z.string(), table: z.string().describe('表名'), }, async ({ connection, table }) => { const pool = getPool(connection); const [rows] = await pool.query(`DESCRIBE \`${table}\``); return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }], }; } ); server.tool( 'query', '执行只读 SQL 查询(仅支持 SELECT/SHOW/DESCRIBE),自动追加 LIMIT 500', { connection: z.string(), sql: z.string().describe('要执行的 SQL 语句'), }, async ({ connection, sql }) => { try { const result = await runQuery(connection, sql); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } catch (err: any) { return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true, }; } } ); const transport = new StdioServerTransport(); await server.connect(transport);

写完跑一下npx tsx src/server.ts,如果没报错就说明 Server 能正常启动。接下来把它接进 AI 工具。

4. 接入 Cline MCP 与 CC Switch 并验证真实查询

先接 Cline。Cline 的 MCP 配置在 VS Code 的设置里,找到 Cline 的 MCP Servers 配置项,添加一个 local 类型的 server。配置片段长这样:

{ "mcpServers": { "mysql": { "command": "npx", "args": ["tsx", "/Users/yourname/mysql-mcp-server/src/server.ts"], "env": { "MYSQL_CONNECTIONS": "{\"main\":{\"host\":\"127.0.0.1\",\"port\":3306,\"user\":\"readonly_user\",\"password\":\"your_password\",\"database\":\"main_db\"}}" } } } }

注意args里的路径要换成你本机的绝对路径。env里的MYSQL_CONNECTIONS是把 JSON 压缩成一行并转义双引号后的结果,如果你不想用connections.json文件,也可以走环境变量这条路。

接 CC Switch 的话,配置方式类似,但 CC Switch 走的是 Anthropic 协议,需要在配置里指定 Base URL 和 API Key。CC Switch 的配置文件通常在~/.cc-switch/config.json,加上 MCP 部分:

{ "providers": { "taotoken": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-your-taotoken-key", "model": "claude-sonnet-4-5" } }, "mcpServers": { "mysql": { "command": "npx", "args": ["tsx", "/Users/yourname/mysql-mcp-server/src/server.ts"] } } }

这里三件套要写全:Base URL 是https://taotoken.net/api,API Key 是你从控制台拿到的那个,Model ID 填你要用的模型。CC Switch 的好处是可以在多个 provider 之间切换,MCP Server 配置是共用的。

配置保存后重启 AI 工具,然后在对话里让它调用工具。我实测下来,直接问"帮我看看 main 库有哪些表"就能触发list_tables。模型会返回类似这样的结果:

+------------------+ | Tables_in_main_db | +------------------+ | users | | orders | | products | +------------------+

接着问"users 表的结构是什么",它会调describe_table,返回字段列表。最后让它执行一次真实查询,比如"查一下 users 表里前 5 条记录",模型会生成SELECT * FROM users LIMIT 5,经过我们的安全校验后执行,返回真实数据。整个过程你能在 Cline 的工具调用日志里看到完整的 JSON-RPC 请求和响应。

验证成功的标志是:模型不再"编造"字段名,而是基于DESCRIBE的真实结果来写查询。如果它查了一个不存在的列,数据库会报ER_BAD_FIELD_ERROR,这个错误会原样返回给模型,它就能自我纠正。

5. 常见报错排查:401、local proxy failed 与 reading choices

接入过程中最容易踩的坑集中在几个报错上,我一个个说。

401 Unauthorized:这个基本是 API Key 的问题。先确认 Key 有没有复制完整,前后有没有多余空格。然后检查 Base URL 格式,Cline 要求https://taotoken.net/api/v1,CC Switch 和 Claude Code 要求https://taotoken.net/api,填错了会 401 或者 404。如果 Key 是对的但还报 401,去控制台看一下 Key 是不是被禁用或者额度用完了。

local proxy failed / connection refused:这个通常是 MCP Server 进程没起来。先在终端手动跑npx tsx src/server.ts,看有没有报错。常见原因是connections.json路径不对,或者数据库连不上。如果 Server 能起来但工具里报这个错,检查args里的路径是不是绝对路径,相对路径在不同工作目录下会找不到文件。

reading 'choices' of undefined:这个报错一般出现在模型返回格式不符合预期的时候。如果你用的是 OpenAI 兼容接口,检查请求体里model字段填的对不对。有些工具会把 Anthropic 格式的请求发到 OpenAI 端点,导致响应结构对不上。解决办法是确认工具走的协议和 Base URL 匹配,Anthropic 协议走/api,OpenAI 协议走/api/v1。

OAuth 相关报错:Claude Code 这类工具首次接入会走 OAuth 流程,如果卡在授权页面,检查网络能不能正常访问授权端点。有时候是浏览器缓存问题,换个无痕窗口重试。如果一直失败,可以改用 API Key 直连的方式,在配置里直接填 Key 跳过 OAuth。

MCP Server 启动了但工具列表为空:检查server.tool()的注册代码有没有执行到,await server.connect(transport)有没有加。另外确认 SDK 版本,@modelcontextprotocol/sdk1.0 之后的 API 和早期版本有差异,McpServer的导入路径是@modelcontextprotocol/sdk/server/mcp.js。

查询返回结果被截断:这是正常的,我们的安全机制在结果超过 100KB 时会截断并提示。让模型加上更严格的WHERE条件或者减小LIMIT就行。

排障的时候建议开两个终端,一个跑 Server 看日志,一个在 AI 工具里操作,对照着看请求和响应。MCP 的 JSON-RPC 消息在 stdio 上是明文传输的,调试起来其实很方便。

6. 把数据库能力沉淀为 AI 工具链的标准组件

走到这里,你已经有了一个能跑的 MySQL MCP Server,模型可以真实地查表结构、执行只读查询。这套东西的价值不在于单次查询,而在于它把数据库操作变成了 AI 可调用的标准能力——你写一次 Server,Cline、CC Switch、Claude Code 都能接,换工具不用重写。

几个实战建议。第一,数据库账户一定要用只读权限,GRANT SELECT ON main_db.* TO 'readonly_user'@'%'这样配,从源头杜绝写操作风险。第二,connections.json加进.gitignore,密码不要提交到仓库。第三,生产环境用 PM2 守护 Server 进程,pm2 start "npx tsx src/server.ts" --name mysql-mcp,避免进程挂了工具调不通。

如果你想让模型在编码时自动带上数据库上下文,可以在项目根目录放一个.mcp.json或者对应的工具配置文件,把 MCP Server 配置写进去,这样团队其他人拉代码就能直接用。模型 ID 和 Base URL 这些走 TaoToken 统一管理,换模型只改一处配置。

最后留个可操作的收尾:打开你的 AI 工具,问它"帮我分析一下 orders 表里最近一周的订单趋势",看它会不会自动调describe_table再生成查询。如果它做到了,说明整条链路通了。接下来你可以照着同样的模式,把 Redis、MongoDB 甚至内部 API 都包成 MCP Server,让 AI 助手真正成为你技术栈里的一个"成员"。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询