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)开发环境搭建的第一篇,搭建完毕,大家也大致了解几个组件之间的调用顺序和配合关系了吧,下期见

相关推荐
冷崖几秒前
Redis事务与驱动的学习(一)
数据库·redis·学习
雨果talk5 分钟前
【一文看懂多模块Bean初始化难题】Spring Boot多模块项目中的Bean初始化难题:包名不一致的优雅解决方案
java·spring boot·后端·spring·springboot
李元豪11 分钟前
【行云流水AI笔记】根据上面泳道图,请问如果加入强化学习,在哪些模块添加比较好,返回添加后的泳道图。
人工智能·笔记
2501_9153743521 分钟前
LangChain开发智能问答(RAG)系统实战教程:从零构建知识驱动型AI助手
人工智能·langchain
AI设计小站34 分钟前
AI 工具打造专业级 PPT 配图:从文字到视觉的高效转化指南
人工智能·信息可视化·powerpoint
羊小猪~~41 分钟前
数据库学习笔记(十六)--控住流程与游标
数据库·笔记·学习
stein_java1 小时前
springMVC-13 文件下载及上传
java·spring
新知图书1 小时前
OpenCV图像金字塔
人工智能·opencv·计算机视觉
Eric.Lee20211 小时前
数据集-目标检测系列- 狮子 数据集 lion >> DataBall
人工智能·目标检测·目标跟踪
yanmengying1 小时前
目标检测yolo算法
人工智能·yolo·目标检测