Weather Forecast MCP Server: Tool Design Plan
I haven't seen your API's docs, so this assumes a typical weather REST API with geocoding, current conditions, hourly and daily forecasts, and alerts. Send me the real endpoints and I'll adjust the plan.
1. Server basics
| Item | Choice |
|---|---|
| Server name | weather-mcp-server (TypeScript convention: {service}-mcp-server; Python would be weather_mcp) |
| Language | TypeScript with the MCP SDK and Zod |
| Transport | stdio for local use. Use stateless JSON streamable HTTP if you host it remotely. |
| Auth | API key from an env var (WEATHER_API_KEY). It never appears as a tool parameter. |
| Tool prefix | weather_ on every tool, so they stay discoverable next to other servers |
2. Tools
All tools are read-only, so each gets the annotations readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true.
weather_search_locations
Resolves a place name to coordinates. Forecast tools depend on this, since LLMs are bad at guessing lat/lon.
query(string, 2–100 chars): e.g. "Paris" or "Portland, Oregon".country_code(optional string, ISO-3166 alpha-2): disambiguates results.limit(int, 1–50, default 10) andoffset(int, default 0).- Returns:
location_id,name,admin_region,country,latitude,longitude,timezone, plus pagination metadata.
weather_get_current_conditions
location: a shared location input (see below).units:metricorimperial(defaultmetric).response_format:markdownorjson(defaultmarkdown).- Returns: temperature, feels-like, humidity, wind speed and direction, precipitation, condition text, observation time and the location's timezone.
weather_get_hourly_forecast
location,unitsandresponse_format, as above.start_hour_offset(int, default 0) andlimit(int, 1–48, default 24). These act as the pagination window.fields(optional enum array, e.g.temperature,precipitation_probability,wind). This trims the output, since hourly data is large.
weather_get_daily_forecast
location,unitsandresponse_format, as above.days(int, 1–16, default 7), withoffsetfor paging past the first window if the API supports it.- Returns: high/low, precipitation total and probability, wind, UV index, sunrise/sunset, and a condition summary.
weather_get_alerts
location.severity(optional enum:minor,moderate,severe,extreme).limitandoffset.- Returns: event, severity, onset/expiry, affected area, and description. An empty list is a valid result, reported as "no active alerts".
Optional, if the API supports them
weather_get_historical_weather:location,start_dateandend_date(ISOYYYY-MM-DD, with a max range), pluslimitandoffset.weather_get_air_quality: AQI and pollutants.
Shared location input
Every forecast tool accepts one of two forms, validated with a Zod union:
{ latitude: number (-90..90), longitude: number (-180..180) }{ location_id: string }fromweather_search_locations
I'd also consider letting forecast tools take a plain place_name and geocode internally. That saves the model a round trip, but it can silently pick the wrong "Springfield", so the response would have to echo the resolved place. My recommendation is to require explicit coordinates or an ID, because ambiguity is the most common failure here.
3. Pagination
Most weather data is small, so pagination only matters for location search, alerts, hourly data and history.
- Style:
limitplusoffsetas inputs, even if the upstream API uses cursors or page numbers. The server translates between them. If the API is cursor-only, return an opaquenext_cursorinstead. - Defaults: keep them small (10 for search, 24 hours for hourly) and enforce hard maximums in the schema.
- Response metadata, included in both text and
structuredContent:{ "total": 87, "count": 20, "offset": 0, "has_more": true, "next_offset": 20 } - Truncation: cap output at about 25k characters. If exceeded, cut the result and add a note like "Truncated; use
limit/fieldsor a smaller range."
4. Error handling
Errors are returned as tool results with isError: true, not as protocol errors. Each message should say what happened and what to do next.
| Upstream condition | Message to the model |
|---|---|
| 400 / invalid params | "Invalid latitude 123. Must be between -90 and 90." |
| 401 / 403 | "API key missing or rejected. Check WEATHER_API_KEY on the server." This one is for the user, so don't tell the model to retry. |
| 404 / no location found | "No location matched 'Xyzzy'. Try weather_search_locations with a broader query or a country_code." |
| 429 | "Rate limited. Retry after N seconds." (taken from the Retry-After header) |
| 5xx / timeout / network | "Weather service temporarily unavailable. Retry shortly." |
| Empty result | A normal success: "No alerts active for this location." |
Implementation notes:
- Use one shared
handleApiError()helper that maps status codes to these messages. Never leak raw stack traces or the API key. - Use a request timeout (about 10s). Retry once on 5xx or network errors with a short backoff, and never retry 4xx.
- Add a small in-memory cache (TTL of about 5–10 minutes for forecasts, 24 hours for geocoding) to cut rate-limit pressure.
- Validate inputs with Zod
.strict()schemas, so bad inputs fail before any HTTP call.
5. Output conventions
- Each tool defines an
outputSchemaand returnsstructuredContent, plus a text rendering in the requestedresponse_format. - Always include units and the location's timezone in output, because "18°" and "3 pm" are ambiguous without them.
- Markdown output is human-readable (dates and place names, not raw IDs). JSON output is complete and meant for programmatic use.
6. Suggested project layout
weather-mcp-server/
├── src/
│ ├── index.ts # server init, transport
│ ├── constants.ts # base URL, CHARACTER_LIMIT, defaults
│ ├── schemas/ # shared Zod: location, units, pagination
│ ├── services/
│ │ └── weatherClient.ts # HTTP, auth, retry, cache, handleApiError
│ ├── formatters.ts # markdown/json rendering, truncation
│ └── tools/ # one file per tool group
└── package.json, tsconfig.json
7. Next steps
- Send me the API docs or the name of the provider, and I'll finalize the tool list and field mappings.
- Build the API client and shared helpers first, then the tools.
- Test with
npx @modelcontextprotocol/inspector. - Write about 10 read-only evaluation questions, such as "Which of Lisbon or Madrid has the higher forecast high on Friday?", to check that a model can chain
search_locationsinto a forecast tool.
Questions that would change the plan:
- Which weather provider are you using?
- Is this server local or remote?
- Do you need history or air quality, or just forecasts and alerts?
Real run recorded with claude-code / claude-sonnet-5-5. Output is shown verbatim, unmodified.
하는 일
AI 에이전트가 잘 설계된 도구를 통해 외부 서비스를 다룰 수 있게 하는 MCP(Model Context Protocol) 서버를 모델이 구축하도록 안내합니다. 실용적인 관례가 담겨 있습니다: 서버와 도구의 이름 짓는 법, JSON과 Markdown 출력을 함께 제공할 시점, 페이지네이션 방법, 에이전트에게 다음에 할 일을 알려 주는 오류 메시지 작성법, 전송 방식 선택 등.
진행 방식
- 조사와 계획: MCP 사양과 SDK 문서를 읽고, 도구를 고르고, API를 폭넓게 다루는 계획을 세웁니다.
- 구현: 프로젝트, 공용 API 클라이언트, 오류 처리를 갖춘 뒤, 타입이 있는 입력 스키마(Zod 또는 Pydantic)와 어노테이션으로 각 도구를 등록합니다.
- 검토와 테스트: 코드 품질을 점검하고 빌드한 뒤 MCP Inspector로 서버를 시험합니다.
- 평가: 현실적이고 읽기 전용이며 검증 가능한 질문을 약 10개 작성해, 모델이 도구를 실제로 잘 쓸 수 있는지 확인합니다. 함께 들어 있는 Python 스크립트 2개로 서버에 대해 이 평가를 실행할 수 있습니다.
이런 분께: REST API나 SaaS 제품용 MCP 서버를 만드는 개발자 (TypeScript 권장, Python도 가능).
지침과 참고 가이드, Python 스크립트 2개(scripts/connections.py, scripts/evaluation.py)가 들어 있습니다. 스크립트는 한 줄씩 읽어 확인했으며 숨겨진 동작은 없었습니다. evaluation.py를 실행하려면 본인의 Anthropic API 키(환경 변수 ANTHROPIC_API_KEY)가 필요하고 유료 API 호출이 발생합니다. stdio 모드에서는 지정한 서버 명령을 실행합니다. 또한 에이전트가 웹에 접근할 수 있다면, 모델이 MCP 사양과 SDK 문서를 가져오도록 안내합니다. 코드 예시는 참고용이므로 사용 전에 직접 검토하세요(예: 파일 리소스 예시는 경로를 검증하지 않습니다).