PTENES
MODULE 3.5

🌤️ Weather Lookup Server

Create a tool that queries a real external API—learn how to make HTTP requests inside an MCP Server.

6
Topics
35
Minutes
Intermediate
Level
Practical
Type
1

🌐 Choosing the API

For our weather server, we'll use the wttr.in — a free weather API that doesn't require registration or an authentication key. It returns weather data as plain text or JSON, is extremely simple to use, and is perfect for learning. Just make a GET request, and the data arrives ready to use.

💎 Core Concept

A wttr.in is accessible via a simple URL. To get data in JSON, just add the parameter ?format=j1:

# URL examples:

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

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

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

The returned JSON contains temperature, weather conditions, humidity, feels-like temperature, wind speed, and more. Let’s extract the most relevant data to create a useful response.

📊 Data and Research

wttr.in is an open-source project hosted on GitHub. It supports more than 50 languages, accepts city names in any language and also works with geographic coordinates. It’s used by millions of developers and requires no authentication — ideal for rapid prototyping and learning projects like ours.

💡 Practical Tip

Before implementing the tool, test the API directly in your browser or terminal. Go to https://wttr.in/São Paulo?format=j1 and inspect the JSON structure. This helps you understand which fields are available and how to extract the data we want. You can also test in the terminal with curl "wttr.in/São Paulo?format=j1".

2

📡 HTTP Requests in Python

To make HTTP requests in our MCP server, we need an HTTP library. In Python, the two main options are requests (synchronous) and httpx (with async support). For MCP servers, httpx is the preferred choice because it supports asynchronous calls natively, which fits the MCP architecture better.

💎 Core Concept

Install httpx in your project using the package manager uv:

# Add httpx as a dependency

uv add httpx

With the httpx, we can use async with httpx.AsyncClient() to make asynchronous HTTP requests inside MCP tools. The tool function will be declared as async def, allowing the server to remain responsive while waiting for the external API response.

❌ Avoid
  • •Use requests (synchronous) in async tools
  • •Make blocking requests in the event loop
  • •Ignore timeouts in HTTP requests
  • •Create HTTP clients on every call
✅ Do
  • •Use httpx with AsyncClient for async
  • •Declare tools as async def when they perform I/O
  • •Always set a timeout for requests
  • •Use a context manager (async with) for the client
3

💻 Implementing the Tool

Now let’s implement the weather lookup tool. It will receive a city name as a string, make an HTTP request to wttr.in, parse the response JSON, and extract the most relevant information: temperature, weather conditions, humidity, and feels-like temperature.

💎 Core Concept

from mcp.server.fastmcp import FastMCP

import httpx


mcp = FastMCP("Weather")


@mcp.tool()

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

"""Gets the current weather for a city."""

async with httpx.AsyncClient() the client:

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

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

response.raise_for_status()

data = response.json()


current = dados["current_condition"][0]

temp = current["temp_C"]

feeling = current["FeelsLikeC"]

humidity = current["humidity"]

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


return (

f"Weather in {cidade}:\n"

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

f"Feels like: {sensacao}°C\n"

f"Humidity: {umidade}%\n"

f"Condition: {descricao}"

)

💡 Practical Tip

Notice that we use async def e await in the tool. FastMCP supports async tools natively—just declare the function as async. O timeout=10.0 ensures the request doesn’t hang forever if the API doesn’t respond. Always set timeouts for external HTTP requests.

4

📊 Formatting the Response

How you return tool data directly affects the quality of the response the model gives the user. Returning a readable, formatted string is much better than returning the API's raw JSON. The model receives this string and presents it to the user in a natural, conversational way.

💎 Core Concept

The tool response should be a structured but readable string. The model reads this string and turns it into natural language for the user. Compare the two approaches:

Raw JSON (bad)

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

Formatted string (good)

Weather in São Paulo:
Temperature: 25°C
Humidity: 60%
Condition: Clear

❌ Avoid
  • •Return the raw API JSON
  • •Include irrelevant fields in the response
  • •Use technical codes or abbreviations
  • •Return data without units of measurement
✅ Do
  • •Format a string with selected data
  • •Include only information useful to the user
  • •Use clear labels: "Temperature:", "Humidity:"
  • •Always include units (°C, %, km/h)
5

⚠️ Error Handling

When your tool depends on an external API, many things can go wrong: the city may not exist, the internet may go down, the API may be unavailable, or the request may take too long. Each of these scenarios needs to be handled individually so the server keeps working and the model receives a useful error message.

💎 Core Concept

@mcp.tool()

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

"""Gets the current weather for a city."""

try:

async with httpx.AsyncClient() the client:

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

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

response.raise_for_status()

data = response.json()

# ... extract and return data

except httpx.HTTPStatusError:

return f"Error: city '{cidade}' not found."

except httpx.ConnectError:

return "Error: No internet connection."

except httpx.TimeoutException:

return "Error: The API took too long to respond."

except Exception the e:

return f"Unexpected error: {str(e)}"

🚨 Alert

Never trust external APIs! Any HTTP request can fail. A tool that works perfectly in your local test can break in production because the API went down, changed its response format, or hit the rate limit. Error handling is not optional — it's required for any tool that makes external requests.

📋 Error Types and How to Handle Them
HTTPStatusError (404, 500, etc.)

The API returned an error code. City not found (404) or an internal API error (500). Return a clear message indicating the problem.

ConnectError

Couldn't connect to the API — this usually means there's no internet connection or DNS couldn't be resolved. Tell the user that the connection failed.

TimeoutException

The API took longer than the defined timeout (10 seconds). The server may be overloaded. Let the user know that the API didn't respond in time.

Generic Exception

Any unexpected error — malformed JSON, missing field, etc. The generic except at the end ensures no error goes unhandled.

6

🧪 Testing with Claude

With the tool implemented and error handling in place, configure the server in Claude Desktop and test with different cities and scenarios. The goal is to verify that the tool returns correct data and that errors are handled gracefully.

💎 Core Concept

Test with natural questions to verify that Claude uses the tool correctly:

  • 1. "What's the weather like in São Paulo?" — should return the temperature, humidity, and conditions.
  • 2. "What's the temperature in Tokyo?" — test with a city in another language.
  • 3. "Do I need an umbrella in London today?" — Claude should infer this from the weather conditions.
  • 4. "Weather in XyzCidadeInexistente" — should return a handled error message.
💡 Practical Tip

Observe how Claude interprets the data returned by the tool. It doesn't just repeat the numbers—it puts them in context. If the temperature is 35°C, it can say "it's quite hot." If the humidity is 90%, it can warn about the muggy conditions. The clearer the tool's response, the better the model's interpretation.

📊 Data and Research

This weather server is a classic example of "API bridge" — a very common pattern in MCP servers. The server acts as a bridge between the model and an external API, translating the model's request into an HTTP call and the HTTP response into text for the model. This same pattern can be applied to any API: weather forecasts, stock quotes, news, financial data, service status, and much more.

📝 Module Summary

  • ✓ We chose the wttr.in API—free, requires no authentication, and ideal for learning.
  • ✓ We use httpx (async) to make HTTP requests from within the MCP server.
  • ✓ We implemented the consultar_clima tool with async def and await.
  • ✓ We format the response with selected data and clear units of measurement.
  • ✓ We handle specific errors: city not found, no internet connection, timeout, and unexpected errors.
  • ✓ We tested with different cities and error scenarios in Claude Desktop.

Next Module: 3.6 — Adding Resources