mcp-server について
mcp-server は、OpenWeatherMap を使って Cline などの MCP クライアントに現在の天気と予報を提供します。単位を設定でき、結果のキャッシュで API の重複呼び出しを抑えます。
主な機能
- 01地点別の現在の天気
- 021~5 日間の予報
- 03メートル法とヤード・ポンド法
- 04天気データのキャッシュ
- 05Markdown 形式の結果
活用シーン
- 01都市の現在の天気を確認
- 02地点ごとの今後の天気を確認
MCP 対応
接続クライアント向けに get_current_weather と get_weather_forecast のツール、および README リソースを提供する MCP サーバーです。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mcp-server
{ "name": "weather-mcp", "version": "1.0.0", "description": "Cline 向けの OpenWeatherMap API を使用する天気 MCP サーバー", "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 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];
// 1日の平均値を計算
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形式の応答
セットアップ
-
OpenWeatherMapで無料のAPIキーを取得します
-
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": [] } } } ```
-
`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)
出力例
現在の天気
```
London, GBの現在の天気 ☁️
気温: 12°C(体感温度:10°C) 天気: 曇り 湿度: 76% 風: 南西から4.12 m/s 視程: 10.0 km 気圧: 1013 hPa
最終更新:1/15/2024, 2:30:00 PM ```
天気予報
```
London, GBの3日間天気予報
Monday, Jan 15 🌧️
気温: 10°C~13°C 天気: 小雨 降水量: 2.4 mm 風: 5.2 m/s
Tuesday, Jan 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:実装(実行モード)
-
セットアップ
- Web サービス、JavaScript 連携、または Node.js 環境の場合:
npx @modelcontextprotocol/create-server my-server cd my-server npm install - データサイエンス、ML ワークフロー、または Python 環境の場合:
pip install mcp # Or with uv (recommended) uv add "mcp[cli]"
- Web サービス、JavaScript 連携、または Node.js 環境の場合:
-
コア実装
- 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)}')
- TypeScript(Web/JS プロジェクト向け):
- 型定義を追加する
- コンテキストを含めてエラーを処理する
- 必要に応じてレート制限を実装する
-
設定
- 必要に応じてユーザーから認証情報を取得する
- 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": [] } } }
- TypeScript プロジェクトの場合:
ステップ 3:テスト(ブロッカー ⛔️)
attempt_completion を使用する前に、必ず以下を確認してください: □ すべてのツールをテストしましたか? □ 各テストについて、ユーザーから成功の確認を得ましたか? □ テスト結果を記録しましたか?いずれかの答えが「いいえ」の場合、attempt_completion を使用してはいけません。
- 各ツールをテストする(必須) □ 有効な入力で各ツールをテストする □ 出力形式が正しいことを確認する ⚠️ すべてのツールのテストが完了するまで先に進まないでください
ステップ 4:完了
❗ 停止して確認してください: □ すべてのツールを有効な入力でテストした □ 各ツールの出力形式が正しい
すべてのツールをテストした後に限り、attempt_completion を使用できます。
主な要件
-
✓ MCP SDK を使用すること
-
✓ 包括的なログ記録を行うこと
-
✓ 各ツールを個別にテストすること
-
✓ エラーを適切に処理すること
-
⛔️ 完了前にテストを決して省略しないこと
Weather MCP Server
OpenWeatherMap API を使用してリアルタイムの天気データを提供する Cline MCP サーバーです。
機能
- 世界中のあらゆる場所の現在の天気を取得
- 詳細情報付きの5日間天気予報を取得
- メートル法とヤード・ポンド法の単位に対応
- API 呼び出しを最小限に抑える効率的なキャッシュ
- 見やすく整形された Markdown レスポンス
セットアップ
-
OpenWeatherMap で無料の API キーを取得します
-
このリポジトリをクローンします:
git clone https://github.com/yourusername/weather-mcp.git cd weather-mcp npm install npm run build -
Cline の設定に MCP サーバーを追加します:
{ "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": [] } } } -
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)
開発
-
依存関係をインストールします:
npm install -
開発モードで実行します:
npm run dev -
本番環境向けにビルドします:
npm run build
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。