SpringAI构建MCP服务器
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服务器。
项目初始化
-
初始化一个SpringBoot应用。使用IDE创建一个标准的Maven项目,创建Maven项目的步骤此处不进行展开,有需要的可自行搜索相关教程。
-
引入依赖。在
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模式的MCPmysql-connector-j
是MySQL的驱动spring-boot-configuration-processor
用于配置处理hutool-all
是一个工具类库
- JDK版本务必使用
实现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(); } }
-
最终目录如下:
测试与应用
我们使用CherryStudio
来作为MCP的客户端,配置如下:
成功调用如下:
准确无误!AI模型可以充分理解需求并根据需求自行调用相关工具获取结果。