springAi+MCP三种

我们将构建两个独立的 Spring Boot 应用:

  1. MCP Server:提供天气查询工具,支持 Stdio、SSE、Streamable HTTP 三种模式。
  2. MCP Client:连接 Server,获取工具并供 AI 调用。

核心依赖 (通用)

无论 Server 还是 Client,首先确保 pom.xml 包含以下基础依赖(版本号以 Spring AI 1.0.0-M6+ 或 1.1.x 为准):

复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring AI MCP 核心 SDK -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
        <version>1.0.0-SNAPSHOT</version> <!-- 请根据实际情况调整 -->
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-chat-model</artifactId> <!-- 用于 Client 调用大模型 -->
    </dependency>
</dependencies>

第一部分:MCP Server (服务端)

我们要在一个项目中同时支持三种协议。通常 Stdio 和 HTTP (SSE/Streamable) 是互斥的(因为 Stdio 需要独占控制台),但我们可以通过配置不同的 Bean 来演示如何定义它们。

1. 定义业务工具 (WeatherTool.java)
复制代码
package com.example.mcpserver;

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

@Service
public class WeatherTool {

    @Tool(description = "根据城市名称查询当前天气")
    public String getWeather(@ToolParam(description = "城市名称,例如:北京、上海") String city) {
        // 模拟耗时操作
        try { Thread.sleep(500); } catch (InterruptedException e) {}
        return String.format("🌤️ %s 的天气是:晴朗,气温 26°C,湿度 30%%。", city);
    }
}
2. 纯 Java 配置 (McpServerConfig.java)

这里我们不使用 application.yml,而是手动装配 Bean。

复制代码
package com.example.mcpserver;

import org.springframework.ai.mcp.server.McpServer;
import org.springframework.ai.mcp.server.McpSyncServer;
import org.springframework.ai.mcp.server.transport.StdioServerTransport;
import org.springframework.ai.mcp.server.transport.SseServerTransport;
import org.springframework.ai.mcp.server.transport.StreamableHttpServerTransport;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
public class McpServerConfig {

    // 1. 注册工具提供者
    @Bean
    public ToolCallbackProvider weatherTools(WeatherTool weatherTool) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherTool)
                .build();
    }

    // 2. 创建 MCP Server 核心实例
    @Bean
    public McpSyncServer mcpSyncServer(ToolCallbackProvider toolCallbackProvider) {
        return McpServer.sync()
                .name("weather-service")
                .version("1.0.0")
                .tools(toolCallbackProvider)
                .build();
    }

    // --- 模式 A: Streamable HTTP (推荐) ---
    // 对应 Profile: streamable
    @Bean
    @Profile("streamable")
    public StreamableHttpServerTransport streamableHttpTransport(McpSyncServer mcpSyncServer) {
        // 监听 /mcp 路径
        return new StreamableHttpServerTransport("/mcp", mcpSyncServer);
    }

    // --- 模式 B: SSE ---
    // 对应 Profile: sse
    @Bean
    @Profile("sse")
    public SseServerTransport sseServerTransport(McpSyncServer mcpSyncServer) {
        // SSE 通常需要两个端点:/sse (连接) 和 /message (通信)
        return new SseServerTransport("/sse", "/message", mcpSyncServer);
    }

    // --- 模式 C: Stdio ---
    // 对应 Profile: stdio
    // 注意:Stdio 模式下通常不能同时开启 Web Server (Tomcat),否则会抢占控制台
    @Bean
    @Profile("stdio")
    public StdioServerTransport stdioServerTransport(McpSyncServer mcpSyncServer) {
        System.err.println("Starting MCP Server in Stdio mode...");
        return new StdioServerTransport(System.in, System.out, mcpSyncServer);
    }
}
3. 启动类 (McpServerApplication.java)
复制代码
package com.example.mcpserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

第二部分:MCP Client (客户端)

客户端需要连接上面的 Server。我们同样使用 Java Config 来定义连接方式。

1. 定义 ChatClient (McpClientConfig.java)

这里的核心是将 MCP 发现的工具注入到 ChatClient 中。

复制代码
package com.example.mcpclient;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.mcp.client.McpClient;
import org.springframework.ai.mcp.client.transport.stdio.StdioClientTransport;
import org.springframework.ai.mcp.client.transport.sse.SseClientTransport;
import org.springframework.ai.mcp.client.transport.streamablehttp.StreamableHttpClientTransport;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.List;

@Configuration
public class McpClientConfig {

    // --- 模式 A: Streamable HTTP Client ---
    @Bean
    @Profile("streamable")
    public McpClient streamableMcpClient() {
        var transport = new StreamableHttpClientTransport(
                WebClient.builder().baseUrl("http://localhost:8080").build(), 
                "/mcp"
        );
        return McpClient.sync().transport(transport).build();
    }

    // --- 模式 B: SSE Client ---
    @Bean
    @Profile("sse")
    public McpClient sseMcpClient() {
        var transport = new SseClientTransport(
                WebClient.builder().baseUrl("http://localhost:8080").build(),
                "/sse", "/message"
        );
        return McpClient.sync().transport(transport).build();
    }

    // --- 模式 C: Stdio Client ---
    // 这种方式用于让 Client 自己去启动一个本地进程作为 Server
    @Bean
    @Profile("stdio")
    public McpClient stdioMcpClient() {
        // 假设你已经打包好了 server.jar
        var transport = new StdioClientTransport("java", List.of("-jar", "path/to/server.jar"));
        return McpClient.sync().transport(transport).build();
    }

    // --- 统一封装为 ChatClient ---
    @Bean
    public ChatClient chatClient(ChatModel chatModel, List<McpClient> mcpClients) {
        // 获取第一个可用的 MCP Client (实际生产中建议用 Map 区分)
        if (mcpClients.isEmpty()) throw new RuntimeException("No MCP Client found");
        
        McpClient client = mcpClients.get(0);
        
        // 将 MCP 工具转换为 ChatClient 可用的 ToolCallback
        var tools = client.getToolCallbacks();

        return ChatClient.builder(chatModel)
                .defaultTools(tools)
                .build();
    }
}
2. 控制器测试 (ChatController.java)
复制代码
package com.example.mcpclient;

import org.springframework.ai.chat.client.ChatClient;
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 chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(defaultValue = "北京今天天气怎么样?") String msg) {
        // 这里的调用会自动触发 MCP Server 中的 getWeather 工具
        return chatClient.prompt(msg).call().content();
    }
}

第三部分:怎么发布与运行

由于没有配置文件,我们通过 JVM 启动参数 (-Dspring.profiles.active=...) 来切换模式。

1. 打包

在两个项目的根目录执行:

复制代码
mvn clean package -DskipTests
2. 运行场景演示

场景一:HTTP Streamable 模式 (生产环境推荐)

  1. 启动 Server

    复制代码
    java -jar target/mcp-server.jar --spring.profiles.active=streamable

    Server 启动 Tomcat,监听 8080 端口,暴露 /mcp 接口。

  2. 启动 Client

    复制代码
    java -jar target/mcp-client.jar --spring.profiles.active=streamable

    Client 初始化 HTTP 连接指向 localhost:8080/mcp。

  3. 测试 :

    访问 http://localhost:8081/chat?msg=上海天气,Client 会通过 HTTP 调用 Server 的工具。

场景二:SSE 模式 (兼容旧版)

  1. 启动 Server : --spring.profiles.active=sse
  2. 启动 Client : --spring.profiles.active=sse

场景三:Stdio 模式 (本地调试/IDE插件)

这种模式比较特殊,通常 Client 不需要独立运行,或者 Client 负责拉起 Server。

  1. 单独运行 Server (作为子进程)

    复制代码
    java -jar target/mcp-server.jar --spring.profiles.active=stdio

    此时控制台会挂起,等待输入。

  2. Client 调用 :

    如果你在 Client 代码里配置了 StdioClientTransport 指向这个 jar 包,Client 启动时会自动执行命令拉起 Server 进程,并通过管道通信。

总结

表格

特性 Stdio SSE Streamable HTTP
Java Config 关键类 StdioServerTransport SseServerTransport StreamableHttpServerTransport
Client Transport StdioClientTransport SseClientTransport StreamableHttpClientTransport
启动参数 -Dspring.profiles.active=stdio -Dspring.profiles.active=sse -Dspring.profiles.active=streamable
网络依赖 无 (本地进程) 需要 Web容器 (Tomcat) 需要 Web容器 (Tomcat)
发布形式 命令行工具/Jar Web服务 Web服务
相关推荐
贫民窟的勇敢爷们5 小时前
SpringBoot整合AOP切面编程实战,实现日志统一记录+接口权限校验
java·spring boot·spring
AC赳赳老秦6 小时前
供应链专员提效:OpenClaw自动跟踪物流信息、更新库存数据,异常自动提醒
java·大数据·服务器·数据库·人工智能·自动化·openclaw
迈巴赫车主6 小时前
Java基础:list、set、map一遍过
java·开发语言
灵犀学长6 小时前
基于 Spring ThreadPoolTaskScheduler + CronTrigger 实现的动态定时任务调度系统
java·数据库·spring
好家伙VCC8 小时前
【无标题】
java
小碗羊肉8 小时前
【JavaWeb | 第十一篇】文件上传(本地&阿里云OSS)
java·阿里云·servlet
吾疾唯君医8 小时前
Java SpringBoot集成积木报表实操记录
java·spring boot·spring·导出excel·积木报表·数据文件下载
Byron Loong9 小时前
【c++】为什么有了dll和.h,还需要包含lib
java·开发语言·c++
hexu_blog9 小时前
vue+java实现图片批量压缩
java·前端·vue.js
头歌实践平台9 小时前
Hadoop开发环境搭建
java·大数据·hadoop