using ModelContextProtocol; using ModelContextProtocol.Server; using System.ComponentModel; using System.Text.Json; using System.Text.Json.Serialization; namespace AspNetCoreMcpServer.Tools; [McpServerToolType] public sealed class WeatherTools { private readonly IHttpClientFactory _httpClientFactory; private const string WeatherApiUrl = "http://10.1.57.124:8002/ledclock/weather.txt"; public WeatherTools(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [McpServerTool, Description("获取今天的天气信息")] [McpMeta("category", "weather")] [McpMeta("dataSource", "local")] public async Task GetTodayWeather() { try { var weatherData = await FetchWeatherDataAsync(); if (weatherData?.Data == null || weatherData.Data.Count == 0) { return "无法获取天气数据"; } // 获取今天的日期 var today = DateTime.Now.ToString("yyyy-MM-dd"); // 查找今天的天气数据 var todayWeather = weatherData.Data.FirstOrDefault(d => d.Date == today); if (todayWeather == null) { // 如果没有今天的数据,返回第一天的数据 todayWeather = weatherData.Data.First(); return $""" 今天天气信息 (数据日期: {todayWeather.Date}) 地区: {weatherData.Province}/{weatherData.City} 天气: {todayWeather.WeatherDescription} 温度: {todayWeather.MinTemperature}°C ~ {todayWeather.MaxTemperature}°C 风向风力: {todayWeather.WindDescription} 注意: 未找到今日准确数据,显示的是最近日期的天气信息。 """; } return $""" 今天天气信息 地区: {weatherData.Province}/{weatherData.City} 日期: {todayWeather.Date} 天气: {todayWeather.WeatherDescription} 温度: {todayWeather.MinTemperature}°C ~ {todayWeather.MaxTemperature}°C 风向风力: {todayWeather.WindDescription} {GetWeatherAdvice(todayWeather)} """; } catch (HttpRequestException ex) { return $"网络请求失败: {ex.Message}"; } catch (JsonException ex) { return $"数据解析失败: {ex.Message}"; } catch (Exception ex) { return $"获取天气信息时发生错误: {ex.Message}"; } } [McpServerTool, Description("获取未来几天的天气预报")] [McpMeta("category", "weather")] [McpMeta("dataSource", "local")] public async Task GetWeatherForecast( [Description("预报天数,默认为3天,最多15天")] int days = 3) { try { if (days < 1 || days > 15) { return "预报天数必须在1-15天之间"; } var weatherData = await FetchWeatherDataAsync(); if (weatherData?.Data == null || weatherData.Data.Count == 0) { return "无法获取天气数据"; } var forecasts = weatherData.Data.Take(days).Select((weather, index) => { var dateLabel = index == 0 ? "今天" : index == 1 ? "明天" : index == 2 ? "后天" : weather.Date; return $""" {dateLabel} ({weather.Date}) 天气: {weather.WeatherDescription} 温度: {weather.MinTemperature}°C ~ {weather.MaxTemperature}°C 风向风力: {weather.WindDescription} """; }); return $""" {weatherData.Province}/{weatherData.City} 未来{days}天天气预报 {string.Join("\n---\n", forecasts)} """; } catch (Exception ex) { return $"获取天气预报时发生错误: {ex.Message}"; } } [McpServerTool, Description("获取指定日期的天气信息")] [McpMeta("category", "weather")] [McpMeta("dataSource", "local")] public async Task GetWeatherByDate( [Description("日期,格式: yyyy-MM-dd,例如: 2025-11-15")] string date) { try { // 验证日期格式 if (!DateTime.TryParseExact(date, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out var parsedDate)) { return "日期格式错误,请使用 yyyy-MM-dd 格式,例如: 2025-11-15"; } var weatherData = await FetchWeatherDataAsync(); if (weatherData?.Data == null || weatherData.Data.Count == 0) { return "无法获取天气数据"; } var targetWeather = weatherData.Data.FirstOrDefault(d => d.Date == date); if (targetWeather == null) { var availableDates = string.Join(", ", weatherData.Data.Select(d => d.Date).Take(5)); return $"未找到日期 {date} 的天气数据\n\n可用的日期: {availableDates}..."; } var daysFromNow = (parsedDate - DateTime.Now).Days; var dateLabel = daysFromNow switch { 0 => "今天", 1 => "明天", 2 => "后天", _ => daysFromNow > 0 ? $"{daysFromNow}天后" : $"{-daysFromNow}天前" }; return $""" {dateLabel}的天气信息 地区: {weatherData.Province}/{weatherData.City} 日期: {targetWeather.Date} 天气: {targetWeather.WeatherDescription} 温度: {targetWeather.MinTemperature}°C ~ {targetWeather.MaxTemperature}°C 风向风力: {targetWeather.WindDescription} {GetWeatherAdvice(targetWeather)} """; } catch (Exception ex) { return $"获取天气信息时发生错误: {ex.Message}"; } } [McpServerTool, Description("获取完整的天气数据概览")] [McpMeta("category", "weather")] [McpMeta("dataSource", "local")] public async Task GetWeatherOverview() { try { var weatherData = await FetchWeatherDataAsync(); if (weatherData?.Data == null || weatherData.Data.Count == 0) { return "无法获取天气数据"; } // 统计信息 var totalDays = weatherData.Data.Count; var avgMaxTemp = weatherData.Data.Average(d => int.Parse(d.MaxTemperature)); var avgMinTemp = weatherData.Data.Average(d => int.Parse(d.MinTemperature)); var rainyDays = weatherData.Data.Count(d => d.WeatherDescription.Contains("雨")); var sunnyDays = weatherData.Data.Count(d => d.WeatherDescription.Contains("晴")); // 今天的天气 var today = DateTime.Now.ToString("yyyy-MM-dd"); var todayWeather = weatherData.Data.FirstOrDefault(d => d.Date == today) ?? weatherData.Data.First(); return $""" 天气数据概览 地区: {weatherData.Province}/{weatherData.City} 数据范围: {weatherData.Data.First().Date} 至 {weatherData.Data.Last().Date} 今日天气: {todayWeather.WeatherDescription} | {todayWeather.MinTemperature}°C ~ {todayWeather.MaxTemperature}°C | {todayWeather.WindDescription} 统计信息 (未来{totalDays}天): - 平均最高温度: {avgMaxTemp:F1}°C - 平均最低温度: {avgMinTemp:F1}°C - 晴天: {sunnyDays}天 - 雨天: {rainyDays}天 温馨提示: {GetOverviewAdvice(weatherData.Data)} """; } catch (Exception ex) { return $"获取天气概览时发生错误: {ex.Message}"; } } // ==================== 私有辅助方法 ==================== private async Task FetchWeatherDataAsync() { var client = _httpClientFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(10); using var response = await client.GetAsync(WeatherApiUrl); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; return JsonSerializer.Deserialize(content, options); } private static string GetWeatherAdvice(WeatherData weather) { var advice = new List(); // 根据天气状况给建议 if (weather.WeatherDescription.Contains("雨")) { advice.Add("☔ 记得带伞"); } else if (weather.WeatherDescription.Contains("晴")) { advice.Add("☀️ 天气晴朗,适合外出"); } else if (weather.WeatherDescription.Contains("多云")) { advice.Add("⛅ 天气多云,可以外出活动"); } // 根据温度给建议 var maxTemp = int.Parse(weather.MaxTemperature); var minTemp = int.Parse(weather.MinTemperature); var tempDiff = maxTemp - minTemp; if (maxTemp > 30) { advice.Add("🌡️ 高温天气,注意防暑降温"); } else if (minTemp < 10) { advice.Add("🧥 气温较低,注意保暖"); } if (tempDiff > 10) { advice.Add("👔 昼夜温差大,适当增减衣物"); } return advice.Count > 0 ? $"温馨提示: {string.Join(";", advice)}" : ""; } private static string GetOverviewAdvice(List weatherList) { var adviceList = new List(); // 检查是否有连续雨天 var consecutiveRainyDays = 0; var maxConsecutiveRainy = 0; foreach (var weather in weatherList) { if (weather.WeatherDescription.Contains("雨")) { consecutiveRainyDays++; maxConsecutiveRainy = Math.Max(maxConsecutiveRainy, consecutiveRainyDays); } else { consecutiveRainyDays = 0; } } if (maxConsecutiveRainy >= 3) { adviceList.Add($"未来有连续{maxConsecutiveRainy}天降雨,注意出行安全"); } // 检查温度变化 var firstTemp = int.Parse(weatherList.First().MaxTemperature); var lastTemp = int.Parse(weatherList.Last().MaxTemperature); var tempChange = lastTemp - firstTemp; if (Math.Abs(tempChange) > 5) { adviceList.Add(tempChange > 0 ? "未来气温逐渐上升" : "未来气温逐渐下降"); } return adviceList.Count > 0 ? string.Join(";", adviceList) : "天气状况平稳"; } } // ==================== 数据模型 ==================== internal class WeatherResponse { [JsonPropertyName("code")] public int Code { get; set; } [JsonPropertyName("message")] public string Message { get; set; } = ""; [JsonPropertyName("province")] public string Province { get; set; } = ""; [JsonPropertyName("city")] public string City { get; set; } = ""; [JsonPropertyName("data")] public List Data { get; set; } = new(); } internal class WeatherData { [JsonPropertyName("date")] public string Date { get; set; } = ""; [JsonPropertyName("weatherDescription")] public string WeatherDescription { get; set; } = ""; [JsonPropertyName("maxTemperature")] public string MaxTemperature { get; set; } = ""; [JsonPropertyName("minTemperature")] public string MinTemperature { get; set; } = ""; [JsonPropertyName("wea_img")] public string WeaImg { get; set; } = ""; [JsonPropertyName("windDescription")] public string WindDescription { get; set; } = ""; }