
1. 为什么要把 MySQL 接进 AI 工具链先说清楚我们在解决什么问题。你平时用 Cline、Claude Code 或者 CC Switch 这类 AI 编程助手时模型对数据库的理解基本靠猜——它会根据你项目里的 ORM 定义、SQL 迁移文件去推断表结构然后给你写查询语句。问题是真实数据库里往往有历史遗留字段、命名不规范的列、甚至和代码里对不上的索引。模型猜错了你复制粘贴执行报错来回折腾。MCP Server 就是来解决这个断层的。MCPModel 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册登录后进入控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建一个新的 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_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 试一下确认 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_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它针对高频调用场景做了额度优化比按量付费划算。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各工具的详细配置示例遇到不确定的地方可以对照查。3. 可复制的 MySQL MCP Server 配置与工具注册现在进入正题开始写 Server。先建项目目录初始化package.jsonmkdir 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.mdconnections.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 Mapstring, mysql.Pool(); const rateMap new Mapstring, number[](); function loadConnections(): Recordstring, 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/apiAPI 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/v1CC 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 协议走/apiOpenAI 协议走/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 可调用的标准能力——你写一次 ServerCline、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 助手真正成为你技术栈里的一个成员。