Spring AI 框架中集成 MCP 的完整指南:从服务端到客户端的全流程实践

目录

  • [1. 引言](#1. 引言)
  • [2. MCP 核心概念速览](#2. MCP 核心概念速览)
    • [2.1 Server 与 Client](#2.1 Server 与 Client)
    • [2.2 Tool 与 Function Calling](#2.2 Tool 与 Function Calling)
    • [2.3 传输方式](#2.3 传输方式)
  • [3. 环境准备](#3. 环境准备)
  • [4. 服务端实践:构建 MCP Server](#4. 服务端实践:构建 MCP Server)
    • [4.1 引入依赖](#4.1 引入依赖)
    • [4.2 创建工具定义](#4.2 创建工具定义)
    • [4.3 启用 MCP 支持](#4.3 启用 MCP 支持)
    • [4.4 Server 端配置](#4.4 Server 端配置)
  • [5. 客户端实践:集成 MCP Client](#5. 客户端实践:集成 MCP Client)
    • [5.1 引入依赖](#5.1 引入依赖)
    • [5.2 配置 MCP Server 连接](#5.2 配置 MCP Server 连接)
    • [5.3 注入工具并调用模型](#5.3 注入工具并调用模型)
    • [5.4 验证调用](#5.4 验证调用)
  • [6. 全流程工作原理解析](#6. 全流程工作原理解析)
  • [7. 进阶实践与生产注意事项](#7. 进阶实践与生产注意事项)
    • [7.1 多工具与多服务端编排](#7.1 多工具与多服务端编排)
    • [7.2 错误处理与重试](#7.2 错误处理与重试)
    • [7.3 安全建议](#7.3 安全建议)
    • [7.4 常见问题排查](#7.4 常见问题排查)
  • [8. 总结](#8. 总结)

1. 引言

随着大模型应用的快速发展,让 AI 模型安全、标准化地访问外部工具和数据源,成为工程化落地的关键难题。Model Context Protocol(MCP)正是 Anthropic 提出的开放协议,旨在统一模型与外部系统之间的交互方式------无论模型是调用本地文件、数据库,还是远程 API,都可以通过同一套协议完成。

Spring AI 从 1.0.0 版本开始,对 MCP 提供了原生支持。开发者可以用几乎相同的编程模型构建 MCP Server(暴露工具)和 MCP Client(消费工具),让 Spring 生态的大模型应用快速具备「调用外部能力」的标准化能力。

本文将带你从零开始,完整走通 Spring AI 集成 MCP 的全流程:理解核心概念、搭建 MCP Server、配置 MCP Client,最后完成一个可运行的端到端示例。

2. MCP 核心概念速览

在动手之前,先厘清 MCP 中的几个关键角色。

2.1 Server 与 Client

  • MCP Server:能力的提供方。它暴露若干工具(Tools)、资源(Resources)或提示模板(Prompts),供模型或上游应用调用。
  • MCP Client:能力的消费方。它连接一个或多个 MCP Server,发现并调用 Server 暴露的能力。

在 Spring AI 中,一个应用既可以作为 Server 暴露工具,也可以作为 Client 调用其他 Server;两者甚至可以同时存在于同一个应用中。

2.2 Tool 与 Function Calling

MCP 中最常用的场景是 Tool(工具)。一个 Tool 本质上就是一个「可被模型调用的函数」:

  • 模型输出一个函数调用请求(包含函数名和参数 JSON);
  • Client 将请求转发给对应的 MCP Server;
  • Server 执行本地函数逻辑,返回结构化结果;
  • Client 再把结果返回给模型,由模型组织最终回复。

这与 OpenAI 的 Function Calling 机制高度一致,但通过 MCP,工具定义和调用被抽象为跨应用、跨语言的协议。

2.3 传输方式

Spring AI MCP 支持多种传输协议:

  • stdio:通过标准输入输出通信,适合本地进程间调用;
  • SSE / HTTP:通过 HTTP 长连接传输,适合远程服务;
  • WebFlux / WebMvc:与 Spring Web 体系集成,适合微服务架构。

本文将以最简单的 stdio 传输方式为主线,演示完整的工具注册与调用流程。

3. 环境准备

本文示例基于以下环境:

依赖 版本
JDK 17 及以上
Maven 3.8+
Spring Boot 3.3.5
Spring AI 1.0.0 GA

pom.xml 中引入 BOM,统一管理 Spring AI 依赖版本:

xml 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

由于 Spring AI 的部分依赖尚未进入 Maven Central,还需要在 pom.xml 中补充仓库:

xml 复制代码
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <releases>
            <enabled>false</enabled>
        </releases>
    </repository>
</repositories>

4. 服务端实践:构建 MCP Server

本节创建一个独立的 Spring Boot 应用,作为「天气查询」MCP Server。它暴露一个 getWeather 工具,接收城市名,返回模拟的天气信息。

4.1 引入依赖

pom.xml 中引入 MCP Server 相关的 Starter:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

4.2 创建工具定义

通过 @Tool 注解,我们可以非常简洁地把一个普通方法暴露为 MCP Tool。创建一个天气服务类:

java 复制代码
package com.example.mcpserver;

import java.util.Map;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

@Service
public class WeatherService {

    @Tool(description = "根据城市名称查询当前天气,返回温度与天气状况")
    public String getWeather(
            @ToolParam(description = "需要查询天气的城市名称,例如北京") String city) {

        // 模拟天气查询逻辑,实际项目中可替换为真实 API 调用
        Map<String, String> mockData = Map.of(
                "北京", "晴,25°C",
                "上海", "多云,28°C",
                "深圳", "阵雨,30°C"
        );

        return mockData.getOrDefault(city, "暂无该城市的天气数据");
    }
}

4.3 启用 MCP 支持

在启动类上启用 MCP Server 能力,并在配置文件中关闭默认 Web 路径(stdio 模式下不需要 HTTP 端点):

java 复制代码
package com.example.mcpserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.MethodToolCallbackProvider;

@SpringBootApplication
public class McpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }

    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}

MethodToolCallbackProvider 会扫描被 @Tool 注解的方法,并自动注册为可调用的 MCP Tool。

4.4 Server 端配置

application.properties 中配置 MCP Server 的基本信息、协议版本和工具开关:

properties 复制代码
spring.application.name=mcp-weather-server

# MCP Server 配置
spring.ai.mcp.server.name=weather-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC

# 工具变更通知
spring.ai.mcp.server.tool-change-notification=true

这样,一个最小可用的 MCP Server 就完成了。它会在启动后通过 stdio 协议等待客户端连接。

5. 客户端实践:集成 MCP Client

接下来创建另一个 Spring Boot 应用,作为 MCP Client。它连接上一节的天气 Server,并把天气工具注入到一个 Chat 客户端中,让大模型可以按需调用。

5.1 引入依赖

在 Client 项目的 pom.xml 中引入 MCP Client Starter 以及模型调用相关依赖:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

这里以 OpenAI 兼容模型为例,你也可以替换为其他支持的模型厂商。

5.2 配置 MCP Server 连接

application.properties 中声明要连接哪些 MCP Server,并配置传输参数:

properties 复制代码
spring.application.name=mcp-client-demo

# 连接一个名为 weather 的 MCP Server
spring.ai.mcp.client.connections.weather.transport=STDIO
spring.ai.mcp.client.connections.weather.command=java
spring.ai.mcp.client.connections.weather.args=-jar,mcp-weather-server.jar

# 模型配置(OpenAI 兼容接口示例)
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.base-url=${OPENAI_BASE_URL}
spring.ai.openai.chat.options.model=gpt-4o-mini

其中 commandargs 指定了以 stdio 方式启动本地 MCP Server 进程的命令。若连接远程 Server,可改用 HTTP 传输:

properties 复制代码
spring.ai.mcp.client.connections.remote.transport=HTTP
spring.ai.mcp.client.connections.remote.url=http://localhost:8080/mcp

5.3 注入工具并调用模型

在客户端代码中,通过 ToolCallbackProvider 拿到已连接的 MCP Server 工具,并注入到 ChatClient

java 复制代码
package com.example.mcpclient;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder builder,
                          ToolCallbackProvider toolCallbackProvider) {
        this.chatClient = builder
                .defaultTools(toolCallbackProvider.getToolCallbacks())
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

5.4 验证调用

启动客户端应用后,可以通过 HTTP 请求验证工具调用是否打通:

bash 复制代码
curl "http://localhost:8081/chat?message=北京今天天气怎么样?"

预期返回类似:

text 复制代码
北京今天天气晴,气温 25°C。

此时,完整的链路是:用户提问 → 大模型识别需要调用 getWeather 工具 → MCP Client 将调用请求转发给 MCP Server → Server 执行本地逻辑并返回结果 → 大模型整合结果生成自然语言回复。

6. 全流程工作原理解析

为了加深理解,下面用一张流程图总结上述调用链:
#mermaid-svg-WcN2MuOo4irfqAFn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WcN2MuOo4irfqAFn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WcN2MuOo4irfqAFn .error-icon{fill:#552222;}#mermaid-svg-WcN2MuOo4irfqAFn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WcN2MuOo4irfqAFn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn .marker.cross{stroke:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WcN2MuOo4irfqAFn p{margin:0;}#mermaid-svg-WcN2MuOo4irfqAFn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster-label text{fill:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster-label span{color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster-label span p{background-color:transparent;}#mermaid-svg-WcN2MuOo4irfqAFn .label text,#mermaid-svg-WcN2MuOo4irfqAFn span{fill:#333;color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .node rect,#mermaid-svg-WcN2MuOo4irfqAFn .node circle,#mermaid-svg-WcN2MuOo4irfqAFn .node ellipse,#mermaid-svg-WcN2MuOo4irfqAFn .node polygon,#mermaid-svg-WcN2MuOo4irfqAFn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .rough-node .label text,#mermaid-svg-WcN2MuOo4irfqAFn .node .label text,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape .label,#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape .label{text-anchor:middle;}#mermaid-svg-WcN2MuOo4irfqAFn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .rough-node .label,#mermaid-svg-WcN2MuOo4irfqAFn .node .label,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape .label,#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape .label{text-align:center;}#mermaid-svg-WcN2MuOo4irfqAFn .node.clickable{cursor:pointer;}#mermaid-svg-WcN2MuOo4irfqAFn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn .arrowheadPath{fill:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WcN2MuOo4irfqAFn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WcN2MuOo4irfqAFn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WcN2MuOo4irfqAFn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WcN2MuOo4irfqAFn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WcN2MuOo4irfqAFn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WcN2MuOo4irfqAFn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster text{fill:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster span{color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WcN2MuOo4irfqAFn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WcN2MuOo4irfqAFn rect.text{fill:none;stroke-width:0;}#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape p,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape .label rect,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WcN2MuOo4irfqAFn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WcN2MuOo4irfqAFn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WcN2MuOo4irfqAFn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不需要
需要调用工具
用户提问
ChatClient
大模型判断是否需要调用工具
直接生成回复
MCP Client
MCP Server(工具实现方)
执行本地函数逻辑
返回结构化结果
整合结果生成最终回复
返回给用户

关键点在于:

  1. 工具定义与实现解耦:Server 只关心「能力」本身,Client 只关心「会话编排」,协议负责两者之间的标准化通信。
  2. 模型决定调用时机:工具是否被调用、参数如何填充,完全由大模型根据上下文判断,开发者无需编写硬编码的分支逻辑。
  3. 可组合性:一个 Client 可以同时连接多个 MCP Server,例如天气 Server、数据库 Server、浏览器 Server,形成一个能力丰富的智能体。

7. 进阶实践与生产注意事项

7.1 多工具与多服务端编排

当系统需要多个能力来源时,可以在配置文件中声明多个连接,并在代码中聚合它们的工具:

java 复制代码
@Configuration
public class ToolConfiguration {

    @Bean
    public ToolCallbackProvider allTools(
            List<ToolCallbackProvider> providers) {
        List<ToolCallback> callbacks = providers.stream()
                .flatMap(p -> List.of(p.getToolCallbacks()).stream())
                .toList();
        return ToolCallbackProvider.from(callbacks);
    }
}

7.2 错误处理与重试

生产环境中,远程 MCP Server 可能因网络等原因暂时不可用。建议在 Client 侧做以下处理:

  • 为工具调用设置超时时间;
  • 捕获调用异常时,向模型返回友好的错误信息,让模型能够向用户解释;
  • 对关键工具实现幂等逻辑,避免重复调用产生副作用。

7.3 安全建议

MCP 本质上是让模型获得了「操作外部系统」的权限,因此安全边界非常重要:

  • 最小权限原则:每个 Server 只暴露必要工具,避免把危险操作(如删除数据、执行命令)无差别开放;
  • 输入校验:即使模型生成的参数通常是合理的,也必须对入参做严格校验,防止注入或越权访问;
  • 敏感信息隔离:API Key、数据库连接串等敏感信息不要出现在工具描述或返回结果中;
  • 审计日志:记录工具调用的入参、结果和调用方,便于问题追踪。

7.4 常见问题排查

现象 可能原因 排查方向
模型不调用工具 工具描述不清晰 优化 @Tool 的 description,说明适用场景
连接建立失败 命令或传输配置错误 检查 stdio 的 command/args,或 HTTP 的 url
工具返回空结果 参数名不匹配 确认 @ToolParam 描述与模型传参一致
依赖解析失败 仓库未配置 确认已添加 Spring Milestones/Snapshots 仓库

8. 总结

本文从 MCP 的核心概念出发,带你完整走通了 Spring AI 集成 MCP 的实践路径:

  • 使用 @Tool 注解和 MethodToolCallbackProvider 构建 MCP Server;
  • 通过 spring.ai.mcp.client.connections 配置 MCP Client;
  • 将远程工具注入 ChatClient,实现大模型驱动的工具调用;
  • 了解了生产环境中的多工具编排、安全与排错要点。

MCP 的价值在于用统一协议连接「模型」与「世界」。结合 Spring AI 的深度集成,Java 开发者可以用熟悉的 Spring 编程模型,快速构建出能够操作真实数据与系统的大模型应用。希望本文能成为你上手 Spring AI + MCP 的实用起点。

相关推荐
Csvn12 分钟前
第 7 章 MCP 标准化工具接入
人工智能·aigc·agent
武子康14 分钟前
拆开 Pi Monorepo:改模型、循环、产品和 UI 时,代码应该放在哪一层
人工智能·llm·agent
hh95015 分钟前
Agent Plan × DeepSeek Harness:角色 Prompt 驱动的 Agent 分工优化与协作质量实验
java·前端·人工智能·prompt·adg·agent plan·adg成都社区
YHL15 分钟前
🐉 天龙八部 RAG 知识库实战:从零构建你的武侠 AI 助手
数据库·人工智能
阿基拉de_Akir15 分钟前
② 跨层禁止:机器如何拦截非法语义绑定
人工智能
青 春 记 忆16 分钟前
零基础入门python29:用 pytest 验收完整 Flask 业务流程
python·flask·后端开发
科技小E18 分钟前
把人从百米高空拉下来:自动化AI算法训练服务器DLTM+无人机巡检让风机光伏缺陷无所遁形
人工智能·自动化·无人机
wear工程师18 分钟前
MySQL 死锁怎么排查:把 innodb status 里的 HOLDS 和 WAITING 对上
sql·mysql
武子康19 分钟前
一次 Agent 失败后,到底该改模型、Prompt 还是 Router?
人工智能·llm·agent