MCP Weather Server
Enables AI agents to retrieve real-time weather conditions and forecasts via OpenWeatherMap API. Supports interactive weather queries and travel planning through MCP tools, resources, and prompts.
README
MCP Weather Server & Travel Agent
A learning project that builds a local Model Context Protocol (MCP) server backed by the free OpenWeatherMap API, a diagnostic MCP client, and an AI-powered travel-planning agent that combines MCP tool execution with OpenAI function calling.
Business Context
The project answers a practical question: How can an LLM fetch live, external data — and act on it — through a standardised protocol?
MCP lets any AI host (Cursor, Claude Desktop, custom agents) discover and invoke server-provided Tools, read Resources, and retrieve reusable Prompt templates without hard-coding integrations. This project wires that idea end-to-end using real-time weather data.
What you can do with it
| Capability | Example |
|---|---|
| Ask Cursor for live weather | "What's the weather in Tokyo?" — Cursor calls get_weather via MCP |
| Generate a travel plan | uv run travel_agent.py "Paris" 5 — GPT reasons over real forecasts |
| Explore all three MCP primitives | uv run client.py — enumerates and exercises tools, resources, prompts |
Project Structure
MCP2/
├── server.py # MCP server — exposes tools, resources, prompts
├── client.py # Diagnostic MCP client — exercises every primitive
├── travel_agent.py # AI travel agent — MCP + OpenAI function calling
├── main.py # Scaffold entry point (placeholder)
├── .env # API keys (gitignored)
├── .python-version # Pins Python 3.14
├── pyproject.toml # Project metadata & dependencies (managed by uv)
├── uv.lock # Locked dependency graph
├── .gitignore
└── .cursor/
└── mcp.json # Cursor IDE MCP server config
Architecture & End-to-End Flows
Component Overview
┌─────────────────────────────────────────────────────────────────┐
│ MCP HOST / CLIENT │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Cursor │ │ client.py │ │ travel_agent.py │ │
│ │ IDE │ │ (diagnostic) │ │ (AI agent) │ │
│ └────┬─────┘ └──────┬───────┘ └───────────┬────────────┘ │
│ │ │ │ │
│ │ stdio │ stdio │ stdio │
│ └────────┬───────┘────────────────────────┘ │
│ │ │
├────────────────┼────────────────────────────────────────────────┤
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ server.py (FastMCP) │ │
│ │ │ │
│ │ TOOLS RESOURCES PROMPTS │ │
│ │ ───── ───────── ─────── │ │
│ │ get_weather weather://cities weather_report │ │
│ │ get_forecast weather://help travel_advisory │ │
│ │ │ │
│ │ fetch_weather(endpoint, params) │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ │ httpx (async) │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ OpenWeatherMap REST API │ │
│ │ /weather /forecast │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Flow 1 — Cursor IDE (interactive usage)
User types in Cursor:
"What's the weather in Bangalore?"
1. Cursor reads .cursor/mcp.json
2. Spawns `uv run server.py` as a child process (stdio transport)
3. MCP handshake: Cursor sends `initialize`, server responds with capabilities
4. Cursor discovers tools via `tools/list` → learns about get_weather, get_forecast
5. The LLM decides get_weather(city="Bangalore") is needed
6. Cursor sends `tools/call` → server receives request
7. server.py → fetch_weather("weather", {"q": "Bangalore"})
→ httpx GET https://api.openweathermap.org/data/2.5/weather?q=Bangalore&appid=…&units=metric
8. OpenWeatherMap returns JSON → server formats a string → returns via MCP
9. Cursor displays the result to the user
Flow 2 — Diagnostic Client (uv run client.py)
The client runs a single, linear script that exercises every MCP primitive:
1. Launch server.py as subprocess via stdio_client(StdioServerParameters)
2. ClientSession handshake → session.initialize()
┌──────────────────────────────────────────────────┐
│ TOOLS │
│ a. list_tools() → enumerate all tools │
│ b. call_tool("get_weather", {city: "Toronto"}) │
│ c. call_tool("get_forecast", {city, days: 3}) │
├──────────────────────────────────────────────────┤
│ RESOURCES │
│ a. list_resources() → enumerate all resources │
│ b. read_resource("weather://cities") │
│ c. read_resource("weather://help") │
├──────────────────────────────────────────────────┤
│ PROMPTS │
│ a. list_prompts() → enumerate all prompts │
│ b. get_prompt("weather_report", {city: "Tokyo"}) │
│ c. get_prompt("travel_advisory", {city, days}) │
└──────────────────────────────────────────────────┘
3. Results printed to terminal; process exits
Flow 3 — Travel Planner Agent (uv run travel_agent.py "Tokyo" 5)
This is the most complex flow. It implements an agentic loop where GPT decides which MCP tools to call and when to stop.
1. Parse CLI args (city, days)
2. Connect to Weather MCP server via stdio (same as client.py)
3. MCP handshake → session.initialize()
4. Tool discovery:
a. session.list_tools() → get MCP tool definitions
b. mcp_tools_to_openai_functions() → translate MCP inputSchema
into OpenAI function-calling format
5. Resource prefetch:
a. session.list_resources()
b. Read all resources (cities list, help text) → inject as system context
6. Build initial message list:
┌────────────────────────────────────────────────────────┐
│ system: SYSTEM_PROMPT (travel planner personality) │
│ system: resource context (cities list, help text) │
│ user: "Plan a 5-day trip to Tokyo. Use the │
│ weather tools to check conditions..." │
└────────────────────────────────────────────────────────┘
7. Agent reasoning loop (max 4 iterations):
┌───────────────────────────────────────────────────────────┐
│ LOOP START │
│ │
│ a. Send messages + tool definitions to OpenAI API │
│ → openai_client.chat.completions.create( │
│ model, messages, tools, tool_choice="auto") │
│ │
│ b. IF response contains tool_calls: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ For each tool_call: │ │
│ │ - Parse function name + arguments │ │
│ │ - Execute via MCP: │ │
│ │ session.call_tool(name, args) │ │
│ │ - Extract text from MCP result │ │
│ │ - Append tool result to messages │ │
│ └─────────────────────────────────────────────────┘ │
│ → Continue loop (LLM sees tool results next round) │
│ │
│ c. ELSE IF response contains text content: │
│ → This is the FINAL answer. Print travel plan. BREAK │
│ │
│ d. ELSE (empty response): │
│ → Retry │
│ │
│ LOOP END │
└───────────────────────────────────────────────────────────┘
8. Print travel plan with iteration count
Typical execution: 3 LLM calls — (1) LLM requests get_weather, (2) LLM
requests get_forecast, (3) LLM produces the final travel plan using both results.
MCP Primitives Reference
Tools (callable functions)
| Tool | Parameters | Returns |
|---|---|---|
get_weather |
city: str |
Current temp, feels-like, humidity, wind, description |
get_forecast |
city: str, days: int (1-5, default 3) |
One line per day with temp and description |
Both call fetch_weather() internally, which appends the API key and units=metric,
then makes an async httpx.GET to the OpenWeatherMap endpoint. Errors are caught
and returned as user-friendly strings (not exceptions).
Resources (read-only context)
| URI | Description |
|---|---|
weather://cities |
JSON array of 10 example city names |
weather://help |
Human-readable help text listing all capabilities |
Resources are synchronous and return static/computed strings.
Prompts (reusable templates)
| Prompt | Parameters | What it generates |
|---|---|---|
weather_report |
city: str |
Instruction for the LLM to fetch weather + forecast and summarise |
travel_advisory |
city: str, days: int |
Instruction for the LLM to build a packing/activity/warning list |
Prompts return instruction strings; they do not call tools themselves.
Note: MCP prompt arguments are
dict[str, str]on the wire. When callingget_prompt(...)from the Python SDK, pass all values as strings (e.g.{"days": "5"}not{"days": 5}).
Tech Stack
| Layer | Technology |
|---|---|
| Language | Python 3.14 |
| Package manager | uv |
| MCP SDK | mcp[cli] >= 1.26.0 (FastMCP) |
| HTTP client | httpx >= 0.28.1 (async) |
| LLM (travel agent) | OpenAI gpt-4o-mini via openai >= 2.30.0 |
| Config loading | python-dotenv >= 1.2.2 |
| External API | OpenWeatherMap 2.5 (free tier) |
Prerequisites
- Python >= 3.14
- uv installed and on your PATH
- OpenWeatherMap API key — free at https://openweathermap.org/appid
- OpenAI API key (only needed for
travel_agent.py)
Setup
- Install dependencies:
uv sync
- Create
.envin the project root:
OPENWEATHER_API_KEY=your_openweather_key
OPENWEATHER_BASE_URL=https://api.openweathermap.org/data/2.5
OPENAI_API_KEY=sk-... # only needed for travel_agent.py
.envis gitignored. Never commit API keys.
Running
MCP Server (standalone / via Cursor)
uv run server.py
The server starts on stdio and waits for MCP messages. You don't interact with it directly in the terminal — it's designed to be driven by an MCP host (Cursor, the client, or the travel agent).
Diagnostic Client
uv run client.py
Runs through tools, resources, and prompts sequentially and prints results.
Travel Planner Agent
uv run travel_agent.py "Tokyo" 5
uv run travel_agent.py "Paris" 3
uv run travel_agent.py "Toronto" # defaults to 3 days
Connects to the MCP server, lets GPT reason with real weather data, and outputs a day-by-day travel plan.
MCP Inspector (interactive debugging)
npx @modelcontextprotocol/inspector uv run server.py
Opens a web UI to manually invoke tools, read resources, and test prompts.
Cursor Integration
Project-level config lives at .cursor/mcp.json:
{
"mcpServers": {
"weather": {
"type": "stdio",
"command": "uv",
"args": ["run", "${workspaceFolder}/server.py"],
"envFile": "${workspaceFolder}/.env"
}
}
}
${workspaceFolder}is resolved by Cursor to the directory containing.cursor/mcp.json.envFileinjects.envvariables into the spawned server process.- After editing this file, reload MCP servers or restart Cursor.
Once loaded, Cursor's agent can call get_weather and get_forecast directly
when you ask weather-related questions in chat.
Key Technical Details
-
Transport: All three clients (Cursor,
client.py,travel_agent.py) connect over stdio. The server is launched as a subprocess; MCP messages flow over stdin/stdout as JSON-RPC. -
Async throughout:
server.pyusesasync deffor tool handlers andhttpx.AsyncClientfor non-blocking HTTP.client.pyandtravel_agent.pyrun insideasyncio.run(). -
MCP → OpenAI schema translation:
travel_agent.pyconverts MCPinputSchema(JSON Schema) to OpenAI'stools[].function.parametersformat. The schemas are nearly identical by design. -
Agent loop safety: The travel agent caps the reasoning loop at 4 iterations to prevent runaway API calls. Typical runs complete in 2–3 iterations.
-
Error handling: Tool handlers catch
httpx.HTTPStatusErrorand generic exceptions, returning error strings instead of raising. This keeps the MCP session alive even if the upstream API fails. -
Forecast de-duplication: OpenWeatherMap's
/forecastreturns 3-hour intervals.get_forecastdeduplicates by date, picking the first entry per calendar day.
Gotchas
get_prompt(...)arguments must be strings in the Python MCP SDK (e.g.{"days": "5"}not5).- The free OpenWeatherMap tier has rate limits (~60 calls/min). The travel agent makes 2 API calls per run.
travel_agent.pyrequiresOPENAI_API_KEYin.env. The server and diagnostic client do not.
License
No license specified.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。