Spring AI + MCP:从入门到实战

Spring AI + MCP:从入门到实战

一、什么是MCP?

MCP(Model Context Protocol,模型上下文协议)是由Anthropic推出的一种开放协议,旨在解决AI助手与外部数据源和工具之间的连接问题。MCP定义了一套标准化的通信机制,使得AI应用能够安全、高效地访问外部资源。

1.1 MCP核心概念

MCP协议主要包含三个核心角色:

  • MCP Client(客户端) :发起请求的应用程序,通常是AI助手或应用
  • MCP Server(服务端) :提供资源和工具的服务,封装了数据访问逻辑
  • Host(宿主) :运行MCP客户端的应用程序

1.2 MCP工作原理

MCP使用JSON-RPC 2.0协议进行通信,支持两种传输方式:

  • stdio(标准输入输出) :适用于本地进程间通信
  • SSE(Server-Sent Events) :适用于基于HTTP的远程通信

1.3 为什么选择MCP?

  1. 标准化接口:统一的协议,降低集成复杂度
  2. 安全性:清晰的权限控制机制
  3. 可扩展性:支持自定义资源和工具
  4. 跨平台:语言无关的协议标准

二、环境搭建

2.1 Maven依赖配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>mcp-demo</artifactId>
    <version>1.0.0</version>
    <name>MCP Demo Project</name>
    <description>Spring AI + MCP Integration Demo</description>

    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M4</spring-ai.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring AI OpenAI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>

        <!-- MCP SDK -->
        <dependency>
            <groupId>org.anthropic</groupId>
            <artifactId>mcp-sdk</artifactId>
            <version>0.1.0</version>
        </dependency>

        <!-- JSON处理 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

2.2 配置文件

复制代码
# application.yml
spring:
  application:
    name: mcp-demo
  ai:
    openai:
      api-key: ${OPENAI_API_KEY:sk-your-api-key}
      chat:
        options:
          model: gpt-4
          temperature: 0.7

server:
  port: 8080

mcp:
  server:
    name: demo-mcp-server
    version: 1.0.0
  client:
    timeout: 30000
logging:
  level:
    com.example.mcp: DEBUG
    org.springframework.ai: DEBUG

三、MCP服务端实现

3.1 MCP服务端核心接口

MCP服务端需要实现以下核心功能:

3.2 实现MCP服务端

复制代码
package com.example.mcp.server;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * MCP服务端核心实现
 */
@Slf4j
@Component
public class McpServer {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final Map<String, McpTool> tools = new HashMap<>();
    private final Map<String, McpResource> resources = new HashMap<>();

    public McpServer() {
        initializeTools();
        initializeResources();
    }

    /**
     * 初始化可用工具
     */
    private void initializeTools() {
        // 用户查询工具
        tools.put("query_user", McpTool.builder()
                .name("query_user")
                .description("根据用户ID查询用户信息")
                .inputSchema(Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "userId", Map.of(
                            "type", "string",
                            "description", "用户ID"
                        )
                    ),
                    "required", List.of("userId")
                ))
                .build());

        // 数据分析工具
        tools.put("analyze_data", McpTool.builder()
                .name("analyze_data")
                .description("分析业务数据并生成报告")
                .inputSchema(Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "dataType", Map.of(
                            "type", "string",
                            "description", "数据类型",
                            "enum", List.of("sales", "traffic", "user")
                        ),
                        "dateRange", Map.of(
                            "type", "object",
                            "description", "日期范围",
                            "properties", Map.of(
                                "start", Map.of("type", "string", "format", "date"),
                                "end", Map.of("type", "string", "format", "date")
                            )
                        )
                    ),
                    "required", List.of("dataType")
                ))
                .build());

        // 文件操作工具
        tools.put("read_file", McpTool.builder()
                .name("read_file")
                .description("读取文件内容")
                .inputSchema(Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "filePath", Map.of(
                            "type", "string",
                            "description", "文件路径"
                        )
                    ),
                    "required", List.of("filePath")
                ))
                .build());

        log.info("Initialized {} tools", tools.size());
    }

    /**
     * 初始化可用资源
     */
    private void initializeResources() {
        // 用户列表资源
        resources.put("user://list", McpResource.builder()
                .uri("user://list")
                .name("用户列表")
                .description("系统中的所有用户列表")
                .mimeType("application/json")
                .build());

        // 系统配置资源
        resources.put("config://system", McpResource.builder()
                .uri("config://system")
                .name("系统配置")
                .description("系统级别的配置信息")
                .mimeType("application/json")
                .build());

        log.info("Initialized {} resources", resources.size());
    }

    /**
     * 处理MCP请求
     */
    public String handleRequest(String requestJson) {
        try {
            JsonNode root = objectMapper.readTree(requestJson);
            String method = root.get("method").asText();

            log.debug("Handling MCP request: {}", method);

            return switch (method) {
                case "initialize" -> handleInitialize(root);
                case "tools/list" -> handleListTools();
                case "tools/call" -> handleCallTool(root);
                case "resources/list" -> handleListResources();
                case "resources/read" -> handleReadResource(root);
                default -> createErrorResponse(-32601, "Method not found: " + method);
            };
        } catch (Exception e) {
            log.error("Error handling MCP request", e);
            return createErrorResponse(-32603, "Internal error: " + e.getMessage());
        }
    }

    /**
     * 处理初始化请求
     */
    private String handleInitialize(JsonNode request) {
        Map<String, Object> result = new HashMap<>();
        result.put("protocolVersion", "2024-11-05");
        result.put("serverInfo", Map.of(
            "name", "demo-mcp-server",
            "version", "1.0.0"
        ));
        result.put("capabilities", Map.of(
            "tools", Map.of(),
            "resources", Map.of()
        ));

        return createSuccessResponse(request, result);
    }

    /**
     * 处理工具列表请求
     */
    private String handleListTools() {
        List<Map<String, Object>> toolList = new ArrayList<>();
        for (McpTool tool : tools.values()) {
            Map<String, Object> toolInfo = new HashMap<>();
            toolInfo.put("name", tool.getName());
            toolInfo.put("description", tool.getDescription());
            toolInfo.put("inputSchema", tool.getInputSchema());
            toolList.add(toolInfo);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("tools", toolList);

        return createSuccessResponse(null, result);
    }

    /**
     * 处理工具调用请求
     */
    private String handleCallTool(JsonNode request) throws JsonProcessingException {
        String toolName = request.get("params").get("name").asText();
        Map<String, Object> arguments = objectMapper.convertValue(
            request.get("params").get("arguments"),
            Map.class
        );

        log.info("Calling tool: {} with arguments: {}", toolName, arguments);

        Map<String, Object> result = executeTool(toolName, arguments);
        return createSuccessResponse(request, result);
    }

    /**
     * 执行工具逻辑
     */
    private Map<String, Object> executeTool(String toolName, Map<String, Object> arguments) {
        return switch (toolName) {
            case "query_user" -> executeQueryUser(arguments);
            case "analyze_data" -> executeAnalyzeData(arguments);
            case "read_file" -> executeReadFile(arguments);
            default -> Map.of("error", "Unknown tool: " + toolName);
        };
    }

    /**
     * 查询用户工具实现
     */
    private Map<String, Object> executeQueryUser(Map<String, Object> arguments) {
        String userId = (String) arguments.get("userId");

        // 模拟数据库查询
        Map<String, Object> user = new HashMap<>();
        user.put("id", userId);
        user.put("name", "张三");
        user.put("email", "zhangsan@example.com");
        user.put("role", "管理员");
        user.put("status", "活跃");
        user.put("createdAt", "2024-01-15T10:30:00Z");

        return Map.of(
            "success", true,
            "data", user
        );
    }

    /**
     * 数据分析工具实现
     */
    private Map<String, Object> executeAnalyzeData(Map<String, Object> arguments) {
        String dataType = (String) arguments.get("dataType");

        // 模拟数据分析
        Map<String, Object> analysis = new HashMap<>();
        analysis.put("dataType", dataType);
        analysis.put("totalRecords", 15420);
        analysis.put("growth", "+23.5%");
        analysis.put("trend", "上升");

        // 详细数据
        List<Map<String, Object>> details = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            Map<String, Object> dayData = new HashMap<>();
            dayData.put("date", "2024-01-" + (15 + i));
            dayData.put("value", 1000 + (int)(Math.random() * 500));
            details.add(dayData);
        }
        analysis.put("details", details);

        return Map.of(
            "success", true,
            "analysis", analysis
        );
    }

    /**
     * 读取文件工具实现
     */
    private Map<String, Object> executeReadFile(Map<String, Object> arguments) {
        String filePath = (String) arguments.get("filePath");

        // 模拟文件读取
        return Map.of(
            "success", true,
            "content", "文件内容示例:\n这是从 " + filePath + " 读取的内容。\n" +
                      "在实际应用中,这里应该返回真实的文件内容。",
            "lineCount", 2
        );
    }

    /**
     * 处理资源列表请求
     */
    private String handleListResources() {
        List<Map<String, Object>> resourceList = new ArrayList<>();
        for (McpResource resource : resources.values()) {
            Map<String, Object> resourceInfo = new HashMap<>();
            resourceInfo.put("uri", resource.getUri());
            resourceInfo.put("name", resource.getName());
            resourceInfo.put("description", resource.getDescription());
            resourceInfo.put("mimeType", resource.getMimeType());
            resourceList.add(resourceInfo);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("resources", resourceList);

        return createSuccessResponse(null, result);
    }

    /**
     * 处理资源读取请求
     */
    private String handleReadResource(JsonNode request) {
        String uri = request.get("params").get("uri").asText();

        McpResource resource = resources.get(uri);
        if (resource == null) {
            return createErrorResponse(-32602, "Resource not found: " + uri);
        }

        // 模拟资源内容
        String content = switch (uri) {
            case "user://list" -> """
                {
                  "users": [
                    {"id": "1", "name": "张三", "email": "zhangsan@example.com"},
                    {"id": "2", "name": "李四", "email": "lisi@example.com"},
                    {"id": "3", "name": "王五", "email": "wangwu@example.com"}
                  ],
                  "total": 3
                }
                """;
            case "config://system" -> """
                {
                  "appName": "MCP Demo",
                  "version": "1.0.0",
                  "environment": "production",
                  "features": ["mcp", "ai", "analytics"]
                }
                """;
            default -> "{}";
        };

        Map<String, Object> result = new HashMap<>();
        result.put("contents", List.of(Map.of(
            "uri", uri,
            "mimeType", resource.getMimeType(),
            "text", content
        )));

        return createSuccessResponse(request, result);
    }

    /**
     * 创建成功响应
     */
    private String createSuccessResponse(JsonNode request, Map<String, Object> result) {
        Map<String, Object> response = new HashMap<>();
        response.put("jsonrpc", "2.0");

        if (request != null && request.has("id")) {
            response.put("id", request.get("id"));
        }

        response.put("result", result);

        try {
            return objectMapper.writeValueAsString(response);
        } catch (JsonProcessingException e) {
            return "{"error":"Failed to serialize response"}";
        }
    }

    /**
     * 创建错误响应
     */
    private String createErrorResponse(int code, String message) {
        Map<String, Object> response = new HashMap<>();
        response.put("jsonrpc", "2.0");
        response.put("error", Map.of(
            "code", code,
            "message", message
        ));
        response.put("id", null);

        try {
            return objectMapper.writeValueAsString(response);
        } catch (JsonProcessingException e) {
            return "{"error":"Failed to serialize error"}";
        }
    }
}

3.3 MCP数据模型

复制代码
package com.example.mcp.server;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Map;

/**
 * MCP工具定义
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class McpTool {
    private String name;
    private String description;
    private Map<String, Object> inputSchema;
}

/**
 * MCP资源定义
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class McpResource {
    private String uri;
    private String name;
    private String description;
    private String mimeType;
}

3.4 MCP服务端HTTP接口

复制代码
package com.example.mcp.controller;

import com.example.mcp.server.McpServer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * MCP服务端HTTP接口
 */
@Slf4j
@RestController
@RequestMapping("/mcp")
@RequiredArgsConstructor
public class McpController {

    private final McpServer mcpServer;

    /**
     * 处理MCP请求
     */
    @PostMapping
    public String handleMcpRequest(@RequestBody String request) {
        log.info("Received MCP request: {}", request);
        String response = mcpServer.handleRequest(request);
        log.info("Sending MCP response: {}", response);
        return response;
    }

    /**
     * 健康检查
     */
    @GetMapping("/health")
    public Map<String, String> health() {
        return Map.of("status", "healthy", "service", "mcp-server");
    }
}

四、MCP客户端实现

4.1 MCP客户端架构

MCP客户端负责与MCP服务端通信,并向应用层提供简洁的API。

4.2 实现MCP客户端

复制代码
package com.example.mcp.client;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.*;

/**
 * MCP客户端实现
 */
@Slf4j
@Component
public class McpClient {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RestTemplate restTemplate = new RestTemplate();
    private final String serverUrl;
    private long requestId = 0;

    public McpClient(String serverUrl) {
        this.serverUrl = serverUrl;
    }

    /**
     * 初始化连接
     */
    public void initialize() {
        Map<String, Object> params = new HashMap<>();
        params.put("protocolVersion", "2024-11-05");
        params.put("capabilities", Map.of(
            "roots", Map.of("listChanged", true),
            "sampling", Map.of()
        ));

        JsonNode response = sendRequest("initialize", params);
        log.info("MCP client initialized: {}", response);
    }

    /**
     * 获取可用工具列表
     */
    public List<McpClientTool> listTools() {
        JsonNode response = sendRequest("tools/list", null);
        JsonNode toolsArray = response.get("result").get("tools");

        List<McpClientTool> tools = new ArrayList<>();
        for (JsonNode toolNode : toolsArray) {
            McpClientTool tool = new McpClientTool(
                toolNode.get("name").asText(),
                toolNode.get("description").asText(),
                toolNode.get("inputSchema").toString()
            );
            tools.add(tool);
        }

        log.info("Retrieved {} tools from MCP server", tools.size());
        return tools;
    }

    /**
     * 调用工具
     */
    public Map<String, Object> callTool(String toolName, Map<String, Object> arguments) {
        Map<String, Object> params = new HashMap<>();
        params.put("name", toolName);
        params.put("arguments", arguments);

        JsonNode response = sendRequest("tools/call", params);
        JsonNode resultNode = response.get("result");

        return objectMapper.convertValue(resultNode, Map.class);
    }

    /**
     * 获取可用资源列表
     */
    public List<McpClientResource> listResources() {
        JsonNode response = sendRequest("resources/list", null);
        JsonNode resourcesArray = response.get("result").get("resources");

        List<McpClientResource> resources = new ArrayList<>();
        for (JsonNode resourceNode : resourcesArray) {
            McpClientResource resource = new McpClientResource(
                resourceNode.get("uri").asText(),
                resourceNode.get("name").asText(),
                resourceNode.get("description").asText(),
                resourceNode.get("mimeType").asText()
            );
            resources.add(resource);
        }

        log.info("Retrieved {} resources from MCP server", resources.size());
        return resources;
    }

    /**
     * 读取资源
     */
    public String readResource(String uri) {
        Map<String, Object> params = new HashMap<>();
        params.put("uri", uri);

        JsonNode response = sendRequest("resources/read", params);
        JsonNode contentsArray = response.get("result").get("contents");

        if (contentsArray != null && contentsArray.size() > 0) {
            return contentsArray.get(0).get("text").asText();
        }

        return null;
    }

    /**
     * 发送JSON-RPC请求
     */
    private JsonNode sendRequest(String method, Map<String, Object> params) {
        try {
            Map<String, Object> request = new HashMap<>();
            request.put("jsonrpc", "2.0");
            request.put("id", ++requestId);
            request.put("method", method);
            if (params != null) {
                request.put("params", params);
            }

            String requestJson = objectMapper.writeValueAsString(request);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<String> entity = new HttpEntity<>(requestJson, headers);

            String responseJson = restTemplate.postForObject(
                serverUrl, entity, String.class
            );

            log.debug("MCP request: {}", requestJson);
            log.debug("MCP response: {}", responseJson);

            return objectMapper.readTree(responseJson);
        } catch (Exception e) {
            log.error("Error sending MCP request", e);
            throw new RuntimeException("MCP request failed: " + e.getMessage(), e);
        }
    }

    /**
     * 客户端工具定义
     */
    public record McpClientTool(String name, String description, String inputSchema) {}

    /**
     * 客户端资源定义
     */
    public record McpClientResource(String uri, String name, String description, String mimeType) {}
}

4.3 MCP客户端配置

复制代码
package com.example.mcp.config;

import com.example.mcp.client.McpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MCP客户端配置
 */
@Configuration
public class McpClientConfig {

    @Value("${mcp.server.url:http://localhost:8080/mcp}")
    private String serverUrl;

    @Bean
    public McpClient mcpClient() {
        McpClient client = new McpClient(serverUrl);
        client.initialize();
        return client;
    }
}

五、Spring AI集成

5.1 创建MCP函数调用

复制代码
package com.example.mcp.ai;

import com.example.mcp.client.McpClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.function.Function;

/**
 * MCP工具函数,供Spring AI调用
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class McpFunction {

    private final McpClient mcpClient;
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 查询用户信息
     */
    @Description("根据用户ID查询用户详细信息,包括姓名、邮箱、角色等")
    public Function<String, String> queryUser() {
        return userId -> {
            log.info("AI调用queryUser工具,参数: {}", userId);

            Map<String, Object> arguments = Map.of("userId", userId);
            Map<String, Object> result = mcpClient.callTool("query_user", arguments);

            return formatResult("查询用户", result);
        };
    }

    /**
     * 分析数据
     */
    @Description("分析业务数据并生成分析报告,支持销售、流量、用户等数据类型")
    public Function<String, String> analyzeData() {
        return dataTypeJson -> {
            log.info("AI调用analyzeData工具,参数: {}", dataTypeJson);

            try {
                @SuppressWarnings("unchecked")
                Map<String, Object> arguments = objectMapper.readValue(dataTypeJson, Map.class);
                Map<String, Object> result = mcpClient.callTool("analyze_data", arguments);

                return formatResult("数据分析", result);
            } catch (Exception e) {
                log.error("数据分析失败", e);
                return "数据分析失败: " + e.getMessage();
            }
        };
    }

    /**
     * 读取文件
     */
    @Description("读取指定路径的文件内容")
    public Function<String, String> readFile() {
        return filePath -> {
            log.info("AI调用readFile工具,参数: {}", filePath);

            Map<String, Object> arguments = Map.of("filePath", filePath);
            Map<String, Object> result = mcpClient.callTool("read_file", arguments);

            return formatResult("读取文件", result);
        };
    }

    /**
     * 获取系统资源
     */
    @Description("获取系统配置和资源信息")
    public Function<String, String> getSystemResource() {
        return uri -> {
            log.info("AI调用getSystemResource工具,参数: {}", uri);

            String content = mcpClient.readResource(uri);

            if (content != null) {
                return String.format("资源 %s 的内容:\n%s", uri, content);
            }

            return "未找到资源: " + uri;
        };
    }

    /**
     * 格式化结果
     */
    private String formatResult(String operation, Map<String, Object> result) {
        if (Boolean.TRUE.equals(result.get("success"))) {
            return String.format("%s成功:\n%s", operation,
                result.get("data") != null ? result.get("data") : result.get("content"));
        } else {
            return String.format("%s失败: %s", operation, result.get("error"));
        }
    }
}

5.2 AI服务实现

复制代码
package com.example.mcp.service;

import com.example.mcp.ai.McpFunction;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.stereotype.Service;

/**
 * AI对话服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiChatService {

    private final ChatClient.Builder chatClientBuilder;
    private final McpFunction mcpFunction;
    private final ChatMemory chatMemory = new InMemoryChatMemory();

    /**
     * 处理用户消息
     */
    public String chat(String userId, String message) {
        log.info("用户 {} 发送消息: {}", userId, message);

        // 创建带记忆和工具调用的ChatClient
        ChatClient chatClient = chatClientBuilder
            .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
            .defaultFunctions(
                mcpFunction.queryUser(),
                mcpFunction.analyzeData(),
                mcpFunction.readFile(),
                mcpFunction.getSystemResource()
            )
            .build();

        String response = chatClient.prompt()
            .user(message)
            .call()
            .content();

        log.info("AI回复: {}", response);
        return response;
    }

    /**
     * 清除对话历史
     */
    public void clearHistory(String userId) {
        chatMemory.clear(userId);
        log.info("已清除用户 {} 的对话历史", userId);
    }
}

5.3 ChatClient配置

复制代码
package com.example.mcp.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Configuration;

/**
 * ChatClient配置
 */
@Configuration
public class ChatClientConfig {

    public ChatClient.Builder chatClientBuilder(OpenAiChatModel model) {
        return ChatClient.builder(model);
    }
}

六、生产环境实战案例

6.1 智能客服系统

下面是一个完整的智能客服系统实现,展示如何使用Spring AI + MCP构建实际应用。

6.2 客服控制器

复制代码
package com.example.mcp.controller;

import com.example.mcp.service.AiChatService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * 智能客服控制器
 */
@Slf4j
@RestController
@RequestMapping("/api/chat")
@RequiredArgsConstructor
public class ChatController {

    private final AiChatService aiChatService;

    /**
     * 发送消息
     */
    @PostMapping("/send")
    public Map<String, Object> sendMessage(@RequestBody Map<String, String> request) {
        String userId = request.get("userId");
        String message = request.get("message");

        if (userId == null || message == null) {
            return Map.of("success", false, "error", "userId和message不能为空");
        }

        try {
            String response = aiChatService.chat(userId, message);
            return Map.of(
                "success", true,
                "response", response,
                "timestamp", System.currentTimeMillis()
            );
        } catch (Exception e) {
            log.error("处理消息失败", e);
            return Map.of("success", false, "error", e.getMessage());
        }
    }

    /**
     * 清除历史
     */
    @PostMapping("/clear")
    public Map<String, Object> clearHistory(@RequestBody Map<String, String> request) {
        String userId = request.get("userId");
        aiChatService.clearHistory(userId);
        return Map.of("success", true, "message", "对话历史已清除");
    }
}

七、MCP通信流程

7.1 完整的交互流程

7.2 消息格式示例

复制代码
// 客户端请求示例
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "query_user",
    "arguments": {
      "userId": "12345"
    }
  }
}

// 服务端响应示例
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "success": true,
    "data": {
      "id": "12345",
      "name": "张三",
      "email": "zhangsan@example.com",
      "role": "管理员"
    }
  }
}

八、测试与调试

8.1 单元测试

复制代码
package com.example.mcp.server;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

/**
 * MCP服务端测试
 */
class McpServerTest {

    private McpServer mcpServer;

    @BeforeEach
    void setUp() {
        mcpServer = new McpServer();
    }

    @Test
    void testInitialize() {
        String request = "{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}";
        String response = mcpServer.handleRequest(request);

        assertNotNull(response);
        assertTrue(response.contains(""serverInfo""));
        assertTrue(response.contains("demo-mcp-server"));
    }

    @Test
    void testListTools() {
        String request = "{"jsonrpc":"2.0","id":2,"method":"tools/list"}";
        String response = mcpServer.handleRequest(request);

        assertNotNull(response);
        assertTrue(response.contains("query_user"));
        assertTrue(response.contains("analyze_data"));
        assertTrue(response.contains("read_file"));
    }

    @Test
    void testCallTool() {
        String request = "{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{" +
                         ""name":"query_user"," +
                         ""arguments":{"userId":"123"}" +
                         "}}";
        String response = mcpServer.handleRequest(request);

        assertNotNull(response);
        assertTrue(response.contains(""success":true"));
        assertTrue(response.contains("张三"));
    }

    @Test
    void testListResources() {
        String request = "{"jsonrpc":"2.0","id":4,"method":"resources/list"}";
        String response = mcpServer.handleRequest(request);

        assertNotNull(response);
        assertTrue(response.contains("user://list"));
        assertTrue(response.contains("config://system"));
    }

    @Test
    void testReadResource() {
        String request = "{"jsonrpc":"2.0","id":5,"method":"resources/read"," +
                         ""params":{"uri":"user://list"}}";
        String response = mcpServer.handleRequest(request);

        assertNotNull(response);
        assertTrue(response.contains("users"));
    }
}

8.2 客户端测试

复制代码
package com.example.mcp.client;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockserver.model.JsonBody.json;

/**
 * MCP客户端测试
 */
class McpClientTest {

    private ClientAndServer mockServer;
    private McpClient mcpClient;

    @BeforeEach
    void setUp() {
        mockServer = ClientAndServer.startLocal(1080);
        mcpClient = new McpClient("http://localhost:1080/mcp");
    }

    @Test
    void testListTools() {
        // 模拟服务端响应
        mockServer.when(HttpRequest.request()
            .withMethod("POST")
            .withPath("/mcp"))
            .respond(HttpResponse.response()
                .withBody("{"jsonrpc":"2.0","id":1," +
                          ""result":{"tools":[{" +
                          ""name":"test_tool"," +
                          ""description":"测试工具"," +
                          ""inputSchema":{"type":"object"}" +
                          "]}}"));

        var tools = mcpClient.listTools();

        assertNotNull(tools);
        assertEquals(1, tools.size());
        assertEquals("test_tool", tools.get(0).name());
    }

    @Test
    void testCallTool() {
        mockServer.when(HttpRequest.request()
            .withMethod("POST")
            .withPath("/mcp"))
            .respond(HttpResponse.response()
                .withBody("{"jsonrpc":"2.0","id":2," +
                          ""result":{"success":true,"data":{"result":"test"}}}"));

        var result = mcpClient.callTool("test_tool", Map.of("param", "value"));

        assertNotNull(result);
        assertTrue((Boolean) result.get("success"));
    }

    @Test
    void testReadResource() {
        mockServer.when(HttpRequest.request()
            .withMethod("POST")
            .withPath("/mcp"))
            .respond(HttpResponse.response()
                .withBody("{"jsonrpc":"2.0","id":3," +
                          ""result":{"contents":[{" +
                          ""uri":"test://resource"," +
                          ""text":"resource content"}]}}"));

        String content = mcpClient.readResource("test://resource");

        assertNotNull(content);
        assertEquals("resource content", content);
    }
}

九、部署架构

9.1

9.2 Docker部署

复制代码
# Dockerfile
FROM openjdk:17-slim

WORKDIR /app

COPY target/mcp-demo-1.0.0.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

# docker-compose.yml
version: '3.8'
services:
  mcp-demo:
    build: .
    ports:
      - "8080:8080"
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - SPRING_PROFILES_ACTIVE=production
    restart: unless-stopped

十、总结

Spring AI + MCP为构建AI应用提供了强大的基础设施。随着AI技术的不断发展,MCP协议将成为连接AI应用和外部服务的重要标准。建议持续关注MCP生态的发展,积极探索更多的应用场景。

相关推荐
薛定谔的猫19822 小时前
四、基于LangChain与HuggingFace Pipeline的本地大语言模型对话系统搭建指南(使用阿里千问-模型)
人工智能·语言模型·langchain
ZCXZ12385296a2 小时前
【无标题】
人工智能·计算机视觉·目标跟踪
赛卓电子Semiment2 小时前
汽车油门踏板 | 国产应用方案
人工智能
callJJ2 小时前
Docker 代码沙箱与容器池技术详解
java·运维·docker·容器·oj系统·代码沙箱
wangmengxxw2 小时前
SpringAI-mcp-入门案例
java·服务器·前端·大模型·springai·mcp
燕山石头2 小时前
java模拟Modbus-tcp从站
java·开发语言·tcp/ip
觉醒大王2 小时前
简单说说参考文献引用
java·前端·数据库·学习·自然语言处理·学习方法·迁移学习
wangmengxxw2 小时前
SpringAI-MySQLMcp服务
java·人工智能·mysql·大模型·sse·springai·mcp
weixin_449290012 小时前
EverMemOS 访问外部(deepinfra)API接口
java·服务器·前端