news 2026/9/29 20:09:00

Java大模型MCP服务端开发:数据库查询与数据分析(附源码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java大模型MCP服务端开发:数据库查询与数据分析(附源码)

1. 为什么要在 Java 里自己写一个 MCP 服务端

如果你正在做企业内部的 AI 助手,大概率会遇到一个尴尬场景:模型很聪明,但它看不到你数据库里的真实数据。你问它"上个月华东区退货率是多少",它只能编一个看起来合理的数字。要解决这个问题,就得让模型能主动调用你的数据库查询能力,而 MCP(Model Context Protocol)就是目前最顺手的方案。

MCP 本质上是一套"模型和外部工具之间的约定"。你可以把它理解成 USB 接口:模型是电脑,你的数据库查询服务是 U 盘,只要双方都遵守 MCP 协议,插上就能用,不需要为每个模型单独写适配层。服务端负责把"查表列表""查表结构""执行 SQL"这些能力暴露成工具,客户端(比如 Cherry Studio、Claude 桌面端、各类支持 MCP 的 IDE)发现这些工具后,模型就能在对话中自主决定调用哪个。

这篇面向的是需要让 AI 工具安全访问业务库的 Java 开发者。我会用一个可运行的 Spring Boot 项目,把 MCP 服务端的骨架搭起来,接入 MySQL,暴露三个工具,最后用客户端跑一次"从提问到返回分析结果"的完整链路。源码结构参考了开源项目 DataExploration-MCP 的思路,但配置和排障部分我会写得更细,方便你直接抄。

适合谁:有 Java/Spring Boot 基础,想让大模型安全查询业务库,又不想把数据库账号密码直接塞给模型平台的开发者。读完你能拿到一份可复制的settings.json和一套能跑通的工具定义。

2. 前置准备:依赖、TaoToken 与项目骨架

2.1 Maven 依赖怎么选

MCP 官方提供了 Java SDK,核心包是io.modelcontextprotocol.sdk:mcp。传输层有两种选择:WebMVC 的 SSE 和 WebFlux 的 SSE。如果你项目本来就是 Spring Boot WebMVC,直接用mcp-spring-webmvc,别为了这个引入 WebFlux,否则线程模型会对不上。

<dependency> <groupId>io.modelcontextprotocol.sdk</groupId> <artifactId>mcp</artifactId> <version>0.10.0</version> </dependency> <dependency> <groupId>io.modelcontextprotocol.sdk</groupId> <artifactId>mcp-spring-webmvc</artifactId> <version>0.10.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.4.0</version> </dependency>

版本号建议去 Maven Central 确认一下最新的,SDK 迭代比较快,0.10.x 和 0.9.x 在 API 上有细微差别,尤其是McpServer.sync()的构建方式。

2.2 模型侧接入用 TaoToken

服务端跑起来只是第一步,你还需要一个能连 MCP 客户端的模型入口来验证。我这边习惯用 TaoToken 做模型调用,它的 API 地址是https://taotoken.net/api,兼容 OpenAI 风格的请求格式,接进 Cherry Studio 或者自己写的测试客户端都很顺。如果你还没配 Key,先去控制台建一个:

  • 模型对话入口:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

注意:MCP 服务端本身不负责模型推理,它只暴露工具。模型调用走的是 TaoToken 这类 API,两者是配合关系,别搞混。

2.3 项目目录结构

src/main/java/com/example/mcp/ ├── McpApplication.java ├── config/McpConfig.java ├── server/McpServerManager.java ├── tools/DatabaseTools.java └── util/JdbcExecutor.java

McpConfig负责注册传输层 Bean,McpServerManager在启动时创建同步服务器并挂载工具,DatabaseTools放三个工具的定义,JdbcExecutor封装 JDBC 查询和结果格式化。

3. 可复制配置:传输层、服务端与三个工具

3.1 传输层配置(WebMVC SSE)

@Configuration public class McpConfig { @Bean public WebMvcSseServerTransportProvider transportProvider(ObjectMapper mapper) { return new WebMvcSseServerTransportProvider(mapper, "/messages"); } @Bean public RouterFunction<ServerResponse> mcpRouterFunction( WebMvcSseServerTransportProvider provider) { return provider.getRouterFunction(); } }

这里的/messages是客户端发消息的 URI,不是建立 SSE 连接的 URI。SSE 连接默认走/sse。这两个 URI 在客户端配置里要分清,后面排障会重点讲。

3.2 创建同步 MCP 服务器

@Component public class McpServerManager { private static final Logger log = LoggerFactory.getLogger(McpServerManager.class); @Bean public McpSyncServer mcpSyncServer(WebMvcSseServerTransportProvider provider, DatabaseTools tools) { McpSyncServer server = McpServer.sync(provider) .serverInfo("db-query-server", "1.0.0") .capabilities(McpSchema.ServerCapabilities.builder() .tools(true) .logging() .build()) .build(); try { server.addTool(tools.listTables()); server.addTool(tools.getTableSchema()); server.addTool(tools.executeQuery()); server.loggingNotification(McpSchema.LoggingMessageNotification.builder() .level(McpSchema.LoggingLevel.INFO) .logger("db-query") .data("MCP server initialized") .build()); } catch (Exception e) { log.error("MCP 服务器初始化失败: {}", e.getMessage(), e); } return server; } }

同步模式意味着客户端发请求后会阻塞等待结果。数据库查询通常几百毫秒到几秒,同步完全够用,编程模型也简单。异步模式适合长任务,这里没必要。

3.3 三个工具的定义

工具的核心是Tool对象加一个执行 lambda。Tool的 schema 用 JSON 描述参数,模型会根据这个 schema 决定传什么。

@Component public class DatabaseTools { private final JdbcExecutor executor; public DatabaseTools(JdbcExecutor executor) { this.executor = executor; } public McpServerFeatures.SyncToolSpecification listTables() { String desc = "获取当前数据库中所有表的列表。"; String schema = """ {"type":"object","id":"urn:jsonschema:Operation","properties":{}} """; return new McpServerFeatures.SyncToolSpecification( new McpSchema.Tool("listTables", desc, schema), (exchange, args) -> { List<McpSchema.Content> result = new ArrayList<>(); try { result.add(new McpSchema.TextContent( "所有表: " + executor.query("SHOW TABLES;"))); } catch (Exception e) { result.add(new McpSchema.TextContent("查询失败: " + e.getMessage())); } return new McpSchema.CallToolResult(result, false); }); } public McpServerFeatures.SyncToolSpecification getTableSchema() { String desc = "获取指定数据表的结构(列信息)。参数 tableName 为表名。"; String schema = """ {"type":"object","id":"urn:jsonschema:Operation", "properties":{"tableName":{"type":"string"}}} """; return new McpServerFeatures.SyncToolSpecification( new McpSchema.Tool("getTableSchema", desc, schema), (exchange, args) -> { List<McpSchema.Content> result = new ArrayList<>(); String tableName = (String) args.get("tableName"); try { result.add(new McpSchema.TextContent( "表结构: " + executor.query("DESCRIBE " + tableName + ";"))); } catch (Exception e) { result.add(new McpSchema.TextContent("查询失败: " + e.getMessage())); } return new McpSchema.CallToolResult(result, false); }); } public McpServerFeatures.SyncToolSpecification executeQuery() { String desc = "执行给定的 SQL 查询语句并返回结果。参数 sqlQuery 为 SQL 语句。"; String schema = """ {"type":"object","id":"urn:jsonschema:Operation", "properties":{"sqlQuery":{"type":"string"}}} """; return new McpServerFeatures.SyncToolSpecification( new McpSchema.Tool("execute_query_sql", desc, schema), (exchange, args) -> { List<McpSchema.Content> result = new ArrayList<>(); String sql = (String) args.get("sqlQuery"); try { result.add(new McpSchema.TextContent( "查询结果: " + executor.query(sql))); } catch (Exception e) { result.add(new McpSchema.TextContent("查询失败: " + e.getMessage())); } return new McpSchema.CallToolResult(result, false); }); } }

3.4 JDBC 执行与结果格式化

模型看不懂ResultSet,得转成文本表格。这里用StringBuilder拼一个简单的对齐表格,列宽按内容动态算。

@Component public class JdbcExecutor { private final DataSource dataSource; public JdbcExecutor(DataSource dataSource) { this.dataSource = dataSource; } public String query(String sql) throws SQLException { try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { ResultSetMetaData meta = rs.getMetaData(); int cols = meta.getColumnCount(); StringBuilder sb = new StringBuilder(); for (int i = 1; i <= cols; i++) { sb.append(meta.getColumnLabel(i)).append("\t"); } sb.append("\n"); int rows = 0; while (rs.next() && rows < 200) { for (int i = 1; i <= cols; i++) { sb.append(rs.getString(i)).append("\t"); } sb.append("\n"); rows++; } if (rows == 200) { sb.append("... 结果已截断,仅显示前 200 行\n"); } return sb.toString(); } } }

application.yml里配好数据源:

spring: datasource: url: jdbc:mysql://127.0.0.1:3306/business_db?useSSL=false&serverTimezone=Asia/Shanghai username: readonly_user password: your_password driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080

注意:生产环境务必用只读账号,并且只授予 SELECT 权限。MCP 工具暴露给模型后,模型理论上能执行任何 SQL,权限收窄是第一道防线。

4. 验证请求:从提问到返回分析结果

4.1 客户端 settings.json 配置

以 Cherry Studio 为例,在 MCP 服务器配置里填:

{ "mcpServers": { "db-query-server": { "type": "sse", "url": "http://127.0.0.1:8080/sse" } } }

这里填的是/sse,不是/messages。这是最容易踩的坑,后面单独讲。

4.2 启动服务端

mvn clean package -DskipTests java -jar target/mcp-db-server-1.0.0.jar

启动日志里看到MCP server initialized就说明工具挂载成功。如果报Address already in use,改server.port。

4.3 第一次验证:查表列表

在客户端里问:"数据库里一共有多少张表?"

模型会调用listTables,服务端执行SHOW TABLES;,返回类似:

Tables_in_business_db orders order_items customers products

模型拿到结果后回答:"当前数据库共有 4 张表,分别是 orders、order_items、customers、products。"

4.4 第二次验证:多工具串联分析

问:"2024 年上半年各月的订单总金额是多少?"

模型会依次调用:

  1. listTables→ 发现orders表
  2. getTableSchema传tableName=orders→ 拿到列信息,发现order_date和amount字段
  3. execute_query_sql传生成的 SQL:
SELECT DATE_FORMAT(order_date, '%Y-%m') AS month, SUM(amount) AS total FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-06-30' GROUP BY month ORDER BY month;

服务端返回:

month total 2024-01 128340.50 2024-02 98320.00 2024-03 156780.25 2024-04 142100.00 2024-05 167890.75 2024-06 189450.30

模型据此给出分析:"上半年订单金额呈上升趋势,6 月达到峰值 18.9 万,2 月因春节因素最低。"

这条链路跑通,说明 MCP 服务端的工具发现、参数传递、结果回传全部正常。

5. 本篇常见错误排查

5.1 客户端连不上,报 SSE 404

最常见的原因是 URL 填错。/messages是客户端发消息的端点,/sse才是建立连接的端点。客户端配置里必须填/sse。如果你在McpConfig里改了/messages这个路径,客户端那边不用跟着改,因为客户端只关心/sse。

5.2 工具列表为空

检查server.addTool()是否在build()之后调用。SDK 要求先构建服务器再挂载工具,顺序反了不会报错,但工具不会注册。另外确认capabilities里.tools(true)开了。

5.3 模型不调用工具,直接编答案

两个原因:一是工具描述写得太模糊,模型不知道什么时候用。把desc写清楚,比如"获取当前数据库中所有表的列表"比"查表"好得多。二是客户端没把工具列表传给模型。在 Cherry Studio 里确认 MCP 服务器状态是绿色,并且对话时勾选了工具。

5.4 SQL 执行报权限错误

只读账号执行SHOW TABLES和DESCRIBE需要SELECT权限,information_schema的访问也要放开。如果报Access denied,检查账号授权:

GRANT SELECT ON business_db.* TO 'readonly_user'@'%'; FLUSH PRIVILEGES;

5.5 结果太长导致模型截断

JdbcExecutor里我加了 200 行截断。如果你的表字段特别多,单行就很长,可以再加列数限制,或者让模型先COUNT(*)再决定要不要拉明细。

5.6 中文乱码

MySQL 连接串加characterEncoding=utf8,服务端返回的TextContent默认 UTF-8,一般不会乱。如果客户端显示乱码,检查客户端的编码设置。

6. 把查询能力稳定接进 AI 工具

服务端跑通只是起点,真正上线要考虑的是稳定性。我自己的做法是给JdbcExecutor加一层超时控制,用stmt.setQueryTimeout(10),避免模型生成一个全表扫描的 SQL 把连接池占满。另外把工具调用日志单独打到一张审计表里,记录谁在什么时候查了什么,方便回溯。

如果你打算长期跑编码类或 Agent 类任务,模型调用量会比较大,可以考虑用 Coding Plan 来管理额度:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite

需要看完整可运行源码的话,参考这个结构自己搭一遍最快:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 建好 Key,把application.yml里的数据源换成你自己的库,启动后先用curl http://127.0.0.1:8080/sse确认 SSE 端点有响应,再进客户端配。这一步过了,后面基本不会卡。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 20:08:46

OpenClaw框架核心技术解析:从Skill到MCP的TaoToken配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 20:08:27

企业用AI,钱花了,效果呢?(漫画)

回到最开始那组数字。95% 的试点对利润表几乎没有影响&#xff0c;80% 的人说"我更快了"&#xff0c;却只有 37% 的企业说"利润动了"。这三个数字放在一起&#xff0c;很容易得出一个悲观的结论&#xff1a;AI 是场泡沫。但把这篇文章里的证据串起来看&…

作者头像 李华
网站建设 2026/9/29 20:08:04

《代码随想录》刷题打卡day44:图论-part02

文章目录【99.计数孤岛】DFS版本BFS版本【100.岛屿的最大面积】DFS写法BFS写法【99.计数孤岛】 思路&#xff1a; 用遇到一个没有遍历过的节点陆地&#xff0c;计数器就加一&#xff0c;然后把该节点陆地所能遍历到的陆地都标记上。 在遇到标记过的陆地节点和海洋节点的时候…

作者头像 李华
网站建设 2026/9/29 20:07:06

基于Hadoop+SpringBoot的健康饮食推荐系统设计与实现

每年毕业季都会看到大量同学在选题和落地之间反复纠结&#xff0c;尤其是“大数据”方向的题目。要么是纯理论分析落到纸面上变成“PPT项目”&#xff0c;要么是技术栈堆得过高&#xff0c;答辩时连自己都解释不清楚。这次我拆解的这个题目——“基于HadoopSpringBoot的健康饮食…

作者头像 李华
网站建设 2026/9/29 20:06:45

Clara BBS 怎么升级?覆盖文件 + 数据库升级的正确姿势

Clara BBS 升级只需两步&#xff1a;先完整覆盖上传新版本文件&#xff08;保留 config/、uploads/、content/plugins/ 等目录&#xff09;&#xff0c;再进后台「系统工具 → 数据库升级」执行一次增量 DDL&#xff0c;整个过程幂等可重复执行&#xff0c;不需要 Composer、不…

作者头像 李华
网站建设 2026/9/29 20:06:25

影刀RPA实操指南:企业年报与工商公示信息批量采集

影刀RPA实操指南&#xff1a;企业年报与工商公示信息批量采集 每个月要对账、审供应商、做背调的时候&#xff0c;最折磨人的就是打开国家企业信用信息公示系统&#xff0c;一家一家搜企业名称&#xff0c;等滑块验证码&#xff0c;再翻年报找股东和资产数据。三十家企业查下来…

作者头像 李华