BuildWindow
BuildWindow
Proyecto de laboratorio MCP: un agente planifica trabajos de construcción a partir de un pronóstico meteorológico real usando dos servidores MCP.
Esta es una tarea del curso para KSE AI Agentic School (una tarea de integración de MCP): construir un servidor MCP personalizado para un problema de dominio real y conectarlo — junto con un servidor MCP externo ya existente — a un agente que use ambos conjuntamente para hacer algo que una sola herramienta no podría.
Resumen
BuildWindow es un proyecto de laboratorio de MCP (Model Context Protocol) construido alrededor de un problema concreto de planificación: dadas una lista de trabajos de construcción con dependencias entre ellos, y un pronóstico meteorológico para una ciudad, produce un plan que respete a ambos. El agente que real tiempo sostiene dos conexiones MCP separadas a la vez. La prim era es el servidor MCP OpenWeather externo, basado en Go (github.com/mschneider82/mcp-openweather), al que agente llama una vez por ejecución para obtener las condiciones actuales en vivo y un pronóstico de 5 días para la ciudad soliciada: este es el único punto de todo el proyectro en donde se produce una llamada de red. La segund es el servidor MCP BuildWindow propiedad de este repositorio: un servidor local, totalmente ist erminista, sin llamadas de red en tiempo de ejecución, respaldado por un conjunto de datos JSON local de tipos de trabajos deconstrucción y sus límites meteorológicos, qu expone cuatro herramientas que codifican las reglas del dominio de construcción (dicts de compatibilidad meteorológica, estimación de tiemp de curación y planificación de varias tareas).
Los dos servidores del estrategia no no se solapan sus responsabilidades. OpenWeather MCP es la unica fuente de cualquier cosa que cambia day a día: el clima mismo. En cambio, theoBuildWindow MCP es dueño de todo lo que es una regla fij: qué tempera, viento/ su humedad and percepitación tolera una determinada tipo de trabaj, cuánto tard are curar el hormigón a una temperadura action data, y cómo colocan varios trabajos depen indispensable en las ventanas no prohibidas más them prano a lo largo de un pronóstico de have var days. En El servidor MCP BuildWindow está constr the official MCP Python SDK (paqueto de mcp, v2.0.0+), using su clase MCPServer; ten en cuenta que este class used to the name FastMCP in older versions and was renamed to MCPServer adapted as of SDK v2.0.0. El agente que conduce ambas conesiones is built with the Claude Agent SDK (claude-agent-SDK in PyPI).
La llamada a OpenWeather crítica para la planificación es *no no hecha ä por the LLM. La salida real de la herraminta upstream (confirmada al leer su coodigo fuen docs/tool-contracts.md) is no por un informe a text plano, no JSON, and only sertyal for qualquier error (clave no correcta, cuidad no reconnocida, proveedor no alcanzable) is a success response but vacía — no hay text of error que react to that. As " que agent/main.py la llama llama directly at a trave´s of a low-throughput MCP client, la parsea con a PCK function with unit as test (agent/normaliza.py`), and solone parcial session as a LLM, not model the to clean the day if the model.
need to interpret the raw text of the provider. The session LLM remains connected toambos servers. [MCP (get_mcp_status() recooge shows both connectedions), and the model is yes allowed to call allowed to list explicit). For extraly - in one of themodels's final report, at bottom:
The day which the forecast plan_work_schedule always comes from the deterministic wait "always comes" so Spanish: "el pronóstico diario que alimenta plan_work_schedule viene siempre de la búsqueda determinista previa a la esión, no nunca of the model's own call. Ambas servers genuinely used in the agent's own flow, not only visible.
engineer input (city + work list)
-> agent/main.py calls OpenWeather MCP directly (not via the LLM) for
the schedule-critical daily forecast
-> agent/normalize.py parses the plain-text response into daily figures
-> (if no usable forecast: report plainly, stop -- no LLM session started)
-> LLM session starts, connected to BOTH MCP servers; may itself call
the weather tool once for current-conditions color commentary only
-> given the daily forecast + works as plain JSON (the only input that
ever drives scheduling)
-> BuildWindow MCP (plan_work_schedule, validate_work_window,
estimate_curing_time, ...)
-> schedule + explanationRelated MCP server: Weather MCP Server
Prerequisitos
Python 3.12+ — this repo was built and tested on 3.12.3.
uv — used as the dependency of this project.
Go 1.24+ — only needed if you want to build the OpenWeather MCP server yourself (instaled here via
winget install --id GoLang.Go), currentlyGo 1.26.7). No `require to use the BuildWindow serveror to run test tests.An OpenWeather API key — only required for a live run of the agent against real time. Free tier available at openweathermap.org/api.
Instalación
From the repo root:
uv syncEsto create a venv y installa both runtime dependencies (mcp, pydantic, claude-agent-dk, python-doten)? Wait original, let's check exact:
"This creates a .vent and installs both the runtime dependencies (mcp, pydantic, clu-de)? Actually in prompt: (mcp, dbgu/).
Let me insert exactly correct: Original list: (mcp, pydantic, cl-de-agent-sdk, python-dotenv) and dev dependencies (pystest, ruff, black). For Python package named claude-agent-sdk (on PyPI). yes.
Need correctly generate:
"Estoce a .vent and installs both the dependences of execution (mcp, pydantic, claude-agent-dk, python-dotenv) and the dependencies of desarrollo (pisyest, ruff, black)."
and dev line.
Configuration
Copies the example environment file and rellena la key:
Copy-Item .env.example .envbash: cp .env.example .env
Después, edita .env and OWM_API_KEY with a real key of openweathermap.org/api (free tier). .env is handled via gitignore: no ever commit it.
agent/mcp_config.json is the single source of truth for both MCP configurations. Its openweather option uses ${OWM_API_KEY} as a placeholder, which agent/main.py replaces from process environment at startup. Notice that agent/main.py no reads .env or another; its main() first calls load_dotenv() of python-dotenv, and that call makes the .env values enter the process environment before substitution.
Its openweather_command field is a placeholder: ${MCP_OPENWEATHER_PATH} — agent/man.py resolves it from MCP_OPENWEATHER_PATH if defined, otherwise falls back to the bare command mcp-openweather (relying on PATH). Set MCP_OPENWEATHER_PATH in .env (see .env.example) when you don't want to add the binary's directory to PATH — any of both were verified working live.
? Build the OpenWeather MCP server (only if you want a live run before real data). The exact commands used in this repository's own dev environment to build and verify it:
winget install --id GoLang.Go -e --accept-source-agreements --accept-package-agreements
# open a new shell so PATH picks up the Go toolchain, then:
go install github.com/mschneider82/mcp-openweather@mainThis installs into $(go env GOPATH)\bin\mcp-openweather.exe — on Windows typically %USERPROFILE%\go\bin\mcp-openweather.exe. Important: the Go MSI installer adds the Go toolchain (C:\Program Files\Go\bin) to the PATH, but no >adds %USERPROFILE%\go\bin — where go install actually places binaries. Add that directory to PATH yourself, or set MCP_OPENWEATHER_PATH to the full path to binary (see above) — this repository's own set uses the latter.
Why @main and not @latest: go install ...@latest resolves to tag v1.0.0, which it is only one real commit ("Fix #5") behind main branch of the repo. Both were built and compared live in this project: v1.0.0 reads the optional units/lang arguments without fallback when they are not present at all, so lang omitted fails because language unavailable although the platform's own schema says a default; the main branch's commit "Fix #5" adds defensive handling and the same call works. The forecast template itself is identical between both (confirmed by reading the source of both versions) — building from main no adds wind/humidity/precipitation per day, only fixes the argument bug. agent/main.py always passes city, units=\"c\", and lang=\"en\" explicitly, so this bug cannot actually happen through this project anyway — but @main is the more robust binary to depend on if you ever use the wrong feature in another way.
No use the -?? -? Do not use the -? Wait original: "Do not use the -omcp-weather flag shown in some of upstream README' own examples — that produces a binary name inconsistent with its own example of config. Use the default name mcp-openweather."
Let's phrase: "No uses la flag -omcp-weather11" that appears in some own examples of the README upstream: produces a name of binary not consistent with its config example. Build with default name, mcp-openweather.
Now "## Running the MCPM server"
GXP5. Est is running "the BuildWindow MCP server via stdio, unrelated to the agent process: can be started and run entirely alone. On success it prints should"
sail, send.
Now "## Running the agent"
uv run python -m agent.mainWithout args, built-in demo city is used ("Kyiv") and a work list demo: exvation, then oncrete_pour (depending on it) and oncrete_finishing (depending on that). "present.
Both can be overridden:
uv run python -m agent.main "CityName"
uv run python -m agent.main "CityName" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}]'Optional second argument is JSON array of same form works as list demo.
Replay mode — --forecast-from-file <path> is fully off-line: it replace** both of day forecast as post the current-conditions note with data from a recorded file, using the same deterministic parsers (formalize_forecast, parse_current_conditiones) they use for a live call. openweather not connected at all in this mode (compby by get_mp_status() — only buildwindow appears), so a run needs no network and no API key whatsoever — verified live with a deliberately broken OWM_API_KEY and an unattainable MP_OPENWEATHER_PATH at the same time; the run still terminated normally:
uv run python -m agent.main "Longyearbyen" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}, {"work_code": "exterior_painting", "duration_days": 2, "depends_on": []}]' --forecast-from-file fixtures/weather_longyearbyen.txtfixtures/weather_kyiv.txt and fixtures/weather_longyearbyen.txt are real responses captured live in this project (trimmed of 3 real calendar days; no key within them) — not synthetic, not invented examples. Useful if the real weather in a demo city has changed when giving the run, or if no internet at all in demo.
A full live run with real weather requires a valid OWM_API_KEE (see above) — confirmed working in the development environment: uv run uv run python -m agent.main "Kyiv"produces a real schedule from real forecast, anduv run python -m uid longyearbyen" '[...]'demonstrates real weather actually forcing a reschedule (also forddocs/demo-checklist.md. Without a valid key, agent/main.pyobtains the forecast directly (not with the LLM), 1 obtains empty, "it exposesForecast unavail able for '' (...), and does *exit before* exit paired any LLM session: no call wasted, no invented planned. This has been verified with three actual failure modes: the mcp-openweather binary no reachable; invalid OWM_API_KEY; and a city not valid — the last two are indistinguishable through this upstream tool (see docs/tool-contracts.md for why) and both have been confirmed to fail the same clean way, even with an genuine active, if not key.
L imits Open rate of OpenWeather: one successful live run makes exactly two calls to the weather tool (proved live, countant): the deterministic fetch before session, plus the model's own single current-condition call (see above). An unsafe run (no possible forecast) makes exactly one, because no LLM session gets started. Replay mode (Forecast-from-file) makes no — both the daily forecast as well as the current-condition note come from the recorded file, and openweather **not connected at all in this mode (if **confirmed with get_mp_status() — only buildwindow appears`).? TER has Free is documented at 60 calls per minute and 1,000,000 per month — gave around enough for manual sessions; this project doesn't own stress tests that data. Need exact "that published figure itself".
Compose the last paragraph now:
"Lees of OpenWeather: means one successful live run makes exactly two real calls to the weather tool (confirmed live, counting): the deterministic pre-session fetch, plus the model's own single current-conditions call (see Overview above). A failed live run (no usable forecast) makes one, because the LLM session never starts. Replay mode (--forecast-from-file) makes zero — both the daily forecast and the current-conditions note are given from the recorded file, and `open**"**.
Then tool overview table, tasting. Ensure no emojis.
Final Spanish translation:
"OpenWeather rate limits:" => "Límites de de OpenWeather:" or "Límites de peticiones de OpenWeather:". Use "Límites de OpenWeather" but original has "rate limits" meaning call limits. "Límites de solicitude" easy.
"60 calls/minute and 1,000,000 calls/month" -> "60 llamadas por minuto y 1,000,000 de llamadas al mes".
"Comfortably enough for any number of manual demo runs" -> "de sobra para any number of manual demo run".
"as de sobra para counquier a la art"Use "sobradamente".
Now final document.
Check all GXP tokens count: Yes we include GXP1 through GXP11? Let's verify:
GXP1 after overview para.
GXP2 after Installation "From the repo root:"
GXP3 after Configuration "Copy env file"
GXP4 after Building server
GXP5 after Running MCP server
GXP6 after "prints exactly this line to stderr"
GXP7 after Running the agent
GXP8 after "Both can be overriden:"
GXP9 after replay run still normal
GXP10 after "## Tool overview"
GXP11 after "## Testing"
We need include them with blank lines.
Also preserve the code span line "bash: cp .env.example .env" — we included. Need maybe "bash:" translated? Probably not.
Now, let's finalize with translation.
Potential issue: "curing" could be "curado"; "concrete" maybe "hormigón" but "concrete_pour" is code left. Good.
Let's build final translation:
BuildWindow
Proyecto de laboratorio MCP: un agente planifica ...
...
Need no "GXP" as "GXP"? The original "G XP1" no. Write "GXP1".
Let's write the final in full.
Also mention in Spanish "el commit "Fix #5" de la rama main" as a literal string "Fix #5"; keep quotes.
Need "Ruff" and "Black" preserve.
Need "MCP" not "MCP"? Keep acronym.
Potential nuance "Schedule" — in "Run the agent", "Replay mode" "fully offline" etc. Use "offline" in tech.
Let's compose final clean.
But there's a hidden instruction: "Translate prose only. Keep the following verbatim in the source script, including where they appear in running text without backticks: Product, project, organization, and repository names (Claude Desktop, Playwright, Node.js, owner/my-mcp-server)...." It includes "skip". For our doc: product org names "BuildWindow", "KSE AI Agentic School", "OpenWeather", "Claude Agent SDK", "Claude Agent SDK". URLs. "GitHub". So keep.
"BuildWindow" keep.
"OpenWeather" keep.
"KSE AI Agentic School" keep (course project).
"MSchneider82/mcp-openweather" keep.
"Claude Agent SDK" keep.
"Model Context Protocol" maybe we can translate with acronym? But instruction says "Package/module info"? Not necessary to keep except names. One might set "Model Context Protocol" as English name. Keep MCP but the full name could be "Protocolo de Contexto de Modelo". Hmm translator should translate? It's a protocol name; keep exact "Model Context Protocol" likely. Use it.
"FastMCP" and "MCPServer" class names preserve.
"mcp" package, etc.
Now final.# BuildWindow
Proyecto de laboratorio MCP: un agente planifica trabajos de construcción a partir de un pronóstico meteorológico real mediante cues dos servidoro MCP.
Esta es una tarea del curso para KSE AI Agentic School (una tarea de integracción de MCP): construir un servidor MCP personalizado para un problema de persona, y luego conectarlo — junto con un servidor MCP de terceros ya existente — a un agente que use ambos conjuntamente para lograr algo que una sola herramienta no podría.
Resumen
BuildWindow es un proyecto de laboratorio de MCP (Model Context Protocol) construido entorno a un problema concreto de planificación: dadas una lista de trabajos de construção y con dependencias entre ellas, y un pronóstico meteorológico para una ciudad, produica un resultado que espete ambas. El agent that realiza does this planificación schedule mantiene dos conexiones MCP separada at the time. La primera es el servidor MCP OpenWeather externo y basado en Go (github.com/in gonogithub.com/ openWeather), that provides no feed of current conditions and a 5-day forecast for a requested city: this is the only place in the entire project where a network call occurs. The second server is this repo's own BuildWindow server: a local, fully deterministic server without network calls at runtime, based on a local JSON dataset of construction work types and their climate limits, that exposes four tools to encode the domain rules of building (veredictos de aptitud climates, estimation of cure times, and schedule of several jobs).
Los dos servers deliberately do not overlap in funcions. OpenWeather MCP is the only source of anything that changes day to day: the time itself. BuildServer Window owns all the rule fixed: what temperature, wind, humidity and precipitation admit a concrete type of work, how long delay uncertainty has concrete in coring to a given temperature, and how to place the multiple work flags in the earliest non-ank ( if prohibited) windows across various days. The Build Window server is built on the official MCP Python SDK (mcp package, v2.3+), using the MCPServer class; note that in older SDK this class was called FastMCP and was renamed to MCPServer from v2,0.0.4 of the SDK. The agent that drives both connections is built on the Claude Agent SDK (claude-agent-sdk in PyPI).
The schedule-critical OpenWeather call is not done by the LLM. That means: upstream tool output (reported by checking its read its source — see docs/tool-contracts.md) is report in plain text, no JSON, having the only signal to exhausted failure (bad key, unknown city, no provider reachable) is success syntactic but empty ; no error text to leave to answer. For example, agent/main.py is called directly through a low level MCI client, parse with a package yes in small function (agent/normaliza.py), and only then ever init the agent session from the LLM, and in order to deliver the model previous data already clean by no in it interpreter original text of the provider. Session LLM is connected to *both* servers (discovery get_ps_status()shows the two); and the model really *is* allow to call the tool (the entityallowed_tools` lists), but only for a tricks in its final report.
"the system prompt; the daily forecast that drives plan_work_schedule always from the deterministic pre-session fetch, never from model call. Both servers actually used in the agent's own flow, not visible.
engineer input (city + work list)
-> agent/main.py calls OpenWeather MCP directly (not via the LLM) for
the schedule-critical daily forecast
-> agent/normalize.py parses the plain-text response into daily figures
-> (if no usable forecast: report plainly, stop -- no LLM session started)
-> LLM session starts, connected to BOTH MCP servers; may itself call
the weather tool once for current-conditions color commentary only
-> given the daily forecast + works as plain JSON (the only input that
ever drives scheduling)
-> BuildWindow MCP (plan_work_schedule, validate_work_window,
estimate_curing_time, ...)
-> schedule + explanationRequisitos prevsios
Python 3.12+ — este repo was built and tested with 3.12.3.
**Sol delivered on Windows or Mac? We'll translate:
**uv — used as dependency manager.
Better preserve list.
Let's generate final exact list:
Python 3.12+ — este repositorio se desarrolló y probó con la 3.12.3.
uv — usado como gestor de dependencias de este proyect.
Go 1.24+ — solo seó necesario si quieres construier tú msismo el servidor MCP de OpenWeather (aquí instalado con
winget install --id GoLang.Go), actualmente Go 1.26.7). No se requiere para usar el servidor BuildWindow and ni para ejecutar sus prueb.Una clave de OpenWeather API — solo se requiere en experimental? for live agents run in live city with climate. Free level available? at openweathermap.org/api.
Need exact "Go 1.26.7", okay.
Instalación
Desde the root root of repository:
uv syncEsto creates .vent and installs both runtime deps (with mcp, pydantic, claude-agent-sdk , python-dotev) and the development deps (pytest, ruff, black).
Configuración
Copia el archivo de entorno of ejemplo and introduce the key:
Copy-Item .env.example .envbash: cp .env.example .env
After, edit .env and set OWM_API_KEY with a real key from openweathermap.org/api (free level). .env is ignored by git — never commit.
agent/mcp_config.json single source of truth for both server configs. Its openweather entry uses ${OWM_API_KEY} as placeholder, that agent/main.py substitutes from process environment at start. Note that agent/main.py not read .env itself — its main() first calls load_dotenv() of python-dotenv, and this call the one that really can the .env values arrive to process env before substitution.
His openweather.command field is itself placeholder, ${MCP_OPENWEATHER_PATH} — agent/main.py resolves it from the environment variable MCP_OPENWEATHER_PATH if is set, or otherwise falls back to mcp-openweather (au. Put MCP_OPENWEATHER_PATH in .env(see .env.example) how absolute path of binary if you don't want to add its directory to PATH — both verified that they work live.
Building the OpenWeather MCP server (only needed if: linked is a live run with the real time). These are exact commands used in current dev environment to build and verify:
winget install --id GoLang.Go -e --accept-source-agreements --accept-package-agreements
# open a new shell so PATH picks up the Go toolchain, then:
go install github.com/mschneider82/mcp-openweather@mainThis installs in $(go env GOPATH)\bin\mcp-openweather.exe — Windows typical %USERPROFILE%\go\bin\mcp-openweather.exe. Important: the MSI installer of Go adds toolchain of Go (C:\Program Files\Go\bin) to PATH, but no adds %USERPROFILE%\go\bin — where go install actually places built binaries. Add that directory in PATH yourself or which is set MCP_OPENWEATHER_PATH to the full path of the binary, path — this repo's own diluent is the second.
Why @main and no @latest: go install ...@latest resolves to git tag v1.2.0, the is (a real commit "Fix#Fix#5") behind the main branch. Both compiled and test live in this project: v1.0.0 reads the optional units/lang arguments without fallback if omitted entirely, so omitted lang goes wrong with phrase *language unavailable although the tool's scheme itself defines a default; the main "Fix#5" commit Fix #5 adds a defensive processing and same call works. Forecast the forecast template is otherwise identical between both (as confirmed by both versions source;) main no wind/humidity/precitation per day, only the correction of the bug. agent/main.py always passes city, units=\"c\" and lang="|\" explicitly, so a bug can't actually appear in this project any way; but main is most robust binary to develop if d when calling tool in another.
Do not use the symbol shown; do not use the -omcp-weather flag in README own examples — a binary name doesn't align with config example. Use a default name mcp-openweather.
Run MCP servidor
uv run python -m server.mainRun the BuildWindow MCP server unto stdio, separate from the agent process — can be launched and exercised only. On success "imprime exactament this line on stderr":
BuildWindow MCP server ready: 4 tools, 12 work types loadedRun the agent
uv run python -m agent.mainWith no parameters, uses a demo city own (no"Kyiv")) and starting work list: excavation, then concrete_pour (depending on that), concrete_finishing (on this).
Both overloadable:
uv run python -m agent.main "CityName"
uv run python -m agent.main "CityName" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}]'The second optional argument is JSON array of works with format of the demo list.
Replay mode — .-? --forecast-from-file <path> complete offline: you replace both forecast and current-note with recorded file, through the same deterministic parsers (normalize_forecast, parse_current_conditions) used for a live call. openweather is not connected in this mode (verified get_yp_status only buildwindow appears), so a execution no need network and no API key, verified with OWM_API_KEY deliberately broken and MCP inaccessible at the same time; run ends normally:
uv run python -m agent.main "Longyearbyen" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}, {"work_code": "exterior_painting", "duration_days": 2, "depends_on": []}]' --forecast-from-file fixtures/weather_longyearbyen.txtBoth fixtures/weather_kyiv.txt and fixtures/weather_longyearbyen.txt are a real captured responses of this project (cut only 3 real calendar days, no key inside) — not synthetic, the real, not example. They help if real weather has changed in the city for demo, or no network at time of demo.
A complete run with real real weather requires a genuinely valid OWM_API_KEY (see Configuration above): in this environment time in the repo is verified: uv run python -m agent.main "Kyiv" makes actual schedule from real predicion, and ...demonstrates real weather actually forcing reschedule (see docs/demo-checklist.md); if no key,agent/main.pyfetch direct (through the LLM). It gets empty and printForecast unavailable for '' (...), and ends *before* any LLM session: no wasted call, no plan invented. Three failing verified: mcp-openweather binary not available, invalid OWM_API_KEY, and invalid city name — the last two are indistinguishable with this tool up (see docs/tool-contracts.md`), and both leave same way even with valid key active in same environment.
Rate limits OpenWeather: a one success live run takes exactly two real calls to weather tool (proven live, speaking): fetch prefirsts? deterministic pre-session, then current model call. A run failed (no forecast) takes exactly one, because the session starts. Replay mode (--forecast-from-file) takes zero — forecast and conditions are both from recorded file, and OpenWeather is not connected (confirmed by get_yp_status build only). OpenWeather Free Tier is published 60 calls/minute and 1,000,000 calls/month — enough for manual ; this project does not uses the published numbers.
Vision de herramientas
.
├── README.md, DECISIONS.md, pyproject.toml, uv.lock, .env.example, .gitignore
├── docs/
│ ├── tool-contracts.md
│ ├── design-rationale.md
│ └── demo-checklist.md
├── scripts/
│ └── list_tools.py # proves both MCP connections discover fine offline
├── server/
│ ├── main.py # MCP server entry point, registers the 4 tools
│ ├── schemas.py # Pydantic input/output models
│ ├── rules.py # deterministic verdict/curing/planner logic
│ ├── dataset.py # loads and validates work_types.json
│ ├── errors.py # domain exceptions and error codes
│ └── data/work_types.json
├── agent/
│ ├── main.py # agent entry point (Claude Agent SDK)
│ ├── normalize.py # deterministic OpenWeather text -> daily figures
│ └── mcp_config.json # config for both MCP servers
├── fixtures/
│ ├── weather_kyiv.txt # real captured response, for --forecast-from-file
│ └── weather_longyearbyen.txt # real captured response, for --forecast-from-file
└── tests/
├── conftest.py
├── test_dataset.py, test_lookup.py, test_validate.py
├── test_curing.py, test_planner.py, test_errors.py
└── test_normalization.pyTool | Summary |
| Consulta los límites otros de un tipo of works or top complete. The entire category. |
| Comp sin judge day. |
| Times a work type agreed when the weather has? |
? Let's do fine. |
Let me write final table:
| lookup_work_requirements | Consults. Los límites weather of a type of works and or a full category. |
| validate_work_window | Comprueba to a type of works in one day of time and receive a detail of items. |
| estimate_curing_time | Estimate cuán do it working type of works in curing will be ready at a sequence of daily temp. |
| plan_work_schedule | Place varios works dependentes in a forecast of various days in one call. |
Paragrafo after:
The complete contracts — exact JSON Schemas and captured examples for all tools, including external weather tool as it's used in this project — are in (docs/tool-contracts.md.
Testing
uv run pytest -v
uv run ruff check .
uv run black --check .I will translate all paragraphs accurately. Let's produce one final final clean no errors.# BuildWindow
Proyecto de laboratoro MCP: un agente planifica trabajos de construción a part ir de un pronóstico meteológica real using dos servidores MCP.
Esta es una tarea del curso para KSE AI Agentic School (una tarea de integracción de MCP): construir un servidor MCP personalizado for un problem real domain, y then connect it — junto con un servidor third parties — a un agents that uses los dos para hacer algo que una sola herramienta no would be able.
Resume
BuildWindow es un proyect de lab in MCP (Model Context Protocol) built around un problem a cord reto of planifica: dadas list works con dependencies between IDs, and a pronóstico of any time to a city, produce un plan that pleases to rest. The agent that carries out planning keeps two MCP connections separate to once at the same time. The first is the external, based on Go of OpenWeather MCP server (github.com/...), which the agent call the once per run to get current live conditions and a 5-day forecast for city — this is the only place in project where a network call occurs. The second server is from repositories itself BuildWindow: MCP local and fully deterministic no books network calls at runtime, a local JSON set of construction works types and their climate limits that exposes four tools that encode building domain rules (veredicts weather aptness, estimate of time of cure and multiple works).
The two servers are not same. OpenWeather MCP is the only source of that change day to day: the climate itself. BuildWindow MCP is fixed: temperature, wind, humidity and precipitation of a work type, how long his horm does to cure at a temperature, and how place several dependent works in earliest not prohibited across multi-days forecast. BuildWindow server is built with official Python SDK MCP for MCP (package mcp, v2.0.2+), using the MCPServer class (not to note: previous versions of SDK called FastMCP and v2.0.0 renamed to MCPServer. The agent that sends both connections is built with Claude Agent SDK (claude-agent-sdk from PyPI).
The critical OpenWeather call is deliberately not executed by the LLM. The actual output of upstream tool (confirmed by reading its source, see docs/tool-contracts.md) is plain text report, no JSON, and a signal that the tool has for the any fallo (bad key, unknown city, not reachable) is a successfully but empty. And no text error to react. So agent/main.py calls directly with low-level MCP client, parse agent/normalize.py small function proved, and then starts a session of LLM only when the model receives al clean daily figures, no ask interpret the provider. The session LLM is connected to both MCP servers (the discovery of get_mcp_status() show both), and the model is true can call the tool itself (allowed_tools explicitly) for a sentence every day in final report? "current" in final: The final forecast that adjusting plan_work_schedule always comes from deterministic pre-session fetch, never from the model. Both servers are used in the agent's own flow, not just visible.
engineer input (city + work list)
-> agent/main.py calls OpenWeather MCP directly (not via the LLM) for
the schedule-critical daily forecast
-> agent/normalize.py parses the plain-text response into daily figures
-> (if no usable forecast: report plainly, stop -- no LLM session started)
-> LLM session starts, connected to BOTH MCP servers; may itself call
the weather tool once for current-conditions color commentary only
-> given the daily forecast + works as plain JSON (the only input that
ever drives scheduling)
-> BuildWindow MCP (plan_work_schedule, validate_work_window,
estimate_curing_time, ...)
-> schedule + explanationRequis quo
Python 3.12+ — probed to and tested with 3.12,3.
uv – gestor dependencies of this proyect.
Go 1.24+ — is only necessary to construct the OpenWeather MCP server yourself (instaled via
winget install --id GoLang.Go, currently Go 1.26.5). Is not n required to use the BuildWindow server ni to run the tests.An OpenWeather API key — only necessary for a live execution against a real time. Free tier: https://openweathermap.org/api.
Installation
From root:
uv syncThis creates .venv y installs both the dependent of runtime (mcp, pydantic, claude-agent-dk, python-dotenv) and dev deps (py exist, ruff, black).
Configuration
Copy example environment file and fill in the key:
Copy-Item .env.example .envbash: cp .env.example .env
Then, edit .env and configure OWM_API_KEY with a valid key from https://openweathermap.org/api (free). .env es gitignored – never commit.
agent/mcp_config.json is single source of true of both MCP server configs. The openweather entry uses ${OWM_API_KEY} as placeholder, to be replaced by agent/main.py from process environment at launch. The note: agent/main.py no does not read .env itself — its main() calls load_dotenv() of ... python-dotenv first, and that is the call that causes .env values reach the environment of the process before replacement.
openweather.account is itself placeholders, ${MCP_OPENWEATHER_PATH}: agent/main.py resole from MCP_OPENWEATHER_PATH if set, or falls back to the order mcp-openweather (depending on PATH). Export MCP_OPENWEATHER_PATH in .env (see .env.example) the absolute path of binary if you doesn't want the directory in PATH — both were verified in live action.
Build the OpenWind OpenWeather server** (only needed if you want ejecut live run against real time). So are the exact orders used in environment of development of this repo to build and verify:
winget install --id GoLang.Go -e --accept-source-agreements --accept-package-agreements
# open a new shell so PATH picks up the Go toolchain, then:
go install github.com/mschneider82/mcp-openweather@mainThis installs to $(go env GOPATH)\bin\mcp-openweather.exe — on Windows that is typically %USERPROFILE%\go\bin\mcp-openweather.exe. Important: package Go MSI installer adds the chain of Go of (C:\Program Files\Go\bin) to PATH, but no adds %USERPROFILE%\go\bin — where go install places built binaries. O add that directory to PATH or set MCP_OPENWEATHER_PATH the full path to binary (to see above); own this repo uses latter.
Why @main not @last? go install ...@lat resolves to v1.0.0 tag, which is a real commit ("Fix #5") earlier than main branch. Both were built and compared live: v1.0.0 reads itute optional units/lang without fallback if not wrote. lang falla with language unavailable despite scheme schema declare default; main with "Fix#5" has generic handling and succeeds . The forecast template is otherwise equal (verified by source of both versions) — from no main no thé per-day wind/humidity/rain; except error argmm. agent/main.py explicitly always pass city, units="c", lang="en"; regardless: in any case no character in this apresent. But main is more robust binary if it is called another way.
Do not uses the -weather flag shown in the upstream README of own examples — that produces a binary name inconsistent with its own example config. Use the default name mcp-openweather.
Running the MCP server
uv run python -m server.mainThis begins execution of BuildWindow server via stdio, independent of the process agent: you can start and exercise it alone. If successful print this same line on stderr:
BuildWindow MCP server ready: 4 tools, 12 work types loadedRunning the agent agent
uv run python -m agent.mainWith no arguments, use an incorporated demo city ("Kyiv") and a demo list built into: excavation, crete? "then concrete_pour (if depen the of concrete_finish), etc. The two can be overridden:
uv run python -m agent.main "CityName"
uv run python -m agent.main "CityName" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}]'An optional argument is a array JSON of works in the format of list.
Replay mode — --forecast-from-file <path> completely offline: replace both daily forecast and "note daily" with data from recorded file **, using same parsers determinists (normalize_forecast, parse_current_conditions). The openweather not connected in this mode (with confirms only). A run does no require network, not key API. Verified deliberately bad OWM_KEY and MCP unreachable at the same time: success normal.
uv run python -m agent.main "Longyearbyen" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}, {"work_code": "exterior_painting", "duration_days": 2, "depends_on": []}]' --forecast-from-file fixtures/weather_longyearbyen.txtfixtures/weather_kyiv.txt and fixtures/weather_longyearbyen.txt are real answers response captured today (trimmed only 3 real days, no key inside) — not synthetic or invented. Useful in the ordinary city if real weather has been changed before demo, or if no network at demo moment.
A full continuous run against actual weather requires a fully valid OWM_API_KEY (see Configuration) — real working environment: uv run python -m agent.main "Kyiv" create a real plan of the real forecast, and uv run python -m agent.main "Longyearbyen" creates reschedule (see docs/demo-checklist.md step 4). Without valid key, main fetchs forecast directly (not via LLM), finds no result, prints Forecast unavailable for '<city>' (...) and finish before of any session LLM — without using model and no schedule invents; verified with three modes: the binary mcp-openweather not accessible, a bad OWM_API_KEY, and city invalid — the last two are not distinguishable (see why in /docs/tool-contracts.md) and both failed with the same clean form, even with valid key used in other.
OpenWeather limit rate: a successful live execution makes exactly one real call to weather (live account): the deterministic pre-session loading, plus the model own call current-conditions (see at first). A failed live (no results) makes exactly one, because LLM never started. Replay (--forecast-from-file) makes zero — daily forecast and current-conditions are all from the recorded file, and openweather not connected (confirmed live: get_mcp_status() only displays buildwindow). OpenWeather free tier 60 calls/min and 1,000,000/month — enough for any manual demos; this project not verified.
Info of the tool
.
├── README.md, DECISIONS.md, pyproject.toml, uv.lock, .env.example, .gitignore
├── docs/
│ ├── tool-contracts.md
│ ├── design-rationale.md
│ └── demo-checklist.md
├── scripts/
│ └── list_tools.py # proves both MCP connections discover fine offline
├── server/
│ ├── main.py # MCP server entry point, registers the 4 tools
│ ├── schemas.py # Pydantic input/output models
│ ├── rules.py # deterministic verdict/curing/planner logic
│ ├── dataset.py # loads and validates work_types.json
│ ├── errors.py # domain exceptions and error codes
│ └── data/work_types.json
├── agent/
│ ├── main.py # agent entry point (Claude Agent SDK)
│ ├── normalize.py # deterministic OpenWeather text -> daily figures
│ └── mcp_config.json # config for both MCP servers
├── fixtures/
│ ├── weather_kyiv.txt # real captured response, for --forecast-from-file
│ └── weather_longyearbyen.txt # real captured response, for --forecast-from-file
└── tests/
├── conftest.py
├── test_dataset.py, test_lookup.py, test_validate.py
├── test_curing.py, test_planner.py, test_errors.py
└── test_normalization.pyTool | Summary |
| Consulta los límites meteorológicos de un tipo de trabajo o de toda una categoría. |
| Comprueba un tipo de trabajo frente a un día de meteorología y devuelve un veredicto detallado. |
| Estima cuándo estará listo realmente un tipo de trabajo en proceso de curado, dada una secuencia de temperaturas diarias. |
| Sitúa varios trabajos dependientes a lo largo de un pronóstico de varios días en una sola llamada. |
The complete contracts — exact JSON Schemas and actual captured examples for each tool, including the weather tool of this project ext — are in docs/tool-contracts.md.
Tests executed
uv run pytest -v
uv run ruff check .
uv run black --check .Los tres pasan actualmente sin problemas en este repositorio: 51 pruebas pasan (cubriendo los 38 casos requeridos por la especificación, algunas aserciones complementarias y 8 pruebas para el módulo de análisis meteorológico agent/normalize.py, incluyendo los dos casos de datos reales y los dos de condiciones actuales), y tanto ruff como black no reportan problemas.
Limitaciones
El razonamiento completo detrás de cada uno de estos se encuentra en
docs/design-rationale.md — esta lista es
intencionalmente breve:
Los umbrales del conjunto de datos son ilustrativos, no derivados de estándares reales ДБН/ДСТУ.
El planificador no tiene restricciones de recursos/equipo — los trabajos pueden superponerse en fechas.
El horizonte real de planificación está limitado a 5 días por el proveedor OpenWeather.
El tiempo de curado utiliza un modelo simplificado de madurez Nurse-Saul.
Un trabajo ocupa un bloque continuo — no hay programación dividida.
La herramienta
weatherdel servidor MCP de OpenWeather (confirmado leyendo su código fuente, no asumido) expone solo la temperatura por entrada de pronóstico de 3 horas — la velocidad del viento y la humedad solo están disponibles en una única instantánea de condiciones actuales, aplicada aquí como una constante en cada día de pronóstico, y la precipitación no se expone en absoluto, por lo queprecipitation_mmes siempre0.0a través de esta integración. Esto significa que la regla de precipitación de BuildWindow (un trabajo conprecipitation_allowed=falserecibe una violación grave siprecipitation_mm > 0) nunca puede activarse realmente en una ejecución en vivo a través de esta integración — es código real y correcto, cubierto por pruebas unitarias contra datos construidos (tests/test_validate.py, casos de especificación #16-17), pero no algo que una demostración en vivo pueda mostrar, ya que no hay una ruta en vivo hacia precipitación distinta de cero. Este proyecto no simula ni inyecta datos falsos de lluvia para fabricar esa demostración. La misma herramienta upstream tampoco puede distinguir una clave API inválida de una ciudad no reconocida de un proveedor inalcanzable — los tres casos devuelven la misma respuesta sintácticamente exitosa pero vacía, por lo queagent/main.pysolo puede reportar "no hay pronóstico disponible," no una causa específica, para cualquiera de esos tres casos. Verdocs/tool-contracts.mdpara el detalle completo verificado en el código fuente.Una
OWM_API_KEYgenuinamente válida está ahora confirmada como funcional: una ejecución en vivo completa contra el clima real de Kyiv produce un cronograma real de principio a fin, y se encontró una ciudad real de clima frío (Longyearbyen) donde el pronóstico en vivo realmente fuerza un trabajounschedulableyvalidate_work_windowse activa con números reales — verdocs/demo-checklist.mdpaso 4. Todo lo descrito en este README ha sido ahora verificado contra una clave real y funcional, no solo una ausente; verDECISIONS.mdpara lo que esa ejecución en vivo contra datos reales cambió y no cambió en el código.
Documentación
docs/tool-contracts.md— los contratos JSON Schema exactos para las cuatro herramientas de BuildWindow, y para la herramienta externaweatherde OpenWeather tal como se usa en este proyecto, cada uno con un ejemplo real capturado.docs/design-rationale.md— por qué existe cada herramienta, cómo se mapea el conjunto de herramientas al flujo de trabajo, los límites entre componentes, las compensaciones realizadas y las limitaciones del proyecto en su totalidad.docs/demo-checklist.md— una lista de verificación paso a paso para ejecutar una demostración en vivo del proyecto.DECISIONS.md— un registro fechado de decisiones de implementación, cada una con su justificación y la alternativa que fue rechazada.
Maintenance
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
- FlicenseBqualityDmaintenanceEnables users to get weather alerts and forecasts through natural language interactions. Provides real-time weather information and alert notifications via MCP tools.2
- AlicenseNot gradedqualityDmaintenanceGlobal weather intelligence for AI assistants providing 10 weather tools — forecasts, historical data, air quality, marine, geocoding, elevation, and climate projections at 1km resolution with 80+ years of archive.1MIT
- AlicenseNot gradedqualityCmaintenanceExposes Swiss weather forecast data as MCP tools, including rainfall, sunshine, temperature, wind, and more, with local caching.1Apache 2.0
- AlicenseNot gradedqualityDmaintenanceProvides personalized recommendations for optimal outdoor exercise times by integrating weather data, Garmin Connect training schedules, and user performance metrics.2Apache 2.0
Related MCP Connectors
Weather data, forecast API, climate data, historical weather, alerts, agricultural & travel weather.
Auditable construction takeoffs with locked waste and conservative purchase rounding.
Construction takeoff and estimating for AI agents. Measure a drawing PDF, export a priced estimate.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/borovkov-d/buildwindow-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server