mcpField
M
mcp-server

mcp-server

本地服务
github.com/milk19地图与出行081 周前更新

关于 mcp-server

mcp-server 通过 OpenWeatherMap 为 Cline 和其他 MCP 客户端提供当前天气与预报,支持配置单位,并缓存结果以减少重复 API 请求。

主要功能

  1. 01按地点查询当前天气
  2. 02查询 1–5 天预报
  3. 03公制与英制单位
  4. 04缓存天气结果
  5. 05Markdown 格式的结果

适用场景

  1. 01查询城市当前天气
  2. 02查看某地未来天气预报

MCP 相关

该 MCP 服务向连接的客户端提供 get_current_weather 和 get_weather_forecast 工具,以及 README 资源。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"milk19": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/create-server"
]
}
}
}
启动命令npx -y @modelcontextprotocol/create-server

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@modelcontextprotocol/create-server

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

mcp-server

{ "name": "weather-mcp", "version": "1.0.0", "description": "适用于 Cline 的天气 MCP 服务,使用 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: "获取指定位置的当前天气",
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "城市名称(例如,'London'),或带国家代码的城市名称(例如,'London,uk')"
            }
          },
          required: ["location"]
        }
      },
      {
        name: "get_weather_forecast",
        description: "获取指定位置未来 5 天的天气预报",
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "城市名称(例如,'London'),或带国家代码的城市名称(例如,'London,uk')"
            },
            days: {
              type: "number",
              description: "预报天数(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 服务 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 `# ${data.name},${data.sys.country} 当前天气 ${weatherEmoji}\n\n` +
       `**温度:** ${temp}${unit}(体感温度:${feelsLike}${unit})\n` +
       `**天气状况:** ${this.capitalizeFirst(weatherDescription)}\n` +
       `**湿度:** ${data.main.humidity}%\n` +
       `**风:** ${data.wind.speed} ${windUnit},来自${this.getWindDirection(data.wind.deg)}\n` +
       `**能见度:** ${(data.visibility / 1000).toFixed(1)} km\n` +
       `**气压:** ${data.main.pressure} hPa\n\n` +
       `*最后更新:${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 = `# ${city},${country}未来${days}天天气预报\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` +
            `**温度:** ${Math.round(forecast.minTemp)}${unit} 至 ${Math.round(forecast.maxTemp)}${unit}\n` +
            `**天气状况:** ${this.capitalizeFirst(mostCommonCondition)}\n`;
  
  if (forecast.rainfall > 0) {
    result += `**降水量:** ${forecast.rainfall.toFixed(1)} mm\n`;
  }
  
  result += `**风速:** ${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

一个使用 OpenWeatherMap API 提供实时天气数据的 Cline MCP 服务。

功能

  • 获取全球任意地点的当前天气
  • 获取包含详细信息的 5 天天气预报
  • 支持公制和英制单位
  • 通过高效缓存尽量减少 API 调用
  • 以精美格式呈现 Markdown 响应

设置

  1. 在 OpenWeatherMap 注册并获取免费 API 密钥

  2. 安装 MCP 服务:

    ```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. 将 `WEATHER_UNITS` 设置为 "metric"(°C、m/s)或 "imperial"(°F、mph)

可用工具

get_current_weather

获取指定地点的当前天气状况。

参数:

  • `location`:城市名称(例如,'London'),或带国家/地区代码的城市名称(例如,'London,uk')
get_weather_forecast

获取指定地点的 5 天天气预报。

参数:

  • `location`:城市名称(例如,'London'),或带国家/地区代码的城市名称(例如,'London,uk')
  • `days`:预报天数(1-5,默认值:3)

输出示例

当前天气

```

伦敦当前天气,GB ☁️

温度: 12°C(体感温度:10°C) 天气状况: 阴云密布 湿度: 76% 风速: 4.12 m/s,风向 SW 能见度: 10.0 km 气压: 1013 hPa

上次更新时间:2024/1/15 下午 2:30:00 ```

天气预报

```

伦敦 3 天天气预报,GB

星期一,1 月 15 日 🌧️

温度: 10°C 至 13°C 天气状况: 小雨 降水量: 2.4 mm 风速: 5.2 m/s

星期二,1 月 16 日 ☁️

温度: 9°C 至 12°C 天气状况: 多云 风速: 4.8 m/s

周三,1 月 17 日 ☀️

**气温:**8°C 至 14°C **天气:**晴朗 **风速:**3.5 m/s ```

许可证

MIT `; }

public start(): void { this.server.start(); console.error("[Server] Weather MCP server started successfully"); } }

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

MCP 服务开发协议

⚠️ 关键:测试前请勿使用 attempt_completion ⚠️

步骤 1:规划(规划模式)

  • 此工具解决什么问题?
  • 它将使用什么 API/服务?
  • 有哪些身份验证要求? □ 标准 API 密钥 □ OAuth(需要单独的设置脚本) □ 其他凭据

步骤 2:实施(执行模式)

  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(适用于数据科学/机器学习项目):
        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:测试(阻断项 ⛔️)

在使用 attempt_completion 之前,我必须确认: □ 是否已测试每个工具? □ 是否已针对每项测试获得用户确认,确认测试成功? □ 是否已记录测试结果?

如果任何一项的答案是“否”,我绝不能使用 attempt_completion。

  1. 测试每个工具(必需) □ 使用有效输入测试每个工具 □ 验证输出格式正确 ⚠️ 所有工具都测试完成前,请勿继续

步骤 4:完成

❗ 停止并确认: □ 已使用有效输入测试每个工具 □ 每个工具的输出格式都正确

只有在所有工具都测试完成后,才能使用 attempt_completion。

关键要求

  • ✓ 必须使用 MCP SDK

  • ✓ 必须实现全面的日志记录

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

  • ✓ 必须妥善处理错误

  • ⛔️ 完成前绝不能跳过测试

    Weather MCP 服务

一个使用 OpenWeatherMap API 提供实时天气数据的 Cline MCP 服务。

功能

  • 获取全球任意地点的当前天气
  • 获取包含详细信息的 5 天天气预报
  • 支持公制和英制单位
  • 通过高效缓存尽量减少 API 调用
  • 提供格式精美的 Markdown 响应

设置

  1. 在 OpenWeatherMap 注册并获取免费的 API 密钥

  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 设置为 "metric"(°C、m/s)或 "imperial"(°F、mph)

可用工具

get_current_weather

获取指定地点的当前天气状况。

参数:

  • location:城市名称(例如,'London'),或带国家/地区代码的城市名称(例如,'London,uk')
get_weather_forecast

获取指定地点的 5 天天气预报。

参数:

  • location:城市名称(例如,'London'),或带国家/地区代码的城市名称(例如,'London,uk')
  • days:预报天数(1-5,默认值:3)

开发

  1. 安装依赖项:

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

    npm run dev
    
  3. 构建生产版本:

    npm run build
    

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。