前言
近来,AI大模型的应用在各种场景下出尽了风头,那么不爱学习的java程序员,不是一个好程序员。这里我学习并简单使用了SpringBoot 和 MCP ,大模型使用通义千问。
MCP(Model Context Protocol,模型上下文协议)是一种用于大语言模型(LLM)与外部环境交互的标准化协议。它主要用于管理模型的上下文信息,使AI模型能够在多步骤、有状态的交互中保持上下文连贯。
相比传统API每次请求独立处理的方式,MCP通过统一的协议支持更高效、复杂的交互流程,提升了AI应用的智能性和实用性。
本文将以最基础的【学生查询功能】来搭配MCP 和 通义千问,实现智能对话功能。
另外,顺便标记了一个【创建学生】的功能,也可以使用自然语言来触发。
一、正文
1.1 项目结构
项目父模块是一个pom打包方式的模块,主要管理依赖和版本。另外配置了spring未发布的jar仓库地址。
核心代码在【sse-server】模块中。起初项目是想做成sse连接对话式的,后来做成了同步。所以这里的项目名也懒的改了。(SSE 的方式以后再说,后续如果再写一版,我会补充进来)
PS:这里以 manager 层作为 DAO 层,做数据访问,使用内存数据进行模拟数据库。
1.2 项目环境
项目采用 java 21 + springboot 3.4.2 版本来进行开发,没有前端页面。没有数据库,只提供一个对话接口。
内部整合了 通义千问。需要在阿里的申请页面进行申请,有免费的token。这个是申请页面:https://help.aliyun.com/zh/model-studio/get-api-key
1.3 完整代码
1.3.1 spring-mcp-demo的pom文件
<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>org.mcp</groupId>
<artifactId>spring-mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-mcp-demo</name>
<url>http://maven.apache.org</url>
<modules>
<module>sse-server</module>
</modules>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>maven2</id>
<name>maven2</name>
<url>https://repo1.maven.org/maven2/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<!-- MCP 服务器支持 - WebMVC版本 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M6.1</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
这里需要额外注意一点,如果下载包有问题,需要在 maven 的 setting 配置文件中调整镜像配置:
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
<!-- 表示除了spring-milestones、maven2其它都走阿里云镜像 -->
<mirrorOf>*,!spring-milestones,!maven2</mirrorOf>
</mirror>
1.3.2 sse-server 的pom文件
<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.mcp</groupId>
<artifactId>spring-mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>sse-server</artifactId>
<packaging>jar</packaging>
<name>sse-server</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<optional>true</optional>
</dependency>
<!-- mcp-mvc的starter-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>
<!-- 阿里ai的starter-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<inherited>true</inherited>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<parameters>true</parameters>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.3.3 ChatRequest
package org.mcp.beans;
import lombok.Data;
/**
* 聊天请求体
*/
@Data
public class ChatRequest {
/**
* 用户消息内容,不能为空
*/
private String message;
}
1.3.4 ChatResponse
package org.mcp.beans;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 聊天响应
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatResponse {
/**
* 响应内容
*/
private String content;
}
1.3.5 ChatClientConfig
package org.mcp.config;
import org.springframework.ai.chat.client.ChatClient;
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 ChatClientConfig {
@Autowired
private ToolCallbackProvider toolCallbackProvider;
/**
* 配置ChatClient,注册系统指令和工具函数
*/
@Bean
public ChatClient chatClient(ChatClient.Builder builder){
return builder
.defaultSystem("你是一个学生管理助手,可以帮助用户查询学生信息。" +
"你可以根据学生名字模糊查询学生,根据性别查学生信息,查询全部学生信息,并做出一些基本的统计"+
"回复时,请使用简洁友好的语言,并将学生信息整理为易读的格式。")
// 注册工具方法
.defaultTools(toolCallbackProvider)
.build();
}
}
1.3.6 ServiceProviderConfig
package org.mcp.config;
import org.mcp.service.StudentService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服务提供者配置
*/
@Configuration
public class ServiceProviderConfig {
@Autowired
private StudentService studentService;
@Bean
public ToolCallbackProvider serverTools() {
return MethodToolCallbackProvider.builder()
// 可以注册多个服务
.toolObjects(studentService)
.build();
}
}
1.3.7 ChatController
package org.mcp.controller;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.mcp.beans.ChatRequest;
import org.mcp.beans.ChatResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Resource
private ChatClient chatClient;
@PostMapping("/chatting")
@SneakyThrows
public ResponseEntity<ChatResponse> chatting(@RequestBody ChatRequest request) {
String userMessage = request.getMessage();
// 调用聊天
String content = chatClient.prompt()
.user(userMessage)
.call()
.content();
return ResponseEntity.ok(new ChatResponse(content));
}
}
1.3.8 Student
package org.mcp.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private int age;
private String sex;
private String className;
private String phone;
private String email;
private String address;
}
1.3.9 StudentManager
package org.mcp.manager;
import org.mcp.entity.Student;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StudentManager {
private static final List<Student> STUDENTS = new ArrayList<>();
static {
init();
}
private static void init() {
// 初始化学生数据
STUDENTS.add(new Student("张三", 18, "男", "1班", "12345678901", "12345678901@qq.com", "北京"));
STUDENTS.add(new Student("李四", 19, "女", "2班", "12345678902", "12345678902@qq.com", "上海"));
STUDENTS.add(new Student("王五", 20, "男", "3班", "12345678903", "12345678903@qq.com", "广州"));
STUDENTS.add(new Student("赵六", 21, "女", "4班", "12345678904", "12345678904@qq.com", "深圳"));
STUDENTS.add(new Student("孙七", 22, "男", "5班", "12345678905", "12345678905@qq.com", "杭州"));
STUDENTS.add(new Student("钱八", 23, "女", "6班", "12345678906", "12345678906@qq.com", "南京"));
STUDENTS.add(new Student("李九", 24, "男", "7班", "12345678907", "12345678907@qq.com", "西安"));
}
public List<Student> findStudentByName(String name) {
List<Student> result = new ArrayList<>();
for (Student student : STUDENTS) {
if (student.getName().contains(name)) {
result.add(student);
}
}
return result;
}
public List<Student> findStudentBySex(String sex) {
List<Student> result = new ArrayList<>();
for (Student student : STUDENTS) {
if (student.getSex().equals(sex)) {
result.add(student);
}
}
return result;
}
public List<Student> findAllStudents() {
return STUDENTS;
}
public void createStudent(Student student) {
STUDENTS.add(student);
}
}
1.3.10 StudentService
提供基本的查询功能。
package org.mcp.service;
import org.mcp.entity.Student;
import java.util.List;
public interface StudentService {
List<Student> findStudentByName(String name);
List<Student> findStudentBySex(String sex);
List<Student> findAllStudents();
void createStudent(Student student);
}
1.3.11 StudentServiceImpl
服务实现层,实现基本的查询功能。
使用spring-ai的注解 @Tool 将方法标记为 可供大模型调用的工具。
@ToolParam 注解,可对方法参数进行描述,便于模型正确传递参数。
- name:定义该工具在模型侧使用的名称。
- description:提供工具功能的简要说明,帮助模型理解何时以及如何使用它。
package org.mcp.service.impl;
import jakarta.annotation.Resource;
import org.mcp.entity.Student;
import org.mcp.manager.StudentManager;
import org.mcp.service.StudentService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Resource
private StudentManager studentManager;
@Override
@Tool(name = "findStudentByName", description = "根据姓名查询学生信息,支持模糊查询")
public List<Student> findStudentByName(@ToolParam(description = "学生名字关键字,或学生全名") String name) {
return studentManager.findStudentByName(name);
}
@Override
@Tool(name = "findStudentBySex", description = "根据性别查学生信息,性别只有男、女")
public List<Student> findStudentBySex(String sex) {
return studentManager.findStudentBySex(sex);
}
@Override
@Tool(name = "findAllStudents", description = "查找当前的全部学生信息,不过滤任何条件")
public List<Student> findAllStudents() {
return studentManager.findAllStudents();
}
@Override
@Tool(name = "createStudent", description = "创建学生信息")
public void createStudent(@ToolParam(description = "创建学生信息") Student student) {
studentManager.createStudent(student);
}
}
1.3.12 SseServerApplication
package org.mcp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SseServerApplication {
public static void main(String[] args) {
SpringApplication.run(SseServerApplication.class, args);
}
}
1.3.13 application,yml
server:
port: 8081
spring:
application:
name: mcp-server
ai:
mcp:
server:
enabled: true
type: SYNC # 同步
name: mcp-server # MCP服务器名称
version: 0.0.1 # 服务器版本号
dashscope:
api-key: sk-xxxxx你自己在阿里那边申请的key
chat:
options:
model: qwen-plus
1.4 调用聊天接口
1.4.1 请求1
POST http://localhost:8081/api/chat/chatting
Content-Type: application/json
{
"message" : "你好,在全部的学生中,找一下年龄在22岁之上的学生信息,并简单整理其信息,给我一个json数据"
}
响应结果为:
{
"content": "根据你的要求,我整理了年龄在22岁及以上的学生信息。以下是符合条件的学生的JSON数据:\n\n```json\n[\n {\"name\":\"钱八\",\"age\":23,\"sex\":\"女\",\"className\":\"6班\",\"phone\":\"12345678906\",\"email\":\"12345678906@qq.com\",\"address\":\"南京\"},\n {\"name\":\"李九\",\"age\":24,\"sex\":\"男\",\"className\":\"7班\",\"phone\":\"12345678907\",\"email\":\"12345678907@qq.com\",\"address\":\"西安\"}\n]\n```\n\n以上学生信息展示了他们的姓名、年龄、性别、班级、电话、邮箱和地址。如果你需要进一步的信息或者其他帮助,请随时告诉我!"
}
1.4.2 请求2
POST http://localhost:8081/api/chat/chatting
Content-Type: application/json
{
"message" : "你好,我找名字是张的学生"
}
响应结果为:
{
"content": "我找到了一位名字包含\"张\"的学生,信息如下:\n\n- 姓名:张三\n- 年龄:18岁\n- 性别:男\n- 班级:1班\n- 电话:12345678901\n- 邮箱:12345678901@qq.com\n- 地址:北京\n\n如果还有其他需要,请告诉我!"
}
1.4.3 请求3
POST http://localhost:8081/api/chat/chatting
Content-Type: application/json
{
"message" : "你好,我找来自北上广的学生,并使用表格列出他们的基本信息"
}
响应结果为:
{
"content": "根据查询结果,我筛选出来自北京、上海和广州的学生,并以表格形式列出他们的基本信息如下:\n\n| 姓名 | 年龄 | 性别 | 班级 | 电话号码 | 邮箱 | 地址 |\n|------|------|------|------|------------------|----------------------|--------|\n| 张三 | 18 | 男 | 1班 | 12345678901 | 12345678901@qq.com | 北京 |\n| 李四 | 19 | 女 | 2班 | 12345678902 | 12345678902@qq.com | 上海 |\n| 王五 | 20 | 男 | 3班 | 12345678903 | 12345678903@qq.com | 广州 |\n\n如有其他需求,请随时告诉我!"
}
1.4.4 请求4
POST http://localhost:8081/api/chat/chatting
Content-Type: application/json
{
"message" : "你好,创建一个学生,名字叫 冯宝宝,性别是女,别的属性你随便加,不要为空就行"
}
响应结果为:
{
"content": "好的,已经为你创建了学生信息:\n\n姓名:冯宝宝 \n性别:女 \n年龄:18岁 \n班级:高一班 \n地址:北京市朝阳区 \n电话:13812345678 \n邮箱:fengbaobao@example.com"
}
1.4.5 请求5
在创建了学生【冯宝宝】之后,使用了查询功能,可以看到已经创建到“数据库”中了。并筛选出来的数据也是一致的。
POST http://localhost:8081/api/chat/chatting
Content-Type: application/json
{
"message" : "查一下有没有叫冯宝宝的学生"
}
响应结果:
{
"content" : "找到一位叫冯宝宝的学生,信息如下:\n\n姓名:冯宝宝 \n年龄:18岁 \n性别:女 \n班级:高一班 \n电话:13812345678 \n邮箱:fengbaobao@example.com \n地址:北京市朝阳区"
}