Skip to main content
Glama
xiaonieli7

Flight Ticket MCP Server

by xiaonieli7

getWeatherByLocation

Retrieve weather forecasts for flight planning by providing location coordinates. Returns current and next day weather data using Open-Meteo API to support travel decisions.

Instructions

天气信息查询 - 根据经纬度查询天气信息,使用Open-Meteo API。如果不提供日期,默认查询今天和明天的天气数据

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
end_dateNo
latitudeYes
longitudeYes
start_dateNo

Implementation Reference

  • Core handler function that implements the getWeatherByLocation tool. Validates inputs, queries Open-Meteo API for hourly temperature data, computes statistics, formats output, and handles errors.
    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"
            }
  • Tool registration in main.py using FastMCP @mcp.tool() decorator. This wrapper function defines the tool schema via type hints and docstring, and delegates to the core handler in weather_tools.
    @mcp.tool()
    def getWeatherByLocation(latitude: float, longitude: float, start_date: str = None, end_date: str = None):
        """天气信息查询 - 根据经纬度查询天气信息,使用Open-Meteo API。如果不提供日期,默认查询今天和明天的天气数据"""
        logger.debug(f"调用天气查询工具: latitude={latitude}, longitude={longitude}, start_date={start_date}, end_date={end_date}")
        return weather_tools.getWeatherByLocation(latitude, longitude, start_date, end_date)
  • Supporting helper function used by the handler to format raw API weather data into a user-friendly string output with daily temperature ranges and statistics.
    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)}" 
  • Data helper providing predefined latitude/longitude coordinates for major cities, used by getWeatherByCity (related tool).
    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": "台北"},
    }
  • Log statement confirming successful registration of tools including getWeatherByLocation.
    logger.info("MCP工具注册完成 - 已注册工具: searchFlightRoutes, getCurrentDate, getTransferFlightsByThreePlace, getWeatherByLocation, getWeatherByCity, getFlightInfo, getFlightStatus, getAirportFlights, getFlightsInArea, trackMultipleFlights")
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions the data source (Open-Meteo API) and default date behavior, but doesn't describe important behavioral traits such as rate limits, authentication requirements, error conditions, response format, or what specific weather data is returned. For a tool with 4 parameters and no annotation coverage, this leaves significant gaps in understanding how the tool behaves.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately concise with two sentences that each add value. The first sentence establishes the core purpose and method, while the second provides important default behavior information. There's no wasted language or redundancy, though it could be slightly more structured for clarity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (4 parameters, no output schema, no annotations), the description is insufficiently complete. It covers the basic purpose and default date behavior but misses critical information about parameter formats, return values, error handling, and differentiation from sibling tools. For a weather query tool that likely returns structured data, the lack of output description is particularly problematic.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage (none of the 4 parameters have descriptions in the schema), the description provides minimal parameter guidance. It only mentions that dates are optional and have default behavior, but doesn't explain what the latitude/longitude parameters represent (coordinate format, valid ranges), what date format to use, or the relationship between start_date and end_date. The description doesn't adequately compensate for the complete lack of schema documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: '天气信息查询 - 根据经纬度查询天气信息' (Weather information query - query weather information based on latitude and longitude). It specifies the verb (query) and resource (weather information) with the specific method (using latitude/longitude). However, it doesn't explicitly differentiate from its sibling 'getWeatherByCity' which likely queries by city name instead of coordinates.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides some usage context: '如果不提供日期,默认查询今天和明天的天气数据' (If no date is provided, it defaults to querying today and tomorrow's weather data). This gives guidance on default behavior when parameters are omitted. However, it doesn't explicitly state when to use this tool versus the sibling 'getWeatherByCity' or other weather-related alternatives, nor does it mention any prerequisites or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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