天气数据服务

一个基于OpenWeatherMap API的天气数据服务,提供实时天气和5天天气预报功能,支持多种单位和缓存优化。

  • by milk19
  • 本地部署
  • 更新于
在 GitHub 查看 milk19/mcp-server

使用说明

mcp服务器

{ “name”:“天气mcp”, “版本”:“1.0.0”, “description”:“Weather MCP server for Cline using OpenWeatherMap API”, “main”:“build/index.js”, “脚本”:{ “构建”:“tsc”, “start”:“node-build/index.js”, “dev”:“ts节点src/index.ts” }, 作者 “许可证”:“MIT”, “依赖关系”:{ “@modelcontextprotocol/server”:“^0.5.0”, “轴”:“^1.6.7”, “节点缓存”:“^5.1.2” }, “devDependencies”: { “@types/node”:“^20.11.5”, “ts节点”:“^10.9.2”, “打字稿”:“^5.3.3” } } { “编译器选项”:{ “目标”:“es2020”, “模块”:“commonjs”, “lib”:[“es2020”], “outDir”:“./build”, “rootDir”:“./src”, “严格”:正确, “esModuleInterop”:正确, “forceConsistent-CasingInFileName”:正确, “skipLibCheck”:true }, “包括”:[“src/**/*”], “exclude”:[“node_modules”] } 进口{ 服务器, McpError, 错误代码, 工具, 工具参数, 工具执行请求模式, ListResourcesRequestSchema, ReadResourceRequestSchema }来自“@modelcontextprotocol/server”; 从“axios”导入axios; 从“节点缓存”导入NodeCache;

// 日志级别 const LOGLEVEL=进程.env。LOGLEVEL ||“信息”; const DEBUG=LOGLEVEL===“DEBUG”;

// 天气API配置 接口WeatherConfig{ apiKey:字符串; baseUrl:字符串; 单位:公制|英制|标准; }

// 缓存配置(秒) const CACHE_TTL={ CURRENT_WEATHER: 30 * 60, // 30分钟 FORECAST: 60 * 60, // 1小时 };

// 主类 类WeatherMcpServer{ 私有配置:WeatherConfig; 私有服务器:服务器; 私有缓存:NodeCache;

构造者(){ console.error(“[Setup]正在初始化天气MCP服务器…”);

// 获取API密钥
const apiKey = process.env.OPENWEATHERMAP_API_KEY;
if (!apiKey) {
  console.error("[Error] Missing OPENWEATHERMAP_API_KEY environment variable");
  process.exit(1);
}

// 初始化配置
this.config = {
  apiKey,
  baseUrl: "https://api.openweathermap.org/data/2.5",
  units: (process.env.WEATHER_UNITS as any) || "metric"
};

// 初始化缓存
this.cache = new NodeCache({
  stdTTL: CACHE_TTL.CURRENT_WEATHER,
  checkperiod: 120
});

// 初始化MCP服务器
this.server = new Server();

// 注册处理程序
this.registerHandlers();

console.error("[Setup] Weather MCP server initialized successfully");

}

私有注册表处理程序():void{ // 工具执行处理 this.set.setRequestHandler(工具执行请求模式,异步(请求)=>{ console.error([Tool] Executing tool: ${request.params.name});

  try {
    switch (request.params.name) {
      case "get_current_weather":
        return await this.getCurrentWeather(request.params.params);
      case "get_weather_forecast":
        return await this.getWeatherForecast(request.params.params);
      default:
        throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
    }
  } catch (error) {
    console.error(`[Error] Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
    
    if (error instanceof McpError) {
      throw error;
    }
    
    return {
      content: [{
        type: "text",
        text: `Error: ${error instanceof Error ? error.message : String(error)}`
      }],
      isError: true
    };
  }
});

// 工具列表
this.server.setRequestHandler(ToolExecuteRequestSchema.definitions.toolsRequest, async () => {
  console.error("[Tools] Getting tools list");
  
  return {
    tools: [
      {
        name: "get_current_weather",
        description: "Get the current weather for a location",
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "City name (e.g., 'London'), or city name with country code (e.g., 'London,uk')"
            }
          },
          required: ["location"]
        }
      },
      {
        name: "get_weather_forecast",
        description: "Get a 5-day weather forecast for a location",
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "City name (e.g., 'London'), or city name with country code (e.g., 'London,uk')"
            },
            days: {
              type: "number",
              description: "Number of days to forecast (1-5)",
              default: 3
            }
          },
          required: ["location"]
        }
      }
    ]
  };
});

// 资源列表
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
  console.error("[Resources] Getting resources list");
  
  return {
    resources: [
      {
        uri: "file:///weather-mcp/readme.md",
        name: "Weather MCP Server README",
        mimeType: "text/markdown"
      }
    ]
  };
});

// 资源读取
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  console.error(`[Resources] Reading resource: ${request.params.uri}`);
  
  if (request.params.uri === "file:///weather-mcp/readme.md") {
    return {
      contents: [{
        uri: request.params.uri,
        mimeType: "text/markdown",
        text: this.getReadmeContent()
      }]
    };
  }
  
  throw new McpError(ErrorCode.ResourceNotFound, `Resource not found: ${request.params.uri}`);
});

}

私有异步getCurrentWeather(params:any):Promise<Tool。执行响应>{ this.validateLocation(params.location);

const location = params.location;
const cacheKey = `current:${location}`;

// 检查缓存
const cachedData = this.cache.get<any>(cacheKey);
if (cachedData) {
  console.error(`[Cache] Using cached current weather for: ${location}`);
  return cachedData;
}

try {
  console.error(`[API] Getting current weather for: ${location}`);
  
  const response = await axios.get(`${this.config.baseUrl}/weather`, {
    params: {
      q: location,
      appid: this.config.apiKey,
      units: this.config.units
    }
  });
  
  const data = response.data;
  
  // 格式化数据
  const result = {
    content: [{
      type: "text",
      text: this.formatCurrentWeather(data)
    }]
  };
  
  // 存入缓存
  this.cache.set(cacheKey, result, CACHE_TTL.CURRENT_WEATHER);
  
  return result;
} catch (error) {
  if (axios.isAxiosError(error) && error.response?.status === 404) {
    throw new McpError(ErrorCode.InvalidParams, `Location not found: ${location}`);
  }
  throw error;
}

}

私有异步getWeatherForecast(参数:任意):Promise<Tool。执行响应>{ this.validateLocation(params.location);

const location = params.location;
const days = Math.min(Math.max(parseInt(params.days || "3", 10), 1), 5);
const cacheKey = `forecast:${location}:${days}`;

// 检查缓存
const cachedData = this.cache.get<any>(cacheKey);
if (cachedData) {
  console.error(`[Cache] Using cached forecast for: ${location} (${days} days)`);
  return cachedData;
}

try {
  console.error(`[API] Getting ${days}-day forecast for: ${location}`);
  
  const response = await axios.get(`${this.config.baseUrl}/forecast`, {
    params: {
      q: location,
      appid: this.config.apiKey,
      units: this.config.units
    }
  });
  
  const data = response.data;
  
  // 格式化数据
  const result = {
    content: [{
      type: "text",
      text: this.formatForecast(data, days)
    }]
  };
  
  // 存入缓存
  this.cache.set(cacheKey, result, CACHE_TTL.FORECAST);
  
  return result;
} catch (error) {
  if (axios.isAxiosError(error) && error.response?.status === 404) {
    throw new McpError(ErrorCode.InvalidParams, `Location not found: ${location}`);
  }
  throw error;
}

}

private-validateLocation(位置:未知):断言位置是字符串{ if(位置类型!==“string”|| location.trim()==“”){ 抛出新的McpError(ErrorCode.InvalidParams,“需要有效位置”); } }

私有格式CurrentWeather(数据:任意):字符串{ const temp=Math.round(data.main.temp); const feelsLike=Math.round(data.main.feels_like); const unit=this配置单位===“英制”?“°F”:“°C”; const windUnit=this.config.units===“英制”?“mph”:“m/s”;

const weatherDescription = data.weather[0].description;
const weatherEmoji = this.getWeatherEmoji(data.weather[0].id);

return `# Current Weather in ${data.name}, ${data.sys.country} ${weatherEmoji}\n\n` +
       `**Temperature:** ${temp}${unit} (Feels like: ${feelsLike}${unit})\n` +
       `**Condition:** ${this.capitalizeFirst(weatherDescription)}\n` +
       `**Humidity:** ${data.main.humidity}%\n` +
       `**Wind:** ${data.wind.speed} ${windUnit} from ${this.getWindDirection(data.wind.deg)}\n` +
       `**Visibility:** ${(data.visibility / 1000).toFixed(1)} km\n` +
       `**Pressure:** ${data.main.pressure} hPa\n\n` +
       `*Last updated: ${new Date(data.dt * 1000).toLocaleString()}*`;

}

private formatForecast(数据:任意,天数:数字):字符串{ const city=数据城市名称; const country=数据城市国家; const unit=this配置单位===“英制”?“°F”:“°C”;

// 按天分组
const dailyForecasts: any = {};

// 处理5天/3小时预报数据
for (const item of data.list) {
  const date = new Date(item.dt * 1000);
  const day = date.toISOString().split('T')[0];
  
  if (!dailyForecasts[day]) {
    dailyForecasts[day] = {
      date: date,
      minTemp: Infinity,
      maxTemp: -Infinity,
      conditions: [],
      conditionIds: [],
      rainfall: 0,
      windSpeed: 0,
      entries: 0
    };
  }
  
  const forecast = dailyForecasts[day];
  forecast.entries++;
  
  // 更新温度
  forecast.minTemp = Math.min(forecast.minTemp, item.main.temp_min);
  forecast.maxTemp = Math.max(forecast.maxTemp, item.main.temp_max);
  
  // 更新天气状况
  forecast.conditions.push(item.weather[0].description);
  forecast.conditionIds.push(item.weather[0].id);
  
  // 统计降水量
  if (item.rain && item.rain['3h']) {
    forecast.rainfall += item.rain['3h'];
  }
  
  // 统计风速
  forecast.windSpeed += item.wind.speed;
}

// 选择最靠前的几天
const forecastDays = Object.keys(dailyForecasts).sort().slice(0, days);

// 构建结果字符串
let result = `# ${days}-Day Weather Forecast for ${city}, ${country}\n\n`;

for (const day of forecastDays) {
  const forecast = dailyForecasts[day];
  
  // 计算每天的平均值
  const avgWindSpeed = forecast.windSpeed / forecast.entries;
  
  // 找出最常见的天气状况
  const conditionCounts = forecast.conditionIds.reduce((acc: any, id: number) => {
    acc[id] = (acc[id] || 0) + 1;
    return acc;
  }, {});
  
  const mostCommonConditionId = Object.entries(conditionCounts)
    .sort((a: any, b: any) => b[1] - a[1])[0][0];
  
  const conditionIndex = forecast.conditionIds.indexOf(Number(mostCommonConditionId));
  const mostCommonCondition = forecast.conditions[conditionIndex];
  const weatherEmoji = this.getWeatherEmoji(Number(mostCommonConditionId));
  
  // 格式化日期
  const dateOptions: Intl.DateTimeFormatOptions = { weekday: 'long', month: 'short', day: 'numeric' };
  const formattedDate = forecast.date.toLocaleDateString('en-US', dateOptions);
  
  result += `## ${formattedDate} ${weatherEmoji}\n\n` +
            `**Temperature:** ${Math.round(forecast.minTemp)}${unit} to ${Math.round(forecast.maxTemp)}${unit}\n` +
            `**Conditions:** ${this.capitalizeFirst(mostCommonCondition)}\n`;
  
  if (forecast.rainfall > 0) {
    result += `**Precipitation:** ${forecast.rainfall.toFixed(1)} mm\n`;
  }
  
  result += `**Wind:** ${avgWindSpeed.toFixed(1)} ${this.config.units === "imperial" ? "mph" : "m/s"}\n\n`;
}

return result;

}

private getWeatherEmoji(weatherId:number):字符串{ // 根据OpenWeatherMap的天气ID选择表情 如果(weatherId>=200&&weatherId<300)返回“⛈️"; // 雷暴 如果(weatherId>=300&weatherId<400)返回“🌧️"; // 小雨 如果(weatherId>=500&&weatherId<600)返回“🌧️"; // 雨 如果(weatherId>=600&&weatherId<700)返回“❄️"; // 雪 如果(weatherId>=700&weatherId<800)返回“🌫️"; // 雾 如果(weatherId===800)返回“☀️"; // 晴天 如果(weatherId>800和weatherId<900)返回“☁️"; // 多云 return "🌡️"; // 默认 }

private getWindDirection(度数:数字):字符串{ const directions=[‘N’、‘NE’、‘E’、‘SE’、‘S’、‘SW’、‘W’、‘NW’]; const索引=数学整数(度/45)%8; 返回方向[索引]; }

私有大写First(str:string):string{ 返回str.charAt(0).toUpperCase()+str.slice(1); }

私有getReadmeContent():字符串{ return `#气象MCP服务器

Cline MCP服务器,使用OpenWeatherMap API提供实时天气数据。

特性

  • 获取全球任何地点的当前天气
  • 获取5天天气预报和详细信息
  • 支持公制和英制单位
  • 高效缓存,最大限度地减少API调用
  • 格式精美的降价响应

设置

  1. 在上注册免费API密钥 OpenWeatherMap

  2. 安装MCP服务器:

    ```json { “mcpServers”:{ “天气mcp”:{ “command”:“node”, “args”:[ “/path/to/weather-mcp/build/index.js” ], “env”:{ “OPENWEATHERMAP_API_KEY”:“YOUR_API_KEY”, “WEATHER_UNITS”:“公制” }, “disabled”:false, “自动批准”:[] } } } ```

  3. 将“WEATHER_UNITS”设置为“公制”(°C,m/s)或“英制”(°F,mph)

可用工具

get_current天气

获取指定位置的当前天气状况。

参数:

  • `地点:城市名称(例如“伦敦”),或带有国家代码的城市名称(如“英国伦敦”)

get_weather_forecast

获取指定位置的5天天气预报。

参数:

  • `地点:城市名称(例如“伦敦”),或带有国家代码的城市名称(如“英国伦敦”)
  • `days:要预测的天数(1-5,默认值:3)

输出示例

当前天气

```

英国伦敦当前天气☁️

温度: 12°C(感觉像:10°C) 条件: 阴云密布 湿度: 76% 风: 西南方向4.12 m/s 可见性: 10.0公里 压力: 1013百帕

最后更新时间:2024年1月15日下午2:30:00 ```

天气预报

```

英国伦敦三天天气预报

1月15日,星期一🌧️

温度: 10°C至13°C 条件: 小雨 降水量: 2.4毫米 风: 5.2米/秒

1月16日,星期二☁️

温度: 9°C至12°C 条件: 多云 风: 4.8米/秒

1月17日,星期三☀️

温度: 8°C至14°C 条件: 晴空 风: 3.5米/秒 ```

许可证

麻省理工学院 `; }

public start():无效{ this.server.start(); console.error(“[服务器]天气MCP服务器已成功启动”); } }

// 启动服务器 const服务器=新的WeatherMcpServer(); server.start();

MCP服务器开发协议

⚠️ 关键:测试前不要使用尝试完成⚠️

第一步:计划(计划模式)

  • 这个工具解决了什么问题?
  • 它将使用什么API/服务?
  • 身份验证要求是什么? □ API标准密钥 □ OAuth(需要单独的安装脚本) □ 其他证件

步骤2:实施(ACT模式)

  1. 独自创立

    • 对于web服务、JavaScript集成或Node.js环境:
      npx @modelcontextprotocol/create-server my-server
      cd my-server
      npm install
      
    • 对于数据科学、机器学习工作流或Python环境:
      pip install mcp
      # Or with uv (recommended)
      uv add "mcp[cli]"
      
  2. 核心实施

    • 使用MCP SDK
    • 实施全面日志记录
      • TypeScript(用于web/JS项目):
        console.error('[Setup] Initializing server...');
        console.error('[API] Request to endpoint:', endpoint);
        console.error('[Error] Failed with:', error);
        
      • Python(用于数据科学/ML项目):
        import logging
        logging.error('[Setup] Initializing server...')
        logging.error(f'[API] Request to endpoint: {endpoint}')
        logging.error(f'[Error] Failed with: {str(error)}')
        
    • 添加类型定义
    • 根据上下文处理错误
    • 必要时实施限速
  3. 配置

    • 如果需要,从用户处获取凭据
    • 添加到MCP设置:
      • 对于TypeScript项目:
        {
          "mcpServers": {
            "my-server": {
              "command": "node",
              "args": ["path/to/build/index.js"],
              "env": {
                "API_KEY": "key"
              },
              "disabled": false,
              "autoApprove": []
            }
          }
        }
        
      • 对于Python项目:
        # Directly with command line
        mcp install server.py -v API_KEY=key
        
        # Or in settings.json
        {
          "mcpServers": {
            "my-server": {
              "command": "python",
              "args": ["server.py"],
              "env": {
                "API_KEY": "key"
              },
              "disabled": false,
              "autoApprove": []
            }
          }
        }
        

步骤3:测试(阻塞器⛔️)

BEFORE using attempt_completion, I MUST verify: □ Have I tested EVERY tool? □ Have I confirmed success from the user for each test? □ Have I documented the test results?

如果任何答案为“否”,则不得使用try_coompletion。

  1. 测试每个工具(必需) □ 使用有效输入测试每个工具 □ 验证输出格式是否正确 ⚠️ 在所有工具都经过测试之前,不要继续

第四步:完成

❗ 停止并验证: □ 每个工具都经过了有效输入的测试 □ 每个工具的输出格式都是正确的

只有在所有工具都经过测试后,才能使用try_coompletion。

关键要求

  • ✓ 必须使用MCP SDK

  • ✓ 必须有全面的日志记录

  • ✓ 必须单独测试每个工具

  • ✓ 必须优雅地处理错误

  • ⛔️ 在完成之前,切勿跳过测试

    气象MCP服务器

Cline MCP服务器,使用OpenWeatherMap API提供实时天气数据。

特性

  • 获取全球任何地点的当前天气
  • 获取5天天气预报和详细信息
  • 支持公制和英制单位
  • 高效缓存,最大限度地减少API调用
  • 格式精美的降价响应

设置

  1. 在上注册免费API密钥 OpenWeatherMap

  2. 克隆此存储库:

    git clone https://github.com/yourusername/weather-mcp.git
    cd weather-mcp
    npm install
    npm run build
    
  3. 将MCP服务器添加到您的Cline设置中:

    {
      "mcpServers": {
        "weather-mcp": {
          "command": "node",
          "args": [
            "/path/to/weather-mcp/build/index.js"
          ],
          "env": {
            "OPENWEATHERMAP_API_KEY": "YOUR_API_KEY",
            "WEATHER_UNITS": "metric"
          },
          "disabled": false,
          "autoApprove": []
        }
      }
    }
    
  4. 集 WEATHER_UNITS 转换为“公制”(°C,m/s)或“英制”(°F,mph)

可用工具

get_current天气

获取指定位置的当前天气状况。

参数:

  • location:城市名称(例如“伦敦”),或带有国家代码的城市名称(如“英国伦敦”)

get_weather_forecast

获取指定位置的5天天气预报。

参数:

  • location:城市名称(例如“伦敦”),或带有国家代码的城市名称(如“英国伦敦”)
  • days:要预测的天数(1-5,默认值:3)

发展

  1. 安装依赖项:

    npm install
    
  2. 在开发模式下运行:

    npm run dev
    
  3. 生产建设:

    npm run build
    

许可证

麻省理工学院