BuildWindow
BuildWindow
MCP 实验项目:一个智能体使用两个 MCP 服务器,根据真实天气预报规划施工作业。
这是 KSE AI Agentic School 的课程作业(一个 MCP 集成作业):为真实领域问题构建一个自定义 MCP 服务器,然后将其与现有的第三方 MCP 服务器一起连接到同一个智能体,让智能体同时使用两者来完成单个工具无法完成的事情。
概述
BuildWindow 是一个围绕具体调度问题构建的 MCP(模型上下文协议)实验项目:给定一份带有依赖关系的施工作业清单,以及某个城市的天气预报,生成一个同时满足两者的调度计划。负责规划的智能体同时持有两个独立的 MCP 连接。第一个是外部的、基于 Go 的 OpenWeather MCP 服务器(github.com/mschneider82/mcp-openweather),智能体每次运行调用一次,以获取所请求城市的实时当前天气状况和 5 天预报——这是整个项目中唯一发生网络调用的地方。第二个是本仓库自己的 BuildWindow MCP 服务器:一个本地、完全确定性的服务器,运行时无网络调用,由本地 JSON 数据集(包含施工作业类型及其天气限制)支撑,暴露四个编码了施工领域规则的工具(天气适宜性判定、养护时间估算和多作业调度)。
这两个服务器刻意不重叠职责。OpenWeather MCP 是唯一提供逐日变化信息的来源——即天气本身。BuildWindow MCP 则拥有所有固定规则:给定作业类型能承受的温度、风力、湿度和降水范围,混凝土在给定温度下需要多长时间养护,以及如何将多个有依赖关系的作业安排到跨多天预报中最早的非禁止时段。BuildWindow 服务器使用官方 Python MCP SDK(包 mcp,v2.0.0+)构建,使用其 MCPServer 类——注意,在旧版 SDK 中此类名为 FastMCP,自 SDK v2.0.0 起更名为 MCPServer。驱动两个连接的智能体使用 Claude Agent SDK(PyPI 上的 claude-agent-sdk)构建。
对调度关键的 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+ — 仅当你想自己构建 OpenWeather MCP 服务器时才需要(此处通过
winget install --id GoLang.Go安装,当前为 Go 1.26.7)。使用 BuildWindow 服务器或运行其测试不需要。OpenWeather API 密钥 — 仅当针对真实天气进行实时智能体运行时需要。免费层级可在 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() 首先调用 python-dotenv 的 load_dotenv(),正是这个调用使 .env 中的值在替换发生前进入进程环境。
其 openweather.command 字段本身也是一个占位符 ${MCP_OPENWEATHER_PATH}——agent/main.py 在设置了 MCP_OPENWEATHER_PATH 环境变量时从中解析,否则回退到裸命令 mcp-openweather(依赖 PATH)。如果你不想把二进制文件所在目录加入 PATH,请在 .env 中设置 MCP_OPENWEATHER_PATH(见 .env.example)为二进制的绝对路径——两种方式都已验证可实时工作。
构建 OpenWeather MCP 服务器(仅当你想针对真实天气进行实时运行时需要)。以下是在本仓库自身开发环境中用于构建和验证的确切命令:
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 失败,尽管工具自身的 schema 声明了默认值;main 的 "Fix #5" 提交添加了防御性处理,同样的调用成功。除此之外,两个版本的预报模板完全相同(通过阅读两个版本的源码确认)——从 main 构建不会增加逐日风力/湿度/降水,只修复了参数 bug。agent/main.py 无论如何总是显式传递 city、units="c" 和 lang="en",所以这个 bug 无论如何都不会通过本项目暴露——但如果你以其他方式调用该工具,main 是更稳健的依赖选择。
不要使用上游 README 某些示例中显示的 -o mcp-weather 标志——那会产生与其自身配置示例不一致的二进制名称。使用默认名称 mcp-openweather 构建。
运行 MCP 服务器
uv run python -m server.main这通过 stdio 运行 BuildWindow MCP 服务器,独立于智能体进程——它可以完全独立地启动和运行。成功时它向 stderr 打印恰好这一行:
BuildWindow MCP server ready: 4 tools, 12 work types loaded运行智能体
uv run python -m agent.main不带参数时,使用内置演示城市("Kyiv")和内置演示作业列表:excavation,然后是 concrete_pour(依赖前者),然后是 concrete_finishing(依赖 concrete_pour)。
两者都可以覆盖:
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" '[...]' 演示了真实天气确实迫使重新调度(见 docs/demo-checklist.md 步骤 4))。没有有效密钥时,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 Schema 和真实捕获示例,包括本项目使用的外部 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——此列表有意保持简洁:
数据集阈值仅为示意性数值,并非源自真实的 ДБН/ДСТУ 标准。
规划器没有资源/人员约束——工序之间可以日期重叠。
实际规划周期因 OpenWeather 提供商而限制为 5 天。
养护时间采用简化的 Nurse-Saul 成熟度模型。
每道工序占据一个连续时间段——不支持拆分排程。
OpenWeather MCP 服务器的
weather工具(通过阅读其源码确认,而非臆测)仅暴露每个 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以真实数据触发——见docs/demo-checklist.md第 4 步。本 README 中描述的所有内容现已针对真实有效的密钥验证过,而不仅仅是缺失的密钥;关于那次针对真实数据的实时运行对代码做了哪些改变、未做哪些改变,见DECISIONS.md。
文档
docs/tool-contracts.md— 全部四个 BuildWindow 工具以及本项目使用的外部 OpenWeatherweather工具的精确 JSON Schema 契约,每个均附有真实捕获的示例。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