5、Spring AI(MCPServer+MCPClient+Ollama)开发环境搭建_第一篇

前言:

该开发环境是在 3、后端持久化(SpringBoot3.5.0+MybatisPlus3.5.5+mysql8.4.0)环境搭建

上进行改造的,用到了后端持久化,主要改造的地方为数据库把email字段改为height(身高),该开发环境主要是设计了一个灌篮高手篮球经理对球队成员简单的查询,通过这个场景把MCPServer、MCPClient、大模型、用户客户端相互的职责和关系简单捋一下,其他的改造和实现一边贴代码一边解释

1、数据库表结构

数据库表结构以及数据初始化、user_info表

sql 复制代码
ET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user_info
-- ----------------------------
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ID主键',
  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户名',
  `sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '性别',
  `hobby` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '爱好',
  `special_skill` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '特长',
  `score` int(0) NULL DEFAULT NULL COMMENT '评分',
  `height` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '身高',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_info
-- ----------------------------
INSERT INTO `user_info` VALUES ('3830a6dcd94e60e41e491a8701e10a48', '流川枫', '男', '睡觉', '大前锋', 98, '187cm');
INSERT INTO `user_info` VALUES ('463097bd7d06c7fbf709c40207c356c3', '牧绅一', '男', '未知', '后卫', 98, '184cm');
INSERT INTO `user_info` VALUES ('552cef7ea26dcb134623891417527cff', '樱木花道', '男', '赤木晴子', '大前锋', 97, '188cm');
INSERT INTO `user_info` VALUES ('6ebee37985f6cdf0ff6e7520c5441616', '仙道彰', '男', '钓鱼', '小前锋', 98, '190cm');
INSERT INTO `user_info` VALUES ('716bd21e54e5964058e4b9ad78cbaca3', '宫城良田', '男', '井上彩子', '后卫', 96, '168cm');
INSERT INTO `user_info` VALUES ('821a92f51a87be2d5d052e10a7223e70', '三井寿', '男', '三分球', '小前锋', 98, '184cm');
INSERT INTO `user_info` VALUES ('9f5553b0b67821bae5b5869fc8ff3a69', '赤木晴子', '女', '篮球迷', '啦啦队', 60, '156cm');
INSERT INTO `user_info` VALUES ('af2ab75e939b5cff8a95569b306be911', '井上彩子', '女', '宫城良田', '篮球经理', 70, '163cm');
INSERT INTO `user_info` VALUES ('d3f967929558b8c834396dd8aaa24e24', '泽北荣治', '男', '打篮球', '小前锋', 98, '186cm');
INSERT INTO `user_info` VALUES ('e6bf09db5717d9c60af41c9dde65f0d6', '赤木刚宪', '男', '吃香蕉', '中锋', 97, '199cm');

SET FOREIGN_KEY_CHECKS = 1;

该表中存储了一些球员的基本信息,以便篮球经理查看

2、MCPServer搭建

(1)pom.xml文件

该文件在之前 3、后端持久化(SpringBoot3.5.0+MybatisPlus3.5.5+mysql8.4.0)环境搭建

的基础上改动不多,整体如下:

XML 复制代码
<?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>

    <groupId>com.everything.autotest</groupId>
    <artifactId>spring_ai_project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <java.version>17</java.version>
        <spring-boot.version>3.5.0</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
    </parent>

    <dependencies>
        <!-- mcp-server-webmvc -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- Web 模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- MyBatisPlus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <!-- Selenium WebDriver -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.31.0</version>
        </dependency>
        <!-- WebDriver Manager -->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>6.1.0</version>
        </dependency>
        <!-- SLF4J API -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.9</version>
        </dependency>
        <!-- Log4j2核心实现包 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.24.3</version>
        </dependency>
        <!-- SLF4J与Log4j2的桥接器 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.24.3</version>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-core</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>

只增加了一个MCPServer的必须文件,注意版本号,使用当前最新版本Spring AI 1.0.0,如下:

XML 复制代码
<!-- mcp-server-webmvc -->
<dependency>
     <groupId>org.springframework.ai</groupId>
     <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
     <version>1.0.0</version>
</dependency>

(2)application.yml文件

文件加入了Spring AI所需要的最简参数,整体application.yml如下:

Lua 复制代码
server:
  port: 9091
spring:
  ai:
    mcp:
      server:
        name: slamdunk-mcp-server
        version: 1.0.0
        sse-endpoint: /sse/slamdunk-mcp-server
  datasource:
    url: jdbc:mysql://localhost:3306/everythingtest?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    username: root
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver
  debug: true
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  global-config:
    db-config:
      id-type: ASSIGN_UUID
      field-strategy: NOT_EMPTY
      db-type: MYSQL
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

MCPServer的yml文件配置唯一注意的一点就是sse-endpoint:/sse/slamdunk-mcp-server

sse-endpoint这个参数需要在代码里做一些特殊处理才能生效,处理完成后MCPClient调用时才不会报404错误

(3)java文件

UserInfoMCPServerFacade.java

java 复制代码
package com.everything.autotest.demo.mcpserverdemo;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.everything.autotest.demo.mybatisplus3demo.UserInfo;
import com.everything.autotest.demo.mybatisplus3demo.userinfo.UserInfoService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserInfoMCPServerFacade {

    @Autowired
    UserInfoService userInfoService;

    @Tool(name = "getInfo", description = "根据球员姓名获取某个球员的详细信息")
    public UserInfo getInfo(@ToolParam(description = "球员姓名") String userName) throws Exception {
        var userInfoQueryWrapper = new QueryWrapper<UserInfo>();
        userInfoQueryWrapper.like("user_name", userName);
        return userInfoService.getOne(userInfoQueryWrapper);
    }


    @Tool(name = "getList", description = "获取所有球员信息")
    public List<UserInfo> getList() {
        return userInfoService.list();
    }
}

做一个简单解释:

1、简单的可以理解为这个类相当于之前的Controller层,之前的Controller层的参数是从页面或者第三方的restful调用过来,现在改为从MCPClient客户端调用;调过来之后,后面处理的逻辑不变,依然是调用UserInfoService层来查询数据库和处理逻辑并返回到UserInfoMCPServerFacade

2、为什么这个类名后缀起成Facade,因为目前也没啥标准规范,个人理解是,如果起名成Controller感觉有点怪怪的,如果起名成Service又和对应持久化层的Service有点混淆,所以干脆起成门面(Facade),这样也有面子。

3、关于注解,及api使用方法会在后面更细节的分析,这篇先把运行环境搭起来

SlamDunkMCPRegisterConfiguration.java

java 复制代码
package com.everything.autotest.demo.mcpserverdemo;

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;


@Configuration
public class SlamDunkMCPRegisterConfiguration {

    @Bean
    public ToolCallbackProvider userInfoTools(UserInfoMCPServerFacade userInfoMCPServerFacade){
        return MethodToolCallbackProvider.builder()
                .toolObjects(userInfoMCPServerFacade)
                .build();
    }
}

把UserInfoMCPServerFacade搞进来

SlamDunkMCPServerConfig.java

java 复制代码
package com.everything.autotest.demo.mcpserverdemo;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;


@Configuration
public class SlamDunkMCPServerConfig {

    @Bean
    @Primary
    public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(
            ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) {
        ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
        return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getSseMessageEndpoint(),
                serverProperties.getSseEndpoint());
    }

    @Bean
    public RouterFunction<ServerResponse> mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) {
        return transportProvider.getRouterFunction();
    }

}

SlamDunkMCPServerConfig.java这个类完全就是为了让sse-endpoint这个参数生效,其实可以不配sse-endpoint这个参数也行,但是感觉配出来高大上一点;

通过以上配置MCPServer就搭建完毕了,运行起来,浏览器输入

http://localhost:9091/sse/slamdunk-mcp-server

如果界面是这样,说明mcpServer配置成功

MCPServer搭建成功

3、MCPClient搭建

MCPClient和MCPServer是两个不同的工程,最好不要写在一个工程里分两个Model,这样容易串端口

在这之前,首先参考 4、大模型本地运行环境搭建 把本地大模型搭起来

然后浏览器输入 http://localhost:11434/ 如果显示下图,则说明大模型运行成功

(1)pom.xml文件

文件必要依赖如下:

XML 复制代码
<!-- mcp-client -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<!-- 使用的大模型依赖 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
XML 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

以上配置我不确定能不能生效,因为对于Client配置我改了很多次,又没有重新install可能会有缓存,如果不生效或者有问题,请参考Server的配置,把版本号显示写

还是要注意:Spring AI的版本号一定要选1.0.0版本,当前最新版

(2)application.yml文件

Lua 复制代码
server:
  port: 9100
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: llama3.2
    mcp:
      client:
        enabled: true
        name: slamdunk-mcp-client
        version: 1.0.0
#        request-timeout: 30s
        type: sync
        sse:
          connections:
            server1:
              url: http://localhost:9091
              sse-endpoint: /sse/slamdunk-mcp-server
        toolcallback:
          enabled: true

这里的sse-endpoint: /sse/slamdunk-mcp-server与服务器保持一致(注意:一定要用1.0.0版,别用M版本)

(3)java文件

SlamDunkClientConfig.java

java 复制代码
package com.gempharmatech.mcpclient;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class SlamDunkClientConfig {

    @Autowired
    private OllamaChatModel ollamaChatModel;

    @Autowired
    private ToolCallbackProvider toolCallbackProvider;


    @Bean
    public ChatClient chatClient() {
        StringBuilder sb = new StringBuilder();
        sb.append("你是一个篮球经理,需要对篮球队内的一切事务所处判断和决策。");
        sb.append("根据球员姓名获取某个球员的详细信息。");
        sb.append("获取所有球员信息。");
        sb.append("最后,可以通过诙谐幽默的方式回答出问题,重点是诙谐幽默,可以允许你自由发挥");
        return ChatClient.builder(ollamaChatModel)
                .defaultSystem(sb.toString())
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .defaultToolCallbacks(toolCallbackProvider)
                .build();
    }
}
复制代码

SlamDunkMCPClintController.java

java 复制代码
package com.gempharmatech.mcpclient;


import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SlamDunkMCPClintController {

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private ChatClient chatClient;

    @PostMapping("/slamdunk/gogogo")
    public String getInfo(@RequestBody String jsonData) throws Exception {
        var node = objectMapper.readTree(jsonData);
        var message = node.get("message").asText();
        return chatClient.prompt().user(message).call().content();
    }
}

MCPClient搭建成功

4、整体测试各种场景

首先:说明一下UserInfoService中的两个查询方法

(1)数据库列表全查,调用MybatisPlus封装好的list()接口全查

(2)根据userName模糊查询

(1)postman中输入"把球员名字叫流川枫的信息诙谐幽默的展示一下"

感觉还行,这个大模型呆萌呆萌的

(2)postman中输入"查询分数排名前五的球员信息,每个人的信息一行展示"

从这个结果看,大模型虽然只返回给5条,但是并没有按照分数最高的5个人返回,这一点可以在后面优化,让大模型能更深入的理解返回正确结果

(3)postman中输入"查询性别是女性的球员信息,每个人的信息一行展示"

这个怎么说呢,大模型开起车来也是棒棒的,其实这个llama3.2大模型才2G,另外就是给大模型的指令可以更精细化,后面文章会讲到,当前能做到这样已经很不错了,主要是萌新,后期会换个厉害点的模型

以上就是Spring AI(MCPServer+MCPClient+Ollama)开发环境搭建的第一篇,搭建完毕,大家也大致了解几个组件之间的调用顺序和配合关系了吧,下期见

相关推荐
stbomei1 小时前
当 AI 开始 “理解” 情感:情感计算技术正在改写人机交互规则
人工智能·人机交互
星霜笔记2 小时前
Docker 部署 MariaDB+phpMyAdmin+Nextcloud 完整教程
运维·数据库·docker·容器·mariadb
_Kayo_5 小时前
node.js 学习笔记3 HTTP
笔记·学习
Moshow郑锴6 小时前
人工智能中的(特征选择)数据过滤方法和包裹方法
人工智能
TY-20256 小时前
【CV 目标检测】Fast RCNN模型①——与R-CNN区别
人工智能·目标检测·目标跟踪·cnn
liuhenghui52017 小时前
神经网络 常见分类
ai
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
CareyWYR7 小时前
苹果芯片Mac使用Docker部署MinerU api服务
人工智能
失散138 小时前
自然语言处理——02 文本预处理(下)
人工智能·自然语言处理
wyiyiyi8 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask