mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-03-13 20:53:42 +08:00
feat: mcp-1.0.0
This commit is contained in:
@@ -320,19 +320,4 @@ wechat:
|
||||
token: ''
|
||||
aesKey: ''
|
||||
|
||||
spring:
|
||||
ai:
|
||||
openai:
|
||||
api-key: sk-xX
|
||||
base-url: https://api.pandarobot.chat/
|
||||
ollama:
|
||||
base-url: http://localhost:11434
|
||||
mcp:
|
||||
client:
|
||||
enabled: true
|
||||
name: call-mcp-server
|
||||
sse:
|
||||
connections:
|
||||
server1:
|
||||
url: http://127.0.0.1:6040
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ public class ChatRequest {
|
||||
*/
|
||||
private Boolean search = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* 是否开启mcp
|
||||
*/
|
||||
private Boolean isMcp = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* 知识库id
|
||||
*/
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>call-mcp-server</artifactId>
|
||||
<name>Archetype - call-mcp-server</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CallMcpServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CallMcpServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver.cofing;
|
||||
|
||||
import io.modelcontextprotocol.client.McpClient;
|
||||
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Configuration
|
||||
public class McpClientCfg implements McpSyncClientCustomizer {
|
||||
|
||||
|
||||
@Override
|
||||
public void customize(String name, McpClient.SyncSpec spec) {
|
||||
// do nothing
|
||||
spec.requestTimeout(Duration.ofSeconds(30));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver.view;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.InMemoryChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
|
||||
/**
|
||||
* @author jianzhang
|
||||
* 2025/03/18/下午8:00
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/dashscope/chat-client")
|
||||
public class ChatController {
|
||||
|
||||
private final ChatClient chatClient;
|
||||
|
||||
private final ChatMemory chatMemory = new InMemoryChatMemory();
|
||||
|
||||
public ChatController(ChatClient.Builder chatClientBuilder,ToolCallbackProvider tools) {
|
||||
this.chatClient = chatClientBuilder
|
||||
.defaultTools(tools)
|
||||
.defaultOptions(
|
||||
OpenAiChatOptions.builder().model("gpt-4o-mini").build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/generate_stream", method = RequestMethod.GET)
|
||||
public Flux<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
|
||||
|
||||
|
||||
Flux<ChatResponse> chatResponseFlux = this.chatClient.prompt(prompt)
|
||||
.advisors(messageChatMemoryAdvisor)
|
||||
.stream()
|
||||
.chatResponse();
|
||||
|
||||
Flux<String> content = this.chatClient.prompt(prompt)
|
||||
.advisors(messageChatMemoryAdvisor)
|
||||
.stream()
|
||||
.content();
|
||||
|
||||
content.subscribe(
|
||||
content1 -> System.out.println("chatResponse"+content1)
|
||||
);
|
||||
return chatResponseFlux;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/advisor/chat/{id}/{prompt}")
|
||||
public Flux<String> advisorChat(
|
||||
HttpServletResponse response,
|
||||
@PathVariable String id,
|
||||
@PathVariable String prompt) {
|
||||
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
|
||||
return this.chatClient.prompt(prompt)
|
||||
.advisors(messageChatMemoryAdvisor).stream().content();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver.view;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author jianzhang
|
||||
* 2025/03/18/下午8:00
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String chat(Model model) {
|
||||
//model.addAttribute("name", "User");
|
||||
// 返回视图名称,对应 templates/index.html
|
||||
return "index";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
server:
|
||||
port: 9999
|
||||
spring:
|
||||
ai:
|
||||
openai:
|
||||
api-key: sk-xXe1WMPjhlVb1aiI1b4c6c8934D8463f9e4b67Ed8718B772
|
||||
base-url: https://api.pandarobot.chat/
|
||||
mcp:
|
||||
client:
|
||||
enabled: true
|
||||
name: call-mcp-server
|
||||
sse:
|
||||
connections:
|
||||
server1:
|
||||
url: http://127.0.0.1:6040
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"fileSystem": {
|
||||
"command": "D:\\software\\nodeJs\\npx.cmd",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"D:\\software\\sqlite"
|
||||
]
|
||||
},
|
||||
"sqlLite": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"mcp-server-sqlite",
|
||||
"--db-path",
|
||||
"D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db"
|
||||
]
|
||||
},
|
||||
"fetch": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"mcp-server-fetch"
|
||||
]
|
||||
},
|
||||
"baidu-map": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"run",
|
||||
"--with",
|
||||
"mcp[cli]",
|
||||
"mcp",
|
||||
"run",
|
||||
"D:\\work-space-python\\python-baidu-map\\baidu_map_mcp_server\\map.py"
|
||||
],
|
||||
"env": {
|
||||
"BAIDU_MAPS_API_KEY": "{百度地图API-KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"fileSystem": {
|
||||
"command": "D:\\software\\nodeJs\\npx.cmd",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"D:\\software\\sqlite"
|
||||
]
|
||||
},
|
||||
"sqlLite": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"mcp-server-sqlite",
|
||||
"--db-path",
|
||||
"D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI 对话助手</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<div class="container mx-auto p-4 max-w-3xl">
|
||||
<!-- 标题 -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800">AI 对话助手</h1>
|
||||
<p class="text-gray-600 mt-2">基于 Spring AI 的流式对话系统 By AhuCodingBeast</p>
|
||||
</div>
|
||||
|
||||
<!-- 聊天容器 -->
|
||||
<div id="chat-container" class="bg-white rounded-xl shadow-lg p-4 mb-4 h-[500px] overflow-y-auto space-y-4">
|
||||
<!-- 初始欢迎消息 -->
|
||||
<div class="ai-message flex items-start gap-3">
|
||||
<div class="bg-green-100 p-3 rounded-lg max-w-[85%]">
|
||||
<span class="text-gray-800">您好!我是AI助手,有什么可以帮您?</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="message-input"
|
||||
class="flex-1 border border-gray-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="输入您的问题...">
|
||||
<button id="send-button"
|
||||
class="bg-blue-500 text-white px-6 py-3 rounded-xl hover:bg-blue-600 transition-colors flex items-center">
|
||||
<span>发送</span>
|
||||
<svg id="loading-spinner" class="hidden w-4 h-4 ml-2 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const messageInput = document.getElementById('message-input');
|
||||
const sendButton = document.getElementById('send-button');
|
||||
const loadingSpinner = document.getElementById('loading-spinner');
|
||||
|
||||
// 发送消息处理
|
||||
function handleSend() {
|
||||
const message = messageInput.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
// 添加用户消息
|
||||
addMessage(message, 'user');
|
||||
messageInput.value = '';
|
||||
|
||||
// 构建API URL
|
||||
const apiUrl = new URL('http://localhost:9999/dashscope/chat-client/generate_stream');
|
||||
apiUrl.searchParams.append('id', '01');
|
||||
apiUrl.searchParams.append('prompt', message);
|
||||
|
||||
// 显示加载状态
|
||||
sendButton.disabled = true;
|
||||
loadingSpinner.classList.remove('hidden');
|
||||
|
||||
// 创建EventSource连接
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
let aiMessageElement = null;
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log(data);
|
||||
const content = data.result?.output?.text || '';
|
||||
const finishReason = data.result?.metadata?.finishReason;
|
||||
|
||||
// 创建消息容器(如果不存在)
|
||||
if (!aiMessageElement) {
|
||||
aiMessageElement = addMessage('', 'ai');
|
||||
}
|
||||
|
||||
// 追加内容
|
||||
if (content) {
|
||||
aiMessageElement.querySelector('.message-content').textContent += content;
|
||||
autoScroll();
|
||||
}
|
||||
|
||||
// 处理结束
|
||||
if (finishReason === 'STOP') {
|
||||
eventSource.close();
|
||||
sendButton.disabled = false;
|
||||
loadingSpinner.classList.add('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('连接错误:', error);
|
||||
eventSource.close();
|
||||
sendButton.disabled = false;
|
||||
loadingSpinner.classList.add('hidden');
|
||||
addMessage('对话连接异常,请重试', 'ai', true);
|
||||
};
|
||||
}
|
||||
|
||||
// 添加消息到容器
|
||||
function addMessage(content, type, isError = false) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `${type}-message flex items-start gap-3`;
|
||||
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = `p-3 rounded-lg max-w-[85%] ${
|
||||
type === 'user'
|
||||
? 'bg-blue-500 text-white ml-auto'
|
||||
: `bg-green-100 ${isError ? 'text-red-500' : 'text-gray-800'}`
|
||||
}`;
|
||||
|
||||
const contentSpan = document.createElement('span');
|
||||
contentSpan.className = 'message-content';
|
||||
contentSpan.textContent = content;
|
||||
|
||||
bubble.appendChild(contentSpan);
|
||||
messageDiv.appendChild(bubble);
|
||||
chatContainer.appendChild(messageDiv);
|
||||
|
||||
autoScroll();
|
||||
return bubble;
|
||||
}
|
||||
|
||||
// 自动滚动到底部
|
||||
function autoScroll() {
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
// 事件监听
|
||||
sendButton.addEventListener('click', handleSend);
|
||||
messageInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,7 +14,7 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-mcp-server</module>
|
||||
<module>ruoyi-ai-mcp-webflux-server</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
91
ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml
Normal file
91
ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<relativePath /> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>com.ivy.mcp</groupId>
|
||||
<artifactId>ruoyi-ai-mcp-webflux-server</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
|
||||
<name>spring-ai-mcp-webflux-server</name>
|
||||
<description>Spring AI MCP Server example and invoke by stdio</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-ai.version>1.0.0-M6</spring-ai.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<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>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<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>
|
||||
<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>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ivy.mcp.sse.client;
|
||||
|
||||
|
||||
import io.modelcontextprotocol.client.McpClient;
|
||||
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ClientWebflux {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
|
||||
try (var client = McpClient.sync(transport).build()) {
|
||||
|
||||
client.initialize();
|
||||
// client.ping();
|
||||
|
||||
McpSchema.ListToolsResult toolsList = client.listTools();
|
||||
System.out.println("Available Tools = " + toolsList);
|
||||
|
||||
McpSchema.CallToolResult sumResult = client.callTool(new McpSchema.CallToolRequest("add",
|
||||
Map.of("a", 1, "b", 2)));
|
||||
System.out.println("add a+ b = " + sumResult.content().get(0));
|
||||
|
||||
|
||||
McpSchema.CallToolResult currentTimResult = client.callTool(new McpSchema.CallToolRequest("getCurrentTime", Map.of()));
|
||||
System.out.println("current time Response = " + currentTimResult);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.ivy.mcp.sse.server;
|
||||
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.ToolCallbacks;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootApplication
|
||||
public class McpWebfluxServerApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(McpWebfluxServerApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public List<ToolCallback> tools(MyTools myTools) {
|
||||
return List.of(ToolCallbacks.from(myTools));
|
||||
}
|
||||
|
||||
@Service
|
||||
public static class MyTools {
|
||||
|
||||
@Tool(description = "add two numbers")
|
||||
public Integer add(@ToolParam(description = "first number") int a,
|
||||
@ToolParam(description = "second number") int b) {
|
||||
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@Tool(description = "get current time")
|
||||
public LocalDateTime getCurrentTime() {
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
spring.main.banner-mode=off
|
||||
logging.pattern.console=
|
||||
logging.file.name=mcp-server/spring-ai-mcp-webflux-server/target/target/mcp.mytools.log
|
||||
|
||||
spring.ai.mcp.server.enabled=true
|
||||
spring.ai.mcp.server.name=webflux-server
|
||||
spring.ai.mcp.server.version=1.0.0
|
||||
@@ -1,64 +0,0 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ruoyi-mcp-server</artifactId>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.ruoyi</groupId>-->
|
||||
<!-- <artifactId>ruoyi-system-api</artifactId>-->
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <groupId>org.ruoyi</groupId>-->
|
||||
<!-- <artifactId>ruoyi-common-translation</artifactId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.ruoyi.mcp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class McpServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(McpServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import org.ruoyi.mcp.service.McpCustomService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
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;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class McpServerConfig implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public ToolCallbackProvider openLibraryTools(McpCustomService mcpService) {
|
||||
return MethodToolCallbackProvider.builder().toolObjects(mcpService).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public List<McpServerFeatures.SyncResourceRegistration> resourceRegistrations() {
|
||||
|
||||
// Create a resource registration for system information
|
||||
var systemInfoResource = new McpSchema.Resource(
|
||||
"system://info",
|
||||
"System Information",
|
||||
"Provides basic system information including Java version, OS, etc.",
|
||||
"application/json", null
|
||||
);
|
||||
|
||||
var resourceRegistration = new McpServerFeatures.SyncResourceRegistration(systemInfoResource, (request) -> {
|
||||
try {
|
||||
var systemInfo = Map.of(
|
||||
"javaVersion", System.getProperty("java.version"),
|
||||
"osName", System.getProperty("os.name"),
|
||||
"osVersion", System.getProperty("os.version"),
|
||||
"osArch", System.getProperty("os.arch"),
|
||||
"processors", Runtime.getRuntime().availableProcessors(),
|
||||
"timestamp", System.currentTimeMillis());
|
||||
|
||||
String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
|
||||
|
||||
return new McpSchema.ReadResourceResult(
|
||||
List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to generate system info", e);
|
||||
}
|
||||
});
|
||||
|
||||
return List.of(resourceRegistration);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() {
|
||||
|
||||
var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
|
||||
List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));
|
||||
|
||||
var promptRegistration = new McpServerFeatures.SyncPromptRegistration(prompt, getPromptRequest -> {
|
||||
|
||||
String nameArgument = (String) getPromptRequest.arguments().get("name");
|
||||
if (nameArgument == null) {
|
||||
nameArgument = "friend";
|
||||
}
|
||||
|
||||
var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?"));
|
||||
|
||||
return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage));
|
||||
});
|
||||
|
||||
return List.of(promptRegistration);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public Consumer<List<McpSchema.Root>> rootsChangeConsumer() {
|
||||
return roots -> {
|
||||
System.out.println("rootsChange");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.ruoyi.mcp.service;
|
||||
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
public class McpCustomService {
|
||||
|
||||
public record User(String userName, String userBalance) {
|
||||
}
|
||||
|
||||
@Tool(description = "根据用户名称查询用户信息")
|
||||
public User getUserBalance(String username) {
|
||||
return new User("admin","99.99");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
server:
|
||||
port: 6040
|
||||
spring:
|
||||
application:
|
||||
name: mcp-server
|
||||
ai:
|
||||
mcp:
|
||||
server:
|
||||
name: webmvc-mcp-server
|
||||
version: 1.0.0
|
||||
type: SYNC
|
||||
sse-message-endpoint: /mcp/messages
|
||||
@@ -55,19 +55,17 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
|
||||
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.ai</groupId>-->
|
||||
<!-- <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
<groupId>io.modelcontextprotocol.sdk</groupId>
|
||||
<artifactId>mcp-spring-webflux</artifactId>
|
||||
<version>0.8.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ChatConfig {
|
||||
return openAiStreamClient;
|
||||
}
|
||||
|
||||
public OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
||||
public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
|
||||
@@ -18,9 +18,4 @@ public interface IChatService {
|
||||
SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter);
|
||||
|
||||
|
||||
/**
|
||||
* 客户端发送消息到服务端
|
||||
* @param chatRequest 请求对象
|
||||
*/
|
||||
void mcpChat(ChatRequest chatRequest,SseEmitter emitter);
|
||||
}
|
||||
|
||||
@@ -55,11 +55,4 @@ public interface ISseService {
|
||||
*/
|
||||
String wxCpChat(String prompt);
|
||||
|
||||
/**
|
||||
* 联网查询
|
||||
*
|
||||
* @param prompt 提示词
|
||||
* @return 查询内容
|
||||
*/
|
||||
String webSearch (String prompt);
|
||||
}
|
||||
|
||||
@@ -1,78 +1,88 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import io.modelcontextprotocol.client.McpClient;
|
||||
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatChoice;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
import org.ruoyi.common.chat.entity.chat.Parameters;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.Tools;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.InMemoryChatMemory;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class OpenAIServiceImpl implements IChatService {
|
||||
|
||||
private final ChatClient chatClient;
|
||||
private final OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
private final ChatMemory chatMemory = new InMemoryChatMemory();
|
||||
|
||||
|
||||
public OpenAIServiceImpl(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
|
||||
this.chatClient = chatClientBuilder
|
||||
.defaultTools(tools)
|
||||
.defaultOptions(
|
||||
OpenAiChatOptions.builder()
|
||||
.model("gpt-4o-mini")
|
||||
.temperature(0.4)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
|
||||
return emitter;
|
||||
}
|
||||
WebFluxSseClientTransport webFluxSseClientTransport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
.messages(chatRequest.getMessages())
|
||||
.model(chatRequest.getModel())
|
||||
.stream(false)
|
||||
.build();
|
||||
List<Tools> tools = new ArrayList<>();
|
||||
try (var client = McpClient.sync(webFluxSseClientTransport).build()) {
|
||||
client.initialize();
|
||||
McpSchema.ListToolsResult toolsList = client.listTools();
|
||||
|
||||
@Override
|
||||
public void mcpChat(ChatRequest chatRequest, SseEmitter emitter) {
|
||||
List<Message> msgList = chatRequest.getMessages();
|
||||
// 添加记忆
|
||||
for (int i = 0; i < msgList.size(); i++) {
|
||||
org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString());
|
||||
chatMemory.add(String.valueOf(i), springAiMessage);
|
||||
for (McpSchema.Tool mcpTool : toolsList.tools()) {
|
||||
|
||||
McpSchema.JsonSchema jsonSchema = mcpTool.inputSchema();
|
||||
|
||||
Parameters parameters = Parameters.builder()
|
||||
.type(jsonSchema.type())
|
||||
.properties(jsonSchema.properties())
|
||||
.required(jsonSchema.required()).build();
|
||||
|
||||
Tools tool = Tools.builder()
|
||||
.type(Tools.Type.FUNCTION.getName())
|
||||
.function(ToolsFunction.builder().name(mcpTool.name()).description(mcpTool.description()).parameters(parameters).build())
|
||||
.build();
|
||||
tools.add(tool);
|
||||
}
|
||||
|
||||
completion.setTools(tools);
|
||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(completion);
|
||||
String arguments = chatCompletionResponse.getChoices().get(0).getMessage().getToolCalls().get(0).getFunction().getArguments();
|
||||
String name = chatCompletionResponse.getChoices().get(0).getMessage().getToolCalls().get(0).getFunction().getName();
|
||||
Map<String, Object> map = JSONUtil.toBean(arguments, Map.class);
|
||||
McpSchema.CallToolResult sumResult = client.callTool(new McpSchema.CallToolRequest(name, map));
|
||||
System.out.println("add a+ b = " + sumResult.content().get(0));
|
||||
|
||||
|
||||
McpSchema.Content content = sumResult.content().get(0);
|
||||
|
||||
emitter.send(sumResult.content().get(0));
|
||||
|
||||
} catch (IOException e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId().toString(), 10);
|
||||
emitter.complete();
|
||||
return emitter;
|
||||
|
||||
Flux<String> content = chatClient
|
||||
.prompt(chatRequest.getPrompt())
|
||||
.advisors(messageChatMemoryAdvisor)
|
||||
.stream().content();
|
||||
|
||||
content.publishOn(Schedulers.boundedElastic())
|
||||
.doOnNext(text -> {
|
||||
try {
|
||||
emitter.send(text);
|
||||
} catch (IOException e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.error("Error in SSE stream: ", error);
|
||||
emitter.completeWithError(error);
|
||||
})
|
||||
.doOnComplete(emitter::complete)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user