BuildWindow
BuildWindow
Лабораторный проект MCP: агент планирует строительные работы с учётом реального прогноза погоды, используя два MCP-сервера.
Это учебное задание для KSE AI Agentic School (задание по интеграции MCP): создать собственный MCP-сервер для реальной прикладной задачи, затем подключить его — вместе с существующим сторонним MCP-сервером — к агенту, который использует оба сервера совместно, чтобы сделать то, что не под силу одному инструменту.
Обзор
BuildWindow — это лабораторный проект MCP (Model Context Protocol), построенный вокруг конкретной задачи планирования: имея список строительных работ с зависимостями между ними и прогноз погоды для города, нужно составить расписание, учитывающее и то, и другое. Агент, выполняющий это планирование, одновременно поддерживает два отдельных MCP-подключения. Первый — внешний MCP-сервер OpenWeather на Go (github.com/mschneider82/mcp-openweather), к которому агент обращается один раз за запуск, чтобы получить текущие условия в реальном времени и 5-дневный прогноз для запрошенного города — это единственное место во всём проекте, где происходит сетевой вызов. Второй — собственный MCP-сервер BuildWindow из этого репозитория: локальный, полностью детерминированный сервер без сетевых вызовов во время выполнения, основанный на локальном JSON-наборе данных о типах строительных работ и их погодных ограничениях, который предоставляет четыре инструмента, кодирующих правила строительной предметной области (заключения о пригодности по погоде, оценку времени отверждения и планирование нескольких работ).
Два сервера намеренно не пересекаются по зонам ответственности. OpenWeather MCP — единственный источник всего, что меняется день ото дня, — самой погоды. BuildWindow MCP, наоборот, владеет всем, что является фиксированным правилом: какую температуру, ветер, влажность и осадки выдерживает данный тип работ, сколько времени бетон отвердевает при данной температуре и как разместить несколько зависимых работ в самые ранние незапрещённые окна в рамках многодневного прогноза. Сервер BuildWindow построен на официальном Python MCP SDK (пакет mcp, v2.0.0+), с использованием его класса MCPServer — обратите внимание, что в более старых версиях SDK этот класс назывался FastMCP и был переименован в MCPServer начиная с SDK v2.0.0. Агент, управляющий обоими подключениями, построен на Claude Agent SDK (claude-agent-sdk на PyPI).
Критичный для расписания вызов OpenWeather намеренно не выполняется LLM. Фактический вывод внешнего инструмента (подтверждено чтением его исходного кода — см. docs/tool-contracts.md) — это текстовый отчёт, а не JSON, и единственный сигнал, который он даёт при любой ошибке (неверный ключ, неизвестный город, недоступный провайдер), — это синтаксически успешный, но пустой ответ; реагировать на текст ошибки не на что. Поэтому agent/main.py вызывает его напрямую через низкоуровневый MCP-клиент, разбирает результат небольшой покрытой тестами функцией (agent/normalize.py) и только затем запускает LLM-сессию — передавая модели уже очищенные ежедневные показатели, а не прося её интерпретировать сырой текст провайдера. LLM-сессия подключена к обоим MCP-серверам (обнаружение через get_mcp_status() показывает оба подключения), и модели действительно разрешено самой вызывать инструмент погоды (allowed_tools явно перечисляет его) — но только для одного предложения о текущих условиях в её итоговом отчёте, согласно системному промпту; ежедневный прогноз, который питает plan_work_schedule, всегда берётся из детерминированной предсессионной выборки, а не из собственного вызова модели. Оба сервера действительно используются в собственном процессе агента, а не просто видны.
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
Предварительные требования
Python 3.12+ — этот репозиторий собирался и тестировался на 3.12.3.
uv — используется в качестве менеджера зависимостей для этого проекта.
Go 1.24+ — нужен только если вы хотите самостоятельно собрать MCP-сервер OpenWeather (здесь установлен через
winget install --id GoLang.Go, сейчас Go 1.26.7). Не требуется для использования сервера BuildWindow или для запуска его тестов.Ключ API OpenWeather — нужен только для живого запуска агента с реальной погодой. Бесплатный тариф доступен на openweathermap.org/api.
Установка
Из корня репозитория:
uv syncЭто создаёт .venv и устанавливает как зависимости времени выполнения (mcp, pydantic, claude-agent-sdk, python-dotenv), так и зависимости для разработки (pytest, ruff, black).
Конфигурация
Скопируйте пример файла окружения и заполните свой ключ:
Copy-Item .env.example .envbash: cp .env.example .env
Затем отредактируйте .env и укажите в OWM_API_KEY настоящий ключ с openweathermap.org/api (бесплатный тариф). .env находится в .gitignore — он никогда не коммитится.
agent/mcp_config.json — единственный источник истины для конфигураций обоих MCP-серверов. Его запись openweather ссылается на ${OWM_API_KEY} как на заполнитель, который agent/main.py подставляет из окружения процесса при запуске. Обратите внимание, что agent/main.py сам не читает .env — его main() сначала вызывает load_dotenv() из python-dotenv, и именно этот вызов обеспечивает попадание значений из .env в окружение процесса до того, как произойдёт подстановка.
Поле openweather.command само является заполнителем — ${MCP_OPENWEATHER_PATH}. agent/main.py разрешает его из переменной окружения MCP_OPENWEATHER_PATH, если она задана, а если нет — откатывается к простой команде mcp-openweather (полагаясь на PATH). Укажите MCP_OPENWEATHER_PATH в .env (см. .env.example) как абсолютный путь к бинарнику, если вы не хотите добавлять его каталог в PATH — оба варианта были проверены вживую.
Сборка MCP-сервера OpenWeather (нужна только если вы хотите выполнить живой запуск с реальной погодой). Вот точные команды, которые использовались для сборки и проверки в среде разработки этого репозитория:
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@mainЭто устанавливает в $(go env GOPATH)\bin\mcp-openweather.exe — в Windows это обычно %USERPROFILE%\go\bin\mcp-openweather.exe. Важно: установщик Go MSI добавляет тулчейн Go (C:\Program Files\Go\bin) в PATH, но не добавляет %USERPROFILE%\go\bin — туда, куда go install на самом деле помещает собранные бинарники. Либо добавьте этот каталог в PATH самостоятельно, либо укажите MCP_OPENWEATHER_PATH как полный путь к бинарнику (см. выше) — в этом репозитории используется второй вариант.
Почему @main, а не @latest: go install ...@latest разрешается в тег v1.0.0, который отстаёт от ветки main репозитория на один реальный коммит («Fix #5»). Обе версии были собраны и сравнены вживую в этом проекте: v1.0.0 читает необязательные аргументы units/lang без запасного варианта, когда они полностью опущены, поэтому опущенный lang завершается ошибкой language unavailable, хотя собственная схема инструмента объявляет значение по умолчанию; коммит «Fix #5» в main добавляет защитную обработку, и тот же вызов успешно выполняется. В остальном сам шаблон прогноза идентичен в обеих версиях (подтверждено чтением исходников обеих версий) — сборка из main не добавляет ежедневные ветер/влажность/осадки, а только исправляет баг с аргументами. agent/main.py в любом случае всегда явно передаёт city, units="c" и lang="en", так что через этот проект этот баг в принципе не может проявиться — но main — более надёжный бинарник, на который стоит полагаться, если вы когда-нибудь вызовете инструмент иным способом.
Не используйте флаг -o mcp-weather, показанный в некоторых примерах в самом README вышестоящего проекта, — он даёт имя бинарника, несовместимое с его же примером конфигурации. Собирайте с именем по умолчанию, mcp-openweather.
Запуск MCP-сервера
uv run python -m server.mainЭто запускает MCP-сервер BuildWindow через stdio, независимо от процесса агента — его можно запустить и проверить полностью самостоятельно. При успехе он выводит в stderr ровно эту строку:
BuildWindow MCP server ready: 4 tools, 12 work types loadedЗапуск агента
uv run python -m agent.mainБез аргументов используется встроенный демонстрационный город («Kyiv») и встроенный демонстрационный список работ: excavation, затем concrete_pour (зависит от неё), затем concrete_finishing (зависит от неё).
И то и другое можно переопределить:
uv run python -m agent.main "CityName"
uv run python -m agent.main "CityName" '[{"work_code": "excavation", "duration_days": 1, "depends_on": []}]'Необязательный второй аргумент — это JSON-массив работ в той же форме, что и демонстрационный список.
Режим воспроизведения — --forecast-from-file <path> полностью офлайн: он заменяет и ежедневный прогноз, и сводку о текущих условиях данными из записанного файла, через те же детерминированные парсеры (normalize_forecast, parse_current_conditions), которые используются при живом вызове. В этом режиме openweather вообще не подключён (подтверждено через get_mcp_status() — появляется только buildwindow), поэтому для запуска не нужны ни доступ к сети, ни какой-либо API-ключ — проверено вживую с намеренно сломанным OWM_API_KEY и недоступным MCP_OPENWEATHER_PATH одновременно; запуск всё равно завершился нормально:
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 и fixtures/weather_longyearbyen.txt — это реальные ответы, захваченные вживую в этом проекте (каждый обрезан до 3 полных реальных календарных дней, внутри нет ключа) — не синтетические и не выдуманные примеры. Полезно, если реальная погода в демонстрационном городе к моменту запуска изменилась или если во время демонстрации сети вообще нет.
Полноценный живой запуск с реальной погодой требует действительно валидного OWM_API_KEY (см. раздел «Конфигурация» выше) — подтверждено работой в среде разработки этого репозитория: uv run python -m agent.main "Kyiv" создаёт реальное расписание на основе реального прогноза, а uv run python -m agent.main "Longyearbyen" '[...]' демонстрирует, как реальная погода фактически вынуждает перепланировать работы (см. шаг 4) в docs/demo-checklist.md). Без рабочего ключа agent/main.py получает прогноз напрямую (не через LLM), получает пустой результат, печатает Forecast unavailable for '<city>' (...) и завершается до запуска любой LLM-сессии — никакого потраченного впустую вызова модели, никакого сфабрикованного расписания. Это было проверено на трёх реальных сценариях отказа: бинарник mcp-openweather вообще недоступен, невалидный OWM_API_KEY и невалидное название города — последние два через этот внешний инструмент фактически неразличимы (почему — см. docs/tool-contracts.md), и оба были подтверждены как завершающиеся одинаково чисто, даже при наличии действительно валидного ключа, активного где-то ещё в том же окружении.
Лимиты OpenWeather: один успешный живой запуск выполняет ровно два реальных вызова инструмента weather (подтверждено вживую подсчётом) — детерминированная предсессионная выборка плюс один собственный вызов модели для текущих условий (см. «Обзор» выше). Неудачный живой запуск (без пригодного прогноза) выполняет ровно один, поскольку LLM-сессия так и не запускается. Режим воспроизведения (--forecast-from-file) выполняет ноль — и ежедневный прогноз, и сводка о текущих условиях берутся из записанного файла, а openweather в этом режиме вообще не подключён (подтверждено вживую: get_mcp_status() показывает только buildwindow). Бесплатный тариф OpenWeather задокументирован как 60 вызовов/минуту и 1 000 000 вызовов/месяц — с большим запасом достаточно для любого количества ручных демонстрационных запусков; этот проект сам не проводит стресс-тестирование этой опубликованной цифры.
Структура проекта
.
├── 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.pyОбзор инструментов
Инструмент | Описание |
| Найти погодные ограничения для типа работ или целой категории. |
| Проверить один тип работ на один день погоды и получить детализированный вердикт. |
| Оценить, когда отверждающийся тип работ будет реально готов, с учётом последовательности дневных температур. |
| Разместить несколько зависимых работ в рамках многодневного прогноза одним вызовом. |
Полные контракты — точные JSON-схемы и реальные захваченные примеры для каждого инструмента, включая внешний инструмент weather в том виде, в котором он используется в этом проекте, — находятся в docs/tool-contracts.md.
Тестирование
uv run pytest -v
uv run ruff check .
uv run black --check .Все три сейчас проходят чисто в этом репозитории: 51 тест проходит (покрывая
38 требуемых спецификацией случаев, несколько дополнительных проверок и 8 тестов
для модуля разбора погоды agent/normalize.py, включая два
случая с реальными фикстурами и два случая текущих условий), и оба — ruff и
black — не сообщают о проблемах.
Ограничения
Полное обоснование каждого из этих пунктов находится в
docs/design-rationale.md — этот список
намеренно краток:
Пороговые значения наборов данных носят иллюстративный характер и не основаны на реальных стандартах ДБН/ДСТУ.
В планировщике нет ограничений по ресурсам/бригадам — работы могут пересекаться по датам.
Реальный горизонт планирования ограничен 5 днями со стороны провайдера OpenWeather.
Время выдерживания использует упрощённую модель зрелости Nurse-Saul.
Одна работа занимает один непрерывный блок — разделённое планирование отсутствует.
Инструмент
weatherMCP-сервера OpenWeather (подтверждено чтением его исходного кода, а не предположением) предоставляет только температуру для каждой записи прогноза с шагом 3 часа — скорость ветра и влажность доступны только в одном снимке текущих условий, применяемом здесь как константа на каждый день прогноза, а осадки не предоставляются вовсе, поэтомуprecipitation_mmвсегда0.0через эту интеграцию. Это означает, что правило осадков BuildWindow (работа сprecipitation_allowed=falseполучает жёсткое нарушение, еслиprecipitation_mm > 0) не может фактически сработать при живом запуске через эту интеграцию — это реальный, корректный код, покрытый модульными тестами на синтетических данных (tests/test_validate.py, случаи спецификации #16-17), но его нельзя показать в живом демо, поскольку нет живого пути к ненулевым осадкам. Этот проект не симулирует и не внедряет поддельные данные об осадках, чтобы сфабриковать такое демо. Тот же вышестоящий инструмент также не может отличить недействительный API-ключ от нераспознанного города от недоступного провайдера — все три случая возвращаются как один и тот же синтаксически успешный, но пустой ответ, поэтомуagent/main.pyможет только сообщить «прогноз недоступен», а не конкретную причину, для любого из этих трёх случаев. Полные, подтверждённые исходным кодом подробности см. вdocs/tool-contracts.md.Действительно валидный
OWM_API_KEYтеперь подтверждён рабочим: полный живой запуск против реальной погоды Киева даёт реальное расписание от начала до конца, и найден реальный холодный город (Лонгйирбюен), где живой прогноз действительно заставляет работу статьunschedulable, иvalidate_work_windowсрабатывает с реальными числами — см. шаг 4 вdocs/demo-checklist.md. Всё, описанное в этом README, теперь проверено на реальном, рабочем ключе, а не только на отсутствующем; см.DECISIONS.mdо том, что этот живой запуск на реальных данных изменил и не изменил в коде.
Документация
docs/tool-contracts.md— точные контракты JSON Schema для всех четырёх инструментов BuildWindow и для внешнего инструментаweatherOpenWeather, используемого этим проектом, каждый с реальным захваченным примером.docs/design-rationale.md— зачем существует каждый инструмент, как набор инструментов соотносится с рабочим процессом, границы между компонентами, принятые компромиссы и полный список ограничений проекта.docs/demo-checklist.md— пошаговый чек-лист для проведения живого демо проекта.DECISIONS.md— датированный журнал решений по реализации, каждое с его обоснованием и отвергнутой альтернативой.
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