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.

Install Server
F
license - not found
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • OpenWeather MCP — wraps the OpenWeatherMap API (openweathermap.org)

  • Open-Meteo MCP — weather forecast + historical reanalysis + sister APIs

  • WeatherAPI.com MCP — wraps WeatherAPI.com (api.weatherapi.com)

View all MCP Connectors

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