【注意避坑】基于Spring AI 開發本地天氣 mcp server,通義靈碼測試MCP server連接不穩定,cherry studio連接報錯

springboot 版本: 3.5.4
cherry studio版本:1.4.7
通義靈碼版本: 2.5.13


在這里插入圖片描述


文章目錄

  • 問題描述:
    • 1. 通義靈碼添加mcp server ,配置測試
    • 2. cherry studio工具添加mcp server ,配置測試
  • 項目源代碼:
  • 解決方案:
    • 1. 項目改造
    • 2. 項目重新打包測試
  • 參考鏈接


問題描述:

基于Spring AI 開發本地天氣 mcp server,該mcp server 采用stdio模式與MCP client 通信,本地cherry studio工具測試連接報錯,通義靈碼測試MCP server連接極易不穩定

引用依賴如下:

        <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0-M7</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server</artifactId><version>1.0.0-M7</version></dependency>

application.yml 配置如下:

在這里插入圖片描述

項目打包后,

1. 通義靈碼添加mcp server ,配置測試

mcp server 配置信息如下:

在這里插入圖片描述
在這里插入圖片描述

測試連接效果如下:

demo111 – pom.xml (demo111) 2025-07-05 11-19-32

2. cherry studio工具添加mcp server ,配置測試

配置信息如下:
在這里插入圖片描述
測試連接效果如下:

在這里插入圖片描述


項目源代碼:

①天氣服務

package com.example.demo111.service;import com.example.demo111.model.CurrentCondition;
import com.example.demo111.model.WeatherResponse;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;import javax.net.ssl.SSLException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;@Service
public class WeatherService1 {private static final String BASE_URL = "https://wttr.in";private final RestClient restClient;public WeatherService1() {this.restClient = RestClient.builder().baseUrl(BASE_URL).defaultHeader("Accept", "application/geo+json").defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)").build();}@Tool(description = "Get current weather information for a China city. Input is city name (e.g. 杭州, 上海)")public String getWeather(String cityName) {WeatherResponse response = restClient.get().uri("/{city_name}?format=j1", cityName).retrieve().body(WeatherResponse.class);if (response != null && response.getCurrent_condition() != null && !response.getCurrent_condition().isEmpty()) {CurrentCondition currentCondition = response.getCurrent_condition().get(0);String result = String.format("""城市: %s天氣情況: %s氣壓: %s(mb)溫度: %s°C (Feels like: %s°C)濕度: %s%%降水量:%s (mm)風速: %s km/h (%s)能見度: %s 公里紫外線指數: %s觀測時間: %s""",cityName,currentCondition.getWeatherDesc().get(0).getValue(),currentCondition.getPressure(),currentCondition.getTemp_C(),currentCondition.getFeelsLikeC(),currentCondition.getHumidity(),currentCondition.getPrecipMM(),currentCondition.getWindspeedKmph(),currentCondition.getWinddir16Point(),currentCondition.getVisibility(),currentCondition.getUvIndex(),currentCondition.getLocalObsDateTime());return result;} else {return "無法獲取天氣信息,請檢查城市名稱是否正確或稍后重試。";}}
}

②數據模型

@Data
public class CurrentCondition {private String feelsLikeC;private String humidity;private String localObsDateTime;private String precipMM;private String pressure;private String temp_C;private String uvIndex;private String visibility;private List<WeatherDesc> weatherDesc;private String winddir16Point;private String windspeedKmph;
}
@Data
public class WeatherDesc {private String value;}
@Data
public class WeatherResponse {private List<CurrentCondition> current_condition;
}

③MCP SERVER配置

@Configuration
public class McpConfig {//@Tool注解的方法注冊為可供 LLM 調用的工具@Beanpublic ToolCallbackProvider weatherTools(WeatherService1 weatherService) {return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}
}

解決方案:

1. 項目改造

將基于Tomcat實現mcp server換成基于netty實現

注釋或排除Tomcat依賴,引入netty依賴

	<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-reactor-netty</artifactId></dependency>

天氣服務重新基于netty改造實現

@Service
public class WeatherService {private static final Logger logger = LoggerFactory.getLogger(WeatherService.class);private static final String BASE_URL = "https://wttr.in";private static final ObjectMapper objectMapper = new ObjectMapper();private static final int HTTP_TIMEOUT_SECONDS = 10;private static final int MAX_RESPONSE_SIZE = 1024 * 1024; // 1MBprivate final SslContext sslContext;public WeatherService() throws SSLException {this.sslContext = SslContextBuilder.forClient().build();}@Tool(description = "Get current weather information for a China city. Input is city name (e.g. 杭州, 上海)")public String syncGetWeather(String cityName) {try {return getWeather(cityName).join();} catch (Exception e) {logger.error("Failed to get weather for city: {}", cityName, e);return "獲取天氣信息失敗,請稍后重試或檢查城市名稱是否正確。";}}public CompletableFuture<String> getWeather(String cityName) {CompletableFuture<String> future = new CompletableFuture<>();if (cityName == null || cityName.trim().isEmpty()) {future.completeExceptionally(new IllegalArgumentException("城市名稱不能為空"));return future;}EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = configureBootstrap(group, future);URI uri = buildWeatherUri(cityName);ChannelFuture channelFuture = bootstrap.connect(uri.getHost(), 443);channelFuture.addListener((ChannelFutureListener) f -> {if (f.isSuccess()) {sendWeatherRequest(f.channel(), uri);} else {handleConnectionFailure(future, f.cause(), group);}});} catch (Exception e) {handleInitializationError(future, e, group);}return future;}private Bootstrap configureBootstrap(EventLoopGroup group, CompletableFuture<String> future) {return new Bootstrap().group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(sslContext.newHandler(ch.alloc()));pipeline.addLast(new HttpClientCodec());pipeline.addLast(new ReadTimeoutHandler(HTTP_TIMEOUT_SECONDS, TimeUnit.SECONDS));pipeline.addLast(new HttpObjectAggregator(MAX_RESPONSE_SIZE));pipeline.addLast(new WeatherResponseHandler(future, group));}});}URI buildWeatherUri(String cityName) throws Exception {String encodedCityName = URLEncoder.encode(cityName.trim(), StandardCharsets.UTF_8.toString());return new URI(BASE_URL + "/" + encodedCityName + "?format=j1");}void sendWeatherRequest(Channel channel, URI uri) {FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,HttpMethod.GET,uri.getRawPath() + "?format=j1"  // 確保參數在路徑中);HttpHeaders headers = request.headers();headers.set(HttpHeaderNames.HOST, uri.getHost());headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);headers.set(HttpHeaderNames.ACCEPT, "application/json");headers.set(HttpHeaderNames.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "zh-CN");channel.writeAndFlush(request).addListener(f -> {if (!f.isSuccess()) {logger.error("Failed to send weather request", f.cause());}});}void handleConnectionFailure(CompletableFuture<String> future, Throwable cause, EventLoopGroup group) {logger.error("Connection to weather service failed", cause);future.completeExceptionally(new RuntimeException("無法連接到天氣服務"));group.shutdownGracefully();}void handleInitializationError(CompletableFuture<String> future, Exception e, EventLoopGroup group) {logger.error("Weather service initialization failed", e);future.completeExceptionally(e);group.shutdownGracefully();}private static class WeatherResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {private final CompletableFuture<String> future;private final EventLoopGroup group;public WeatherResponseHandler(CompletableFuture<String> future, EventLoopGroup group) {this.future = future;this.group = group;}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) {try {if (response.status().code() != 200) {String errorMsg = String.format("天氣服務返回錯誤狀態碼: %d", response.status().code());future.complete(errorMsg);return;}String json = response.content().toString(io.netty.util.CharsetUtil.UTF_8);logger.debug("Received JSON: {}", json);  // 記錄原始JSON// 配置ObjectMapper忽略未知屬性ObjectMapper objectMapper = new ObjectMapper();objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 先驗證JSON格式JsonNode jsonNode = objectMapper.readTree(json);WeatherResponse weatherResponse = objectMapper.treeToValue(jsonNode, WeatherResponse.class);//                WeatherResponse weatherResponse = objectMapper.readValue(json, WeatherResponse.class);if (weatherResponse.getCurrent_condition() == null || weatherResponse.getCurrent_condition().isEmpty()) {future.complete("無法獲取天氣信息,請檢查城市名稱是否正確或稍后重試。");return;}CurrentCondition condition = weatherResponse.getCurrent_condition().get(0);System.out.println("condition = " + condition);String result = formatWeatherInfo(condition);future.complete(result);} catch (Exception e) {future.completeExceptionally(new RuntimeException("解析天氣數據失敗", e));} finally {ctx.close();group.shutdownGracefully();}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {future.completeExceptionally(new RuntimeException("獲取天氣信息時發生錯誤", cause));ctx.close();group.shutdownGracefully();}private String formatWeatherInfo(CurrentCondition condition) {return String.format("""天氣情況: %s溫度: %s°C (體感溫度: %s°C)濕度: %s%%氣壓: %s mb降水量: %s mm風速: %s km/h (%s方向)能見度: %s 公里紫外線指數: %s觀測時間: %s""",condition.getWeatherDesc().get(0).getValue(),condition.getTemp_C(),condition.getFeelsLikeC(),condition.getHumidity(),condition.getPressure(),condition.getPrecipMM(),condition.getWindspeedKmph(),condition.getWinddir16Point(),condition.getVisibility(),condition.getUvIndex(),condition.getLocalObsDateTime());}}
}

2. 項目重新打包測試

通義靈碼測試效果如下:

在這里插入圖片描述

在這里插入圖片描述

cherry studio 工具測試如下:

在這里插入圖片描述

emmme,cherry studio工具依舊報錯


參考鏈接

  • 速看!新版SpringAI的2個致命問題
  • Github cherry studio 報錯issue

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88318.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88318.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88318.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Paimon LSM Tree Compaction 策略

壓縮怎么進行的這里的操作都是KValue&#xff0c;內部有row kind&#xff0c;標記了刪除和插入MergeTreeCompactManager 是 Paimon 中 Merge-Tree 結構壓縮任務的總調度中心。它的核心職責就是監控文件的層級狀態&#xff08;Levels&#xff09;&#xff0c;并在合適的時機&…

小米路由器3C刷OpenWrt,更換系統/變磚恢復 指南

基礎篇看這里&#xff1a; 小米路由器3C如何安裝OpenWrt官方編譯的ROM - 嗶哩嗶哩 小米路由器 3C 刷入 Breed 和 OpenWrt - Snoopy1866 - 博客園 一、路由器注入 如果按照上面的文章&#xff0c; telnet、ftp一直連接失敗,那么可以嘗試看 這里&#xff1a; 獲取路由器root權…

Spring Boot 項目啟動時按需初始化加載數據

1、新建類&#xff0c;類上添加注解 Component &#xff0c;該類用于在項目啟動時處理數據加載任務&#xff1b; 2、該類實現 ApplicationRunner 接口&#xff0c;并重寫 run 方法&#xff1b; 3、在重寫的 run 方法里處理數據加載任務&#xff1b; 注意&#xff1a; 有定時加載…

MCP快速入門—快速構建自己的服務器

引言 隨著大語言模型(LLM)技術的快速發展&#xff0c;如何擴展其能力邊界成為開發者關注的重點。MCP(Model Capability Protocol)作為一種協議標準&#xff0c;允許開發者構建自定義服務器來增強LLM的功能。 正文內容 1. MCP核心概念與技術背景 MCP服務器主要提供三種能力類…

Vue 事件總線深度解析:從實現原理到工程實踐

在 Vue 組件通信體系中&#xff0c;事件總線&#xff08;Event Bus&#xff09;是處理非父子組件通信的輕量解決方案。本文將從技術實現細節、工程化實踐、內存管理等維度展開&#xff0c;結合源碼級分析與典型場景&#xff0c;帶你全面掌握這一核心技術點。?一、事件總線的技…

CMake Qt靜態庫中配置qrc并使用

CMake Qt序言環境代碼序言 看網上這資料較少&#xff0c;且我理解起來有歧義&#xff0c;特地補充 環境 CMake&#xff1a;3.29.2 Qt&#xff1a;5.15.2 MSVC&#xff1a;2022 IDE&#xff1a;QtCreator 代碼 方式一&#xff1a; 在CMakeLists.txt里&#xff0c;add_libr…

記錄一下:成功部署k8s集群(部分)

前提條件&#xff1a;安裝了containerd、docker 關閉了firewalld、selinux 配置了時間同步服務 chronyd 關閉swap分區等1、在控制節點、工作節點&#xff0c;安裝kubelet、kubeadm、kubectlyum install -y kubelet-1.26.0 kubeadm-1.26.0 kubectl-1.26.0 …

Idea如何解決包沖突

Idea如何解決包沖突1.Error信息&#xff1a;JAR列表。 在掃描期間跳過不需要的JAR可以縮短啟動時間和JSP編譯時間。SLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar:file:/E:/javapojects/stww-v4-gjtwt-seal/target/stww--v4-platform-proj…

python 協程學習筆記

目錄 python 協程 通俗理解 Python 的 asyncio 協程&#xff0c;最擅長的是&#xff1a; 批量下載文件的例子&#xff1a; 協程的優勢&#xff1a; python 協程 通俗理解 def my_coroutine():print("開始")x yield 1print("拿到了&#xff1a;", x)yi…

【學習筆記】蒙特卡洛仿真與matlab實現

概述 20 世紀 40 年代&#xff0c;由于電子計算機的出現&#xff0c; 借助計算機可以實現大量的隨機抽樣試驗&#xff0c;為利用隨機試驗方法解決實際問題提供了便捷。 非常具代表性的例子是&#xff0c; 美國在第二次世界大戰期間研制原子彈的“曼哈頓計劃”中&#xff0c;為了…

HTTP/3.x協議詳解:基于QUIC的下一代Web傳輸協議

一、HTTP/3協議概述 HTTP/3是超文本傳輸協議&#xff08;HTTP&#xff09;的第三個正式版本&#xff0c;由IETF&#xff08;互聯網工程任務組&#xff09;于2022年正式標準化&#xff08;RFC 9114&#xff09;。其核心創新在于完全基于QUIC協議替代傳統TCP&#xff0c;結合UDP…

【SQL】使用UPDATE修改表字段的時候,遇到1054 或者1064的問題怎么辦?

我在使用python連接sql修改表格的時間字段的時候&#xff0c;遇到這樣一個問題&#xff1a;ProgrammingError: (pymysql.err.ProgrammingError) (1064, “You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the ri…

【字節跳動】數據挖掘面試題0013:怎么做男女二分類問題, 從抖音 app 提供的內容中。

文章大綱 ?? 一、問題定義與數據基礎數據源及預處理:?? 二、特征工程方案1. 文本特征2. 視覺特征3. 音頻與行為特征4. 上下文特征?? 三、模型選型與訓練1. 基礎模型對比2. 多模態融合模型3. 訓練技巧?? 四、評估與優化策略1. 評估指標2. 典型問題優化3. 算法偏差控制?…

HTTP請求走私漏洞

一、漏洞定義與核心原理HTTP請求走私&#xff08;HTTP Request Smuggling&#xff09;是一種利用前端服務器&#xff08;如代理、負載均衡器&#xff09;與后端服務器在解析HTTP請求時的不一致性&#xff0c;繞過安全機制并執行惡意操作的攻擊技術。其核心在于混淆請求邊界&…

Javaweb - 10.1 Servlet

目錄 Servlet 簡介 動態資源和靜態資源 Servlet 簡介 Servlet 開發流程 目標 開發過程 開發一個 web 類型的 module 開發一個 form 表單 開發一個 UserServlet 在 web..xml 為 userServlet 配置請求路徑 Edit Configurations 啟動項目 完&#xff01; Servlet 簡介…

手機能用酒精擦嗎?

對于電視、電腦屏幕來說&#xff0c;為了避免反光、改善顯示效果&#xff0c;會在屏幕表面覆上一層“抗反射涂層”。不同廠商設計的涂層材料并不相同&#xff0c;酒精作為良好的溶劑&#xff0c;確實會損壞可溶的涂層。手機作為觸控產品&#xff0c;通常會在屏幕表面增加“疏水…

【圖像處理基石】圖像超分辨率有哪些研究進展值得關注?

近年來&#xff0c;圖像超分辨率&#xff08;SR&#xff09;領域在深度學習技術的推動下取得了顯著進展&#xff0c;尤其在模型架構優化、計算效率提升和真實場景適應性等方面涌現出諸多創新。以下是基于最新研究的核心進展梳理&#xff1a; 一、高效大圖像處理&#xff1a;像素…

Windows系統下WSL從C盤遷移方案

原因&#xff1a;一開始裝WSL的時候放在了C盤&#xff0c;這下好了&#xff0c;跑了幾個深度學習模型訓練后&#xff0c;C盤快滿了&#xff0c;這可怎么辦&#xff1f;可愁壞了。沒關系&#xff0c;山人自有妙計。我們將WSL遷移到D盤或者E盤呀。一.遷移操作步驟前期準備&#x…

金融時間序列機器學習訓練前的數據格式驗證系統設計與實現

金融時間序列機器學習訓練前的數據格式驗證系統設計與實現 前言 在機器學習項目中&#xff0c;數據質量是決定模型成功的關鍵因素。特別是在金融時間序列分析領域&#xff0c;原始數據往往需要經過復雜的預處理才能用于模型訓練。本文將詳細介紹一個完整的數據格式驗證系統&…

cocos2dx3.x項目升級到xcode15以上的iconv與duplicate symbols報錯問題

cocos2dx3.x項目升級xcode15以上后會有幾處報錯。1. CCFontAtlas.cpp文件下的iconv與iconv_close的報錯。修改如下&#xff1a;// iconv_close(_iconv);iconv_close((iconv_t)_iconv);iconv((iconv_t)_iconv, (char**)&pin, &inLen, &pout, &outLen); /…