SpringAI-MySQLMcp服务:用MCP协议把数据库变成AI可调用的标准化工具
工试云启 考证服务中心整理

SpringAI-MySQLMcp服务——把MySQL数据库通过Model Context Protocol接入AI应用听起来是个挺垂直的方向但实际做下来你会发现这套思路几乎能复用到所有需要让大模型碰业务数据的场景。最近我在一个内部数据分析项目里正好把这条链路完整踩了一遍从Spring AI的McpServer模块、MySQL连接池配置到工具注册、prompt约束、错误处理每个环节都有值得记录的细节。这篇文章就围绕SpringAI-MySQLMcp服务展开讲清楚它解决什么问题、核心实现思路、完整构建步骤以及我实测过程中遇到的坑和排查经验。先说结论SpringAI-MySQLMcp服务本质上就是AI应用访问数据库的标准化桥梁。MCP定了一套通用的协议让AI模型能够发现数据库里有什么表、什么字段、有什么查询能力然后在对话过程中动态发起查询、接收结构化结果。以前做自然语言查数大家各自封装APIAI和数据库之间是紧耦合的引入MCP之后数据源以可发现、可调用、可鉴权的方式挂到AI服务上一个能查MySQL的MCP Server可以同时服务多个AI客户端甚至完全不关心上层用的是Spring AI、LangChain还是其他框架。这个思路在实际项目里非常值钱。适合读这篇文章的主要是两类人。一类是Java后端工程师想在Spring Boot项目里给AI应用加上查库能力但不想自己拼prompt、封装数据库工具、管理上下文另一类是AI应用开发者需要把企业内部的MySQL数据以可控的方式暴露给智能体应用同时对权限、审计、错误处理有要求。读完之后你至少能自己搭出一个可运行的SpringAI-MySQLMcp服务并且知道上线前要检查哪些关键点。1. 项目整体设计与思路拆解1.1 为什么需要SpringAI-MySQLMcp服务需求场景往往都很朴素业务方在对话框里问上个月的订单总额是多少华东区哪些商品退货率超过10%系统需要自动把这些问题翻译成SQL、执行查询、把结果整理成自然语言回复。朴素归朴素落地的时候问题都集中在几个点上——没有统一的协议、AI与数据库直连不安全、查询能力难以复用以及prompt工程和数据库schema之间维护成本太高。MCP解决的是连接与复用问题Spring AI解决的是怎么把MCP Server嵌进Java服务两件事合起来就是SpringAI-MySQLMcp服务。这套设计还有一个隐藏收益把SQL能力抽象成MCP工具后AI客户端拿到的不是一段SQL文本而是一个受控的查询能力。服务端可以规定哪些表可查、哪些字段脱敏、每次查询最多返回多少行这种治理粒度比单纯让大模型写SQL再执行要安全得多。实际项目里这条能力抽象的路径比让AI直接连数据库的路径更稳也更符合企业对数据安全的预期。1.2 技术选型与方案对比做类似能力市面上有几条路线直接让大模型生成SQL应用层执行后返回结果。实现快但SQL注入、权限失控、结果集过大等问题几乎没法收场只适合演示。自己封装一个查询工具在AI应用里注册为function calling。能用但每个AI应用都要改代码、注册一次数据库多了、工具多了以后维护成本高。基于MCP协议做独立服务把MySQL查询封装成标准工具。数据库访问逻辑收敛在服务端AI客户端通过协议发现并调用工具扩展新数据源就是加一个MCP Server的事。对比下来方案3最适合把能力产品化。Spring AI在官方MCP支持上已经比较成熟McpServer模块帮助Java后端不用重复实现协议细节又能复用Spring Boot的配置体系、连接池管理和监控能力。我选的组合是Spring Boot 3.x Spring AI 1.0.0McpServer模块 MySQL驱动 连接池再配一个轻量的admin表结构记录工具调用日志方便审计和排障。1.3 核心功能模块划分整个服务按职责可以拆成四块MCP Server核心层负责协议握手、工具列表注册、JSON-RPC消息处理Spring AI的McpServer模块已经做了大部分工作我们只需要把查询能力注册成Tool。MySQL查询执行层用JDBC模板执行SQL重点是参数绑定、结果集大小控制、查询超时设置、错误分类。Schema发现与缓存层获取数据库的表、字段、索引信息格式化后作为工具描述提供给AI模型。模型看到这个工具有哪些参数、表结构长什么样才能生成结构合理的SQL。安全管理层启动时白名单校验、查询语句只读检查、脱敏配置、调用审计日志。这一层是生产可用和demo之间的分水岭。这四个模块不是平均用力实践中最影响体验的是Schema发现层——描述写得好不好直接决定AI生成的SQL质量。2. 核心细节解析与实操要点2.1 MCP协议的工作原理MCP把数据源暴露给AI的方式可以类比成USB-C接口。AI客户端是设备MySQL数据库是外设MCP Server就是把外设接入标准接口的适配器。设备不需要知道外设内部的实现细节只需要通过协议发现这个接口支持什么功能然后调用即可。具体到MCP协议有三个核心原语Tools工具AI可调用的函数比如execute_select_query。工具声明包含名称、描述、参数schemaAI根据这些信息决定何时调用、传什么参数。Resources资源Server向AI提供的数据内容比如数据库的表结构文档、字段说明。资源通常不是AI主动计算出来的而是AI按需读取的上下文。Prompts提示词预设的交互模板比如根据需求生成查询SQL的标准指令。SpringAI的McpServer实现中Tools是最常用的入口。每个工具通过Tool注解暴露框架自动完成参数schema的序列化和结果回传。资源则适合承载表结构描述AI在生成SQL前先读取schema再把schema和用户问题一起组织成最终指令效果比把schema硬塞进每条prompt要好得多。2.2 Spring AI McpServer模块的关键配置在Spring Boot项目中引入MCP Server能力需要关注这几个配置项spring: ai: mcp: server: name: mysql-mcp-server version: 1.0.0 type: STDIO # 或 HTTP根据部署形态选择 transport: stdio # 与上面对应HTTP模式需要额外配置我先后试过STDIO和HTTP两种传输方式。STDIO适合本地进程间通信比如Claude Desktop或自定义脚本里通过子进程调用HTTP适合远程部署多个AI客户端通过网络访问。这次做的是通用服务所以选HTTP方式配合Spring MVC提供MCP协议端点部署成一独立的Web服务。还有一个容易踩坑的配置点MCP Server要能同时支持多个AI客户端的并发请求。默认情况下工具注册在单个实例里遇到并发调用时连接池和事务边界都要小心。我在配置里显式设置了连接池最大连接数并规定每个查询最多返回100行防止一个暴力请求拖垮整个服务。2.3 MySQL表结构描述与工具参数设计工具参数设计直接决定AI生成SQL的准确率。我的工具定义如下工具名称query_mysql参数1sqlstring必填——只允许SELECT开头的只读查询参数2limitinteger可选——返回最大行数默认50上限200参数3timeout_secondsinteger可选——单次查询超时时间默认10秒用一个工具包打所有查询确实简单但风险也集中。为了降低模型一次生成错误SQL的概率我把表结构描述作为工具描述的一部分返回比如订单表包含哪些字段、字段含义是什么、常用的过滤维度是什么。描述越接近业务语言模型生成的SQL越靠谱。举个例子订单表的描述我写成订单表 orders包含字段id主键、order_no订单号、customer_id客户ID、amount订单金额单位分、status状态pending/paid/shipped/completed/cancelled、created_at下单时间。业务约定金额字段统一按分存储查询时除以100转换为元。核心查询场景按时间范围统计金额、按状态聚合订单数、按客户ID查历史订单。这段描述比ordersid bigint, order_no varchar...有用得多。模型看到金额按分存储就不会出现把amount当元来聚合的乌龙。3. 实操过程与核心环节实现3.1 环境准备与项目初始化先把环境列表摆出来方便对照JDK 17Spring Boot 3.2.xSpring AI 1.0.0MCP模块从1.0.0开始对Server支持更完整MySQL 8.xMaven 3.8项目结构采用常规的单模块Spring Boot工程包路径大致如下com.example.mysqlmcp ├── config // MCP、连接池、安全配置 ├── controller // HTTP健康检查与调试接口 ├── tool // MCP工具定义 ├── schema // 表结构发现与格式化 ├── security // SQL白名单与鉴权 └── audit // 调用日志与审计初始化项目用Spring Initializr即可选依赖时手动加Spring Web、JDBC、MySQL DriverSpring AI的MCP模块在Maven Central上已经有了正式版本。3.2 引入依赖与最小可用配置dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-mcp-server/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency配置文件中除了正常的MySQL数据源配置MCP Server相关配置可以按需开启HTTP传输spring: datasource: url: jdbc:mysql://localhost:3306/analytics username: mcp_user password: ${MYSQL_PASSWORD} ai: mcp: server: name: analytics-mcp version: 1.0.0 type: HTTP transport: http注意MCP的HTTP传输模式需要服务暴露一个固定端点AI客户端通过HTTP请求访问这个端点完成协议交互。在Spring MVC中Spring AI自动配置会注册相关控制器不需要额外写Controller。3.3 开发核心工具类与注册逻辑MCP工具的本质是一个Bean在接口方法上标记Tool注解。框架会扫描这些Bean把方法元信息解析成工具schemas并处理调用入参出参的序列化。Component public class MySQLQueryTool { private final JdbcTemplate jdbcTemplate; private final SchemaCache schemaCache; private final QueryGuard queryGuard; public MySQLQueryTool(JdbcTemplate jdbcTemplate, SchemaCache schemaCache, QueryGuard queryGuard) { this.jdbcTemplate jdbcTemplate; this.schemaCache schemaCache; this.queryGuard queryGuard; } Tool(name query_mysql, description 执行MySQL只读查询返回表格数据。 仅支持SELECT开头的语句禁止执行DML/DDL。 表结构说明orders表金额字段amount以分为单位需要除以100换算元订单状态status包括pending/paid/shipped/completed/cancelled。 常见查询按时间范围聚合金额、按状态统计订单量、按客户查历史订单。 ) public String query(ToolParam(description 只读SQL语句必须以SELECT开头) String sql, ToolParam(required false, description 返回最大行数默认为50) Integer limit) { queryGuard.validateReadOnly(sql); int safeLimit (limit null || limit 0) ? 50 : Math.min(limit, 200); String finalSql applyLimit(sql, safeLimit); try { ListMapString, Object rows jdbcTemplate.queryForList(finalSql); return JSONUtils.toJson(rows); } catch (DataAccessException e) { return fallbackErrorMessage(e); } } }几个关键点Tool注解里的description就是模型理解工具的唯一入口写清楚业务约束比写清楚SQL语法更有用。queryGuard.validateReadOnly(sql)不是简单判断字符串以SELECT开头还要排除SELECT ... INTO、SELECT ... FOR UPDATE这类变体。对结果集JSON序列化时BigDecimal、Date等非标准类型要在JSONUtils里做统一转换否则模型端拿到的值可能是2025-01-01T00:00:00这种很奇怪的结构。3.4 Schema发现与缓存实现MCP的资源机制可以让我们把表结构清单提供给AI但不建议每次对话都实时查一次数据库。我的方案是启动时扫描一次information_schema把表、字段、注释、业务口径说明格式化成一段Markdown缓存到内存中资源端点直接返回这段缓存。Component public class SchemaCache implements InitializingBean { private final JdbcTemplate jdbcTemplate; private String schemaMarkdown; Override public void afterPropertiesSet() { ListMapString, Object tables jdbcTemplate.queryForList( SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA ?, dbName); StringBuilder sb new StringBuilder(); for (MapString, Object table : tables) { String tableName (String) table.get(TABLE_NAME); String tableComment (String) table.get(TABLE_COMMENT); sb.append(## ).append(tableName) .append().append(tableComment).append().append(\n); ListMapString, Object columns jdbcTemplate.queryForList( SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA ? AND TABLE_NAME ?, dbName, tableName); for (MapString, Object col : columns) { sb.append(- ).append(col.get(COLUMN_NAME)) .append( ().append(col.get(COLUMN_TYPE)).append() ) .append(col.get(COLUMN_COMMENT)).append(\n); } } this.schemaMarkdown sb.toString(); } ToolResource(name mysql_schema, description MySQL数据库schema信息) public String getSchema() { return schemaMarkdown; } }这个缓存看似简单实际上对查询准确率提升是决定性的。如果没有schema信息AI只知道有一个工具叫query_mysql但不知道表名、字段名生成的SQL经常会瞎猜字段。把schema放进去以后AI先生成SQL、再执行成功率会从一半以下跳到九成以上差距非常大。3.5 安全与权限控制细节这块必须单独拎出来讲。MCP Server直接暴露SQL执行能力如果没有防护措施上线就是事故。我做了四层防护查询类型白名单只允许SELECT开头且排除FOR UPDATE、INTO OUTFILE等危险后缀。数据源账号权限限制MCP服务连接MySQL使用独立的只读账号只授予SELECT权限从数据库层面掐掉DML/DDL可能。表级过滤在SchemaCache里配置表的黑名单/白名单。比如内部审计表、敏感业务表不允许出现在schema描述中AI根本不知道有这张表也就不会去查。行数上限与超时应用层强制LIMIT 200不允许无界查询JDBC查询超时设置为10~15秒防止慢SQL拖垮连接池。安全防护要层层设防、互不信任。就算某个环节被绕过后面的环节也能兜底。3.6 完整测试流程与效果验证服务起来以后我用一个MCP客户端做端到端验证。测试工具用的是MCP CLI其实任何支持MCP协议的客户端都行包括Claude Desktop、Spring AI客户端等。测试用例设计了三类事实型查询orders表总共有多少条记录聚合型查询按状态统计订单数量带业务口径的查询上个月已支付的订单总金额是多少元第三类最容易暴露问题。模型需要理解amount以分为单位上个月是哪个时间范围paid状态等于已支付这些语义才能生成正确的SQL。工具描述和schema描述如果写得好这个查询会生成类似下面的语句SELECT SUM(amount) / 100 AS total_amount FROM orders WHERE status paid AND created_at 2025-06-01 AND created_at 2025-07-01如果模型先按原值返回了金额说明工具描述里的除以100没有被模型采纳就需要调整描述措辞或者在资源内容里加上一个显式的口径提醒。4. 常见问题与排查技巧实录4.1 工具无法被AI客户端发现症状客户端启动后列出工具列表找不到query_mysql。排查方向确认Spring AI McpServer配置是否正确加载。打开日志看启动时是否出现Registered MCP Tool: query_mysql之类的信息。确认包扫描路径。Tool注解所在的包必须被Spring Boot的主类扫描到否则不会被注册。确认客户端配置的MCP Server端点和服务实际暴露的端点一致特别是HTTP模式下的路径。这类问题八成是配置没生效先用日志定位不要上来就怀疑框架版本。4.2 模型生成非法SQL症状查询结果为空或者报语法错误日志里能看到模型生成的SQL明显不符合预期。原因排查schema信息缺失。AI不知道表名和字段名就不可能生成对的SQL。此时先调资源接口确认schema内容已经正确返回。工具描述太简略。description里如果只写执行SQL查询AI就缺少业务约束生成的SQL自然天马行空。要把表含义、字段单位、常用查询场景都写进去。prompt上下文不够。MCP的resource不是每次调用自动注入的需要客户端把资源内容绑定到上下文中。确认客户端侧是否读取了mysql_schema资源。我的经验是把表级别说明和字段说明格式化得紧凑一点不要用大段文字Markdown表格比长段落更容易被模型消化。4.3 连接池被打满症状并发一高查询报连接超时MySQL服务端连接数飙升。原因排查单次查询被模型重复调用比如模型在一次回答里对一个查询反复执行多次需要限制单次会话的工具调用次数。查询超时设置太宽松慢SQL占着连接不释放。连接池初始化连接数过小突发流量直接击穿。解决措施连接池初始化10最大20空闲回收60秒。单查询超时10秒被超时后会自动释放连接。引入并发信号量限制同时执行的SQL数量超出直接返回错误提示保护数据库。4.4 MCP协议交互出现序列化异常症状工具调用返回结果后AI客户端报invalid response或解析失败。原因排查返回结果不是合法的JSON字符串。检查JSONUtils序列化时是否处理了特殊类型。返回结果超长。MCP协议对大响应有最大消息限制超出后客户端解析会失败。设置LIMIT 200可以基本规避。解决办法把返回结果统一封装成字符串确保JSON格式合法。在工具描述里强调最多返回200行让AI生成SQL时自觉加LIMIT。4.5 常见问题速查表现象可能原因处理建议工具列表为空包扫描路径不对、MCP配置未生效检查Component、Tool注解查看启动日志SQL执行报错schema信息缺失、描述不准确调资源接口确认内容优化description查询结果金额差100倍描述未说明按分存储工具描述中显式写出换算规则高并发时连接耗尽连接池太小、超时过长调整连接池参数增加并发限流序列化异常日期、金额等类型未统一处理统一转换类型后再转JSON5. 生产化经验与部署建议5.1 监控与日志MCP服务作为AI应用和数据库之间的桥梁日志质量决定了排障效率。除了常规的请求日志我把工具调用日志单独开了一张审计表记录了每次调用的时间、会话ID、SQL全文、返回行数、耗时。这个表有两个作用一是数据安全审计能查到谁在什么时间查了什么数据二是模型质量分析能看出来哪些SQL是模型反复生成错误的反推prompt设置的问题。SQL全量日志有个好处可以离线分析模型的SQL失败率比如连续一周统计错误SQL占比如果超过一定比例就说明工具描述或schema设计绕了需要调整。5.2 多数据源扩展SpringAI-MySQLMcp服务如果只服务一个库价值有限。扩展多数据源的方式很简单在MCP Server里注册多个Tool每个Tool绑定各自的JdbcTemplate和SchemaCache。命名规范要清晰比如query_mysql_orders、query_mysql_inventory。描述里写清楚这是哪个库、主要包含哪些表、适合查什么。AI客户端可以根据工具名和描述自动选择正确的数据源不需要用户指定库名。扩展到近十个库的时候这个方式的维护成本还是可控的。5.3 与Spring AI客户端集成服务端做好以后客户端接入就顺理成章了。在Spring AI应用里只需配置MCP Server的地址然后声明McpToolClient拿到工具列表注册到AI模型的工具列表中。spring: ai: mcp: client: connections: - id: mysql-mcp type: http url: http://localhost:8080客户端集成以后AI应用用户可以直接用自然语言提问华东区销量最高的三个商品是什么模型会自主决定调用哪个MCP工具、传什么参数、读取并解读结果整个过程用户无感。部署形态上我建议MCP Server独立部署成一微服务不要和AI应用塞在同一个进程里虽然增加了一次网络开销但能独立扩缩容、独立升级、独立审计明显利大于弊。6. 避坑建议与个人体会6.1 先小后大先窄后宽第一次做SpringAI-MySQLMcp服务时很容易贪多求全想把所有表都暴露出去。我的建议是先挑3-5张核心业务表把schema描述、工具描述配置好端到端跑通再逐步扩大范围。表一多schema描述就会变得很长模型反而更容易注意力分散生成错误SQL的概率上升。我做完一轮实验的体会是这个服务的设计难点不在MCP协议也不在Spring AI配置而是在让模型准确理解数据口径。每一张表、每一个字段、每一种度量单位都要在描述里交代清楚。把这些业务语义沉淀好AI查询的准确率才会从看运气变成可预期。6.2 最后分享一个实践技巧在工具描述里主动提供初始SQL模板让模型先参考模板再改写。例如常见查询模板SELECT DATE(created_at) AS day, SUM(amount)/100 AS amount_yuan FROM orders WHERE created_at ? GROUP BY DATE(created_at) ORDER BY day DESC LIMIT 10实际效果非常好。模型看到模板后生成的SQL结构明显更稳变体主要集中在WHERE条件和聚合维度上而不是从头编一个SQL。这个技巧我后来用到了所有数据库类MCP工具上几乎成了标准做法。SpringAI-MySQLMcp服务做完以后最大的感受是MCP协议把AI访问数据这件事从一次性定制开发变成了可复用的基础设施建设。数据库接入、权限控制、审计日志这些原本各自为政的模块现在都收敛到了同一套协议之下。如果你也正在做AI应用查库的能力这个方向值得投入。