SpringAI集成MCP

发布于:2025-08-19 ⋅ 阅读:(17) ⋅ 点赞:(0)


SpringAI 通过 SpringBoot 集成扩展了 MCP Java SDK ,提供了客户端和服务端 starter,让 AI 应用程序快速支持 MCP。

接下来直接演示。

1_调用公用MCP

在使用其他开发者提供好的 MCP 服务时,仅仅引入 MCP 的客户端依赖即可。

SpringAI 提供了如下两种 MCP Client 依赖,根据需求选择一种引入即可(常见第二种)。

仅支持 Stdio 的依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

既支持 SSE(远程)也支持 Stdio(进程内)

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>

以使用百度地图提供的 MCP 服务为例,演示如何使用 Stdio 的方式调用公用的 MCP Server。

首先,对 MCP 进行配置,Stdio 的配置方式有两种。

一种是直接配置全局配置文件中:

spring:
  ai:
    mcp:
      client:
        enabled: true
        name: spring-ai-mcp-client
        version: 1.0.0
        type: sync # 或 async 调用响应式流应用
        request-timeout: 60000
        stdio:
          connections:
            baidu-map:
              command: cmd
              args:
                - "/c"
                - "npx"
                - "-y"
                - "@baidumap/mcp-server-baidu-map" # 第一次启动会安装此包
              env:
                BAIDU_MAP_API_KEY: xxx # 百度地图开放平台查看

另外一种是使用 Claude Desktop 格式的 JSON 文件单独存放(推荐)

{
  "mcpServers": {
    "baidu-map": {
      "command": "cmd",
      "args": [
        "/c",
        "npx",
        "-y",
        "@baidumap/mcp-server-baidu-map"
      ],
      "env": {
        "BAIDU_MAP_API_KEY": "xxxx"
      }
    }
  }
}

然后在 application.yaml 中指定文件的位置

spring:
  ai:
    mcp:
      client:
        request-timeout: 60000
        stdio:
          servers-configuration: classpath:/mcp/stdio-server-config.json

通过 ToolCallbackProvider 绑定 ChatClient,ToolCallbackProvider 可以看做外部工具提供商。

@Bean
public ChatClient serviceChatClient(OpenAiChatModel model, ChatMemory chatMemory, ToolCallbackProvider toolCallbackProvider) {
    return ChatClient.builder(model)
            .defaultSystem(new ClassPathResource("call.txt"))
            .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(chatMemory).build(), // CHAT MEMORY
                    new SimpleLoggerAdvisor())
            .defaultToolCallbacks(toolCallbackProvider)//关键
            .build();
}

如果想观察到调用 mcp 工具的日志信息进行调试,可以添加如下配置:

logging:
  level:
    io:
      modelcontextprotocol:
        client: debug
        spec: debug

测试结果:

img

2_Stdio方式

以查询天气为目标,定义 MCP-Server 工程,引入 Stdio 服务端依赖

<!-- mcp-server依赖-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

配置 application.yaml 属性:

spring:
  application:
    name: mcp-server
  main:
    web-application-type: none # stdio 并不需要启动为web应用
    banner-mode: off # 关掉banner 下方有说明
  ai:
    mcp:
      server:
        name: mcp-server # 应用名称
        version: 1.0.0 # 版本
# 注意: 您必须禁用 banner 和控制台日志记录,以允许 Stdio 传输工作!!!

定义查询城市天气信息的工具类

@Service
public class WeatherService {
    @Tool(description = "根据城市名称获得天气信息")
    public String getWeather(@ToolParam(description = "城市名称") String cityName) {
        return cityName + "今日天气 晴";
    }
}

将工具绑定到 ToolCallbackProvider:

@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();
    }
    // 客户端使用 .defaultToolCallbacks(toolCallbackProvider) 添加
}

将项目打成 jar 包后,在客户端 mcp/stdio-server-config.json 文件的 mcpServers 属性中新增如下配置:

"mcp-server-weather": {
  "command": "java",
  "args": [
    "-Dspring.ai.mcp.server.stdio=true",
    "-Dlogging.pattern.console=",
    "-jar",
    "D:\\WorkSpace\\IdeaProject\\jar\\mcp-server-weather.jar"
  ]
}

说明:

  • command:内部 java 程序直接调用,所以无需 cmd 命令。
  • args:以 stdio 的方式启动,清空控制台,否则 mcp-server 启动也会打印干扰信息。

img

3_Stdio实现原理

当客户端启动之后,会由 McpClientAutoConfiguration 初始化并启动传输连接获取 tool 列表。

传输连接的关键流程及类图如下:

在这里插入图片描述

在 StdioClientTransport 的 connect 方法中,会利用 ProcessBuilder 对象将配置文件中的命令、参数、环境等信息封装起来创建子进程并启动。

之后建立输入、输出流连接循环地读取或写入信息,源码中的大致实现如下:

// 准备指定的命令、参数、环境
List<String> fullCommand = new ArrayList<>();
fullCommand.add(params.getCommand());
fullCommand.addAll(params.getArgs());
ProcessBuilder processBuilder = this.getProcessBuilder();
processBuilder.command(fullCommand);
processBuilder.environment().putAll(params.getEnv());
// 启动子进程
this.process = processBuilder.start();
// 循环地从连接中读取数据
try (BufferedReader processReader = new BufferedReader(new InputStreamReader(proces
	String line;
	while (!isClosing && (line = processReader.readLine()) != null) {
		try {
			JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objec
			if (!this.inboundSink.tryEmitNext(message).isSuccess()) {
				if (!isClosing) {
					logger.error("Failed to enqueue inbound message: {}", message);
				}
				break;
			}
		}
	}
}
// 如果有消息向连接中写入数据
try {
	String jsonMessage = objectMapper.writeValueAsString(message);
	// Escape any embedded newlines in the JSON message as per spec:
	// https://spec.modelcontextprotocol.io/specification/basic/transports/#stdio
	// - Messages are delimited by newlines, and MUST NOT contain
	// embedded newlines.
	jsonMessage = jsonMessage.replace("\r\n", "\\n").replace("\n", "\\n").replace("\r", "\\n");
	var os = this.process.getOutputStream();
	synchronized (os) {
		os.write(jsonMessage.getBytes(StandardCharsets.UTF_8));
		os.write("\n".getBytes(StandardCharsets.UTF_8));
		os.flush();
	}
	s.next(message);
}

注意:这种方式传输信息时,服务端运行时打印的各种信息,如:banner、日志等都会成为干扰项。

4_SSE方式

最新版 MCP 协议已经弃用了这种方式,转为感知的 Streamable HTTP,但 SpringAI 目前还不支持 Streamable HTTP。

SSE 需要将 MCP Server 部署为 Web 服务。

以加法计算为例,定义 MCP-Server 工程,引入 SSE 服务端依赖

<!--支持webflux以及stdio的方式,还支持webflux的方式暴露服务,-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<!---webmvc是基于mvc的依赖,同样支持stdio,还支持-webmvc的方式暴露服务-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

配置 application.yaml 属性:

spring:
  application:
    name: mcp-server
  main:
    # (mvc,webflux依赖都引入则只能使用webflux,不然客户端会404)
    web-application-type: reactive # webflux的方式暴露服务,除此外还有mvc-servlet
  ai:
    mcp:
      server:
        name: mcp-server # 应用名称
        version: 1.0.0 # 版本
        type: async # 异步响应通信方式
        base-url:  # base-url
        sse-endpoint: /sse # 切入点,client默认会向url/sse下发送请求
server:
  port: 8888

定义加法计算的工具类,这里让它返回错误的结果方便验证

@Service
public class CalculationService {
    @Tool(description = "两数加法计算")
    public Long getWeather(@ToolParam(description = "加数") Long addend,
                           @ToolParam(description = "被加数") Long summand) {
        return addend * summand;
    }
}

将工具绑定到 ToolCallbackProvider:

@SpringBootApplication
public class McpServerApplication {

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

    @Bean
    public ToolCallbackProvider weatherTools(CalculationService calculationService) {
        return MethodToolCallbackProvider.builder().toolObjects(calculationService).build();
    }
    // 客户端使用 .defaultToolCallbacks(toolCallbackProvider) 添加
}

直接启动 MCP Server 后,客户端新增如下配置即调用服务端提供的工具

spring:
  ai:
    mcp:
      client:
        sse: # 新增此处配置,与stdio同级,其他配置无需更改
          connections: # 配置多个连接
            addition-calculation: # 服务名称
              url: http://localhost:8888 # mcp-server 地址

验证:

img

5_自定义MCP客户端

在 Spring AI 中,MCP 客户端分为两类:

  • 同步客户端(Sync Client):默认类型,适用于传统的请求-响应模式,使用阻塞操作。
  • 异步客户端(Async Client):适用于响应式应用,使用非阻塞操作。可以通过配置项 spring.ai.mcp.client.type=async 启用。

Spring 提供了自动装配机制,开发者只需实现 McpSyncClientCustomizer 或 McpAsyncClientCustomizer 接口,就能对 MCP 客户端的各个方面进行个性化配置。

例如,控制请求超时、事件监听以及消息处理逻辑。

import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.List;

/**
 * 自定义同步 MCP 客户端配置器
 * (兼容所有的传输方式: stdio、mvc、webflux 传输方式)
 */
@Slf4j
@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {

    @Override
    public void customize(String serverConfigurationName, McpClient.SyncSpec spec) {
        log.info("正在自定义 MCP 同步客户端配置: {}", serverConfigurationName);
        // 1. 配置请求超时时间
        spec.requestTimeout(Duration.ofSeconds(30));
        // 2. 工具变更监听
        spec.toolsChangeConsumer((List<Tool> tools) -> {
            log.info("工具列表发生变更,共 {} 个", tools.size());
            tools.forEach(tool -> log.debug("工具: {} - {}", tool.name(), tool.description()));
            handleToolsChange(serverConfigurationName, tools);
        });
        // 3. 资源变更监听
        spec.resourcesChangeConsumer((List<Resource> resources) -> {
            log.info("资源列表发生变更,共 {} 个", resources.size());
            resources.forEach(resource -> log.debug("资源: {} - {} ({})",
                    resource.name(), resource.description(), resource.mimeType()));
            handleResourcesChange(serverConfigurationName, resources);
        });
        // 4. 提示变更监听
        spec.promptsChangeConsumer((List<Prompt> prompts) -> {
            log.info("提示列表发生变更,共 {} 个", prompts.size());
            prompts.forEach(prompt -> log.debug("提示: {} - {}", prompt.name(), prompt.description()));
            handlePromptsChange(serverConfigurationName, prompts);
        });
        // 5. 日志消息处理
        spec.loggingConsumer((McpSchema.LoggingMessageNotification logMsg) -> {
            switch (logMsg.level()) {
                case ERROR -> log.error("MCP 服务器错误: {}", logMsg.data());
                case WARNING -> log.warn("MCP 服务器警告: {}", logMsg.data());
                case INFO -> log.info("MCP 服务器信息: {}", logMsg.data());
                case DEBUG -> log.debug("MCP 服务器调试: {}", logMsg.data());
                default -> log.trace("MCP 服务器日志: {}", logMsg.data());
            }
        });
        log.info("MCP 同步客户端配置完成: {}", serverConfigurationName);
        // 2. 设置文件系统根目录访问权限
        // 3. 自定义采样处理器
        //  - 处理LLM生成请求
        //  - 客户端保持对模型访问、选择和权限的控制
        //  - 服务器无需API密钥即可利用AI能力
    }

    /** 工具变更处理逻辑 */
    private void handleToolsChange(String serverName, List<Tool> tools) {
        log.info("为服务器 {} 更新工具缓存", serverName);
        // 可扩展:更新缓存、通知下游服务等
    }

    /** 资源变更处理逻辑 */
    private void handleResourcesChange(String serverName, List<Resource> resources) {
        log.info("为服务器 {} 刷新资源索引", serverName);
        // 可扩展:刷新索引、更新界面等
    }

    /** 提示变更处理逻辑 */
    private void handlePromptsChange(String serverName, List<Prompt> prompts) {
        log.info("为服务器 {} 重新加载提示模板", serverName);
        // 可扩展:重新加载提示、推荐新模板等
    }
}

MCP 客户端 bean 会自动配置并且可以通过注入的方式调用:

@Autowired
private List<McpSyncClient> mcpSyncClients;  // For sync client
// OR
@Autowired
private List<McpAsyncClient> mcpAsyncClients;  // For async client

当启用工具回调(默认行为)时,所有 MCP 客户端注册的 MCP 工具都将作为 ToolCallbackProvider 实例提供:

@Autowired
private SyncMcpToolCallbackProvider toolCallbackProvider;
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();

6_MCP Server权限控制

系统提示词,由调用方传递用户数据:可以将用户信息传递到工具中,但是可以随意去查询其他用户的数据,不可取。

Stdio:

  • 环境变量传递授权信息,但是目前不支持动态修改环境变量。
  • 可以自己尝试实现(销毁原来的 Stdio、建立新的 Stdio,不推荐),由于多个用户存在 Stdio 可能会造成并发竞争因此不推荐。

SSE:

按照传统的 Web 鉴权实现即可,推荐,也不会多个用户竞争同一个进程“切换权限”造成并发。


网站公告

今日签到

点亮在社区的每一天
去签到