目錄結構
- 完善spring boot
- pom.xml添加依賴
- application.yml
- MCP 工具配置 mcp-servers.json
- 配置類
- 編寫API
在我的上一篇文章搭建好本地的聊天機器人后,準備接入MCP進一步增強AI的能力,以實現類似手機AI的功能
參考的是第二篇文章鏈接其內容比較精煉,有些細節被忽略了,導致踩坑不少,可能是因為版本原因,最終我沒能使用他的方案運行成功,轉而使用了另一個方案,原文連接
為什么使用Qwen3
完善spring boot
pom.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.0https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>Qwen3</artifactId><version>0.0.1-SNAPSHOT</version><name>Qwen3</name><description>Qwen3</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.1</version></parent><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency></dependencies><repositories><repository><id>spring-milestones</id><url>https://repo.spring.io/milestone</url></repository><repository><id>spring-snapshots</id><url>https://repo.spring.io/snapshot</url><snapshots><enabled>true</enabled></snapshots></repository></repositories><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass>com.example.qwen3.Qwen3Application</mainClass></configuration></plugin></plugins></build></project>
MCP客戶端和服務端分別作用
我兩者都添加了
application.yml
yml完全改掉了,畢竟
spring:ai:ollama:base-url: http://localhost:11434chat:enabled: true# 聊天模型model: qwen3:4boptions:temperature: 0.7top_p: 0.9num_predict: 5120 # 單次回復最大長度,根據自己電腦性能來定embedding:enabled: true# 向量模型(通常用小點的模型效率高)model: qwen3:4boptions:num-batch: 5120mcp:client:enabled: truename: mcp-clientversion: 1.0type: SYNCrequest-timeout: 30sstdio:servers-configuration: classpath:/mcp-server-settings.jsonlogging:level:org.springframework.ai.mcp.tool: DEBUGserver:port: 8181
MCP 工具配置 mcp-servers.json
我想使用本地文件操作以實現
它需要安裝Node.js 和 npm
自測一下確實安裝過了
并且
在紅框路徑新建mcp-servers.json文件,注意紅色下劃線處要改成自己的桌面路徑
{"mcpServers": {"filesystem": {"command": "F:\\Environment\\nodejs\\npx.cmd","args": ["-y","@modelcontextprotocol/server-filesystem","C:\\Users\\lenovo\\Desktop\\temp"]}}
}
“filesystem” 是 MCP server 的名字
“command”: “npx.cmd” → Windows 下用 npx 啟動(Linux/macOS 下就是 “npx”)
“@modelcontextprotocol/server-filesystem” 是官方提供的文件系統 MCP server
“C:\Users\lenovo\Desktop\temp” 表示允許 AI 訪問的目錄范圍
配置類
package com.example.qwen3.config;import io.modelcontextprotocol.client.McpSyncClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.List;@Configuration
public class McpConfig {@Beanpublic SyncMcpToolCallbackProvider syncMcpToolCallbackProvider(List<McpSyncClient> mcpSyncClients) {return new SyncMcpToolCallbackProvider(mcpSyncClients);}}
編寫API
package com.example.qwen3.controller;import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;import java.time.Duration;@RequestMapping("/mcp")
@RestController
public class McpController {private final OllamaChatModel chatModel;private final SyncMcpToolCallbackProvider syncMcpToolCallbackProvider;public McpController(OllamaChatModel chatModel, SyncMcpToolCallbackProvider syncMcpToolCallbackProvider) {this.chatModel = chatModel;this.syncMcpToolCallbackProvider = syncMcpToolCallbackProvider;}@GetMapping("/mcpChat")public String generate(@RequestParam(value = "prompt") String prompt) {ChatClient chatClient = ChatClient.builder(chatModel).defaultTools(syncMcpToolCallbackProvider.getToolCallbacks()).build();return chatClient.prompt().user(prompt).call().content();}@GetMapping(value = "/mcpChatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ChatResponse> generateStream(@RequestParam(value = "prompt") String prompt) {// 先執行一次完整的調用以確保工具被執行ChatClient chatClient = ChatClient.builder(chatModel).defaultTools(syncMcpToolCallbackProvider.getToolCallbacks()).defaultOptions(OllamaOptions.builder().model("qwen3:4b").build()).build();// 執行工具調用(非流式)String result = chatClient.prompt().user(prompt).call().content();// 然后返回流式響應(基于已執行的結果)return chatClient.prompt().user(prompt + "\n\n基于上述操作,請總結執行結果:").stream().chatResponse();}// 方案5:簡化版本 - 直接使用工作版本的邏輯@GetMapping(value = "/mcpChatStreamFixed", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> mcpChatStreamFixed(@RequestParam("prompt") String prompt) {ChatClient chatClient = ChatClient.builder(chatModel).defaultTools(syncMcpToolCallbackProvider.getToolCallbacks()).build();// 使用已知工作的邏輯,但改為流式輸出return Mono.fromCallable(() -> {// 先執行完整的對話(包括工具調用)return chatClient.prompt().user(prompt).call().content();}).flatMapMany(result -> {// 然后將結果分塊流式返回return Flux.fromArray(result.split("")).delayElements(Duration.ofMillis(50)); // 每個字符延遲50ms}).subscribeOn(Schedulers.boundedElastic());}@GetMapping("/test")public String test(@RequestParam(value = "prompt") String prompt) {return "input=" + prompt;}
}
我在想要實現流式問答部分卡了很久,原因是在流式響應中,工具調用和工具執行的時機與非流式不同,導致工具調用被跳過或執行順序有問題。
所以要么就是一次性輸出內容,比如
http://localhost:8181/mcp/mcpChat?在temp目錄下創建文件e1.txt,內容為我是mcpChat創建的文件
要么是先執行操作,再流式輸出對于上一次操作的總結
http://localhost:8181/mcp/mcpChatStream?在temp目錄下創建文件e2.txt,內容為我是mcpChat創建的文件
或者單純流式
本地模型速度還是太慢了,實測大概需要5分鐘才能創建好一個文件,基本沒有實用性可言