Skip to main content
Glama
JuanPedroSosa

Weather MCP Server

Create a new directory for our project

mkdir weather cd weather

Initialize a new npm project

npm init -y

Install dependencies

npm install @modelcontextprotocol/sdk zod npm install -D @types/node typescript

Create our files

mkdir src touch src/index.ts

SosaJuanPedro: Puedes explicar el código y para qué sirve

GitHub Copilot: Claro, el código que compartiste es una implementación de un servidor que utiliza el protocolo Model Context Protocol (MCP) para proporcionar herramientas relacionadas con el clima. Este servidor interactúa con la API del Servicio Meteorológico Nacional de EE. UU. (NWS API) para obtener datos meteorológicos como alertas y pronósticos. A continuación, te explico las partes principales del código y su propósito:


1. Configuración del servidor MCP

El servidor se crea utilizando la clase McpServer del SDK de MCP. Este servidor tiene un nombre (weather), una versión (1.0.0) y capacidades definidas (aunque en este caso están vacías).

const server = new McpServer({
    name: "weather",
    version: "1.0.0",
    capabilities: {
        resources: {},
        tools: {},
    },
});

2. Función para realizar solicitudes a la API de NWS

La función makeNWSRequest realiza solicitudes HTTP a la API de NWS, manejando encabezados específicos y errores. Devuelve los datos en formato JSON o null si ocurre un error.

async function makeNWSRequest<T>(url: string): Promise<T | null> {
    const headers = {
        "User-Agent": USER_AGENT,
        Accept: "application/geo+json",
    };

    try {
        const response = await fetch(url, { headers });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return (await response.json()) as T;
    } catch (error) {
        console.error("Error making NWS request:", error);
        return null;
    }
}

3. Herramienta get_alerts: Obtener alertas meteorológicas

Esta herramienta permite al usuario obtener alertas meteorológicas activas para un estado específico de EE. UU. (por ejemplo, "CA" para California).

  1. Construye la URL de la API para las alertas.

  2. Realiza la solicitud a la API.

  3. Si hay alertas, las formatea utilizando la función formatAlert y las devuelve como texto.

server.tool(
    "get_alerts",
    "Get weather alerts for a state",
    {
        state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)"),
    },
    async ({ state }) => {
        const stateCode = state.toUpperCase();
        const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
        const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);

        if (!alertsData) {
            return {
                content: [
                    {
                        type: "text",
                        text: "Failed to retrieve alerts data",
                    },
                ],
            };
        }

        const features = alertsData.features || [];
        if (features.length === 0) {
            return {
                content: [
                    {
                        type: "text",
                        text: `No active alerts for ${stateCode}`,
                    },
                ],
            };
        }

        const formattedAlerts = features.map(formatAlert);
        const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;

        return {
            content: [
                {
                    type: "text",
                    text: alertsText,
                },
            ],
        };
    },
);

4. Herramienta get_forecast: Obtener pronóstico del clima

Esta herramienta permite al usuario obtener el pronóstico del clima para una ubicación específica (latitud y longitud).

  1. Realiza una solicitud a la API de NWS para obtener datos de puntos de cuadrícula (grid points).

  2. Usa esos datos para obtener la URL del pronóstico.

  3. Recupera los datos del pronóstico y los formatea en un texto legible.

server.tool(
    "get_forecast",
    "Get weather forecast for a location",
    {
        latitude: z.number().min(-90).max(90).describe("Latitude of the location"),
        longitude: z.number().min(-180).max(180).describe("Longitude of the location"),
    },
    async ({ latitude, longitude }) => {
        const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
        const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);

        if (!pointsData) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
                    },
                ],
            };
        }

        const forecastUrl = pointsData.properties?.forecast;
        if (!forecastUrl) {
            return {
                content: [
                    {
                        type: "text",
                        text: "Failed to get forecast URL from grid point data",
                    },
                ],
            };
        }

        const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
        if (!forecastData) {
            return {
                content: [
                    {
                        type: "text",
                        text: "Failed to retrieve forecast data",
                    },
                ],
            };
        }

        const periods = forecastData.properties?.periods || [];
        if (periods.length === 0) {
            return {
                content: [
                    {
                        type: "text",
                        text: "No forecast periods available",
                    },
                ],
            };
        }

        const formattedForecast = periods.map((period: ForecastPeriod) =>
            [
                `${period.name || "Unknown"}:`,
                `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
                `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
                `${period.shortForecast || "No forecast available"}`,
                "---",
            ].join("\n"),
        );

        const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;

        return {
            content: [
                {
                    type: "text",
                    text: forecastText,
                },
            ],
        };
    },
);

5. Ejecución del servidor

El servidor utiliza StdioServerTransport para comunicarse a través de la entrada/salida estándar. Esto permite que el servidor reciba solicitudes y envíe respuestas.

async function main() {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("Weather MCP Server running on stdio");
}

main().catch((error) => {
    console.error("Fatal error in main():", error);
    process.exit(1);
});

Propósito general

Este servidor MCP permite a los usuarios interactuar con herramientas para obtener:

  1. Alertas meteorológicas para un estado específico.

  2. Pronósticos del clima para una ubicación específica (latitud y longitud).

El servidor utiliza la API de NWS para obtener los datos y los formatea en un formato legible para el usuario. Es útil para aplicaciones que necesitan integrar datos meteorológicos en tiempo real.

SosaJuanPedro: Puedes darme la explicación en formato mark down

GitHub Copilot: ### Explicación del Código

El código proporcionado es una implementación de un servidor MCP (Model Context Protocol) que ofrece herramientas para obtener datos meteorológicos, como alertas y pronósticos, utilizando la API del Servicio Meteorológico Nacional de EE. UU. (NWS API). A continuación, se detalla la funcionalidad del fragmento de código destacado y su contexto:


Fragmento Destacado

const formattedForecast = periods.map((period: ForecastPeriod) =>
    [
        `${period.name || "Unknown"}:`,
        `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
        `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
        `${period.shortForecast || "No forecast available"}`,
        "---",
    ].join("\n"),
);

¿Qué hace este fragmento?

  1. Propósito: Este código formatea los datos de los períodos del pronóstico meteorológico obtenidos de la API de NWS para que sean legibles y fáciles de interpretar.

  2. Entrada:

    • periods: Una lista de objetos que representan los períodos del pronóstico meteorológico. Cada objeto tiene propiedades como:

      • name: Nombre del período (por ejemplo, "Hoy", "Esta noche").

      • temperature: Temperatura esperada.

      • temperatureUnit: Unidad de temperatura (por ejemplo, "F" para Fahrenheit).

      • windSpeed: Velocidad del viento.

      • windDirection: Dirección del viento.

      • shortForecast: Resumen breve del pronóstico (por ejemplo, "Soleado").

  3. Salida:

    • Una lista de cadenas de texto formateadas, donde cada cadena representa un período del pronóstico con información como:

      • Nombre del período.

      • Temperatura y unidad.

      • Velocidad y dirección del viento.

      • Resumen breve del pronóstico.

  4. Manejo de Valores Faltantes:

    • Si alguna propiedad no está disponible, se utiliza un valor predeterminado como "Unknown" o "No forecast available".


Ejemplo de Salida

Si periods contiene los siguientes datos:

[
    {
        name: "Today",
        temperature: 75,
        temperatureUnit: "F",
        windSpeed: "10 mph",
        windDirection: "NW",
        shortForecast: "Sunny",
    },
    {
        name: "Tonight",
        temperature: 60,
        temperatureUnit: "F",
        windSpeed: "5 mph",
        windDirection: "N",
        shortForecast: "Clear",
    },
]

El resultado de formattedForecast será:

Today:
Temperature: 75°F
Wind: 10 mph NW
Sunny
---
Tonight:
Temperature: 60°F
Wind: 5 mph N
Clear
---

Contexto en el Código

Este fragmento forma parte de la herramienta get_forecast, que:

  1. Obtiene el pronóstico meteorológico para una ubicación específica (latitud y longitud) utilizando la API de NWS.

  2. Formatea los datos del pronóstico (como se muestra en este fragmento) para devolverlos al usuario en un formato legible.

  3. Devuelve el pronóstico como una respuesta estructurada que puede ser utilizada por el cliente MCP.


Propósito General

El fragmento es clave para transformar los datos crudos de la API en un formato amigable para el usuario, asegurando que la información meteorológica sea clara y comprensible. Esto es útil para aplicaciones que necesitan mostrar pronósticos detallados de manera estructurada.

Available Tools

2 tools
get_alertsB

Get weather alerts for a state

ParametersJSON Schema
NameRequiredDescriptionDefault
stateYesTwo-letter state code (e.g. CA, NY)

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must disclose behavioral traits. It does not mention that the tool is read-only, any rate limits, or what the output contains. The description is too minimal to inform an agent about side effects or safety.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no unnecessary words. It is efficiently structured and front-loads the essential information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the lack of output schema and annotations, the description is incomplete. It does not describe the return format or structure of alerts, which is important for an agent to process the results. For a simple tool, this gap is significant.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and the schema already documents the 'state' parameter fully (two-letter code, example). The description adds no additional information about parameters, so baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Get' and the resource 'weather alerts' with a qualifier 'for a state', making the purpose unambiguous. The sibling tool 'get_forecast' is distinct enough to differentiate without explicit mention.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives like 'get_forecast'. No mention of prerequisites or conditions for use. The description only states what the tool does, not when to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_forecastB

Get weather forecast for a location

ParametersJSON Schema
NameRequiredDescriptionDefault
latitudeYesLatitude of the location
longitudeYesLongitude of the location

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must disclose behavioral traits, but it only states 'Get weather forecast for a location'. It does not mention what kind of forecast (current, daily, hourly), potential destructive actions (none implied but not confirmed), or any required permissions. The agent has no insight into side effects or return behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no wasted words. It is appropriately concise for a simple tool with two well-documented parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's low complexity (2 params, no output schema, no annotations), the description is minimally adequate. However, it omits key details such as the forecast type, time horizon, units, and any specifics about the response. A slightly more complete description would improve usability.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with each parameter having a clear description. The tool description adds no further parameter meaning. Baseline score of 3 is appropriate since the schema already documents parameters adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action (get) and resource (weather forecast) with a location qualifier. However, it does not differentiate from the sibling tool 'get_alerts', which likely also relates to weather. Without distinguishing context, the agent may not know when to choose one over the other.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus the alternative 'get_alerts'. There is no mention of prerequisites, typical use cases, or selection criteria, leaving the agent without context to make an informed choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updates
    • First observedget_alerts
    • First observedget_forecast

TDQS

B3.1/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: one retrieves weather alerts for a state, while the other retrieves forecasts for a location. There is no overlap in functionality or ambiguity between them.

Naming Consistency5/5

Both tools follow a consistent verb_noun pattern with 'get_' prefix and descriptive nouns (alerts, forecast). The naming is uniform and predictable across the set.

Tool Count2/5

With only two tools, the server feels thin for a weather domain, lacking operations like current conditions, historical data, or radar imagery. This minimal set may limit agent capabilities.

Completeness2/5

The toolset is severely incomplete for weather services, missing core functions such as current weather, hourly forecasts, or severe weather details. Agents will face significant gaps in handling typical weather queries.

Maintenance

ActivityInactive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers