ruoyi-admin/src/main/resources/application.yml
@@ -320,19 +320,4 @@ 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 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java
@@ -42,6 +42,11 @@ private Boolean search = Boolean.FALSE; /** * æ¯å¦å¼å¯mcp */ private Boolean isMcp = Boolean.FALSE; /** * ç¥è¯åºid */ private String kid; ruoyi-extend/call-mcp-server/pom.xml
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/CallMcpServerApplication.java
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/cofing/McpClientCfg.java
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/ChatController.java
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/IndexController.java
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/resources/application.yaml
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/resources/mcp-server-bak.json
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/resources/mcp-server.json
ÎļþÒÑɾ³ý ruoyi-extend/call-mcp-server/src/main/resources/templates/index.html
ÎļþÒÑɾ³ý ruoyi-extend/pom.xml
@@ -14,7 +14,7 @@ <packaging>pom</packaging> <modules> <module>ruoyi-mcp-server</module> <module>ruoyi-ai-mcp-webflux-server</module> </modules> </project> ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml
¶Ô±ÈÐÂÎļþ @@ -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> ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/client/ClientWebflux.java
¶Ô±ÈÐÂÎļþ @@ -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); } } } ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/server/McpWebfluxServerApplication.java
¶Ô±ÈÐÂÎļþ @@ -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(); } } } ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/resources/application.properties
¶Ô±ÈÐÂÎļþ @@ -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 ruoyi-extend/ruoyi-mcp-server/pom.xml
ÎļþÒÑɾ³ý ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java
ÎļþÒÑɾ³ý ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java
ÎļþÒÑɾ³ý ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java
ÎļþÒÑɾ³ý ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml
ÎļþÒÑɾ³ý ruoyi-modules-api/ruoyi-chat-api/pom.xml
@@ -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> ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/config/ChatConfig.java
@@ -36,7 +36,7 @@ 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() ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java
@@ -18,9 +18,4 @@ SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter); /** * 客æ·ç«¯åéæ¶æ¯å°æå¡ç«¯ * @param chatRequest 请æ±å¯¹è±¡ */ void mcpChat(ChatRequest chatRequest,SseEmitter emitter); } ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java
@@ -55,11 +55,4 @@ */ String wxCpChat(String prompt); /** * èç½æ¥è¯¢ * * @param prompt æç¤ºè¯ * @return æ¥è¯¢å 容 */ String webSearch (String prompt); } ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java
@@ -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(); } }