BuildWindow
BuildWindow
MCP 실습 프로젝트: 에이전트가 두 개의 MCP 서버를 사용하여 실제 날씨 예보를 바탕으로 공사 일정을 계획합니다.
이 프로젝트는 KSE AI 에이전틱 스쿨의 수업 과제입니다(MCP 통합 과제): 실제 도메인 문제를 위한 커스텀 MCP 서버를 구축한 다음, 기존의 서드파티 MCP 서버와 함께 에이전트에 연결하여, 단일 도구로는 할 수 없는 작업을 두 서버가 함께 수행하도록 합니다.
개요
BuildWindow는 구체적인 일정 계획 문제를 중심으로 구축된 MCP(Model Context Protocol) 실습 프로젝트입니다: 의존 관계가 있는 공사 작업 목록과 도시의 날씨 예보가 주어졌을 때, 두 가지를 모두 존중하는 일정을 생성합니다. 이 계획을 수행하는 에이전트는 두 개의 별도 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(.env.example 참조)에서 MCP_OPENWEATHER_PATH를 바이너리의 절대 경로로 설정하세요 — 둘 다 라이브로 작동하는 것이 확인되었습니다.
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에 추가하지만, go install이 실제로 빌드된 바이너리를 배치하는 %USERPROFILE%\go\bin은 추가하지 않습니다. 해당 디렉터리를 직접 PATH에 추가하거나, MCP_OPENWEATHER_PATH를 바이너리의 전체 경로로 설정하세요(위 참조) — 이 저장소의 자체 설정은 후자를 사용합니다.
@main이지 @latest가 아닌 이유: go install ...@latest는 태그 v1.0.0으로 확인되는데, 이는 저장소의 main 브랜치보다 한 개의 실제 커밋("Fix #5") 뒤에 있습니다. 이 프로젝트에서 둘 다 빌드하고 라이브로 비교했습니다: v1.0.0은 선택적 units/lang 인수를 완전히 생략했을 때 폴백 없이 읽으므로, 도구 자체 스키마가 기본값을 선언함에도 불구하고 lang을 생략하면 language unavailable로 실패합니다. main의 "Fix #5" 커밋은 방어적 처리를 추가하며 동일한 호출이 성공합니다. 예보 템플릿 자체는 그 외에는 두 버전 간에 동일합니다(두 버전의 소스를 읽어 확인) — main에서 빌드해도 일별 바람/습도/강수량이 추가되지 않으며, 인수 버그만 수정됩니다. agent/main.py는 어떤 경우든 항상 city, units="c", lang="en"을 명시적으로 전달하므로 이 버그는 이 프로젝트를 통해 어느 쪽이든 실제로 표면화될 수 없습니다 — 그러나 도구를 다른 방식으로 호출한다면 main이 의존하기에 더 견고한 바이너리입니다.
일부 업스트림 README 예제에 표시된 -o mcp-weather 플래그를 사용하지 마십시오 — 자체 구성 예제와 일치하지 않는 바이너리 이름이 생성됩니다. 기본 이름인 mcp-openweather로 빌드하세요.
MCP 서버 실행
uv run python -m server.main이렇게 하면 에이전트 프로세스와 독립적으로 BuildWindow MCP 서버가 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" '[...]'는 실제 날씨가 실제로 일정 변경을 강제하는 것을 보여줍니다(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)는 0번 호출합니다 — 일일 예보와 현재 기상 메모 모두 기록된 파일에서 오며, 이 모드에서는 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개의 사양 필수 케이스, 몇 가지 추가 검증, 그리고 agent/normalize.py 날씨 파싱 모듈에 대한 8개의 테스트 — 실제 픽스처 2개와 현재 상태 케이스 2개 포함), 그리고 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). 그러나 라이브 데모에서는 보여줄 수 없습니다. 라이브 경로에서 0이 아닌 강수량으로 이어지는 경로가 없기 때문입니다. 이 프로젝트는 가짜 비 데이터를 시뮬레이션하거나 주입하지 않아 그 데모를 만들지 않습니다. 동일한 상위 도구는 또한 잘못된 API 키와 인식할 수 없는 도시와 연결할 수 없는 제공자를 구분할 수 없습니다 — 세 경우 모두 동일한 구문상 성공하지만 빈 응답으로 돌아오며, 이것이agent/main.py가 특정 원인 대신 "사용 가능한 예보 없음"만 보고할 수 있는 이유입니다. 세 경우 모두에 대한 전체 내용은docs/tool-contracts.md에서 확인할 수 있습니다. 소스로 검증된 세부 사항입니다.진짜 유효한
OWM_API_KEY가 이제 확인되었습니다: 실제 키로 실제 키이우 날씨에 대한 전체 라이브 실행이 종단 간 실제 일정을 생성하며, 실제 추운 날씨 도시(롱이어뷔엔)에서 라이브 예보가 실제로 작업unschedulable을 강제하고validate_work_window가 실제 숫자로 실행되는 것을 발견했습니다 — 전체 내용은docs/demo-checklist.md4단계에서 확인할 수 있습니다. 이 README에 설명된 모든 것이 이제 실제 키로 검증되었으며, 단지 없는 키가 아닙니다. 그 라이브 실행이 코드에서 무엇을 바꾸고 무엇을 바꾸지 않았는지는DECISIONS.md를 참조하십시오.
문서
docs/tool-contracts.md— 네 가지 BuildWindow 도구와 외부 OpenWeatherweather도구에 대한 정확한 JSON 스키마 계약, 각각 실제 캡처된 예시 포함.docs/design-rationale.md— 각 도구가 존재하는 이유, 도구 집합이 워크플로에 매핑되는 방식, 구성 요소 간의 경계, 절충된 trade-off, 그리고 프로젝트의 제한 사항 전체.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