Skip to main content
Glama
borovkov-d

BuildWindow

by borovkov-d

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 + explanation

Related 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 并安装运行时依赖(mcppydanticclaude-agent-sdkpython-dotenv)和开发依赖(pytestruffblack)。

配置

复制示例环境文件并填入你的密钥:

Copy-Item .env.example .env

bash: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-dotenvload_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 无论如何总是显式传递 cityunits="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_forecastparse_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.txt

fixtures/weather_kyiv.txtfixtures/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

工具概述

工具

摘要

lookup_work_requirements

查找某个作业类型或整个类别的天气限制。

validate_work_window

将一个作业类型与一天的天气进行核对,并返回逐项判定。

estimate_curing_time

给定每日温度序列,估算养护作业类型实际何时就绪。

plan_work_schedule

一次调用将多个有依赖关系的作业安排到跨多天预报中。

完整契约——每个工具的确切 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 天气解析模块的测试,其中包括两个真实夹具用例和两个当前天气状况用例),且 ruffblack 均未报告任何问题。

局限性

每项限制背后的完整理由见 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 工具以及本项目使用的外部 OpenWeather weather 工具的精确 JSON Schema 契约,每个均附有真实捕获的示例。

  • docs/design-rationale.md — 每个工具存在的原因、工具集如何映射到工作流、组件之间的边界、所做的权衡以及项目的完整局限性。

  • docs/demo-checklist.md — 运行项目实时演示的分步检查清单。

  • DECISIONS.md — 带日期的实现决策日志,每项决策均附有理由和被否决的替代方案。

Install Server
F
license - not found
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • F
    license
    B
    quality
    D
    maintenance
    Enables users to get weather alerts and forecasts through natural language interactions. Provides real-time weather information and alert notifications via MCP tools.
    2
  • A
    license
    Not graded
    quality
    D
    maintenance
    Global 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.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Exposes Swiss weather forecast data as MCP tools, including rainfall, sunshine, temperature, wind, and more, with local caching.
    1
    Apache 2.0

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

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