Skip to main content
Glama

OpenWeatherMap MCP Server

main.ts40.9 kB
#!/usr/bin/env node import { FastMCP } from "fastmcp"; import { getTransportConfig } from "./config/transport.js"; import { httpStreamAuthenticator } from "./auth/http.js"; import { initializeStdioAuth } from "./auth/stdio.js"; import { getOpenWeatherClient, configureClientForLocation } from "./utils/client-resolver.js"; import { formatCurrentWeather, formatWeatherForecast, formatHourlyForecast } from "./utils/weather-formatter.js"; import type { SessionData } from "./auth/types.js"; import { getCurrentWeatherSchema, getWeatherForecastSchema, getHourlyForecastSchema, getDailyForecastSchema, getMinutelyForecastSchema, getWeatherAlertsSchema, getCurrentAirPollutionSchema, getLocationInfoSchema, getOneCallWeatherSchema, getAirPollutionSchema, geocodeLocationSchema } from "./schemas.js"; // Get transport configuration with validation const transportConfig = getTransportConfig(); const server = new FastMCP({ name: "OpenWeatherMap MCP Server", version: "0.1.3", instructions: ` This MCP server provides access to the OpenWeatherMap API for weather data and forecasts. Available tools: - Current weather: get current weather conditions for any location - Weather forecast: get weather forecast for up to 5 days - Hourly forecast: get hourly weather forecast for up to 48 hours - Daily forecast: get daily weather forecast for up to 8 days - Minutely forecast: get minute-by-minute precipitation forecast - Weather alerts: get active weather alerts and warnings - Current air pollution: get current air quality data and pollutant levels - Location info: get location information from coordinates (reverse geocoding) - OneCall weather: get comprehensive weather data (current + 7-day forecast) - Air pollution: get air quality index and pollution data - Geocoding: convert location names to coordinates or vice versa Authentication is handled via the OPENWEATHER_API_KEY environment variable. The server maintains session state per MCP connection. `.trim(), // dynamically handle authentication ...(transportConfig.transportType === "httpStream" ? { authenticate: httpStreamAuthenticator, } : {}) }); // Current Weather Tool server.addTool({ name: "get-current-weather", description: "Get current weather conditions for a location", parameters: getCurrentWeatherSchema, execute: async (args, { session, log }) => { try { log.info("Getting current weather", { location: args.location }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location, args.units); // Fetch current weather const weatherData = await client.getCurrent(); log.info("Successfully retrieved current weather", { lat: weatherData.lat, lon: weatherData.lon, temp: weatherData.weather.temp.cur }); // Format the response const formattedWeather = formatCurrentWeather({ name: `${weatherData.lat.toFixed(4)}, ${weatherData.lon.toFixed(4)}`, // Use coordinates as name main: { temp: weatherData.weather.temp.cur, feels_like: weatherData.weather.feelsLike.cur, humidity: weatherData.weather.humidity }, weather: [{ description: weatherData.weather.description }], wind: { speed: weatherData.weather.wind.speed, deg: weatherData.weather.wind.deg || 0 }, visibility: weatherData.weather.visibility, dt: weatherData.dtRaw, timezone: weatherData.timezoneOffset }, args.units); return { content: [ { type: "text", text: formattedWeather } ] }; } catch (error) { log.error("Failed to get current weather", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get current weather: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Weather Forecast Tool server.addTool({ name: "get-weather-forecast", description: "Get weather forecast for up to 5 days", parameters: getWeatherForecastSchema, execute: async (args, { session, log }) => { try { log.info("Getting weather forecast", { location: args.location, days: args.days }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location, args.units); // Fetch forecast data const forecastData = await client.getForecast(); // Limit to requested number of days (default 5) const requestedDays = args.days || 5; const limitedForecast = forecastData.slice(0, requestedDays * 8); // 8 entries per day (3-hour intervals) log.info("Successfully retrieved weather forecast", { location: args.location, entries: limitedForecast.length }); // Group forecast by day and take daily summary const dailyForecasts = []; for (let i = 0; i < limitedForecast.length; i += 8) { const dayData = limitedForecast[i]; // Take first entry of each day if (dayData) { dailyForecasts.push({ dt: dayData.dtRaw, main: { temp_min: dayData.weather.temp.min, temp_max: dayData.weather.temp.max, humidity: dayData.weather.humidity }, weather: [{ description: dayData.weather.description }], wind: { speed: dayData.weather.wind.speed, deg: dayData.weather.wind.deg || 0 } }); } } // Format the response const formattedForecast = formatWeatherForecast( dailyForecasts, `${forecastData[0]?.lat.toFixed(4)}, ${forecastData[0]?.lon.toFixed(4)}`, args.units ); return { content: [ { type: "text", text: formattedForecast } ] }; } catch (error) { log.error("Failed to get weather forecast", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get weather forecast: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Hourly Weather Forecast Tool server.addTool({ name: "get-hourly-forecast", description: "Get hourly weather forecast for up to 48 hours", parameters: getHourlyForecastSchema, execute: async (args, { session, log }) => { try { log.info("Getting hourly weather forecast", { location: args.location, hours: args.hours }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location, args.units); // Fetch hourly forecast data const requestedHours = args.hours || 48; const hourlyData = await client.getHourlyForecast(requestedHours); log.info("Successfully retrieved hourly weather forecast", { location: args.location, entries: hourlyData.length }); // Format the response const formattedForecast = formatHourlyForecast( hourlyData, `${hourlyData[0]?.lat?.toFixed(4)}, ${hourlyData[0]?.lon?.toFixed(4)}` || args.location, args.units ); return { content: [ { type: "text", text: formattedForecast } ] }; } catch (error) { log.error("Failed to get hourly weather forecast", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get hourly weather forecast: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Daily Forecast Tool server.addTool({ name: "get-daily-forecast", description: "Get daily weather forecast for up to 8 days", parameters: getDailyForecastSchema, execute: async (args, { session, log }) => { try { log.info("Getting daily weather forecast", { location: args.location, days: args.days, include_today: args.include_today }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location, args.units); // Fetch daily forecast data const includeToday = args.include_today || false; const requestedDays = args.days || 8; const dailyData = await client.getDailyForecast(requestedDays, includeToday); log.info("Successfully retrieved daily weather forecast", { location: args.location, days: dailyData.length, include_today: includeToday }); // Format the response const formattedForecast = JSON.stringify({ location: args.location, units: args.units || 'metric', days_requested: requestedDays, includes_today: includeToday, forecast: dailyData.map(day => ({ date: day.dt.toISOString().split('T')[0], summary: day.weather.description, temperature: { min: day.weather.temp.min, max: day.weather.temp.max, morning: day.weather.temp.morn, day: day.weather.temp.day, evening: day.weather.temp.eve, night: day.weather.temp.night }, feels_like: { morning: day.weather.feelsLike.morn, day: day.weather.feelsLike.day, evening: day.weather.feelsLike.eve, night: day.weather.feelsLike.night }, pressure: day.weather.pressure, humidity: day.weather.humidity, wind: { speed: day.weather.wind.speed, direction: day.weather.wind.deg, gust: day.weather.wind.gust }, clouds: day.weather.clouds, precipitation: { probability: day.weather.pop, rain: day.weather.rain || 0, snow: day.weather.snow || 0 }, uv_index: day.weather.uvi, sunrise: day.astronomical.sunrise.toISOString(), sunset: day.astronomical.sunset.toISOString(), moonrise: day.astronomical.moonrise.toISOString(), moonset: day.astronomical.moonset.toISOString(), moon_phase: day.astronomical.moonPhase })) }, null, 2); return { content: [ { type: "text", text: formattedForecast } ] }; } catch (error) { log.error("Failed to get daily weather forecast", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get daily weather forecast: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Minutely Forecast Tool server.addTool({ name: "get-minutely-forecast", description: "Get minute-by-minute precipitation forecast for next hour", parameters: getMinutelyForecastSchema, execute: async (args, { session, log }) => { try { log.info("Getting minutely weather forecast", { location: args.location, limit: args.limit }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location); // Fetch minutely forecast data const requestedMinutes = args.limit || 60; const minutelyData = await client.getMinutelyForecast(requestedMinutes); log.info("Successfully retrieved minutely weather forecast", { location: args.location, minutes: minutelyData.length }); // Format the response const formattedForecast = JSON.stringify({ location: args.location, minutes_requested: requestedMinutes, forecast: minutelyData.map((minute, index) => ({ minute_offset: index + 1, time: minute.dt.toISOString(), precipitation: minute.weather.rain, precipitation_description: minute.weather.rain > 0 ? (minute.weather.rain < 0.1 ? 'Light rain' : minute.weather.rain < 0.5 ? 'Moderate rain' : 'Heavy rain') : 'No precipitation' })) }, null, 2); return { content: [ { type: "text", text: formattedForecast } ] }; } catch (error) { log.error("Failed to get minutely weather forecast", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get minutely weather forecast: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Weather Alerts Tool server.addTool({ name: "get-weather-alerts", description: "Get active weather alerts and warnings", parameters: getWeatherAlertsSchema, execute: async (args, { session, log }) => { try { log.info("Getting weather alerts", { location: args.location }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location); // Fetch alerts data const alertsData = await client.getAlerts(); log.info("Successfully retrieved weather alerts", { location: args.location, alerts_count: alertsData.length }); // Format the response const formattedAlerts = JSON.stringify({ location: args.location, alerts_count: alertsData.length, alerts: alertsData.map(alert => ({ sender: alert.sender_name, event: alert.event, start_time: new Date(alert.start * 1000).toISOString(), end_time: new Date(alert.end * 1000).toISOString(), description: alert.description, tags: alert.tags, severity: alert.tags.includes('severe') || alert.tags.includes('extreme') ? 'High' : alert.tags.includes('moderate') ? 'Medium' : 'Low' })) }, null, 2); return { content: [ { type: "text", text: formattedAlerts } ] }; } catch (error) { log.error("Failed to get weather alerts", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get weather alerts: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Current Air Pollution Tool server.addTool({ name: "get-current-air-pollution", description: "Get current air quality data", parameters: getCurrentAirPollutionSchema, execute: async (args, { session, log }) => { try { log.info("Getting current air pollution", { location: args.location }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Configure client for this request configureClientForLocation(client, args.location); // Fetch current air pollution data const pollutionData = await client.getCurrentAirPollution(); log.info("Successfully retrieved current air pollution", { location: args.location, aqi: pollutionData.aqi }); // Format the response const formattedPollution = JSON.stringify({ location: args.location, coordinates: { latitude: pollutionData.lat, longitude: pollutionData.lon }, air_quality: { index: pollutionData.aqi, description: pollutionData.aqiName, scale: "1 (Good) to 5 (Very Poor)" }, pollutants: { carbon_monoxide: { value: pollutionData.components.co, unit: "μg/m³", description: "Carbon monoxide" }, nitrogen_monoxide: { value: pollutionData.components.no, unit: "μg/m³", description: "Nitrogen monoxide" }, nitrogen_dioxide: { value: pollutionData.components.no2, unit: "μg/m³", description: "Nitrogen dioxide" }, ozone: { value: pollutionData.components.o3, unit: "μg/m³", description: "Ozone" }, sulphur_dioxide: { value: pollutionData.components.so2, unit: "μg/m³", description: "Sulphur dioxide" }, pm2_5: { value: pollutionData.components.pm2_5, unit: "μg/m³", description: "Fine particles matter" }, pm10: { value: pollutionData.components.pm10, unit: "μg/m³", description: "Coarse particulate matter" }, ammonia: { value: pollutionData.components.nh3, unit: "μg/m³", description: "Ammonia" } }, timestamp: pollutionData.dt.toISOString() }, null, 2); return { content: [ { type: "text", text: formattedPollution } ] }; } catch (error) { log.error("Failed to get current air pollution", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.location}" not found. Please check the spelling or try using coordinates.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get current air pollution: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Location Info Tool (Reverse Geocoding) server.addTool({ name: "get-location-info", description: "Get location information from coordinates (reverse geocoding)", parameters: getLocationInfoSchema, execute: async (args, { session, log }) => { try { log.info("Getting location info", { latitude: args.latitude, longitude: args.longitude }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Set coordinates directly for reverse geocoding client.setLocationByCoordinates(args.latitude, args.longitude); // Fetch location data using reverse geocoding const locationData = await client.getLocation(); log.info("Successfully retrieved location info", { latitude: args.latitude, longitude: args.longitude, location_name: locationData?.name }); // Format the response const formattedLocation = JSON.stringify({ coordinates: { latitude: args.latitude, longitude: args.longitude }, location: locationData ? { name: locationData.name, country: locationData.country, state: locationData.state, local_names: locationData.local_names } : { message: "No location data found for these coordinates" } }, null, 2); return { content: [ { type: "text", text: formattedLocation } ] }; } catch (error) { log.error("Failed to get location info", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('Invalid coordinates')) { throw new Error(`Invalid coordinates: latitude must be between -90 and 90, longitude must be between -180 and 180.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get location info: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // OneCall Weather Tool server.addTool({ name: "get-onecall-weather", description: "Get comprehensive weather data (current + 7-day forecast)", parameters: getOneCallWeatherSchema, execute: async (args, { session, log }) => { try { log.info("Getting OneCall weather data", { latitude: args.latitude, longitude: args.longitude }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Set coordinates for OneCall API client.setLocationByCoordinates(args.latitude, args.longitude); // Set units if provided if (args.units) { client.setUnits(args.units); } // Fetch comprehensive weather data const weatherData = await client.getEverything(); log.info("Successfully retrieved OneCall weather data", { latitude: args.latitude, longitude: args.longitude, has_current: !!weatherData.current, daily_count: weatherData.daily?.length || 0, hourly_count: weatherData.hourly?.length || 0 }); // Format the comprehensive response const formattedData = { location: { latitude: args.latitude, longitude: args.longitude, timezone: weatherData.timezone, timezone_offset: weatherData.timezoneOffset }, current: weatherData.current ? { datetime: weatherData.current.dt.toISOString(), temperature: weatherData.current.weather.temp.cur, feels_like: weatherData.current.weather.feelsLike.cur, pressure: weatherData.current.weather.pressure, humidity: weatherData.current.weather.humidity, dew_point: weatherData.current.weather.dewPoint, uv_index: weatherData.current.weather.uvi, clouds: weatherData.current.weather.clouds, visibility: weatherData.current.weather.visibility, wind: { speed: weatherData.current.weather.wind.speed, direction: weatherData.current.weather.wind.deg, gust: weatherData.current.weather.wind.gust }, weather: { main: weatherData.current.weather.main, description: weatherData.current.weather.description, icon: weatherData.current.weather.icon }, rain: weatherData.current.weather.rain, snow: weatherData.current.weather.snow } : null, minutely: weatherData.minutely?.slice(0, 60).map((minute, index) => ({ minute_offset: index + 1, time: minute.dt.toISOString(), precipitation: minute.weather.rain })) || [], hourly: weatherData.hourly?.slice(0, 48).map(hour => ({ datetime: hour.dt.toISOString(), temperature: hour.weather.temp.cur, feels_like: hour.weather.feelsLike.cur, pressure: hour.weather.pressure, humidity: hour.weather.humidity, dew_point: hour.weather.dewPoint, uv_index: hour.weather.uvi, clouds: hour.weather.clouds, visibility: hour.weather.visibility, wind: { speed: hour.weather.wind.speed, direction: hour.weather.wind.deg, gust: hour.weather.wind.gust }, weather: { main: hour.weather.main, description: hour.weather.description, icon: hour.weather.icon }, precipitation_probability: hour.weather.pop, rain: hour.weather.rain, snow: hour.weather.snow })) || [], daily: weatherData.daily?.slice(0, 7).map(day => ({ date: day.dt.toISOString().split('T')[0], temperature: { min: day.weather.temp.min, max: day.weather.temp.max, morning: day.weather.temp.morn, day: day.weather.temp.day, evening: day.weather.temp.eve, night: day.weather.temp.night }, feels_like: { morning: day.weather.feelsLike.morn, day: day.weather.feelsLike.day, evening: day.weather.feelsLike.eve, night: day.weather.feelsLike.night }, pressure: day.weather.pressure, humidity: day.weather.humidity, dew_point: day.weather.dewPoint, wind: { speed: day.weather.wind.speed, direction: day.weather.wind.deg, gust: day.weather.wind.gust }, weather: { main: day.weather.main, description: day.weather.description, icon: day.weather.icon }, clouds: day.weather.clouds, precipitation_probability: day.weather.pop, rain: day.weather.rain, snow: day.weather.snow, uv_index: day.weather.uvi, moon_phase: day.astronomical.moonPhase, sunrise: day.astronomical.sunrise.toISOString(), sunset: day.astronomical.sunset.toISOString() })) || [], alerts: weatherData.alerts?.map(alert => ({ sender: alert.sender_name, event: alert.event, start_time: new Date(alert.start * 1000).toISOString(), end_time: new Date(alert.end * 1000).toISOString(), description: alert.description, tags: alert.tags })) || [] }; return { content: [ { type: "text", text: JSON.stringify(formattedData, null, 2) } ] }; } catch (error) { log.error("Failed to get OneCall weather", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('Invalid coordinates')) { throw new Error(`Invalid coordinates: latitude must be between -90 and 90, longitude must be between -180 and 180.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get OneCall weather: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Air Pollution Tool server.addTool({ name: "get-air-pollution", description: "Get air quality index and pollution data", parameters: getAirPollutionSchema, execute: async (args, { session, log }) => { try { log.info("Getting air pollution data", { latitude: args.latitude, longitude: args.longitude }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Set coordinates for air pollution API client.setLocationByCoordinates(args.latitude, args.longitude); // Fetch forecasted air pollution data (includes current + forecast) const pollutionData = await client.getForecastedAirPollution(); log.info("Successfully retrieved air pollution data", { latitude: args.latitude, longitude: args.longitude, data_points: pollutionData.length }); // Format the response with current and forecast data const formattedData = { location: { latitude: args.latitude, longitude: args.longitude }, current: pollutionData.length > 0 ? { datetime: pollutionData[0].dt.toISOString(), air_quality_index: pollutionData[0].aqi, air_quality_description: pollutionData[0].aqiName, pollutants: { carbon_monoxide: { value: pollutionData[0].components.co, unit: "μg/m³", description: "Carbon monoxide" }, nitrogen_monoxide: { value: pollutionData[0].components.no, unit: "μg/m³", description: "Nitrogen monoxide" }, nitrogen_dioxide: { value: pollutionData[0].components.no2, unit: "μg/m³", description: "Nitrogen dioxide" }, ozone: { value: pollutionData[0].components.o3, unit: "μg/m³", description: "Ozone" }, sulphur_dioxide: { value: pollutionData[0].components.so2, unit: "μg/m³", description: "Sulphur dioxide" }, pm2_5: { value: pollutionData[0].components.pm2_5, unit: "μg/m³", description: "Fine particles matter" }, pm10: { value: pollutionData[0].components.pm10, unit: "μg/m³", description: "Coarse particulate matter" }, ammonia: { value: pollutionData[0].components.nh3, unit: "μg/m³", description: "Ammonia" } } } : null, forecast: pollutionData.slice(1).map(data => ({ datetime: data.dt.toISOString(), air_quality_index: data.aqi, air_quality_description: data.aqiName, pollutants: { carbon_monoxide: data.components.co, nitrogen_monoxide: data.components.no, nitrogen_dioxide: data.components.no2, ozone: data.components.o3, sulphur_dioxide: data.components.so2, pm2_5: data.components.pm2_5, pm10: data.components.pm10, ammonia: data.components.nh3 } })), air_quality_scale: "1 (Good) to 5 (Very Poor)", units: "μg/m³ (micrograms per cubic meter)" }; return { content: [ { type: "text", text: JSON.stringify(formattedData, null, 2) } ] }; } catch (error) { log.error("Failed to get air pollution data", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('Invalid coordinates')) { throw new Error(`Invalid coordinates: latitude must be between -90 and 90, longitude must be between -180 and 180.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to get air pollution data: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Geocoding Tool server.addTool({ name: "geocode-location", description: "Convert location name to coordinates or vice versa", parameters: geocodeLocationSchema, execute: async (args, { session, log }) => { try { log.info("Geocoding location", { query: args.query, limit: args.limit }); // Get OpenWeather client const client = getOpenWeatherClient(session as SessionData | undefined); // Set location by the query (could be city name, zip code, etc.) client.setLocationByName(args.query); // Get all matching locations up to the specified limit const limit = args.limit || 5; const locations = await client.getAllLocations(limit.toString()); log.info("Successfully retrieved geocoding results", { query: args.query, results_count: locations.length, limit: limit }); // Format the response with all matching locations const formattedData = { query: args.query, limit: limit, results_count: locations.length, locations: locations.map(location => ({ name: location.name, local_names: location.local_names || {}, coordinates: { latitude: location.lat, longitude: location.lon }, country: location.country, state: location.state || null, formatted_address: [ location.name, location.state, location.country ].filter(Boolean).join(', ') })) }; return { content: [ { type: "text", text: JSON.stringify(formattedData, null, 2) } ] }; } catch (error) { log.error("Failed to geocode location", { error: error instanceof Error ? error.message : 'Unknown error' }); // Provide helpful error messages if (error instanceof Error) { if (error.message.includes('city not found')) { throw new Error(`Location "${args.query}" not found. Please check the spelling or try a different location name.`); } if (error.message.includes('Invalid API key')) { throw new Error('Invalid OpenWeatherMap API key. Please check your configuration.'); } } throw new Error(`Failed to geocode location: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); // Add documentation resource server.addResource({ uri: "openweather://api/docs", name: "OpenWeatherMap API Documentation", description: "Documentation for available weather data endpoints and response formats", mimeType: "text/markdown", load: async () => { return { text: `# OpenWeatherMap MCP Server Documentation ## Overview This MCP server provides access to the OpenWeatherMap API for weather data and forecasts. Authentication is handled server-side using environment variables. ## Authentication The server authenticates to OpenWeatherMap using environment variables: - \`OPENWEATHER_API_KEY\`: Your OpenWeatherMap API key (required) Authentication happens automatically on server startup. No client-side authentication is required. ## Available Tools ### Weather Operations - **get-current-weather**: Get current weather conditions - Parameters: - \`location\` (required): City name or coordinates - \`units\` (optional): Temperature units (metric/imperial/standard) - Returns: Current weather data - **get-weather-forecast**: Get weather forecast for up to 5 days - Parameters: - \`location\` (required): City name or coordinates - \`units\` (optional): Temperature units (metric/imperial/standard) - \`days\` (optional): Number of days (1-5) - Returns: Daily forecast data - **get-hourly-forecast**: Get hourly weather forecast for up to 48 hours - Parameters: - \`location\` (required): City name or coordinates - \`units\` (optional): Temperature units (metric/imperial/standard) - \`hours\` (optional): Number of hours (1-48) - Returns: Hourly forecast data - **get-onecall-weather**: Get comprehensive weather data - Parameters: - \`latitude\` (required): Latitude coordinate - \`longitude\` (required): Longitude coordinate - \`units\` (optional): Temperature units (metric/imperial/standard) - \`exclude\` (optional): Data to exclude - Returns: Complete weather data ### Air Quality Operations - **get-air-pollution**: Get air quality index and pollution data - Parameters: - \`latitude\` (required): Latitude coordinate - \`longitude\` (required): Longitude coordinate - Returns: Air quality data ### Geocoding Operations - **geocode-location**: Convert location names to coordinates - Parameters: - \`query\` (required): Location name or zip code - \`limit\` (optional): Maximum results (default: 5) - Returns: Array of matching locations ## Error Handling - All operations require valid OpenWeatherMap authentication - Invalid locations will return validation errors - Network errors are handled gracefully with descriptive messages - Server maintains session state across tool calls ## Environment Setup Required environment variables: - \`OPENWEATHER_API_KEY\`: OpenWeatherMap API key (used for both API access and HTTP stream authentication) - \`PORT\`: Server port (default: 3000, for HTTP transport only) `.trim() }; } }); // Start server with dynamic transport configuration async function startServer() { // Initialize authentication for stdio if (transportConfig.transportType === "stdio") { await initializeStdioAuth(); } if (transportConfig.transportType === "httpStream") { // Log startup information console.log(`HTTP Stream configuration: port=${transportConfig.httpStream?.port}, endpoint=${transportConfig.httpStream?.endpoint}`); await server.start({ transportType: "httpStream", httpStream: { port: transportConfig.httpStream!.port } }); console.log(`OpenWeatherMap MCP Server running on port ${transportConfig.httpStream!.port}`); console.log(`HTTP endpoint: ${transportConfig.httpStream!.endpoint}`); console.log("Authentication: HTTP Bearer token with OpenWeatherMap API key"); } else { await server.start({ transportType: "stdio" }); } } // Start the server startServer().catch((error) => { console.error("Failed to start server:", 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/robertn702/mcp-openweathermap'

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