mcp-server

by milk19

366 downloads
Not rated
GitHub

Description

# mcp-server { "name": "weather-mcp", "version": "1.0.0", "description": "Weather MCP server for Cline using OpenWeatherMap API", "main": "build/index.js", "scripts": { "build": "tsc", "start": "node build/index.js", "dev": "ts-node src/index.ts" }, "author": "", "license"…

About

# mcp-server { "name": "weather-mcp", "version": "1.0.0", "description": "Weather MCP server for Cline using OpenWeatherMap API", "main": "build/index.js", "scripts": { "build": "tsc", "start": "node build/index.js", "dev": "ts-node src/index.ts" }, "author": "", "license": "MIT", "dependencies": {…

Details

Author
milk19
Downloads
366
Categories
Other

- Provides current weather for any city or city,country
- Offers 5‑day weather forecasts (up to 5 days)
- Caches results to reduce API calls (30 min current, 1 hour forecast)
- Supports metric, imperial, and standard unit systems
- Returns formatted weather data including temperature, humidity, wind, and conditions

Setting up with Highlight

This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:

  1. Download and install Highlight from highlightai.com/download
  2. Navigate to the plugins tab and select "Add Custom Plugin"
  3. Configure the plugin with the settings below
    Plugin Name mcp-server
    Command (node, npx, python, etc.)

    Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.

  4. Enable "Start Automatically" if you want the plugin to start when Highlight launches

From the repository

Set the OPENWEATHERMAP_API_KEY environment variable. Optionally set WEATHER_UNITS (metric/imperial/standard) and LOG_LEVEL. Build with npm run build and start with npm start. The server exposes two tools: get_current_weather and get_weather_forecast.

Claude Desktop / Cursor

Paste into your MCP client config file to install this server.

{
    "mcpServers": {
        "mcp-server": {
            "mcp-server-milk19": {
                "command": "npx",
                "args": [
                    "@modelcontextprotocol/create-server",
                    "my-server"
                ]
            }
        }
    }
}

McpServers

{
    "mcp-server-milk19": {
        "command": "npx",
        "args": [
            "@modelcontextprotocol/create-server",
            "my-server"
        ]
    }
}

mcp-server

{ "name": "weather-mcp", "version": "1.0.0", "description": "Weather MCP server for Cline using OpenWeatherMap API", "main": "build/index.js", "scripts": { "build": "tsc", "start": "node build/index.js", "dev": "ts-node src/index.ts" }, "author": "", "license": "MIT", "dependencies": { "@modelcontextprotocol/server": "^0.5.0", "axios": "^1.6.7", "node-cache": "^5.1.2" }, "devDependencies": { "@types/node": "^20.11.5", "ts-node": "^10.9.2", "typescript": "^5.3.3" } } { "compilerOptions": { "target": "es2020", "module": "commonjs", "lib": ["es2020"], "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true }, "include": ["src//"], "exclude": ["node_modules"] } import { Server, McpError, ErrorCode, Tool, ToolParam, ToolExecuteRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/server"; import axios from "axios"; import NodeCache from "node-cache";

// 日志级别
const LOG_LEVEL = process.env.LOG_LEVEL || "info";
const DEBUG = LOG_LEVEL === "debug";

// 天气API配置
interface WeatherConfig {
apiKey: string;
baseUrl: string;
units: "metric" | "imperial" | "standard";
}

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

// 主类
class WeatherMcpServer {
private config: WeatherConfig;
private server: Server;
private cache: NodeCache;

constructor() {
console.error("[Setup] Initializing Weather MCP server...");

// 获取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");
}

private registerHandlers(): void {
// 工具执行处理
this.server.setRequestHandler(ToolExecuteRequestSchema, async (request) => {
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});
});
}

private async getCurrentWeather(params: any): Promise<Tool.ExecuteResponse> {
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;
}
}

private async getWeatherForecast(params: any): Promise<Tool.ExecuteResponse> {
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(location: unknown): asserts location is string {
if (typeof location !== "string" || location.trim() === "") {
throw new McpError(ErrorCode.InvalidParams, "A valid location is required");
}
}

private formatCurrentWeather(data: any): string {
const temp = Math.round(data.main.temp);
const feelsLike = Math.round(data.main.feels_like);
const unit = this.config.units === "imperial" ? "°F" : "°C";
const windUnit = this.config.units === "imperial" ? "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(data: any, days: number): string {
const city = data.city.name;
const country = data.city.country;
const unit = this.config.units === "imperial" ? "°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): string {
// 根据OpenWeatherMap的天气ID选择表情
if (weatherId >= 200 && weatherId < 300) return "⛈️"; // 雷暴
if (weatherId >= 300 && weatherId < 400) return "🌧️"; // 小雨
if (weatherId >= 500 && weatherId < 600) return "🌧️"; // 雨
if (weatherId >= 600 && weatherId < 700) return "❄️"; // 雪
if (weatherId >= 700 && weatherId < 800) return "🌫️"; // 雾
if (weatherId === 800) return "☀️"; // 晴天
if (weatherId > 800 && weatherId < 900) return "☁️"; // 多云
return "🌡️"; // 默认
}

private getWindDirection(degrees: number): string {
const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
const index = Math.round(degrees / 45) % 8;
return directions[index];
}

private capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}

private getReadmeContent(): string {
return # Weather MCP Server

A Cline MCP server that provides real-time weather data using the OpenWeatherMap API.

Features

- Get current weather for any location worldwide
- Get 5-day weather forecasts with detailed information
- Support for metric and imperial units
- Efficient caching to minimize API calls
- Beautifully formatted markdown responses

Setup

1. Sign up for a free API key at OpenWeatherMap
2. Install the MCP server:

\\\json
{
"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": []
}
}
}
\\\

3. Set \WEATHER_UNITS\ to "metric" (°C, m/s) or "imperial" (°F, mph)

Available Tools

get_current_weather

Gets the current weather conditions for a specified location.

Parameters:
- \
location\: City name (e.g., 'London'), or city name with country code (e.g., 'London,uk')

get_weather_forecast

Gets a 5-day weather forecast for a specified location.

Parameters:*
- \
location\: City name (e.g., 'London'), or city name with country code (e.g., 'London,uk')
- \
days\: Number of days to forecast (1-5, default: 3)

Example Output

Current Weather

\\\

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.