Skip to main content
Glama

MCP Weather Service

by terisuke
index.ts7.56 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // MCPサーバーをインポート import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // 標準入出力サーバーをインポート import axios from 'axios'; // HTTPクライアントをインポート import { HttpsProxyAgent } from 'https-proxy-agent'; // HTTPSプロキシエージェントをインポート import { z } from "zod"; // スキーマバリデーションライブラリをインポート // デバッグメッセージを出力する関数 function debug(...args: any[]) { console.error('[DEBUG]', ...args); } // モック天気データを定義 const MOCK_WEATHER_DATA: Record<string, any> = { "Fukuoka": { cityName: "Fukuoka", temperature: "22°C", condition: "Partly cloudy" }, "Tokyo": { cityName: "Tokyo", temperature: "20°C", condition: "Clear sky" }, "Osaka": { cityName: "Osaka", temperature: "21°C", condition: "Mainly clear" }, "Moscow": { cityName: "Moscow", temperature: "10°C", condition: "Overcast" }, "New York": { cityName: "New York", temperature: "15°C", condition: "Light rain showers" } }; // axiosをプロキシ設定で構成 function configureAxios() { const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; if (httpsProxy) { debug(`Using HTTPS proxy: ${httpsProxy}`); axios.defaults.proxy = false; axios.defaults.httpsAgent = new HttpsProxyAgent(httpsProxy); } else if (httpProxy) { debug(`Using HTTP proxy: ${httpProxy}`); axios.defaults.proxy = false; axios.defaults.httpsAgent = new HttpsProxyAgent(httpProxy); } axios.defaults.timeout = 5000; // タイムアウト時間を5秒に設定 } // 天気コードと日本語の天気状態の対応を定義 const weatherCodes: Record<number, string> = { 0: "快晴", 1: "晴れ", 2: "晴れ時々曇り", 3: "曇り", 45: "霧", 48: "霧氷", 51: "小雨", 53: "霧雨", 55: "強い霧雨", 56: "弱い着氷性の霧雨", 57: "強い着氷性の霧雨", 61: "小雨", 63: "雨", 65: "大雨", 66: "弱い着氷性の雨", 67: "強い着氷性の雨", 71: "小雪", 73: "雪", 75: "大雪", 77: "霰", 80: "小雨のにわか雨", 81: "にわか雨", 82: "激しいにわか雨", 85: "小雪のにわか雪", 86: "激しいにわか雪", 95: "雷雨", 96: "小さな雹を伴う雷雨", 99: "大きな雹を伴う雷雨", }; // 都市の天気情報を取得 async function getWeatherForCity(city: string, useMockData: boolean = false) { try { const normalizedCity = city.trim(); debug(`Getting weather for city: ${normalizedCity}`); if (useMockData && MOCK_WEATHER_DATA[normalizedCity]) { debug(`Using mock data for ${normalizedCity}`); return MOCK_WEATHER_DATA[normalizedCity]; } // ジオコーディングAPIを使用して座標を取得 const geocodingResponse = await axios.get( `https://geocoding-api.open-meteo.com/v1/search`, { params: { name: normalizedCity, count: 1, language: 'en' }, timeout: 5000 } ); if (!geocodingResponse.data.results || geocodingResponse.data.results.length === 0) { throw new Error(`City "${normalizedCity}" not found`); } const { latitude, longitude, name } = geocodingResponse.data.results[0]; debug(`Found coordinates for ${name}: ${latitude}, ${longitude}`); // 天気APIを使用して天気情報を取得 const weatherResponse = await axios.get( `https://api.open-meteo.com/v1/forecast`, { params: { latitude, longitude, current: 'temperature_2m,weather_code', timezone: 'Asia/Tokyo' }, timeout: 5000 } ); const weatherCode = weatherResponse.data.current.weather_code; const weatherDescription = weatherCodes[weatherCode] || "不明"; return { cityName: name, temperature: `${weatherResponse.data.current.temperature_2m}${weatherResponse.data.current_units.temperature_2m}`, condition: weatherDescription }; } catch (error) { debug(`Error getting weather for city ${city}:`, error); if (MOCK_WEATHER_DATA[city]) { debug(`Falling back to mock data for ${city}`); return MOCK_WEATHER_DATA[city]; } throw error; } } // MCPサーバーを開始するメイン関数 async function main() { debug('Starting MCP Weather Server'); configureAxios(); const server = new McpServer({ name: "Weather MCP", version: "1.0.0", description: "A simple MCP server for weather data", }, { capabilities: { logging: {}, sampling: {}, roots: { listChanged: true } } }); server.tool( 'get-weather', 'Get weather information for a city', { city: z.string().describe('City name to get weather for') }, async ({ city }) => { debug(`Handling get-weather request for city: ${city}`); try { let attempts = 0; const maxAttempts = 3; let lastError: Error | null = null; while (attempts < maxAttempts) { try { attempts++; debug(`Attempt ${attempts}/${maxAttempts} for city: ${city}`); // 最後の試行では、可能であればモックデータを使用 const useMockData = attempts === maxAttempts; const weather = await getWeatherForCity(city, useMockData); const conditionJapanese = weather.condition; return { content: [{ type: 'text', text: `${weather.cityName}の天気:\n気温: ${weather.temperature}\n状態: ${conditionJapanese}` }] }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); debug(`Attempt ${attempts} failed:`, lastError.message); if (attempts < maxAttempts) { const waitTime = 1000 * attempts; debug(`Waiting ${waitTime}ms before next retry`); await new Promise(resolve => setTimeout(resolve, waitTime)); } } } return { content: [{ type: 'text', text: `${city}の天気情報の取得に失敗しました: ${lastError?.message || '不明なエラー'}` }] }; } catch (error) { debug(`Error processing weather request:`, error); return { content: [{ type: 'text', text: `${city}の天気情報の取得に失敗しました: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); debug('Creating StdioServerTransport'); const transport = new StdioServerTransport(); await server.connect(transport); debug('MCP server ready to receive requests'); } process.on('uncaughtException', (error) => { debug('Uncaught exception:', error); }); process.on('unhandledRejection', (reason, promise) => { debug('Unhandled rejection at:', promise, 'reason:', reason); }); main().catch((error) => { debug('Fatal error in main:', error); process.exit(1); });

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/terisuke/my-weather-mcp'

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