何鑫个人博客

SpringAI构建MCP服务器

  • 2025-05-28 15:57:17
  • 技术
  • 62

MCP简介

chatgpt的横空出世标志人工智能技术发展迈向一个新的台阶,但是早期的应用更多聚焦于问答,缺乏实际应用,解决知的问题,下一步就要解决行的问题——使AI技术更好的应用于具体场景,实现AI的自主思考和任务执行,于是各种立足于函数调用的Agent开始出现,但是一个领域的发展需要一套统一的标准来指导以避免生态分裂,于是AI领域的领头羊之一,开发出claude模型的Anthropic公司于2024年末发布了模型上下文协议(Model Context Protocol),简称MCP,MCP定义了一套AI模型与外部数据和工具交互的协议,使得AI模型可以自主获取知识并调用相关工具完成相应任务,包括OpenAI在内的各大AI模型厂商也纷纷适配支持该协议,统一的标准使得相关生态发展迅速,各类MCP服务器井喷式出现,国内大厂也纷纷下场,淘宝,高德等公司推出各自的MCP服务器,可以预料,MCP前景无限,成为连接AI与现实世界应用的一道重要桥梁。

使用SpringAI实现操作MySQL的MCP服务器

AI技术的发展催生了新的开发需求,老牌Web开发框架Spring也增加了对AI模型应用开发的支持——SpringAI项目。SpringAI项目中也增加了对MCP应用的支持,下面我将用一个简单的支持MySQL操作的MCP服务器来展示如何使用SpringAI构建MCP服务器。

项目初始化

  1. 初始化一个SpringBoot应用。使用IDE创建一个标准的Maven项目,创建Maven项目的步骤此处不进行展开,有需要的可自行搜索相关教程。

  2. 引入依赖。在pom.xml文件中引入需要的依赖,pom文件参考如下:

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.ihexin</groupId>
        <artifactId>mcp-server</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>mcp-server</name>
        <description>mcp-server</description>
        <properties>
            <java.version>17</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <spring-boot.version>3.3.6</spring-boot.version>
            <spring-ai.version>1.0.0</spring-ai.version>
            <hutool.version>6.0.0-M18</hutool.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <scope>runtime</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.dromara.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
    
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <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.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>17</source>
                        <target>17</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <configuration>
                        <mainClass>com.ihexin.mcp.McpServerApplication</mainClass>
                        <skip>true</skip>
                    </configuration>
                    <executions>
                        <execution>
                            <id>repackage</id>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    
    • JDK版本务必使用17版本,低版本不受支持(SpringBoot3.0+支持的最低版本为JDK17)
    • 此处SpringBoot使用3.3.6版本,如果使用其他版本请自行验证兼容性
    • MCP一般使用两种模式实现MCP客户端与服务端的交互,分别是标准输入 / 输出(stdio)和服务器发送事件(SSE)
    • 依赖说明
      • spring-ai-starter-mcp-server-webmvc的引入是为了支持SSE模式的MCP
      • mysql-connector-j是MySQL的驱动
      • spring-boot-configuration-processor用于配置处理
      • hutool-all是一个工具类库

实现MCP工具

  • 配置文件

    # Using spring-ai-starter-mcp-server-webmvc
    spring:
      ai:
        mcp:
          server:
            name: webmvc-mcp-server
            version: 1.0.0
            type: SYNC
            sse-message-endpoint: /mcp/messages
    
    database:
      url: jdbc:mysql://localhost:3306
      username: root
      password: root
    
    • sse-message-endpoint定义请求端点

    • database定义MySQL的连接地址,用户名和密码相关信息

      • 增加对应属性类

        package com.ihexin.mcp.config;
        
        import org.springframework.boot.context.properties.ConfigurationProperties;
        
        /**
         * @author hexin
         * @version 1.0
         */
        @ConfigurationProperties(prefix = "database")
        public record DatabaseProperties(String url, String username, String password) {}
        
  • 定义常量

    package com.ihexin.mcp.constant;
    
    /**
     * @author hexin
     * @version 1.0
     */
    public class DatabaseConstants {
    
        /**
         * SHOW DATABASES SQL
         */
        public static final String SHOW_DATABASES_SQL = "SHOW DATABASES";
    
        /**
         * SHOW TABLES SQL
         */
        public static final String SHOW_TABLES_SQL = "SHOW TABLES FROM ";
    
        /**
         * SHOW CREATE TABLE SQL
         */
        public static final String SHOW_CREATE_TABLE_SQL = "SHOW CREATE TABLE ";
    
        /**
         * USE
         */
        public static final String USE = "USE ";
    
    
  • 定义枚举

    package com.ihexin.mcp.enums;
    
    import java.util.Optional;
    
    /**
     * @author hexin
     * @version 1.0
     */
    public enum DatabaseOperationTypeEnum {
    
        READ(0),
        WRITE(1),
        ;
        private final Integer code;
    
        DatabaseOperationTypeEnum(Integer code) {
            this.code = code;
        }
    
        public Integer getCode() {
            return code;
        }
    
        // getEnumByCode
        public static Optional<DatabaseOperationTypeEnum> getEnumByCode(Integer code) {
            for (DatabaseOperationTypeEnum value : values()) {
                if (value.getCode().equals(code)) {
                    return Optional.of(value);
                }
            }
            return Optional.empty();
        }
    
    }
    
  • 实现工具

    package com.ihexin.mcp.service;
    
    import com.ihexin.mcp.config.DatabaseProperties;
    import com.ihexin.mcp.constant.DatabaseConstants;
    import com.ihexin.mcp.enums.DatabaseOperationTypeEnum;
    import jakarta.annotation.PreDestroy;
    import org.springframework.ai.tool.annotation.Tool;
    import org.springframework.stereotype.Service;
    
    import java.sql.*;
    import java.util.Objects;
    import java.util.Optional;
    
    /**
     * @author hexin
     * @version 1.0
     */
    @Service
    public class DatabaseService {
    
        /**
         * 数据库连接
         */
        private static Connection connection;
    
        public DatabaseService(DatabaseProperties databaseProperties) throws SQLException {
            connection = DriverManager.getConnection(databaseProperties.url(),
                    databaseProperties.username(), databaseProperties.password());
        }
    
        @PreDestroy
        public void cleanup() {
            if (Objects.nonNull(connection)) {
                try {
                    connection.close();
                    System.out.println("JDBC连接已关闭");
                } catch (SQLException e) {
                    System.err.println("关闭JDBC连接时出错: " + e.getMessage());
                }
            }
        }
    
        /**
         * 获取数据库列表
         */
        @Tool(description = "数据库工具-获取数据库列表")
        public String getDatabaseList() throws SQLException {
            Statement stmt = connection.createStatement();
            ResultSet rs = stmt.executeQuery(DatabaseConstants.SHOW_DATABASES_SQL);
            StringBuilder sb = new StringBuilder();
            while (rs.next()) {
                sb.append(rs.getString(1)).append(",");
            }
            if (!sb.isEmpty()) {
                sb.deleteCharAt(sb.length() - 1);
            }
            return sb.toString();
        }
    
        /**
         * 获取数据库表列表
         */
        @Tool(description = "数据库工具-根据数据库名称获取表列表")
        public String getTableList(String database) throws SQLException {
            Statement stmt = connection.createStatement();
            ResultSet rs = stmt.executeQuery(DatabaseConstants.SHOW_TABLES_SQL + database);
            StringBuilder sb = new StringBuilder();
            while (rs.next()) {
                sb.append(rs.getString(1)).append(",");
            }
            if (!sb.isEmpty()) {
                sb.deleteCharAt(sb.length() - 1);
            }
            return sb.toString();
        }
    
        /**
         * 获取数据库表DDL
         */
        @Tool(description = "数据库工具-根据数据库名称和表名称获取表的DDL")
        public String getTableDDL(String database, String table) throws SQLException {
            Statement stmt = connection.createStatement();
            ResultSet rs = stmt.executeQuery(DatabaseConstants.SHOW_CREATE_TABLE_SQL + database + "." + table);
            if (rs.next()) {
                return rs.getString(2);
            }
            return "";
        }
    
        /**
         * 执行SQL
         */
        @Tool(description = "数据库工具-根据类型(读-0 写-1)和数据库名称(不是表名称)以及具体语句来执行SQL")
        public String executeSQL(Integer type, String database, String sql) throws SQLException {
            Statement stmt = connection.createStatement();
            stmt.execute(DatabaseConstants.USE + database);
            Optional<DatabaseOperationTypeEnum> databaseOperationTypeEnumOpt = DatabaseOperationTypeEnum.getEnumByCode(type);
            if (databaseOperationTypeEnumOpt.isEmpty()) {
                return "Unsupported operation";
            }
            DatabaseOperationTypeEnum databaseOperationTypeEnum = databaseOperationTypeEnumOpt.get();
            switch (databaseOperationTypeEnum) {
                case WRITE: {
                    Integer i = stmt.executeUpdate(sql);
                    if (i == 0) {
                        return "ok";
                    } else {
                        return String.format("Affected rows: %d", i);
                    }
                }
                case READ: {
                    ResultSet rs = stmt.executeQuery(sql);
                    StringBuilder sb = new StringBuilder();
                    while (rs.next()) {
                        for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
                            sb.append(rs.getString(i)).append(",");
                        }
                        sb.deleteCharAt(sb.length() - 1);
                        sb.append("\n");
                    }
                    return sb.toString();
                }
                default:
                    return "Unsupported operation";
            }
        }
    
    }
    
    
    • 使用@Tool来定义工具,description需要描述准确以便于AI模型决定是否调用及如何调用
  • 配置工具

    package com.ihexin.mcp.config;
    
    import com.ihexin.mcp.service.DatabaseService;
    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;
    
    /**
     * @author hexin
     * @version 1.0
     */
    @Configuration
    public class ToolConfig {
    
        @Bean
        public ToolCallbackProvider tools(DatabaseService databaseService) {
            return MethodToolCallbackProvider.builder().toolObjects(databaseService).build();
        }
    
    }
    
    
  • 最终目录如下:

    image20250528152358161.png

测试与应用

我们使用CherryStudio来作为MCP的客户端,配置如下:

image20250528152704979.png

成功调用如下:

image20250528153759963.png

image20250528153957031.png

准确无误!AI模型可以充分理解需求并根据需求自行调用相关工具获取结果。