Skip to main content
Glama
matjmiles

mcp-weather-tutorial

by matjmiles

MCP Weather Tutorial

A working MCP server and two clients, built against Open-Meteo (free, no API key, no signup).

Heads up: most MCP tutorials online are out of date

They open with:

from mcp.server.fastmcp import FastMCP   # ModuleNotFoundError on mcp 2.0

FastMCP was removed in mcp 2.0. It is now MCPServer, from mcp.server. Response fields also moved to snake_case (input_schema, not inputSchema). If a tutorial mentions FastMCP, it is describing 1.x.

Verified against mcp 2.0.0 and claude-agent-sdk 0.2.95.

Related MCP server: open-meteo-mcp

Files

File

What it does

Needs

server.py

MCP server exposing geocode_city and get_forecast

nothing

client_basic.py

Raw protocol client -- no LLM, nothing hidden

nothing

client_agent.py

Claude picks and chains the tools itself

Claude Code login

Run it

uv run client_basic.py                        # defaults to Denver
uv run client_basic.py Lisbon
uv run client_basic.py San Francisco          # spaces are fine, no quotes
uv run client_basic.py Portland --pick 1      # 2nd match, not the 1st
uv run client_basic.py Tokyo --days 7
uv run client_basic.py --lat 27.99 --lon 86.93   # skip geocoding entirely
uv run client_basic.py Lisbon --json          # also dump the raw payload

uv run client_agent.py                        # just ask, in English
uv run client_agent.py "should I bring an umbrella in Lisbon tomorrow?"
uv run client_agent.py "compare Tokyo and Seoul this weekend"

--pick exists because city names are ambiguous: "Denver" matches five places and "Portland" two well-known ones. Open-Meteo ranks by population, so the top hit is a guess. client_basic.py lists every match and marks the one it used; client_agent.py lets Claude choose and say why.

uv run server.py on its own will look like it hangs. It hasn't -- it is waiting for a client on stdin.

Debugging

Press F5 in VS Code and pick a configuration. Start with "1. Step into server tools (in-process)".

The thing that catches people out: normally client_basic.py launches server.py as a subprocess, and a debugger does not follow across a process boundary. Breakpoints in server.py are silently never hit, which looks like the tool is not being called at all.

--in-process fixes this. Client() accepts an MCPServer object directly and runs it in the same process, no pipes involved:

uv run client_basic.py Denver --in-process     # identical output, one process

Now a breakpoint inside geocode_city hits, and Step Into (F11) on call_tool(...) walks from the client, through the protocol layer, into the tool body.

Good breakpoints to start from:

File

Line

What you see

client_basic.py

the list_tools() call

the schemas generated from your type hints

client_basic.py

the first call_tool(...)

arguments going out as a plain dict

server.py

first line of geocode_city

your tool receiving them

server.py

the return in get_forecast

the value before MCP serializes it

The configs set "justMyCode": false, so you can also step down into the mcp library itself and watch the JSON-RPC request get built. Set it to true to stay in your own files.

Debugging the real subprocess

When you specifically want the true stdio path (config 3 debugs the client only), have the server wait for a debugger. Add this at the top of server.py, run the client normally, then launch config 4. Attach to server subprocess:

import debugpy
debugpy.listen(5678)
debugpy.wait_for_client()   # server pauses here until you attach

Remove it when you are done -- it makes the server hang for anything that is not a debugger. For everyday work --in-process is easier.

Why two tools instead of one

get_forecast takes coordinates, not city names, because Open-Meteo's forecast endpoint does. Answering "weather in Denver" therefore takes two calls:

geocode_city("Denver")  ->  lat 39.73915, lon -104.9847
get_forecast(39.73915, -104.9847)  ->  daily highs and lows

client_basic.py does that hand-off by hand so you can see it. client_agent.py never mentions Denver or latitude -- Claude works it out:

[tool] mcp__weather__geocode_city(name='Denver')
[tool] mcp__weather__get_forecast(latitude=39.73915, longitude=-104.9847, days=6)

That is the point of the whole exercise.

Two things that will bite you

Return annotations need a schema. A tool annotated -> dict fails with InvalidSignature: return type <class 'dict'> is not serializable for structured output. Use a TypedDict (see City, Forecast in server.py).

List returns get wrapped, object returns do not. A tool returning a list arrives as {"result": [...]}, because JSON Schema needs an object at the top level. A tool returning a TypedDict arrives as-is:

geo.structured_content["result"]   # list-returning tool
forecast.structured_content        # object-returning tool -- no "result" key

Connecting it to Claude Code

claude mcp add weather -- uv run server.py

Then ask Claude Code about the weather directly. claude mcp remove weather undoes it.

License

MIT -- see LICENSE.

Design notes

See docs/specs/2026-07-28-mcp-weather-tutorial-design.md.

Available Tools

2 tools
geocode_cityA

Find the latitude and longitude of a city by name.

Returns candidate matches, since city names are ambiguous -- there are Portlands in both Oregon and Maine. Use this before get_forecast, which only accepts coordinates.

Args: name: City name to search for, e.g. "Denver" or "Paris". limit: Maximum number of candidate matches to return.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
limitNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior4/5

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

No annotations are provided, so the description carries the burden. It explains the tool returns candidate matches due to ambiguity, implying a read-only lookup. However, it does not disclose potential side effects, rate limits, or authorization needs, which would be beneficial.

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 concise with no filler. It front-loads the purpose, then adds essential context about ambiguity and usage. Every sentence is valuable and efficiently communicates what the tool does.

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

Completeness5/5

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

Given the tool has only two parameters and an output schema is present, the description is sufficiently complete. It explains why multiple results may be returned and how this tool fits into a workflow with its sibling, providing enough context for an AI agent.

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

Parameters5/5

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

Schema description coverage is 0%, so the description fully compensates. It explains the 'name' parameter with examples and describes 'limit' as the maximum number of candidate matches, adding meaning beyond the schema's type and default.

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 tool finds latitude and longitude of a city by name. It distinguishes itself from the sibling tool 'get_forecast', which accepts coordinates, making the purpose unambiguous.

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

Usage Guidelines5/5

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

Explicitly states to use this before get_forecast, and explains that city names are ambiguous, so candidate matches are returned. Provides clear context for when to use and what to expect.

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

get_forecastA

Get the daily weather forecast for a set of coordinates.

This tool does not accept city names. To look up a city, call geocode_city first and pass its latitude and longitude here.

Args: latitude: Degrees north, e.g. 39.74 for Denver. longitude: Degrees east, e.g. -104.98 for Denver. days: How many days ahead to forecast, 1 to 16.

ParametersJSON Schema
NameRequiredDescriptionDefault
daysNo
latitudeYes
longitudeYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
daysYes
unitsYes
timezoneYes

TDQS

A4.4/5.0
Behavior3/5

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

No annotations are provided, so the description carries full burden. It does not disclose behavioral traits such as rate limits, idempotency, or output format. While it describes input behavior, it lacks details on response structure or side effects, which is adequate but minimal.

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 concise at 7 lines, front-loads the main purpose, and each sentence adds value without redundancy. No wasted words.

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

Completeness4/5

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

Given the tool has an output schema (not shown) and only 3 simple parameters, the description covers inputs well. However, it lacks a summary of expected output fields or any note on measurement units, which would help complete the context for an agent.

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

Parameters4/5

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

Schema description coverage is 0%, so the description compensates by explaining latitude with example (e.g., 39.74 for Denver), longitude similarly, and days with a range (1 to 16). It adds meaning beyond the schema, though it could specify coordinate format (decimal degrees) explicitly.

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 tool gets a daily weather forecast for coordinates, and explicitly distinguishes itself from the sibling geocode_city by stating it does not accept city names. The verb 'get' and resource 'forecast' are specific.

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

Usage Guidelines5/5

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

The description provides explicit when-to-use and when-not-to-use guidance: to get a forecast for coordinates, and to use geocode_city first for city names. It also gives examples of coordinate formats, aiding correct invocation.

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. Dates show when Glama detected each change.

  1. 2 tool updatesv0.1.0
    • First observedgeocode_city
    • First observedget_forecast

TDQS

A4.4/5.0
Disambiguation5/5

The two tools have completely distinct purposes: one converts city names to coordinates, the other retrieves forecasts from coordinates. There is no functional overlap.

Naming Consistency5/5

Both tools follow the same verb_noun snake_case pattern (geocode_city, get_forecast), making them predictable and easy to understand.

Tool Count4/5

With only 2 tools, the server feels minimal, but as a tutorial it is appropriately scoped for the basic workflow of converting a city name to coordinates then getting a forecast.

Completeness2/5

The server only provides geocoding and daily forecasts, omitting common weather features like current conditions, hourly forecasts, alerts, or historical data. This is notably incomplete for a general weather domain.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

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/matjmiles/mcp-weather-tutorial'

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