Skip to main content
Glama

Caiyun Weather MCP Server

by marcusbai
caiyun-service.ts19.9 kB
import axios from 'axios'; import { CaiyunWeatherResponse, skyconMap, skyconMapEn, precipitationTypeMap, precipitationTypeMapEn, AirQualityTrend, PrimaryPollutant } from './types.js'; export class CaiyunWeatherService { private apiKey: string; private baseUrl = 'https://api.caiyunapp.com/v2.6'; private language: 'zh_CN' | 'en_US'; private unit: 'metric' | 'imperial'; constructor(apiKey: string, language: 'zh_CN' | 'en_US' = 'zh_CN', unit: 'metric' | 'imperial' = 'metric') { this.apiKey = apiKey; this.language = language; this.unit = unit; } /** * 获取实时天气数据 * @param longitude 经度 * @param latitude 纬度 * @returns 实时天气数据 */ async getRealtime(longitude: number, latitude: number): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/realtime`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 获取分钟级降水预报 * @param longitude 经度 * @param latitude 纬度 * @returns 分钟级降水预报数据 */ async getMinutely(longitude: number, latitude: number): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/minutely`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 获取小时级天气预报 * @param longitude 经度 * @param latitude 纬度 * @param hourlysteps 小时数(1-360) * @returns 小时级天气预报数据 */ async getHourly(longitude: number, latitude: number, hourlysteps: number = 24): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/hourly`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { hourlysteps: Math.min(Math.max(hourlysteps, 1), 360), lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 获取每日天气预报 * @param longitude 经度 * @param latitude 纬度 * @param dailysteps 天数(1-15) * @returns 每日天气预报数据 */ async getDaily(longitude: number, latitude: number, dailysteps: number = 5): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/daily`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { dailysteps: Math.min(Math.max(dailysteps, 1), 15), lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 获取天气预警信息 * @param longitude 经度 * @param latitude 纬度 * @returns 天气预警信息 */ async getAlert(longitude: number, latitude: number): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/weather`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { alert: true, dailysteps: 1, hourlysteps: 1, lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 获取综合天气数据 * @param longitude 经度 * @param latitude 纬度 * @param dailysteps 天数(1-15) * @param hourlysteps 小时数(1-360) * @param alert 是否包含预警信息 * @returns 综合天气数据 */ async getWeather( longitude: number, latitude: number, dailysteps: number = 5, hourlysteps: number = 24, alert: boolean = true ): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/weather`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { dailysteps: Math.min(Math.max(dailysteps, 1), 15), hourlysteps: Math.min(Math.max(hourlysteps, 1), 360), alert, lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 将天气代码转换为可读文本 * @param skycon 天气代码 * @returns 天气描述文本 */ getSkyconText(skycon: string): string { return this.language === 'zh_CN' ? skyconMap[skycon] || skycon : skyconMapEn[skycon] || skycon; } /** * 将降水类型转换为可读文本 * @param type 降水类型 * @returns 降水类型描述文本 */ getPrecipitationTypeText(type?: string): string { if (!type) return this.language === 'zh_CN' ? '未知' : 'Unknown'; return this.language === 'zh_CN' ? precipitationTypeMap[type] || type : precipitationTypeMapEn[type] || type; } /** * 格式化实时天气数据 * @param data 彩云天气API响应 * @returns 格式化后的实时天气数据 */ formatRealtimeData(data: CaiyunWeatherResponse) { const realtime = data.result.realtime; if (!realtime) { throw new Error('没有实时天气数据'); } return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), temperature: realtime.temperature, apparent_temperature: realtime.apparent_temperature, humidity: realtime.humidity, weather: this.getSkyconText(realtime.skycon), weather_code: realtime.skycon, wind: { speed: realtime.wind.speed, direction: realtime.wind.direction }, pressure: realtime.pressure, visibility: realtime.visibility, precipitation: { local: { intensity: realtime.precipitation.local.intensity, type: this.getPrecipitationTypeText(realtime.precipitation.local.type) }, nearest: realtime.precipitation.nearest ? { intensity: realtime.precipitation.nearest.intensity, type: this.getPrecipitationTypeText(realtime.precipitation.nearest.type), distance: realtime.precipitation.nearest.distance } : { intensity: 0, type: this.getPrecipitationTypeText('none'), distance: 0 } }, air_quality: { aqi: realtime.air_quality.aqi.chn, pm25: realtime.air_quality.pm25, pm10: realtime.air_quality.pm10, o3: realtime.air_quality.o3, so2: realtime.air_quality.so2, no2: realtime.air_quality.no2, co: realtime.air_quality.co, description: realtime.air_quality.description.chn, trend: realtime.air_quality.trend, primary_pollutant: realtime.air_quality.primary_pollutant }, life_index: { comfort: realtime.life_index.comfort?.desc || '暂无数据', ultraviolet: realtime.life_index.ultraviolet?.desc || '暂无数据', sport: realtime.life_index.sport?.desc || '暂无数据', travel: realtime.life_index.travel?.desc || '暂无数据', cold: realtime.life_index.cold?.desc || '暂无数据', carWashing: realtime.life_index.carWashing?.desc || '暂无数据', dressing: realtime.life_index.dressing?.desc || '暂无数据' } }; } /** * 格式化分钟级降水预报数据 * @param data 彩云天气API响应 * @returns 格式化后的分钟级降水预报数据 */ formatMinutelyData(data: CaiyunWeatherResponse) { const minutely = data.result.minutely; if (!minutely) { throw new Error('没有分钟级降水预报数据'); } return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), description: minutely.description, precipitation: minutely.precipitation, precipitation_2h: minutely.precipitation_2h, probability: minutely.probability }; } /** * 格式化小时级天气预报数据 * @param data 彩云天气API响应 * @returns 格式化后的小时级天气预报数据 */ formatHourlyData(data: CaiyunWeatherResponse) { const hourly = data.result.hourly; if (!hourly) { throw new Error('没有小时级天气预报数据'); } return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), description: hourly.description, forecast: hourly.temperature.map((temp, index) => ({ time: temp.datetime, temperature: temp.value, apparent_temperature: hourly.apparent_temperature?.[index]?.value, weather: this.getSkyconText(hourly.skycon[index].value), weather_code: hourly.skycon[index].value, wind: { speed: hourly.wind[index].speed, direction: hourly.wind[index].direction }, humidity: hourly.humidity[index].value, cloudrate: hourly.cloudrate[index].value, pressure: hourly.pressure[index].value, visibility: hourly.visibility[index].value, precipitation: { value: hourly.precipitation[index].value, probability: hourly.precipitation[index].probability, type: this.getPrecipitationTypeText(hourly.precipitation[index].type) }, air_quality: { aqi: hourly.air_quality.aqi[index].value.chn, pm25: hourly.air_quality.pm25[index].value, trend: hourly.air_quality.aqi[index].trend, primary_pollutant: hourly.air_quality.primary_pollutant?.[index]?.value } })) }; } /** * 格式化每日天气预报数据 * @param data 彩云天气API响应 * @returns 格式化后的每日天气预报数据 */ formatDailyData(data: CaiyunWeatherResponse) { const daily = data.result.daily; if (!daily) { throw new Error('没有每日天气预报数据'); } return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), forecast: daily.temperature.map((temp, index) => ({ date: temp.date, temperature: { max: temp.max, min: temp.min, avg: temp.avg }, temperature_day: daily.temperature_08h_20h?.[index] ? { max: daily.temperature_08h_20h[index].max, min: daily.temperature_08h_20h[index].min, avg: daily.temperature_08h_20h[index].avg } : undefined, temperature_night: daily.temperature_20h_32h?.[index] ? { max: daily.temperature_20h_32h[index].max, min: daily.temperature_20h_32h[index].min, avg: daily.temperature_20h_32h[index].avg } : undefined, weather: this.getSkyconText(daily.skycon[index].value), weather_code: daily.skycon[index].value, weather_day: this.getSkyconText(daily.skycon_08h_20h[index].value), weather_day_code: daily.skycon_08h_20h[index].value, weather_night: this.getSkyconText(daily.skycon_20h_32h[index].value), weather_night_code: daily.skycon_20h_32h[index].value, wind: { speed: daily.wind[index].avg.speed, direction: daily.wind[index].avg.direction }, wind_day: daily.wind_08h_20h?.[index] ? { speed: daily.wind_08h_20h[index].avg.speed, direction: daily.wind_08h_20h[index].avg.direction } : undefined, wind_night: daily.wind_20h_32h?.[index] ? { speed: daily.wind_20h_32h[index].avg.speed, direction: daily.wind_20h_32h[index].avg.direction } : undefined, humidity: daily.humidity[index].avg, cloudrate: daily.cloudrate[index].avg, pressure: daily.pressure[index].avg, visibility: daily.visibility[index].avg, precipitation: { max: daily.precipitation[index].max, min: daily.precipitation[index].min, avg: daily.precipitation[index].avg, probability: daily.precipitation[index].probability, type: this.getPrecipitationTypeText(daily.precipitation[index].type) }, air_quality: { aqi: daily.air_quality.aqi[index].avg.chn, pm25: daily.air_quality.pm25[index].avg, trend: daily.air_quality.aqi[index].trend, primary_pollutant: daily.air_quality.primary_pollutant?.[index]?.value }, astro: { sunrise: daily.astro[index].sunrise.time, sunset: daily.astro[index].sunset.time }, life_index: { comfort: daily.life_index.comfort?.[index]?.desc || '暂无数据', ultraviolet: daily.life_index.ultraviolet?.[index]?.desc || '暂无数据', carWashing: daily.life_index.carWashing?.[index]?.desc || '暂无数据', dressing: daily.life_index.dressing?.[index]?.desc || '暂无数据', coldRisk: daily.life_index.coldRisk?.[index]?.desc || '暂无数据', sport: daily.life_index.sport?.[index]?.desc, travel: daily.life_index.travel?.[index]?.desc } })) }; } /** * 格式化天气预警信息 * @param data 彩云天气API响应 * @returns 格式化后的天气预警信息 */ formatAlertData(data: CaiyunWeatherResponse) { const alert = data.result.alert; if (!alert) { return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), alerts: [] }; } return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), alerts: alert.content.map(item => ({ title: item.title, description: item.description, code: item.code, source: item.source, location: item.location, region: { province: item.province, city: item.city, county: item.county, adcode: item.adcode }, pub_time: new Date(item.pubtimestamp * 1000).toISOString() })) }; } /** * 格式化综合天气数据 * @param data 彩云天气API响应 * @returns 格式化后的综合天气数据 */ formatWeatherData(data: CaiyunWeatherResponse) { const result: any = { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), forecast_keypoint: data.result.forecast_keypoint }; if (data.result.realtime) { result.realtime = this.formatRealtimeData(data); } if (data.result.minutely) { result.minutely = this.formatMinutelyData(data); } if (data.result.hourly) { result.hourly = this.formatHourlyData(data); } if (data.result.daily) { result.daily = this.formatDailyData(data); } if (data.result.alert) { result.alert = this.formatAlertData(data); } return result; } /** * 获取空气质量趋势 * @param longitude 经度 * @param latitude 纬度 * @returns 空气质量趋势数据 */ async getAirQualityTrend(longitude: number, latitude: number): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/hourly`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { hourlysteps: 24, lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 格式化空气质量趋势数据 * @param data 彩云天气API响应 * @returns 格式化后的空气质量趋势数据 */ formatAirQualityTrendData(data: CaiyunWeatherResponse) { const hourly = data.result.hourly; if (!hourly) { throw new Error('没有小时级天气数据'); } return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), trend: hourly.air_quality.aqi.map((aqi, index) => ({ time: aqi.datetime, aqi: aqi.value.chn, trend: aqi.trend, primary_pollutant: hourly.air_quality.primary_pollutant?.[index]?.value })) }; } /** * 获取详细生活指数 * @param longitude 经度 * @param latitude 纬度 * @returns 详细生活指数数据 */ async getDetailedLifeIndex(longitude: number, latitude: number): Promise<CaiyunWeatherResponse> { try { const url = `${this.baseUrl}/${this.apiKey}/${longitude},${latitude}/daily`; const response = await axios.get<CaiyunWeatherResponse>(url, { params: { dailysteps: 1, lang: this.language, unit: this.unit } }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`彩云天气API错误: ${error.response?.data?.error || error.message}`); } throw error; } } /** * 格式化详细生活指数数据 * @param data 彩云天气API响应 * @returns 格式化后的详细生活指数数据 */ formatDetailedLifeIndexData(data: CaiyunWeatherResponse) { const daily = data.result.daily; if (!daily) { throw new Error('没有每日天气数据'); } const today = daily.life_index; return { location: data.location, server_time: new Date(data.server_time * 1000).toISOString(), life_index: { comfort: { index: today.comfort?.[0]?.index, desc: today.comfort?.[0]?.desc || '暂无数据' }, ultraviolet: { index: today.ultraviolet?.[0]?.index, desc: today.ultraviolet?.[0]?.desc || '暂无数据' }, carWashing: { index: today.carWashing?.[0]?.index, desc: today.carWashing?.[0]?.desc || '暂无数据' }, dressing: { index: today.dressing?.[0]?.index, desc: today.dressing?.[0]?.desc || '暂无数据' }, coldRisk: { index: today.coldRisk?.[0]?.index, desc: today.coldRisk?.[0]?.desc || '暂无数据' }, sport: today.sport?.[0] ? { index: today.sport[0]?.index, desc: today.sport[0]?.desc || '暂无数据' } : undefined, travel: today.travel?.[0] ? { index: today.travel[0]?.index, desc: today.travel[0]?.desc || '暂无数据' } : undefined } }; } }

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/marcusbai/caiyun-weather-mcp'

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