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服务
相关推荐
hsjcjh2 小时前
【MySQL】C# 连接MySQL
java
敖正炀2 小时前
LinkedBlockingDeque详解
java
wangyadong3172 小时前
datagrip 链接mysql 报错
java
untE EADO2 小时前
Tomcat的server.xml配置详解
xml·java·tomcat
ictI CABL2 小时前
Tomcat 乱码问题彻底解决
java·tomcat
敖正炀2 小时前
DelayQueue 详解
java
敖正炀2 小时前
PriorityBlockingQueue 详解
java
shark22222222 小时前
Spring 的三种注入方式?
java·数据库·spring
陈煜的博客3 小时前
idea 项目只编译不打包,跳过测试,快速开发
java·ide·intellij-idea