UNPKG

mcp-server-clima

Version:

MCP server para información del clima

268 lines (267 loc) 9.13 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode, } from "@modelcontextprotocol/sdk/types.js"; import fetch from "node-fetch"; // Definición de herramientas const GET_WEATHER_TOOL = { name: "get_weather", description: "Obtiene el clima actual para una ubicación específica", inputSchema: { type: "object", properties: { location: { type: "string", description: "Ciudad y país (ej. 'Madrid,ES')" }, units: { type: "string", description: "Unidades de temperatura (metric, imperial, standard)", enum: ["metric", "imperial", "standard"] } }, required: ["location"] } }; const GET_FORECAST_TOOL = { name: "get_forecast", description: "Obtiene el pronóstico del clima para los próximos días", inputSchema: { type: "object", properties: { location: { type: "string", description: "Ciudad y país (ej. 'Madrid,ES')" }, days: { type: "number", description: "Número de días para el pronóstico (máximo 5)" }, units: { type: "string", description: "Unidades de temperatura (metric, imperial, standard)", enum: ["metric", "imperial", "standard"] } }, required: ["location"] } }; const WEATHER_TOOLS = [ GET_WEATHER_TOOL, GET_FORECAST_TOOL ]; // Constantes const API_KEY = process.env.OPENWEATHER_API_KEY || "INTRODUCE_TU_API_KEY"; // Necesitarás una API key de OpenWeatherMap const BASE_URL = "https://api.openweathermap.org/data/2.5"; // Manejadores de herramientas async function handleGetWeather(params) { const { location, units = "metric" } = params; try { // Construir la URL de la API const url = new URL(`${BASE_URL}/weather`); url.searchParams.append("q", location); url.searchParams.append("units", units); url.searchParams.append("appid", API_KEY); // Realizar la solicitud const response = await fetch(url.toString()); if (!response.ok) { const errorData = await response.json(); return { content: [{ type: "text", text: JSON.stringify({ error: `Error al obtener el clima: ${errorData.message}`, statusCode: response.status }, null, 2) }], isError: true }; } const data = await response.json(); // Formatear los datos para una mejor presentación const weatherInfo = { location: { name: data.name, country: data.sys.country, coordinates: { lat: data.coord.lat, lon: data.coord.lon } }, current: { temperature: data.main.temp, feels_like: data.main.feels_like, humidity: data.main.humidity, pressure: data.main.pressure, description: data.weather[0].description, icon: data.weather[0].icon }, wind: { speed: data.wind.speed, direction: data.wind.deg }, sun: { sunrise: new Date(data.sys.sunrise * 1000).toLocaleTimeString(), sunset: new Date(data.sys.sunset * 1000).toLocaleTimeString() }, units: units }; return { content: [{ type: "text", text: JSON.stringify(weatherInfo, null, 2) }], isError: false }; } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } async function handleGetForecast(params) { const { location, days = 5, units = "metric" } = params; try { // Limitar el número de días a 5 const limitedDays = Math.min(Math.max(1, days), 5); // Construir la URL de la API const url = new URL(`${BASE_URL}/forecast`); url.searchParams.append("q", location); url.searchParams.append("units", units); url.searchParams.append("appid", API_KEY); // Realizar la solicitud const response = await fetch(url.toString()); if (!response.ok) { const errorData = await response.json(); return { content: [{ type: "text", text: JSON.stringify({ error: `Error al obtener el pronóstico: ${errorData.message}`, statusCode: response.status }, null, 2) }], isError: true }; } const data = await response.json(); // La API devuelve pronósticos cada 3 horas durante 5 días // Vamos a agruparlos por día const forecasts = []; const forecastsByDay = {}; // Agrupar pronósticos por día data.list.forEach((item) => { const date = new Date(item.dt * 1000); const day = date.toISOString().split('T')[0]; if (!forecastsByDay[day]) { forecastsByDay[day] = []; } forecastsByDay[day].push({ time: date.toLocaleTimeString(), temperature: item.main.temp, weather: { main: item.weather[0].main, description: item.weather[0].description, icon: item.weather[0].icon }, wind: { speed: item.wind.speed, direction: item.wind.deg }, humidity: item.main.humidity, pressure: item.main.pressure }); }); // Tomar solo los días solicitados const daysList = Object.keys(forecastsByDay).slice(0, limitedDays); daysList.forEach(day => { forecasts.push({ date: day, forecasts: forecastsByDay[day] }); }); const forecastInfo = { location: { name: data.city.name, country: data.city.country, coordinates: { lat: data.city.coord.lat, lon: data.city.coord.lon } }, forecast: forecasts, units: units }; return { content: [{ type: "text", text: JSON.stringify(forecastInfo, null, 2) }], isError: false }; } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } // Configuración del servidor const server = new Server({ name: "weather-mcp", version: "0.1.0", }, { capabilities: { tools: {}, }, }); // Configurar manejadores de solicitudes server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: WEATHER_TOOLS, })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { switch (request.params.name) { case "get_weather": { return await handleGetWeather(request.params.arguments); } case "get_forecast": { return await handleGetForecast(request.params.arguments); } default: throw new McpError(ErrorCode.MethodNotFound, `Herramienta desconocida: ${request.params.name}`); } } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }); // Iniciar el servidor async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Servidor MCP de clima ejecutándose en stdio"); } runServer().catch((error) => { console.error("Error fatal al ejecutar el servidor:", error); process.exit(1); });