当前位置: 首页 > news >正文

Java MCP 实战:一文跑通 Server、Client 与第三方 MCP 接入

如果你最近开始做 AI 应用开发,很快会遇到一个核心问题:

大模型会聊天,但怎么让它调用我们自己的 Java 方法、业务接口,甚至第三方服务?

这篇文章用一个完整的 `Java MCP` Demo,把 Server、Client、ReactAgent 和第三方高德 MCP 串起来,带你看清楚:

  • MCP 解决什么问题;

  • Java 方法如何变成 AI 可调用工具;

  • 自建 MCP Server 和第三方 MCP Server 有什么区别;

  • Client 如何同时连接多个 MCP Server;

  • 一个完整 MCP 调用链路如何在本地跑起来。

这个 Demo 不追求大而全,重点是适合新手上手。拿到代码后,只需要配置大模型 Key 和高德 Key,再按顺序启动服务,就能跑通从用户提问到工具调用的完整流程。

一、MCP 是什么?

MCP,全称是 `Model Context Protocol`,可以理解成一套让 AI 调用外部工具的标准协议。

用一句新手友好的话来说:

**MCP 就像 AI 工具世界里的 USB 接口。**

以前,不同 AI 平台调用工具的方式可能不一样。你给 A 平台写了一套天气查询工具,换到 B 平台可能又要重新适配。

有了 MCP 之后,我们可以把工具能力放在一个 MCP Server 里,对外暴露统一协议。AI 应用这一侧只需要作为 MCP Client 连接过去,就能发现工具、调用工具、拿到结果。

MCP架构设计:

MCP 的价值主要有几个:

  • 协议统一:工具接入方式标准化。

  • 自动发现:Server 注册工具,Client 自动获取工具列表。

  • 描述清晰:每个工具有名称、描述、参数说明,方便 AI 判断是否调用。

  • 跨平台复用:工具开发一次,可以被不同 AI 应用复用。

我这次实操的 Demo 使用的是一个父工程加3个子模块:

  • mcp-server:自建 MCP Server,负责暴露我们自己写的工具,比如计算器。

  • amap-mcp-server:第三方 MCP 包装服务,内部启动高德官方 MCP,对外也暴露成 HTTP MCP。

  • mcp-client:负责连接多个 MCP Server,并把工具交给 ReactAgent 使用。

二、搭建 MCP Server:先把工具暴露出去

MCP Server 是工具提供方。

在这个 Demo 里,我把服务端端口设置为 `8090`,对外提供 `/mcp` 端点。核心配置在 `mcp-server/src/main/resources/application.yml`。

server: port: ${SERVER_PORT:8090} spring: application: name: wnn-mcp-server mvc: async: request-timeout: -1 # SSE长连接保持,不超时 ai: dashscope: api-key: ${DASHSCOPE_API_KEY:sk-你的key} chat: options: model: ${AI_MODEL:qwen-max} mcp: server: enabled: true name: wnn-mcp-server version: 1.0.0 type: SYNC # 区别说明: # STREAMABLE - 有状态,服务端仍维护session # STATELESS - 真正无状态,请求间不维护任何会话,专为云原生/Serverless设计 protocol: STATELESS # 无状态模式,完美适配FC/Serverless环境 https://www.spring-doc.cn/spring-ai/1.1.0/api_mcp_mcp-stateless-server-boot-starter-docs.html stateless: mcp-endpoint: /mcp # 无状态HTTP端点 annotation-scanner: enabled: true

这里有几个必须看懂的配置:

  • spring.ai.mcp.server.enabled=true:开启 MCP Server。

  • type: SYNC:使用同步调用模式。

  • protocol: STATELESS:无状态模式,请求之间不维护服务端会话,适合云原生、Serverless 场景。

  • stateless.mcp-endpoint: /mcp:MCP Server 暴露出来的 HTTP 端点。

  • annotation-scanner.enabled=true:开启注解扫描,后面写的@McpTool才会被自动注册。

自建 MCP 和第三方 MCP 的差异

这个 Demo 里同时用了两类 MCP Server。

第一类是**自建 MCP Server**,也就是 `mcp-server`。工具方法由我们自己写,比如 `CalculatorTool`,再通过 `@McpTool` 暴露成 MCP 工具。

第二类是**第三方 MCP Server**,这里用的是高德官方 MCP:@amap/amap-maps-mcp-server

高德已经把天气、景点、路线等能力封装成 MCP 工具,我们不需要自己再写这些 API 调用逻辑。不过它常见的运行方式是 `stdio`:npx -y @amap/amap-maps-mcp-server

为了让 `mcp-client` 统一通过 HTTP 连接 MCP,我在项目里新增了 `amap-mcp-server` 模块

也就是说,`amap-mcp-server` 有两层身份:

- 对内:作为 MCP Client,通过 `stdio` 连接高德官方 MCP;

- 对外:作为 MCP Server,通过 `http://127.0.0.1:8000/mcp` 暴露给 `mcp-client`。

最终对 `mcp-client` 来说,不管是自建的 `mcp-server`,还是包装后的高德 `amap-mcp-server`,都是一个 HTTP MCP Server。区别只在于:一个工具由我们自己写,一个工具来自第三方 MCP。

三、用@McpTool开发第一个工具

MCP Server 真正有价值的地方,是它能把普通 Java 方法注册成 AI 可调用的工具。

在 Spring AI MCP 里,主要用两个注解:

  • @McpTool:标记一个方法是 MCP 工具。

  • @McpToolParam:描述工具参数,让 AI 知道参数是什么意思。

可以把 `@McpTool` 理解成给 Java 方法贴了一个标签:

**这个方法,AI 可以调用。**

比如项目里的计算器工具 `CalculatorTool`:

package net.wnn.tool; import lombok.extern.slf4j.Slf4j; import org.springaicommunity.mcp.annotation.McpTool; import org.springaicommunity.mcp.annotation.McpToolParam; import org.springframework.stereotype.Component; @Component @Slf4j public class CalculatorTool { /** * 加法运算 */ @McpTool(name = "calculator_add", description = "执行两个数字的加法运算") public double add( @McpToolParam(description = "第一个数字") double a, @McpToolParam(description = "第二个数字") double b) { log.info("执行加法运算:{} + {}", a, b); return a + b; } /** * 减法运算 */ @McpTool(name = "calculator_subtract", description = "执行两个数字的减法运算") public double subtract( @McpToolParam(description = "被减数") double a, @McpToolParam(description = "减数") double b) { log.info("执行减法运算:{} - {}", a, b); return a - b; } /** * 乘法运算 */ @McpTool(name = "calculator_multiply", description = "执行两个数字的乘法运算") public double multiply( @McpToolParam(description = "第一个数字") double a, @McpToolParam(description = "第二个数字") double b) { log.info("执行乘法运算:{} * {}", a, b); return a * b; } /** * 除法运算 */ @McpTool(name = "calculator_divide", description = "执行两个数字的除法运算") public double divide( @McpToolParam(description = "被除数") double a, @McpToolParam(description = "除数") double b) { if (b == 0) { throw new IllegalArgumentException("除数不能为零"); } log.info("执行除法运算:{} / {}", a, b); return a / b; } /** * 幂运算 */ @McpTool(name = "calculator_power", description = "计算一个数的幂次方") public double power( @McpToolParam(description = "底数") double base, @McpToolParam(description = "指数") double exponent) { log.info("执行幂运算:{} ^ {}", base, exponent); return Math.pow(base, exponent); } /** * 平方根 */ @McpTool(name = "calculator_sqrt", description = "计算一个数的平方根") public double sqrt( @McpToolParam(description = "要计算平方根的数字") double value) { if (value < 0) { throw new IllegalArgumentException("不能计算负数的平方根"); } log.info("执行平方根运算:sqrt({})", value); return Math.sqrt(value); } }

这里最关键的不是 Java 计算逻辑,而是工具描述。

@McpTool(name = "calculator_add", description = "执行两个数字的加法运算")

这行代码告诉 AI:

- 工具名叫 `calculator_add`。

- 这个工具适合做两个数字的加法。

当用户问“5 加 10 等于多少”时,ReactAgent 就可以根据描述判断:这个问题需要调用 `calculator_add`。

四、工具是怎么自动注册的?

服务端启动后,大致会经历这样一个流程:

这也是 MCP 对新手很友好的地方。

我们不需要手动维护一堆工具 JSON,只要把 Java 方法写好、注解写清楚,框架就会帮我们完成工具注册。

不过这里有几个规范建议:

  • 工具名建议使用模块_动作,比如calculator_add

  • 描述要写清楚,因为 AI 会根据描述判断是否调用。

  • 参数一定要写description,否则 AI 很难理解参数含义。

  • 工具内部要做好参数校验,比如除数不能为 0、天气预报天数限制在 1 到 7 天。

  • 每次工具调用最好记录日志,方便排查 AI 到底有没有调用工具。

五、搭建 MCP Client:连接多个 Server,发现工具

MCP Client 是 AI 应用这一侧。

它的职责可以理解成三件事:

1. 连接一个或多个 MCP Server。

2. 获取这些 Server 暴露的工具列表。

3. 把这些工具交给 AI 或 Agent 使用。

客户端的核心配置在 `mcp-client/src/main/resources/application.yml`:

server: port: ${SERVER_PORT:8082} spring: application: name: mcp-client ai: dashscope: api-key: ${DASHSCOPE_API_KEY:sk-你的key} chat: options: model: ${AI_MODEL:qwen-max} retry: max-attempts: 3 initial-interval: 2000 max-interval: 10000 multiplier: 2 mcp: client: enabled: true type: SYNC request-timeout: 60s toolcallback: enabled: true streamable-http: connections: mcp-server: url: http://127.0.0.1:8090 endpoint: /mcp amap: url: ${AMAP_MCP_URL:http://127.0.0.1:8000} endpoint: ${AMAP_MCP_ENDPOINT:/mcp}

有两个 HTTP MCP Server:

- 自建 MCP Server:`http://127.0.0.1:8090/mcp`

- 高德 MCP 包装服务:`http://127.0.0.1:8000/mcp`

只要两个 Server 先启动,Client 启动时就可以连接过去,发现计算器工具和高德 MCP 工具。

六、把 MCP 工具交给 ReactAgent

到这里,MCP Server 已经有工具,MCP Client 也能发现工具。

下一步,就是让 Agent 真正用起来。

项目里使用的是 Spring AI Alibaba Agent Framework 的 `ReactAgent`。

ReactAgent 的名字来自 ReAct 模式:

  • Reasoning:先思考用户问题需要什么能力。

  • Acting:再决定是否调用工具。

  • Observation:观察工具返回结果。

  • Answer:整合结果,输出最终回答。

比如用户问:计算 2 的 10 次方再开平方

这个问题不是简单聊天,而是需要工具。ReactAgent 会判断需要计算,然后调用 MCP 工具,拿到结果后再回答用户。

核心代码在 `ReactAgentService`:

package net.wnn.service; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.stereotype.Service; @Service @Slf4j @RequiredArgsConstructor public class ReactAgentService { private final ChatModel chatModel; private final ToolCallbackProvider mcpToolCallbackProvider; private ReactAgent reactAgent; private static final String SYSTEM_PROMPT = """ 你是一个集成了MCP工具的智能服务助手,拥有强大的工具调用能力。 你当前可以使用以下类型的工具: - 数学计算:加法、减法、乘法、除法、幂运算、开方 - 天气查询:当前天气、多日天气预报、空气质量指数 工作原则: 1. 对于需要计算或查询的问题,优先调用相关工具获取准确结果 2. 对于多步骤复杂问题,请分步骤推理并依次调用工具 3. 工具调用结果需整合后再给出最终回答 4. 如果问题不需要工具,直接回答即可 """; @PostConstruct public void init(){ ToolCallback[] toolCallbacks = mcpToolCallbackProvider.getToolCallbacks(); log.info("======加载{}个工具======", toolCallbacks.length); for(ToolCallback toolCallback : toolCallbacks){ log.info("======加载工具: 名称{},描述{}======", toolCallback.getToolDefinition().name(), toolCallback.getToolDefinition().description()); } reactAgent = ReactAgent.builder() .name("mcp-react-agent") .model(chatModel) .tools(toolCallbacks) .systemPrompt(SYSTEM_PROMPT) .saver(new MemorySaver()) //基于内存的会话存储 .build(); } public String chat(String message) { log.info("ReactAgentService chat 单次调用: {}", message); try { AssistantMessage assistantMessage = reactAgent.call(message); String messageText = assistantMessage.getText(); log.info("ReactAgentService chat AI的回答: {}", messageText); return messageText; }catch (Exception e){ log.error("ReactAgentService chat 异常: {}", e.getMessage()); throw new RuntimeException(e); } } public String chat(String message, String sessionId) { log.info("ReactAgentService chat 带有记忆功能 单次调用: {}", message); RunnableConfig runnableConfig = RunnableConfig.builder().threadId(sessionId).build(); try { AssistantMessage assistantMessage = reactAgent.call(message,runnableConfig); String messageText = assistantMessage.getText(); log.info("ReactAgentService chat 带有记忆功能 AI的回答: {}", messageText); return messageText; }catch (Exception e){ log.error("ReactAgentService chat 带有记忆功能 异常: {}", e.getMessage()); throw new RuntimeException(e); } } }

对应的服务方法:

@GetMapping("/react") public ResponseEntity<Map<String,Object>> reactAgent(@RequestParam String message){ log.info("收到请求: {}", message); String response = reactAgentService.chat(message); Map<String, Object> result = Map.of("message", response); return ResponseEntity.ok(result); }

启动顺序很重要:

1. 先启动 `amap-mcp-server`,端口 `8000`,它负责把高德 MCP 包装成 HTTP MCP。

2. 再启动 `mcp-server`,端口 `8090`,它负责暴露自建工具。

3. 最后启动 `mcp-client`,端口 `8082`。

4. 观察 `mcp-client` 日志中是否加载到了两个 MCP Server 的工具。

5. 通过浏览器或接口工具访问测试接口。

可以测试这些请求:

GET http://localhost:8082/ai/mcp/react?message=计算100除以5的结果

GET http://localhost:8082/ai/mcp/react?message=计算2的10次方再开平方

GET http://localhost:8082/ai/mcp/react?message=根据合肥未来三天的天气,帮我规划热门景点出行路线

如果 `mcp-server` 日志里能看到计算器工具被调用,说明自建 MCP 链路跑通。

如果 `amap-mcp-server` 日志里能看到类似下面的中文日志,说明高德 MCP 链路跑通:

高德 MCP 工具开始调用:工具名=...,入参=...高德 MCP 工具调用成功:工具名=...,耗时=...ms,返回摘要=...

七、带会话记忆的多轮对话

单次调用只能回答当前问题。但真实聊天里,用户经常会追问:

第一轮:北京今天天气怎么样?第二轮:未来 3 天呢?第三轮:我刚才问了什么?

第二轮里,用户没有再说“北京”,但我们希望 AI 能知道上下文。

这就需要会话记忆。

项目里使用 `MemorySaver` 做内存级会话存储:

.saver(new MemorySaver())

调用时,通过 `RunnableConfig.threadId(sessionId)` 指定会话 ID:

public String chat(String message, String sessionId) { log.info("ReactAgentService chat 带有记忆功能 单次调用: {}", message); RunnableConfig runnableConfig = RunnableConfig.builder().threadId(sessionId).build(); try { AssistantMessage assistantMessage = reactAgent.call(message,runnableConfig); String messageText = assistantMessage.getText(); log.info("ReactAgentService chat 带有记忆功能 AI的回答: {}", messageText); return messageText; }catch (Exception e){ log.error("ReactAgentService chat 带有记忆功能 异常: {}", e.getMessage()); throw new RuntimeException(e); } }

控制器也提供了对应接口:

@GetMapping("/react_memory") public ResponseEntity<Map<String,Object>> reactAgentMemory(@RequestParam String message,@RequestParam String sessionId){ log.info("收到请求: {}", message); String response = reactAgentService.chat(message,sessionId); Map<String, Object> result = Map.of("message", response); return ResponseEntity.ok(result); }

测试方式:

GET http://localhost:8082/ai/mcp/react_memory?message=北京今天天气怎么样&sessionId=test001

GET http://localhost:8082/ai/mcp/react_memory?message=未来3天的天气如何&sessionId=test001

GET http://localhost:8082/ai/mcp/react_memory?message=我问过什么问题&sessionId=test001

这里的关键点是:**相同的 `sessionId` 共享同一段对话历史。**

如果换成另一个 `sessionId`,就是一段新的会话。

不过 `MemorySaver` 只是内存存储,应用重启后历史会丢失。生产环境更建议换成 Redis、数据库或其他持久化方案。

八、完整调用链路再梳理一遍

到这里,我这个 Demo 的完整链路就可以串起来了:

  • @McpTool:把 Java 方法变成 AI 可调用工具。

  • MCP Server:负责暴露工具。

  • 自建 MCP Server:工具逻辑由我们自己写,比如计算器。

  • 第三方 MCP Server:工具能力来自第三方,比如高德地图和天气。

  • MCP Client:负责发现和调用一个或多个 MCP Server 的工具。

  • ToolCallbackProvider:把 MCP 工具转换成 Agent 能用的工具回调。

  • ReactAgent:负责思考、选择工具、调用工具、整合答案。

  • sessionId:让多轮对话共享上下文。

九、常见坑和建议

1. Server 一定要先启动

因为 Client 启动时要连接 Server 并发现工具。

这个 Demo 里有两个 Server:

- `amap-mcp-server`:高德 MCP 包装服务。

- `mcp-server`:自建 MCP 工具服务。

两个 Server 都启动成功后,再启动 `mcp-client`。如果其中一个 Server 没启动,Client 可能拿不到对应工具列表,后续 Agent 就无法调用这部分工具。

2. 工具描述要写清楚

AI 是否调用工具,很大程度依赖工具描述。

不建议写:@McpTool(name = "tool1", description = "工具1")

建议写:

@McpTool(name = "weather_get_forecast", description = "获取指定城市未来几天的天气预报")

描述越明确,AI 调用越稳定。

3. 参数描述不能省

`@McpToolParam` 的描述也很重要。

比如 `days` 参数,如果不写说明,AI 不一定知道它代表预报天数,也不知道应该传几天。

4.MemorySaver适合 Demo,不适合直接当生产存储

`MemorySaver` 的优点是简单,适合学习和演示。

它的缺点也明显:

- 应用重启后会话丢失。

- 数据在内存里,容量有限。

- 不适合多实例部署共享会话。

生产环境可以考虑 Redis 或数据库。

总结

这篇文章,我用一个 Spring Boot 多模块 Demo,把 ReactAgent 对接 MCP 的完整流程实操跑通了一遍。

从自建 MCP 看,我用 `@McpTool` 把普通 Java 方法注册成 MCP 工具,比如计算器。

从第三方 MCP 看,我通过 `amap-mcp-server` 把高德官方 MCP 包装成 HTTP MCP,让客户端也能像连接普通 HTTP MCP Server 一样使用它。

从客户端看,我通过 MCP Client 同时连接多个 Server,使用 `ToolCallbackProvider` 获取工具列表,再交给 ReactAgent。

从用户体验看,用户只需要输入自然语言,比如“计算 2 的 10 次方再开平方”或者“根据合肥未来三天的天气规划热门景点路线”,Agent 就可以自动判断是否需要工具,并完成调用。

本文所有调用逻辑、配置、代码我都整理完整可运行项目,不用自己拼凑,直接导入就能测。

需要这份源码的可以关注我技术号,后台回复[SpringMCP-Demo]即可领取

下周更新预告:
「MCP Server 无服务架构 + 多Agent编排落地」
完整源码+实战拆解,蹲住更新,带你把AI真正跑在业务里。

http://www.jsqmd.com/news/730701/

相关文章:

  • 2026年企业认证服务性价比排名,中安质环认证江苏中心如何 - 工业品牌热点
  • 显卡驱动彻底清理的终极指南:DDU工具深度解析与实战应用
  • AI人工智能——解读智能算力服务质量模型
  • mysql基础增删改查语句汇总
  • Equalizer APO终极指南:免费解锁Windows音频调校的完整教程
  • 2026年正规的轮胎制氮机供应商排名 - mypinpai
  • 新手避坑指南:C++ 引用、内联函数与 nullptr 全解析
  • R 4.5模型边缘化落地全链路,从caret/xgboost/lme4到TFLite/Roofline建模→设备端AOT编译
  • 显卡驱动彻底清理终极指南:Display Driver Uninstaller (DDU) 高效解决方案
  • AA制智能记账工具设计:从债务网络到最优结算算法
  • 食品行业净化设备性价比高的品牌 - 工业品牌热点
  • AMD Ryzen处理器底层调试工具SMUDebugTool深度解析与实战指南
  • 深蓝词库转换:20+输入法格式一键互通的终极解决方案
  • 制氧机设备选购指南,金属切割、玻璃制造适用款 - mypinpai
  • 最小差异对比法:高效区分相似概念的教学技术
  • IDEA中使用CodeX
  • 中文作文智能体实战项目:基于大语言模型的Web端写作助手设计与实现
  • 3.2《酒魂》规则设计文档
  • 如何10分钟掌握BepInEx:Unity游戏插件框架终极指南
  • 2026年热门的吨袋品牌排名:优耐集包装怎么样? - 工业品牌热点
  • OneMore:160+功能加持,让OneNote变身专业办公利器
  • DIO2352A/B 技术文档(二)
  • 2026年高纯分子筛靠谱厂家排名,价格费用是多少? - mypinpai
  • 5分钟搞定Unity游戏翻译!XUnity.AutoTranslator新手完全指南 [特殊字符]
  • 学习第一天
  • Cockpit:把 Claude Code 从终端里搬出来,装进浏览器
  • KLayout开源版图设计工具:从新手到专家的终极指南
  • 纬地、鸿业、海地、CASS等横断面数据互转工具V3.2——测绘与道路设计人员的效率神器
  • 搞懂5G QoS配置:QCI/5QI、ARP、GBR/MBR参数到底怎么设?一个实战案例说清楚
  • ViGEmBus:Windows内核级虚拟游戏控制器驱动深度解析