Skip to main content
Glama
xiaonieli7

Flight Ticket MCP Server

by xiaonieli7
weather_tools.py19.8 kB
""" Weather Tools - 天气查询工具 提供根据经纬度查询天气信息的功能,使用Open-Meteo API """ import requests import json import logging from datetime import datetime, timedelta from typing import Dict, List, Optional, Any # 导入地理编码库 try: from geopy.geocoders import Nominatim GEOPY_AVAILABLE = True # 创建地理编码器实例 geolocator = Nominatim(user_agent="FlightTicketMCP_WeatherApp") except ImportError: GEOPY_AVAILABLE = False geolocator = None logger.warning("geopy库未安装,将仅支持预设城市的天气查询") # 初始化日志器 logger = logging.getLogger(__name__) def getWeatherByLocation(latitude: float, longitude: float, start_date: str = None, end_date: str = None) -> Dict[str, Any]: """ 根据经纬度查询天气信息 Args: latitude: 纬度 longitude: 经度 start_date: 开始日期 (YYYY-MM-DD格式),可选,默认为前一天 end_date: 结束日期 (YYYY-MM-DD格式),可选,默认为后一天 Returns: 包含天气查询结果的字典 """ logger.info(f"开始查询天气信息: 纬度={latitude}, 经度={longitude}, 开始日期={start_date}, 结束日期={end_date}") try: # 验证输入参数 if latitude is None or longitude is None: logger.warning("经纬度参数不能为空") return { "status": "error", "message": "经纬度参数不能为空", "error_code": "INVALID_PARAMS" } # 验证经纬度范围 if not (-90 <= latitude <= 90): logger.warning(f"纬度超出有效范围: {latitude}") return { "status": "error", "message": f"纬度必须在-90到90之间,当前值: {latitude}", "error_code": "INVALID_LATITUDE" } if not (-180 <= longitude <= 180): logger.warning(f"经度超出有效范围: {longitude}") return { "status": "error", "message": f"经度必须在-180到180之间,当前值: {longitude}", "error_code": "INVALID_LONGITUDE" } # 设置默认日期(今天和明天,共两天) now = datetime.now() if start_date is None: # 默认从今天开始 start_date = now.strftime('%Y-%m-%d') if end_date is None: # 默认到明天结束 default_end = now + timedelta(days=1) end_date = default_end.strftime('%Y-%m-%d') # 验证日期格式 try: start_dt = datetime.strptime(start_date, '%Y-%m-%d') end_dt = datetime.strptime(end_date, '%Y-%m-%d') logger.debug(f"日期解析成功: {start_dt} 到 {end_dt}") except ValueError as ve: logger.warning(f"日期格式错误: start_date={start_date}, end_date={end_date}") return { "status": "error", "message": "日期格式不正确,请使用YYYY-MM-DD格式", "error_code": "INVALID_DATE_FORMAT" } # 验证日期范围 if start_dt > end_dt: logger.warning(f"开始日期晚于结束日期: {start_date} > {end_date}") return { "status": "error", "message": "开始日期不能晚于结束日期", "error_code": "INVALID_DATE_RANGE" } # 构建API请求URL base_url = "https://api.open-meteo.com/v1/forecast" params = { "latitude": latitude, "longitude": longitude, "hourly": "temperature_2m", "models": "cma_grapes_global", "timezone": "Asia/Shanghai", "start_date": start_date, "end_date": end_date } logger.info(f"请求Open-Meteo API: {base_url}") logger.debug(f"请求参数: {params}") try: # 发送HTTP请求 response = requests.get(base_url, params=params, timeout=30) response.raise_for_status() weather_data = response.json() logger.debug(f"API响应数据: {json.dumps(weather_data, indent=2, ensure_ascii=False)}") # 调试:检查温度数据质量 if "hourly" in weather_data and "temperature_2m" in weather_data["hourly"]: temps = weather_data["hourly"]["temperature_2m"] none_count = sum(1 for temp in temps if temp is None) valid_count = len(temps) - none_count logger.debug(f"温度数据质量检查: 总数据点={len(temps)}, 有效数据点={valid_count}, None值数量={none_count}") # 格式化结果 result = { "status": "success", "latitude": weather_data.get("latitude"), "longitude": weather_data.get("longitude"), "timezone": weather_data.get("timezone"), "timezone_abbreviation": weather_data.get("timezone_abbreviation"), "elevation": weather_data.get("elevation"), "start_date": start_date, "end_date": end_date, "hourly_units": weather_data.get("hourly_units", {}), "hourly_data": weather_data.get("hourly", {}), "formatted_output": _format_weather_result(weather_data, latitude, longitude, start_date, end_date), "query_time": datetime.now().isoformat() } # 添加温度统计信息 if "hourly" in weather_data and "temperature_2m" in weather_data["hourly"]: temperatures = weather_data["hourly"]["temperature_2m"] if temperatures: # 过滤掉None值 valid_temperatures = [temp for temp in temperatures if temp is not None] if valid_temperatures: result["temperature_statistics"] = { "min_temperature": min(valid_temperatures), "max_temperature": max(valid_temperatures), "avg_temperature": round(sum(valid_temperatures) / len(valid_temperatures), 1), "data_points": len(temperatures), "valid_data_points": len(valid_temperatures) } else: logger.warning("所有温度数据都为None值") result["temperature_statistics"] = { "error": "无有效温度数据", "data_points": len(temperatures), "valid_data_points": 0 } logger.info(f"天气查询成功: 纬度={latitude}, 经度={longitude}") return result except requests.exceptions.RequestException as re: logger.error(f"API请求失败: {str(re)}", exc_info=True) return { "status": "error", "message": f"天气API请求失败: {str(re)}", "error_code": "API_REQUEST_FAILED" } except json.JSONDecodeError as je: logger.error(f"API响应解析失败: {str(je)}", exc_info=True) return { "status": "error", "message": f"天气API响应格式错误: {str(je)}", "error_code": "API_RESPONSE_INVALID" } except Exception as e: logger.error(f"查询天气信息失败: {str(e)}", exc_info=True) return { "status": "error", "message": f"查询天气信息失败: {str(e)}", "error_code": "WEATHER_QUERY_FAILED" } def _format_weather_result(weather_data: Dict[str, Any], latitude: float, longitude: float, start_date: str, end_date: str) -> str: """ 格式化天气查询结果 Args: weather_data: API返回的天气数据 latitude: 纬度 longitude: 经度 start_date: 开始日期 end_date: 结束日期 Returns: 格式化后的字符串 """ try: output = [] output.append("🌤️ 天气查询结果") output.append(f"📍 位置: 纬度 {weather_data.get('latitude', latitude)}, 经度 {weather_data.get('longitude', longitude)}") output.append(f"📅 查询时间段: {start_date} 到 {end_date}") output.append(f"🌍 时区: {weather_data.get('timezone', 'N/A')} ({weather_data.get('timezone_abbreviation', 'N/A')})") if "elevation" in weather_data: output.append(f"⛰️ 海拔: {weather_data['elevation']}米") output.append("") # 处理小时温度数据 if "hourly" in weather_data and "temperature_2m" in weather_data["hourly"]: times = weather_data["hourly"].get("time", []) temperatures = weather_data["hourly"].get("temperature_2m", []) if times and temperatures: # 按日期分组显示 daily_data = {} for time_str, temp in zip(times, temperatures): try: dt = datetime.fromisoformat(time_str.replace('T', ' ')) date_key = dt.strftime('%Y-%m-%d') hour = dt.strftime('%H:%M') if date_key not in daily_data: daily_data[date_key] = [] daily_data[date_key].append((hour, temp)) except: continue # 显示每日数据 for date, hourly_temps in daily_data.items(): output.append(f"📆 {date}") # 计算当日统计 day_temps = [temp for _, temp in hourly_temps if temp is not None] if day_temps: min_temp = min(day_temps) max_temp = max(day_temps) avg_temp = sum(day_temps) / len(day_temps) output.append(f" 🌡️ 温度范围: {min_temp:.1f}°C ~ {max_temp:.1f}°C (平均: {avg_temp:.1f}°C)") else: output.append(f" ❌ 当日无有效温度数据") # 显示部分小时数据(每4小时一次) sample_data = hourly_temps[::4] # 每4小时取一个样本 for hour, temp in sample_data[:6]: # 最多显示6个时间点 if temp is not None: output.append(f" {hour}: {temp}°C") else: output.append(f" {hour}: 无数据") output.append("") # 整体统计 all_temps = [temp for _, temp in temperatures if temp is not None] if all_temps: output.append("📊 整体统计:") output.append(f" 最低温度: {min(all_temps):.1f}°C") output.append(f" 最高温度: {max(all_temps):.1f}°C") output.append(f" 平均温度: {sum(all_temps)/len(all_temps):.1f}°C") output.append(f" 数据点数: {len(times)}个") else: output.append("❌ 未获取到温度数据") return "\n".join(output) except Exception as e: logger.error(f"格式化天气结果失败: {str(e)}", exc_info=True) return f"天气数据格式化失败: {str(e)}" # 主要城市经纬度数据 CITY_COORDINATES = { "北京": {"latitude": 39.9042, "longitude": 116.4074, "name": "北京"}, "上海": {"latitude": 31.2304, "longitude": 121.4737, "name": "上海"}, "广州": {"latitude": 23.1291, "longitude": 113.2644, "name": "广州"}, "深圳": {"latitude": 22.5431, "longitude": 114.0579, "name": "深圳"}, "成都": {"latitude": 30.5728, "longitude": 104.0668, "name": "成都"}, "武汉": {"latitude": 30.5928, "longitude": 114.3055, "name": "武汉"}, "西安": {"latitude": 34.3416, "longitude": 108.9398, "name": "西安"}, "杭州": {"latitude": 30.2741, "longitude": 120.1551, "name": "杭州"}, "重庆": {"latitude": 29.5647, "longitude": 106.5507, "name": "重庆"}, "天津": {"latitude": 39.3434, "longitude": 117.3616, "name": "天津"}, "南京": {"latitude": 32.0603, "longitude": 118.7969, "name": "南京"}, "青岛": {"latitude": 36.0986, "longitude": 120.3719, "name": "青岛"}, "大连": {"latitude": 38.9140, "longitude": 121.6147, "name": "大连"}, "宁波": {"latitude": 29.8683, "longitude": 121.5440, "name": "宁波"}, "厦门": {"latitude": 24.4798, "longitude": 118.0819, "name": "厦门"}, "福州": {"latitude": 26.0745, "longitude": 119.2965, "name": "福州"}, "无锡": {"latitude": 31.4912, "longitude": 120.3124, "name": "无锡"}, "合肥": {"latitude": 31.8206, "longitude": 117.2272, "name": "合肥"}, "昆明": {"latitude": 25.0389, "longitude": 102.7183, "name": "昆明"}, "哈尔滨": {"latitude": 45.8038, "longitude": 126.5349, "name": "哈尔滨"}, "沈阳": {"latitude": 41.8057, "longitude": 123.4315, "name": "沈阳"}, "长春": {"latitude": 43.8171, "longitude": 125.3235, "name": "长春"}, "石家庄": {"latitude": 38.0428, "longitude": 114.5149, "name": "石家庄"}, "长沙": {"latitude": 28.2282, "longitude": 112.9388, "name": "长沙"}, "郑州": {"latitude": 34.7466, "longitude": 113.6254, "name": "郑州"}, "南昌": {"latitude": 28.6820, "longitude": 115.8581, "name": "南昌"}, "贵阳": {"latitude": 26.6470, "longitude": 106.6302, "name": "贵阳"}, "兰州": {"latitude": 36.0611, "longitude": 103.8343, "name": "兰州"}, "海口": {"latitude": 20.0458, "longitude": 110.3417, "name": "海口"}, "三亚": {"latitude": 18.2528, "longitude": 109.5122, "name": "三亚"}, "银川": {"latitude": 38.4872, "longitude": 106.2309, "name": "银川"}, "西宁": {"latitude": 36.6171, "longitude": 101.7782, "name": "西宁"}, "呼和浩特": {"latitude": 40.8414, "longitude": 111.7519, "name": "呼和浩特"}, "乌鲁木齐": {"latitude": 43.8256, "longitude": 87.6168, "name": "乌鲁木齐"}, "拉萨": {"latitude": 29.6625, "longitude": 91.1112, "name": "拉萨"}, "南宁": {"latitude": 22.8170, "longitude": 108.3669, "name": "南宁"}, # 港澳台 "香港": {"latitude": 22.3193, "longitude": 114.1694, "name": "香港"}, "澳门": {"latitude": 22.1987, "longitude": 113.5439, "name": "澳门"}, "台北": {"latitude": 25.0330, "longitude": 121.5654, "name": "台北"}, } def getWeatherByCity(city_name: str, start_date: str = None, end_date: str = None) -> Dict[str, Any]: """ 根据城市名查询天气信息 Args: city_name: 城市名(如:武汉、北京、上海等,支持全球任意城市) start_date: 开始日期 (YYYY-MM-DD格式),可选,默认为今天 end_date: 结束日期 (YYYY-MM-DD格式),可选,默认为明天 Returns: 包含天气查询结果的字典 """ logger.info(f"根据城市名查询天气: {city_name}") try: # 清理输入的城市名 city_name = city_name.strip() city_coord = None city_display_name = city_name coordinate_source = "unknown" # 方法1:首先尝试从预设字典查找(更快更准确) search_keys = [ city_name, city_name.replace("市", ""), # 去掉"市"后缀 city_name.replace("省", ""), # 去掉"省"后缀 ] for key in search_keys: if key in CITY_COORDINATES: city_coord = CITY_COORDINATES[key] city_display_name = city_coord["name"] coordinate_source = "preset_dict" logger.info(f"从预设字典找到城市 '{city_name}' 的坐标: 纬度={city_coord['latitude']}, 经度={city_coord['longitude']}") break #如果预设字典中没有,尝试使用geopy进行地理编码 if not city_coord and GEOPY_AVAILABLE and geolocator: try: logger.info(f"使用geopy查找城市 '{city_name}' 的坐标...") location = geolocator.geocode(city_name, timeout=10) if location: city_coord = { "latitude": location.latitude, "longitude": location.longitude, "name": city_name } city_display_name = location.address if location.address else city_name coordinate_source = "geopy" logger.info(f"通过geopy找到城市 '{city_name}' 的坐标: 纬度={city_coord['latitude']}, 经度={city_coord['longitude']}") logger.debug(f"geopy返回的完整地址: {location.address}") else: logger.warning(f"geopy无法找到城市 '{city_name}' 的坐标") except Exception as geo_e: logger.warning(f"geopy查询失败: {str(geo_e)}") # 如果两种方法都没有找到坐标 if not city_coord: error_message = f"无法找到城市 '{city_name}' 的坐标信息。" if GEOPY_AVAILABLE: error_message += "请检查城市名称是否正确。" else: error_message += f"当前仅支持预设城市:不支持" logger.warning(error_message) return { "status": "error", "message": error_message, "error_code": "CITY_NOT_FOUND", "coordinate_source": coordinate_source, "geopy_available": GEOPY_AVAILABLE } # 调用原有的经纬度查询函数 result = getWeatherByLocation( latitude=city_coord["latitude"], longitude=city_coord["longitude"], start_date=start_date, end_date=end_date ) # 在结果中添加城市信息 if result.get("status") == "success": result["city_name"] = city_display_name result["city_input"] = city_name result["coordinate_source"] = coordinate_source # 更新格式化输出,添加城市名称 if "formatted_output" in result: formatted_lines = result["formatted_output"].split('\n') if formatted_lines: # 替换第一行,添加城市名称 formatted_lines[0] = f"🌤️ {city_display_name}天气查询结果" result["formatted_output"] = '\n'.join(formatted_lines) return result except Exception as e: logger.error(f"根据城市名查询天气失败: {str(e)}", exc_info=True) return { "status": "error", "message": f"查询城市 '{city_name}' 天气失败: {str(e)}", "error_code": "CITY_WEATHER_QUERY_FAILED" }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xiaonieli7/FlightTicketMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server