添加McpServer和functionCall的示例

pull/8757/head
chenrui 2025-08-22 16:31:39 +08:00
parent 47107a53c2
commit 3d4e6ba940
8 changed files with 259 additions and 3 deletions

View File

@ -188,6 +188,9 @@ public class SecurityConfig {
.requestMatchers(AntPathRequestMatcher.antMatcher("/openapi/call/**")).permitAll()
// APP版本信息
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/version/app3version")).permitAll()
// mcp接口
.requestMatchers(AntPathRequestMatcher.antMatcher("/sse")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/mcp/message")).permitAll()
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))

View File

@ -0,0 +1,36 @@
<?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>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-parent</artifactId>
<version>3.8.2</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>jeecg-module-mcp-server</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SYSTEM 系统管理模块 -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-biz</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- MCP-Server -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,130 @@
package org.jeecg.modules.mcp;
import com.alibaba.fastjson.JSONObject;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.chat.request.json.JsonStringSchema;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolExecutor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.ai.assistant.AiChatAssistant;
import org.jeecg.ai.factory.AiModelFactory;
import org.jeecg.ai.factory.AiModelOptions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @Description: llm
* @Author: chenrui
* @Date: 2025/8/22 14:13
*/
@RestController
@RequestMapping("/ai/tools/test")
@Slf4j
public class LlmToolsTestController {
@Autowired
ISysBaseAPI sysBaseAPI;
// 根据环境构建模型配置;缺少关键项则返回 null 以便测试跳过
private static AiModelOptions buildModelOptionsFromEnv() {
String baseUrl = "https://api.gpt.ge";
String apiKey = "sk-ZLhvUUGPGyERkPya632f3f18209946F7A51d4479081a3dFb";
String modelName = "gpt-4.1-mini";
return AiModelOptions.builder()
.provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
.modelName(modelName)
.baseUrl(baseUrl)
.apiKey(apiKey)
.build();
}
@GetMapping(value = "/queryUser")
public Result<?> queryUser(@RequestParam(value = "prompt", required = true) String prompt) {
AiModelOptions modelOp = buildModelOptionsFromEnv();
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("query_user_by_name")
.description("通过通过用户名查询用户信息,返回用户信息列表")
.parameters(JsonObjectSchema.builder()
.addProperties(Map.of(
"username", JsonStringSchema.builder()
.description("用户名,多个可以使用逗号分隔")
.build()
))
.build())
.build();
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
Map<String, Object> arguments = JSONObject.parseObject(toolExecutionRequest.arguments());
String username = arguments.get("username").toString();
List<JSONObject> users = sysBaseAPI.queryUsersByUsernames(username);
return JSONObject.toJSONString(users);
};
// 构建同步聊天模型并通过 AiChatAssistant 发起一次非流式对话
ChatModel chatModel = AiModelFactory.createChatModel(modelOp);
AiChatAssistant bot = AiServices.builder(AiChatAssistant.class)
.chatModel(chatModel)
.tools(Map.of(toolSpecification, toolExecutor))
.tools()
.build();
String chat = bot.chat(prompt);
log.info("聊天回复: " + chat);
return Result.OK(chat);
}
// 根据环境构建 MCP 工具提供者;缺少 URL 则返回 null 以便测试跳过
private static McpToolProvider buildMcpTool(String sseUrl) {
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl(sseUrl)
.logRequests(true)
.logResponses(true)
.build();
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
return McpToolProvider.builder()
.mcpClients(List.of(mcpClient))
.build();
}
/**
* JeecgMcp
* @author chenrui
* @date 2025/8/22 10:10
*/
@GetMapping(value = "/queryUserByMcp")
public Result<?> testJeecgToolProvider(@RequestParam(value = "prompt", required = true) String prompt) {
// prompt = "查询一下有没有用户名是admin的用户信息";
// prompt = "新建一个顶级部门,部门名称是:测试部门,机构类别是公司";
AiModelOptions modelOp = buildModelOptionsFromEnv();
McpToolProvider toolProvider = buildMcpTool("http://localhost:8080/jeecgboot/sse");
// 构建同步聊天模型并通过 AiChatAssistant 发起一次非流式对话
ChatModel chatModel = AiModelFactory.createChatModel(modelOp);
AiChatAssistant bot = AiServices.builder(AiChatAssistant.class)
.chatModel(chatModel)
.toolProvider(toolProvider)
.build();
String chat = bot.chat(prompt);
log.info("聊天回复: " + chat);
return Result.OK(chat);
}
}

View File

@ -0,0 +1,20 @@
package org.jeecg.modules.mcp;
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;
/**
* @Description:
* @Author: chenrui
* @Date: 2025/8/21 17:19
*/
@Configuration
public class McpConfiguration {
@Bean
public ToolCallbackProvider weatherTools(UserMcpTool userMcpTool) {
return MethodToolCallbackProvider.builder().toolObjects(userMcpTool).build();
}
}

View File

@ -0,0 +1,47 @@
package org.jeecg.modules.mcp;
import com.alibaba.fastjson.JSONObject;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.service.ISysDepartService;
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;
/**
* @Description:
* @Author: chenrui
* @Date: 2025/8/21 17:10
*/
@Service("userMcpService")
public class UserMcpTool {
@Autowired
ISysBaseAPI sysBaseAPI;
@Autowired
ISysDepartService sysDepartmentService;
@Tool(name = "query_user_by_name", description = "通过通过用户名查询用户信息,返回用户信息列表")
public String queryUserByUsername(@ToolParam(description = "用户名,多个可以使用逗号分隔", required = true) String username) {
if (username == null || username.isEmpty()) {
return "Username cannot be null or empty";
}
List<JSONObject> users = sysBaseAPI.queryUsersByUsernames(username);
return JSONObject.toJSONString(users);
}
@Tool(name = "add_depart", description = "新增部门信息,返回操作结果")
public String saveDepartData(@ToolParam(description = "部门信息,departName(部门名称,必填),orgCategory(机构类别,必填, 1=公司2=组织机构3=岗位),parentId(父部门:父级部门的id,非必填)", required = true) SysDepart sysDepart){
try {
sysDepartmentService.saveDepartData(sysDepart, "mcpService");
} catch (Exception e) {
return "创建部门失败,原因:"+e.getMessage();
}
return "创建部门成功";
}
}

View File

@ -25,6 +25,12 @@
<version>${jeecgboot.version}</version>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-module-mcp-server</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- flyway 数据库自动升级
<dependency>
<groupId>org.flywaydb</groupId>

View File

@ -25,6 +25,12 @@ management:
include: metrics,httpexchanges,jeecghttptrace
spring:
ai:
mcp:
server:
name: jeecg-mcp-server
version: 1.0.0
base-url: /jeecgboot
flyway:
# 是否启用flyway
enabled: true
@ -151,7 +157,7 @@ spring:
slow-sql-millis: 5000
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
url: jdbc:mysql://jeecg-boot-mysql:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
@ -165,7 +171,7 @@ spring:
data:
redis:
database: 0
host: 127.0.0.1
host: jeecg-boot-redis
port: 6379
password:
#mybatis plus 设置

View File

@ -167,6 +167,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-ai -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- system 模块-->
<dependency>
@ -481,7 +489,7 @@
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-starter3-chatgpt</artifactId>
<version>${jeecgboot.version}</version>
<version>3.8.3</version>
</dependency>
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
<!--mysql5.6需要把版本号改成5.2.1-->