PTENES
MÓDULO 3.5

🌤️ Server de consulta del clima

Crea una tool que consulta una API externa real: aprende a hacer solicitudes HTTP dentro de un MCP Server.

6
Temas
35
Minutos
Intermedio
Nivel
Práctico
Tipo
1

🌐 Eligiendo la API

Para nuestro server del clima, vamos a usar la wttr.in — una API meteorológica gratuita, sin necesidad de registrarse ni de una clave de autenticación. Devuelve datos meteorológicos en texto simple o JSON, es muy fácil de usar y perfecta para aprender. Solo tienes que hacer una solicitud GET y los datos llegan listos.

💎 Concepto principal

A wttr.in se accede mediante una URL simple. Para obtener datos en JSON, basta con agregar el parámetro ?format=j1:

# Ejemplos de URL:

https://wttr.in/São Paulo?format=j1

https://wttr.in/Tokyo?format=j1

https://wttr.in/London?format=j1

El JSON devuelto contiene la temperatura, las condiciones climáticas, la humedad, la sensación térmica, la velocidad del viento y mucho más. Vamos a extraer los datos más relevantes para crear una respuesta útil.

📊 Datos e investigación

wttr.in es un proyecto open source alojado en GitHub. Admite más de 50 idiomas, acepta nombres de ciudades en cualquier idioma y también funciona con coordenadas geográficas. La usan millones de desarrolladores y no requiere autenticación — ideal para crear prototipos rápidamente y para proyectos de aprendizaje como el nuestro.

💡 Consejo práctico

Antes de implementar la tool, prueba la API directamente en el navegador o en la terminal. Accede a https://wttr.in/São Paulo?format=j1 y revisa la estructura del JSON. Esto ayuda a entender qué campos están disponibles y cómo extraer los datos que queremos. También puedes probarlo en la terminal con curl "wttr.in/São Paulo?format=j1".

2

📡 Solicitudes HTTP en Python

Para hacer solicitudes HTTP dentro de nuestro server MCP, necesitamos una biblioteca HTTP. En Python, las dos opciones principales son requests (síncrona) y httpx (con soporte async). Para los servers MCP, el httpx es la opción preferida porque admite llamadas asíncronas de forma nativa, lo que encaja mejor con la arquitectura de MCP.

💎 Concepto principal

Instala httpx en tu proyecto usando el gestor de paquetes uv:

# Agregar httpx como dependencia

uv add httpx

Con el httpx, podemos usar async with httpx.AsyncClient() para hacer solicitudes HTTP asíncronas dentro de tools MCP. La función de la tool se declarará como async def, lo que permite que el server siga respondiendo mientras espera la respuesta de la API externa.

❌ Evitar
  • •Usar requests (síncrono) en tools async
  • •Hacer solicitudes bloqueantes en el event loop
  • •Ignorar los timeouts en las solicitudes HTTP
  • •Crear clientes HTTP en cada llamada
✅ Hacer
  • •Usar httpx con AsyncClient para async
  • •Declarar las tools como async def cuando realizan I/O
  • •Siempre define un timeout en las solicitudes
  • •Usar un administrador de contexto (async with) para el client
3

💻 Implementando la tool

Ahora implementaremos la tool para consultar el clima. Recibirá el nombre de una ciudad como string, hará una solicitud HTTP a wttr.in, analizará el JSON de respuesta y extraerá la información más relevante: temperatura, condición climática, humedad y sensación térmica.

💎 Concepto principal

from mcp.server.fastmcp import FastMCP

import httpx


mcp = FastMCP("Clima")


@mcp.tool()

async def consultar_clima(ciudad: str) -> str:

"""Consulta el clima actual de una ciudad."""

async with httpx.AsyncClient() las client:

url = f"https://wttr.in/{cidade}?format=j1"

response = await client.get(url, timeout=10.0)

response.raise_for_status()

dados = response.json()


current = dados["current_condition"][0]

temp = current["temp_C"]

sensacao = current["FeelsLikeC"]

humedad = current["humidity"]

descricao = current["weatherDesc"][0]["value"]


return (

f"Clima en {cidade}:\n"

f"Temperatura: {temp}°C\n"

f"Sensación térmica: {sensacao}°C\n"

f"Humedad: {umidade}%\n"

f"Condición: {descricao}"

)

💡 Consejo práctico

Observa que usamos async def e await en la tool. FastMCP admite tools asíncronas de forma nativa: basta con declarar la función como async. O timeout=10.0 garantiza que la solicitud no quede pendiente para siempre si la API no responde. Siempre define timeouts en las solicitudes HTTP externas.

4

📊 Formateo de la respuesta

La forma en que devuelves los datos de la tool influye directamente en la calidad de la respuesta que el modelo dará al usuario. Devolver una cadena formateada y legible es mucho mejor que devolver el JSON sin procesar de la API. El modelo recibe esta cadena y se la presenta al usuario de forma natural y conversacional.

💎 Concepto principal

La respuesta de la tool debe ser una cadena estructurada pero legible. El modelo lee esta cadena y la transforma en lenguaje natural para el usuario. Compara los dos enfoques:

JSON sin procesar (malo)

{"temp_C": "25", "humidity": "60", "weatherDesc": [{"value": "Clear"}], ...}

Cadena con formato (buena)

Clima en São Paulo:
Temperatura: 25°C
Humedad: 60%
Condición: Clear

❌ Evitar
  • •Devolver el JSON sin procesar de la API
  • •Incluir campos irrelevantes en la respuesta
  • •Usar códigos o abreviaturas técnicas
  • •Devolver datos sin unidades de medida
✅ Hacer
  • •Formatear una cadena con datos seleccionados
  • •Incluir solo información útil para el usuario
  • •Usar etiquetas claras: "Temperatura:", "Humedad:"
  • •Siempre incluye unidades (°C, %, km/h)
5

⚠️ Manejo de errores

Cuando tu tool depende de una API externa, muchas cosas pueden salir mal: la ciudad puede no existir, la conexión a internet puede fallar, la API puede estar fuera de servicio o la solicitud puede tardar demasiado. Cada uno de estos escenarios debe manejarse por separado para que el server siga funcionando y el modelo reciba un mensaje de error útil.

💎 Concepto principal

@mcp.tool()

async def consultar_clima(ciudad: str) -> str:

"""Consulta el clima actual de una ciudad."""

try:

async with httpx.AsyncClient() las client:

url = f"https://wttr.in/{cidade}?format=j1"

response = await client.get(url, timeout=10.0)

response.raise_for_status()

dados = response.json()

# ... extraer y devolver datos

except httpx.HTTPStatusError:

return f"Error: ciudad '{cidade}' no encontrada."

except httpx.ConnectError:

return "Error: no hay conexión a Internet."

except httpx.TimeoutException:

return "Error: la API tardó demasiado en responder."

except Exception las e:

return f"Error inesperado: {str(e)}"

🚨 Alerta

¡Nunca confíes en APIs externas! Toda solicitud HTTP puede fallar. Una tool que funciona perfectamente en tu prueba local puede fallar en producción porque la API dejó de funcionar, cambió el formato de la respuesta o alcanzó el rate limit. El manejo de errores no es opcional: es obligatorio para cualquier tool que haga solicitudes externas.

📋 Tipos de errores y cómo tratarlos
HTTPStatusError (404, 500, etc.)

La API respondió con un código de error. Ciudad no encontrada (404) o error interno de la API (500). Devuelve un mensaje claro que indique el problema.

ConnectError

No fue posible conectarse a la API; generalmente significa que no hay internet o que no se resolvió el DNS. Informa al usuario que la conexión falló.

TimeoutException

La API tardó más que el timeout definido (10 segundos). Puede deberse a una sobrecarga del servidor. Indica que la API no respondió a tiempo.

Exception genérica

Cualquier error inesperado — JSON mal formado, campo ausente, etc. El except genérico al final garantiza que ningún error quede sin manejar.

6

🧪 Probando con Claude

Con la tool implementada y el manejo de errores listo, configura el server en Claude Desktop y prueba distintas ciudades y escenarios. El objetivo es verificar que la tool devuelva datos correctos y que los errores se manejen correctamente.

💎 Concepto principal

Prueba con preguntas naturales para verificar que Claude use la tool correctamente:

  • 1. "¿Cómo está el clima en São Paulo?" — debe devolver la temperatura, la humedad y las condiciones.
  • 2. "¿Cuál es la temperatura en Tokyo?" — prueba con una ciudad en otro idioma.
  • 3. "¿Necesito paraguas en Londres hoy?" — Claude debe inferirlo a partir de las condiciones climáticas.
  • 4. "Clima en XyzCidadeInexistente" — debe devolver un mensaje de error controlado.
💡 Consejo práctico

Observa cómo Claude interpreta los datos devueltos por la tool. No solo repite los números: los contextualiza. Si la temperatura es de 35°C, puede decir «hace bastante calor». Si la humedad es del 90%, puede advertir que el ambiente se siente bochornoso. Cuanto más clara sea la respuesta de la tool, mejor será la interpretación del modelo.

📊 Datos e investigación

Este server del clima es un ejemplo clásico de "API bridge" — un patrón muy común en servers MCP. El server actúa como puente entre el modelo y una API externa: traduce la solicitud del modelo en una llamada HTTP y la respuesta HTTP en texto para el modelo. Este mismo patrón se puede aplicar a cualquier API: pronóstico del tiempo, cotizaciones, noticias, datos financieros, estado de servicios y mucho más.

📝 Resumen del módulo

  • ✓ Elegimos la API wttr.in, gratuita y sin autenticación, ideal para aprender.
  • ✓ Usamos httpx (async) para realizar solicitudes HTTP dentro del server MCP.
  • ✓ Implementamos la tool consultar_clima con async def y await.
  • ✓ Formateamos la respuesta con datos seleccionados y unidades de medida claras.
  • ✓ Manejamos errores específicos: ciudad no encontrada, falta de conexión a Internet, timeout y errores inesperados.
  • ✓ Probamos con diferentes ciudades y escenarios de error en Claude Desktop.

Siguiente Módulo: 3.6 — Añadiendo recursos