Skip to main content
Glama
HumairaShaista

weather-learning-server

Weather MCP Learning

A progressive learning project that shows the path from a plain LLM application to weather capabilities exposed through the Model Context Protocol (MCP).

Learning progression

  1. Plain LLM application — chat with a local open-source model via Ollama

  2. Traditional weather API app — Open-Meteo client (Stage 2A) + direct LLM orchestration (Stage 2B)

  3. Weather MCP server — expose weather as MCP tools over stdio (Stage 3)

  4. MCP client / agent — explicit tool client (Stage 4A) + model-selected tools (Stage 4B)

This repository currently implements stages 1 through 4B.

Related MCP server: MCP Weather Server Demo

Requirements

  • Python 3.12 or later

  • Ollama (or any OpenAI-compatible local server)

  • A local open-source model with tool calling (default: qwen2.5:7b)

No OpenAI or Gemini account is required.

Setup

1. Install and start Ollama

Install from https://ollama.com, then pull a model:

ollama pull qwen2.5:7b

Or use any Responses-API tool-capable model you already have (ollama list), then set LLM_MODEL in .env to that name.

Confirm Ollama is running (usually automatic on macOS after install):

ollama list

2. Create a virtual environment

python3 -m venv .venv
source .venv/bin/activate

On Windows:

python -m venv .venv
.venv\Scripts\activate

3. Install dependencies

pip install -e ".[dev]"

4. Configure environment variables

cp .env.example .env

Defaults in .env target local Ollama:

LLM_BASE_URL=http://localhost:11434/v1
LLM_API_KEY=ollama
LLM_MODEL=qwen2.5:7b
  • LLM_BASE_URL — OpenAI-compatible API URL (Ollama’s default is shown above; used by Chat Completions and Responses)

  • LLM_API_KEY — required by the client library; Ollama ignores it (any non-empty value works)

  • LLM_MODEL — local model name from ollama list (Stage 4B tool-calling works well with qwen2.5:7b)

Other options: LM Studio, vLLM, or any server that speaks the OpenAI chat API — just change LLM_BASE_URL and LLM_MODEL.

Stage 2A: Open-Meteo weather client

app/weather_client.py talks to Open-Meteo in two steps (no LLM, no MCP):

  1. GeocodingGET https://geocoding-api.open-meteo.com/v1/search turns a city name (plus optional state/region and country) into latitude, longitude, canonical name, admin region, country, and timezone.

  2. ForecastGET https://api.open-meteo.com/v1/forecast uses those coordinates to fetch current weather (temperature, humidity, wind, WMO weather code).

Callers receive typed models (Location, CurrentWeather, WeatherResult), not raw provider JSON. WMO weather-code → text translation lives in one place (WMO_WEATHER_CODES / weather_condition_from_code).

Example (async):

from app.weather_client import get_current_weather

result = await get_current_weather("Berlin")
print(result.location.name, result.current.temperature, result.current.condition)

Stage 2B: Direct weather + LLM application

app/direct_weather_app.py is a traditional LLM app: your code decides when to call the weather API, then passes that result to the LLM for a friendly summary.

User
  → direct_weather_app
      → Open-Meteo   (application-controlled)
      → LLM          (summarize only the supplied payload)
  → Response

How to run it

With the virtual environment activated, Ollama running, and network access for Open-Meteo:

python -m app.direct_weather_app "San Francisco"

Optional disambiguation:

python -m app.direct_weather_app "Springfield" --state Illinois --country US

Or the console script:

direct-weather "San Francisco"

On stderr you will see the orchestration steps:

  1. Application received city

  2. Application called weather provider

  3. Application received structured weather

  4. Application sent weather context to the LLM

Stdout shows the structured weather block, then the LLM summary.

How it differs from the plain LLM application

Stage 1 plain_llm_app

Stage 2B direct_weather_app

Weather data

None — model has no live weather

Fetched from Open-Meteo first

Who calls weather?

Nobody

Application code (explicit)

LLM role

Answer a free-form prompt

Summarize an authoritative payload

MCP / tools

No

No

Important learning point: the LLM does not discover or call weather tools. The application orchestrates Open-Meteo, then asks the LLM to phrase the result. The prompt tells the model the payload is authoritative and not to invent missing facts.

Stage 3: Weather MCP server

app/mcp_server.py exposes the existing weather_client as an MCP tool. The server provides capabilities only — it does not talk to an LLM or manage a conversation.

Official SDK version and API used

Inspected in this project environment:

Item

Value

Package

official mcp on PyPI (modelcontextprotocol/python-sdk)

Installed version

2.0.0

Server class

MCPServer from mcp.server

Not used

third-party fastmcp package; older v1 FastMCP import path

from mcp.server import MCPServer

mcp = MCPServer("weather-learning-server")

Server responsibilities

  • Advertise tools to MCP clients (tool discovery)

  • Accept a get_current_weather tool call

  • Delegate to app.weather_client (no duplicated Open-Meteo code)

  • Return a structured weather payload (or a safe tool error)

  • Speak MCP over stdio for this local learning POC

Exposed tool contract: get_current_weather

Arguments

Name

Type

Required

Description

city

string

yes

City or place name

state_or_region

string

no

State / admin region for disambiguation

country

string

no

Country name or ISO-3166-1 alpha-2 code

Structured result fields

resolved_location, region, country, latitude, longitude, temperature, apparent_temperature (when available), condition, wind_speed, observation_time, timezone, units

How to start the server

python -m app.mcp_server

Or:

weather-mcp-server

With stdio, the process waits for an MCP host on stdin/stdout. Running it alone in a terminal looks “hung” — that is expected.

How stdio transport works (conceptually)

MCP host / Inspector
   ├── spawns: python -m app.mcp_server
   ├── writes JSON-RPC MCP messages → server stdin
   └── reads JSON-RPC MCP messages  ← server stdout
  • No port and no HTTP for this POC

  • stdout is the protocol wire (do not print() normal app output there)

  • Logs belong on stderr

Testing independently with the official MCP Inspector

Verified against:

  • official mcp 2.0.0 (MCPServer)

  • official Inspector package @modelcontextprotocol/inspector

  • Node.js 22.19+ (required by current Inspector docs)

  • network access to Open-Meteo

Prerequisites

cd weather-mcp-learning
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"   # includes mcp[cli]

Confirm Node/npx:

node --version   # need 22.19.0 or newer
npx --version

If your system node/npx is broken or too old, use a current Node via nvm (or equivalent), then ensure that npx is first on your PATH.

Option A — Web UI via mcp dev (official SDK helper)

From the project root with the venv active (also needs uv because mcp dev launches the server through uv run):

mcp dev app/mcp_server.py --with-editable .

Expected:

  1. Terminal prints something like MCP Inspector Web is up and running at: http://localhost:6274?MCP_INSPECTOR_API_TOKEN=...

  2. Browser opens the Inspector

  3. Inspector starts/connects to the local stdio server (weather-learning-server)

  4. Session initializes (server name/instructions appear)

  5. Open Tools → list shows get_current_weather

  6. Select the tool → UI shows the docstring/description and input fields from the schema (city required; state_or_region / country optional)

  7. Set city = San FranciscoRun Tool

  8. Result pane shows structured content such as resolved_location, region, temperature, condition, units, etc.

--with-editable . installs this project into the temporary env mcp dev builds so import app... works.

Option B — Web UI via Inspector + project config

mcp-inspector.json in the repo root points Inspector at the local stdio server:

npx -y @modelcontextprotocol/inspector --config ./mcp-inspector.json --server weather-learning-server

Open the printed http://localhost:6274?... URL, confirm the session is connected, then use the Tools tab as in Option A.

Option C — Scriptable CLI checks (no browser)

These are useful to prove the same protocol steps from a terminal. Run from the project root with the venv active and a working Node 22.19+ npx on PATH:

# 1–2. Start/connect over stdio + initialize session
npx -y @modelcontextprotocol/inspector --cli \
  --config ./mcp-inspector.json \
  --server weather-learning-server \
  --method initialize \
  --format json

Expected JSON includes "name": "weather-learning-server" under result.serverInfo.

# 3–4. List tools; confirm description + input schema
npx -y @modelcontextprotocol/inspector --cli \
  --config ./mcp-inspector.json \
  --server weather-learning-server \
  --method tools/list \
  --format json

Expected: one tool named get_current_weather, with inputSchema.required containing city, and a live/current-weather description.

# 5–6. Invoke with city = San Francisco; display structured result
npx -y @modelcontextprotocol/inspector --cli \
  --config ./mcp-inspector.json \
  --server weather-learning-server \
  --method tools/call \
  --tool-name get_current_weather \
  --tool-arg 'city=San Francisco' \
  --format json

Expected: "isError": false and structuredContent with fields like:

{
  "resolved_location": "San Francisco",
  "region": "California",
  "country": "United States",
  "latitude": 37.77493,
  "longitude": -122.41942,
  "temperature": 13.8,
  "apparent_temperature": 12.1,
  "condition": "Fog",
  "wind_speed": 19.1,
  "observation_time": "2026-08-12T22:45",
  "timezone": "America/Los_Angeles",
  "units": {
    "temperature": "°C",
    "wind_speed": "km/h",
    "apparent_temperature": "°C"
  }
}

Numeric weather values change over time; the field names and "isError": false are what matter.

Official Inspector docs: MCP Inspector · SDK run docs: Running your server

Stage 4A: Basic MCP client (explicit tool call)

app/basic_mcp_client.py is a non-LLM MCP client. It launches the local weather MCP server over stdio, discovers tools, then explicitly calls get_current_weather.

basic_mcp_client
    → list_tools
    → get_current_weather   (hardcoded by this app — not chosen by an LLM)
    → MCP server (app.mcp_server via stdio)
    → Open-Meteo

Important: this client still invokes the weather tool explicitly. The LLM has not yet chosen the tool. That comes in a later stage.

How to run it

With the virtual environment activated (no need to start the MCP server yourself — this client spawns it):

python -m app.basic_mcp_client "San Francisco"

Optional filters:

python -m app.basic_mcp_client "Springfield" --state Illinois --country US

Or:

basic-mcp-client "San Francisco"

You should see:

  1. Connection / protocol info for weather-learning-server

  2. Each discovered tool’s name, description, and input schema

  3. An explicit call to get_current_weather

  4. The structured MCP tool result JSON

Leaving the process cleans up the MCP session and child server process.

Stage 4B: OpenAI Responses agent (model-selected MCP tools)

app/mcp_agent.py connects to the weather MCP server, discovers tools at runtime, gives those definitions to the model via the official OpenAI Responses API, executes any model-requested tool calls through MCP, returns tool results to the model, and prints the final answer.

user question
  → mcp_agent
      → MCP list_tools          (discovery)
      → OpenAI Responses API    (question + tool schemas)
      → model may request tool(s)
      → MCP tools/call          (only discovered names)
      → Responses function_call_output
      → final natural-language answer

There is no if "weather" in question, no city regex, and no hardcoded get_current_weather call. The model chooses whether to use a tool.

Agent loop (detail)

  1. Start MCP session — spawn python -m app.mcp_server over stdio; initialize client

  2. Tool discoverylist_tools; log each tool name/description

  3. Schema translation — MCP tools → Responses type: "function" tools

  4. Model turnclient.responses.create(..., tools=..., tool_choice="auto")

  5. Inspect output — if function_call items exist:

    • validate tool name against the discovered set

    • parse/validate JSON arguments

    • call MCP; preserve structured results

    • submit function_call_output with previous_response_id

  6. Repeat until the model returns a final text message (or hit max iterations)

  7. Print final answer and close the MCP session/child process

How to run it

ollama pull qwen2.5:7b   # once, if needed
source .venv/bin/activate
python -m app.mcp_agent "What is the current weather in San Francisco?"
python -m app.mcp_agent "Explain what dependency injection is."

Expected:

  • Weather question → logs show model_requested_tools / tool_call for get_current_weather, then a weather answer

  • Dependency-injection question → logs show a final response without tool calls

Watch stderr for [mcp-agent] lines: discovery, model output types, tool name/arguments/duration/outcome. API keys are never logged.

Running the plain application

With the virtual environment activated and Ollama running:

python -m app.plain_llm_app

Or with a custom prompt:

python -m app.plain_llm_app "What is the Model Context Protocol in one sentence?"

You can also use the installed console script:

plain-llm "Hello!"

Running tests

pytest

Project layout

weather-mcp-learning/
  README.md
  .env.example
  .gitignore
  pyproject.toml
  mcp-inspector.json
  app/
    __init__.py
    config.py
    llm_client.py
    plain_llm_app.py
    weather_client.py
    direct_weather_app.py
    mcp_server.py
    basic_mcp_client.py
    mcp_agent.py
  tests/

Notes

  • The official openai Python package is used as an OpenAI-compatible client (Chat Completions earlier; Responses API in Stage 4B). Requests go to your configured LLM_BASE_URL (Ollama by default).

  • Weather lookups use Open-Meteo over httpx (app/weather_client.py).

  • Stage 2B (direct_weather_app.py) explicitly orchestrates weather → LLM; no MCP and no tool calling.

  • Stage 3 uses the official mcp 2.0.0 SDK (MCPServer from mcp.server) over stdio. Do not use the third-party fastmcp package.

  • Stage 4A (basic_mcp_client.py) still calls the weather tool explicitly (no LLM tool choice).

  • Stage 4B (mcp_agent.py) lets the model select tools after MCP discovery via the Responses API.

Available Tools

1 tool
get_current_weatherGet current weatherA

Get live/current weather for a city.

This tool returns real-time current conditions from the Open-Meteo weather provider (not a forecast summary and not model-invented weather). Provide a city name; optionally add state_or_region and/or country when the city name is ambiguous. The response includes the resolved location, coordinates, temperature, condition, wind, observation time, timezone, and units.

ParametersJSON Schema
NameRequiredDescriptionDefault
cityYesCity or place name to look up (required).
countryNoOptional country name or ISO-3166-1 alpha-2 code (for example 'United States' or 'US').
state_or_regionNoOptional state or first-level administrative region used to disambiguate the city (for example 'California' or 'Illinois').

Output Schema

ParametersJSON Schema
NameRequiredDescription
unitsYes
regionNoAdministrative region / state when available
countryYesCountry name
latitudeYes
timezoneYes
conditionYesHuman-readable weather condition
longitudeYes
wind_speedNo
temperatureYes
observation_timeYesObservation timestamp from the provider
resolved_locationYesCanonical place name from geocoding
apparent_temperatureNoApparent (feels-like) temperature when the provider returns it

TDQS

A4.3/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 full burden. It discloses that data comes from Open-Meteo, that it's real-time (not forecast or invented), and lists the response fields. It does not mention rate limits or availability constraints, which would be helpful.

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 very concise: two sentences. The first sentence states the core purpose, and the second adds key details in a natural flow. No wasted words.

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 an output schema, the description doesn't need to explain return values. It also covers the important semantic clarifications (real-time, provider, disambiguation hints). For a simple 3-param weather tool, this is complete.

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%, so baseline is 3. The description restates that city is required and that state_or_region and country help disambiguate, but does not add new format or usage details beyond the schema.

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 'Get live/current weather for a city' with specific verb and resource. It explicitly distinguishes itself from non-forecast and non-invented weather, and mentions the provider (Open-Meteo).

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

Usage Guidelines4/5

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

The description explains when to use (for current real-time conditions) and notes when to add optional parameters for disambiguation. It doesn't explicitly state when NOT to use or list alternative tools, but context is clear enough.

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. 1 tool updatev0.1.0
    • First observedget_current_weather

TDQS

A3.9/5.0
Disambiguation5/5

With only one tool, there is no risk of confusion between tools. The tool's purpose is clearly defined and distinct by default.

Naming Consistency3/5

The single tool uses a clear verb_noun pattern (get_current_weather), but with only one tool, consistency cannot be meaningfully evaluated across a set.

Tool Count2/5

A server named 'weather-learning-server' with only one weather tool feels incomplete for learning purposes. A 'learning' server typically benefits from multiple tools (e.g., forecast, history, alerts) to cover educational use cases.

Completeness2/5

The server only provides current weather data, missing obvious complementary tools like forecasts, historical data, or weather alerts. This severely limits its usefulness for weather-related learning or applications.

Maintenance

ActivityMaintained
ResponsivenessNo issues

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/HumairaShaista/Weather-MCP-Learning'

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