IrradianceCrew Brief
Uses Google Maps Platform APIs to brief rooftop solar jobs: geocodes job addresses (Geocoding API), reads each roof's pitch and azimuth plus the panel layout (Solar API), and pulls the hourly forecast of gusts, rain, thunder and temperature (Weather API) against crew-safety thresholds. Requires a GOOGLE_MAPS_API_KEY and falls back to keyless NWS forecasts where configured.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@IrradianceCrew BriefBuild tomorrow's crew brief from this jobs CSV and flag any KEEP, MOVE, or SLIP."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
IrradianceCrew Brief
An MCP server and Bun CLI for rooftop solar installers: give it tomorrow's booked jobs as a CSV and it returns a ranked crew schedule (Markdown + JSON). For every job it geocodes the address, reads the roof's pitch and azimuth from the Google Solar API, checks each shift hour of the Google Weather API hourly forecast against crew-safety thresholds, estimates irradiance on that roof plane, and says KEEP, MOVE (new start the same day) or SLIP, naming the rule that decided it.
Decision support only, not a safety authority. The thresholds are examples written for this lab, the irradiance figures are a documented estimate (not a measurement or a forecast product), and forecasts can be wrong. The person in charge on site decides whether work goes ahead.
The problem
An install dispatcher's evening question is "which of tomorrow's roofs can we work, from when, and which jobs slip?". The answer needs three things joined per address: the hourly forecast (gusts, rain, thunder, temperature), the roof itself (which way it faces and how steep it is, for commissioning work that wants strong sun on the array), and the crew's own limits. IrradianceCrew Brief does that join in one read-only tool call and prints the reason next to every decision: "J-102 MOVE to 11:00-17:00: wind_gust_kmh 52 km/h at 08:00 breaks gust".
Related MCP server: RoofMeasure MCP
Quickstart
bun install --frozen-lockfile
bun run demo # keyless: 9 synthetic jobs -> ranked schedule (reports/example-crew-brief.md)
bun run irradiancecrew profiles
cp .env.example .env # add GOOGLE_MAPS_API_KEY (Geocoding + Solar + Weather), optional GEMINI_API_KEY
bun run irradiancecrew brief --jobs my-jobs.csv --format both --out briefs/ # live, tomorrow by default
bun run irradiancecrew brief --jobs my-jobs.csv --source nws # keyless US forecast, lat/lng rowsjobs.csv (header names, any order; job_id, date and an address or lat+lng are required):
job_id,date,address,crew,task,start,duration_hours,panels,priority
J-101,2026-11-03,"100 Example Ave, Sampletown",crew-a,install,07:00,6,,2
J-104,2026-11-03,"55 Mockingbird Way, Sampletown",crew-b,commission,09:00,2,12,task: commission (any task listed under the profile's irradiance.tasks) additionally needs the irradiance estimate on the roof plane to reach the threshold (example: 400 W/m2) every hour of the block. panels picks the first N panels of the Solar API layout to decide which roof segments the crew works on. Up to 25 jobs per brief; jobs booked on other dates are listed, not briefed.
MCP server
{
"mcpServers": {
"irradiancecrew": {
"command": "bun",
"args": ["/abs/path/irradiancecrew-brief/src/cli.ts", "serve"],
"env": { "GOOGLE_MAPS_API_KEY": "" }
}
}
}Add "--fixtures" to args for the keyless synthetic demo. Five read-only tools:
Tool | Input | Upstream calls |
|
| per distinct site: 1 geocode (addresses only) + 1 Solar + 1 Weather call per 24 forecast hours; or 2 NWS calls on fallback |
| one job: | same, for one site |
| none | none: formulas, references, attribution, caching terms |
| none | none |
| none | none |
How a job is decided
Each shift hour (default 07:00-17:00 local) is checked against the profile's rules. A rule states what must hold for GO: broken = NO-GO, within its margin = MARGINAL, value missing = its on_missing (MARGINAL by default). Bundled example profile crew-example:
Rule | Example threshold |
|
|
|
|
|
|
|
|
|
|
|
|
irradiance (commission tasks) | estimate |
Decision rules, first match wins:
Rule | Action | When |
| slip | the address does not geocode and the CSV has no lat/lng |
| slip | no hourly forecast for the job day (past, beyond 240 h, both sources failed, or no time zone) |
| keep | every hour of the planned block (start + duration) is GO or MARGINAL, and meets the irradiance threshold when the task needs it |
| move | the planned block fails, but an equally long block inside the shift passes; the earliest one is proposed |
| slip | safe blocks exist, but none meets the irradiance threshold (irradiance tasks only) |
| slip | no block of the needed length without a NO-GO hour; the rule behind most NO-GO hours is named |
Ranking: keep, move, slip; then CSV priority; then fewer MARGINAL rules in the block; then the higher irradiance estimate; then job id. Same-crew overlaps after moves are flagged for the dispatcher, not resolved. Override thresholds with --rule "wind_gust_kmh < 30 margin 5 id gust" (same id replaces), or --profile-file (CLI only).
Irradiance proxy (an estimate)
Neither API gives plane-of-array irradiance: the Weather API ForecastHour has cloudCover and uvIndex but no irradiance field, and the Solar API gives roof geometry and yearly sunshine, not hourly forecasts. The proxy per hour is:
Sun position from NOAA's "General Solar Position Calculations" (fractional-year equation of time and declination).
Clear-sky GHI = 1098 cos z exp(-0.059 / cos z) W/m2 (Haurwitz 1945; constants as in pvlib-python's
clearsky.haurwitz).Plane of array with an assumed clear-sky diffuse fraction of 0.15: POA = DNI max(0, cos AOI) + DHI (1 + cos tilt) / 2, with tilt = Solar API
pitchDegreesand azimuth =azimuthDegrees(0 = N, 90 = E, 180 = S).Cloud factor 1 - 0.75 (cloudCover/100)^3.4 (Kasten & Czeplak 1980).
Mean of four samples per hour; several roof segments weighted by their share of the panels.
Not modelled: shading, aerosols, altitude, the shift to diffuse light under cloud, ground reflection. Use it to rank hours and roofs against each other, not as an energy forecast.
Measured
Produced by bun run report (scripts/report.ts) on darwin-x64, Bun 1.4.3: each row is one call through the MCP server (in-memory client, real tools, service, HTTP client, rules and irradiance model) with every upstream answered by the synthetic fixtures (invented sites, roofs and weather; now = 2026-11-02T18:00:00-05:00, brief date 2026-11-03). No network, no keys. Source: reports/irradiancecrew-brief.md and .json; full example brief: reports/example-crew-brief.md.
Scenario | Tool | Jobs | KEEP | MOVE | SLIP | Decided by | Weather code path (synthetic data) | Roof source | Upstream calls |
demo CSV, source auto (Weather API path, NWS fallback) | crew_brief | 8 | 3 | 2 | 3 | planned-window-ok 3, moved-within-day 2, no-irradiance-window 1, no-location 1, no-safe-window 1 | google 6, none 1, nws (fallback) 1 | solar-panels 5, unavailable 2, solar-largest-segment 1 | 30 |
demo CSV, source google only | crew_brief | 8 | 2 | 2 | 4 | moved-within-day 2, planned-window-ok 2, no-forecast 1, no-irradiance-window 1, no-location 1, no-safe-window 1 | google 6, none 2 | solar-panels 5, unavailable 2, solar-largest-segment 1 | 28 |
demo CSV, inline rule gust < 25 margin 5 | crew_brief | 8 | 3 | 1 | 4 | planned-window-ok 3, no-safe-window 2, moved-within-day 1, no-irradiance-window 1, no-location 1 | google 6, none 1, nws (fallback) 1 | solar-panels 5, unavailable 2, solar-largest-segment 1 | 30 |
demo CSV, --gemini with no key (gemini: skipped: no GEMINI_API_KEY) | crew_brief | 8 | 3 | 2 | 3 | planned-window-ok 3, moved-within-day 2, no-irradiance-window 1, no-location 1, no-safe-window 1 | google 6, none 1, nws (fallback) 1 | solar-panels 5, unavailable 2, solar-largest-segment 1 | 30 |
demo CSV, upstream budget 12 | crew_brief | 8 | 1 | 1 | 6 | no-location 5, moved-within-day 1, no-safe-window 1, planned-window-ok 1 | none 5, google 3 | unavailable 5, solar-panels 3 | 12 |
one commission job by lat/lng (west roof, cloudy morning) | job_windows | 1 | 0 | 1 | 0 | moved-within-day 1 | google 1 | solar-panels 1 | 3 |
Demo CSV, per job (synthetic data; irradiance figures are the documented ESTIMATE, not measurements):
# | Job | Task | Action | Planned | Proposed | Decided by | Roof az/pitch (source) | Shift GO / MARGINAL / NO-GO h | Irradiance est. block Wh/m2 | Peak est. W/m2 |
1 | J-101 | install | KEEP | 07:00-13:00 | 07:00-13:00 |
| 180/25 + 95/25 (solar-panels) | 10 / 0 / 0 | 3664 | 784 |
2 | J-108 | install | KEEP | 08:00-13:00 | 08:00-13:00 |
| 150/18 (solar-panels) | 10 / 0 / 0 | 3449 | 787 |
3 | J-107 | install | KEEP | 07:00-13:00 | 07:00-13:00 |
| 180/0 (unavailable) | 7 / 1 / 2 | 2694 | 624 |
4 | J-102 | install | MOVE | 08:00-14:00 | 11:00-17:00 |
| 200/30 (solar-panels) | 6 / 0 / 4 | 4135 | 871 |
5 | J-104 | commission | MOVE | 09:00-11:00 | 12:00-14:00 |
| 265/22 + 85/22 (solar-panels) | 10 / 0 / 0 | 1170 | 600 |
6 | J-103 | install | SLIP | 07:00-14:00 | - |
| 178/20 (solar-panels) | 3 / 2 / 5 | - | 487 |
7 | J-105 | commission | SLIP | 10:00-12:00 | - |
| 2/35 (solar-largest-segment) | 10 / 0 / 0 | - | 56 |
8 | J-106 | install | SLIP | - | - |
| 180/0 (unavailable) | 0 / 0 / 0 | - | - |
Live checks (bun run test:live, 2026-10-21, this machine, keys from dev-test-461117)
Check | Result |
Geocoding v4 | HTTP 200, |
Solar API | HTTP 403 |
Weather API | HTTP 403 |
End to end: one lat/lng job tomorrow, | Weather API 403 -> NWS fallback, roof |
Gemini | HTTP 402 ( |
Without keys the Google tests print skipped: ... no GOOGLE_MAPS_API_KEY; with IRRADIANCECREW_LIVE=0 (CI) every live test prints a skipped: line. Live responses are not stored.
Sources (checked 2026-10-21)
Source | Used for | Reference |
Geocoding API v4 | job coordinates | |
Solar API |
| |
Weather API |
| |
NWS | fallback hourly gust, wind, precipitation probability and amount, thunder probability, sky cover, temperature, time zone | |
Gemini | wording of the brief from ids, rule ids, times and rounded numbers only; rejected if it adds a number |
About "WeatherNext-powered". The idea text calls the Weather API "WeatherNext-powered". Google's Weather API FAQ (last updated 2026-10-20) describes its forecasts as built "with advancements from Google DeepMind's MetNet and WeatherNext AI models (2025)", combining AI and traditional forecasting systems, and says the raw WeatherNext models (GraphCast, GenCast) are a separate offering through Earth Engine and BigQuery. So the Weather API draws on WeatherNext alongside other systems; it does not expose WeatherNext output, and this tool makes no claim about which model produced a given hour (FAQ).
Attribution and caching
Live output prints, as the Weather, Solar and Geocoding policies require:
Source: Includes weather data from Google,Source: Includes solar data from Google,Includes data from Google Maps(for the derived schedule and irradiance estimate) andGoogle Maps(geocoded addresses shown without a map), plus an NWS line when the fallback is used. Fixture runs print a SYNTHETIC note instead, because no Google data is involved.Caching: the Google Maps Platform Service Specific Terms allow Weather API hourly forecast values to be cached for one hour, Solar API Building Insights for up to 30 days, and Geocoding lat/lng for up to 30 days. This tool caches nothing between runs (each site is fetched once per run, in memory) and commits no real responses. A written brief contains hourly forecast values: regenerate it rather than keeping it for reuse.
The Solar API terms allow use to assess, design or install an energy system; this tool reads roof geometry for installs that are already booked. The Weather API terms forbid recreating a weather service; this tool only derives crew decisions.
Limits
Not verified live in this build: Solar API and Weather API responses. Both APIs answer 403
SERVICE_DISABLEDin the only Cloud project available here and were not enabled for this build. Their clients run only against synthetic fixtures built from the published references (the fixtures are checked against the documented field lists). The live path that ran end to end is Geocoding + NWS fallback.Not verified: the Gemini wording path. The key answers HTTP 402, so
--geminiwas only exercised against mocked responses. The default model idgemini-3.1-flash-litecomes from SkilledDragonCorp/pollenbrief-mcp's record and was not confirmed from this build; override withIRRADIANCECREW_GEMINI_MODEL.The irradiance figure is an estimate with an assumed diffuse fraction and no shading; the 400 W/m2 commissioning threshold is an example, not taken from any standard or equipment manual.
Formula sources: the NOAA sun-position equations and the Haurwitz constants (pvlib
clearsky.py) were checked against the source documents during this build; the Kasten & Czeplak cloud factor is the widely cited form of the 1980 paper, which was not retrieved here.Forecast wind is not given at roof height; treat it as a lower bound for wind at the eave.
Hours are whole local hours; blocks must start on the hour. The shift is one window per profile.
Moves stay within the same day; slipped jobs are not re-planned onto later days, and crew overlaps are flagged, not resolved.
NWS fallback: US only; no UV, no condition type; precipitation amounts arrive in multi-hour blocks and are spread evenly.
One Solar request per site at
requiredQuality(default MEDIUM); a 404 leaves the job with a flat-roof proxy rather than retrying lower qualities.The per-session call budget (default 60) is per process, not per key or per day.
Development
bun run check is the local gate and the only CI step: frozen install, skills layout, strict YAML (workflow + profiles), Biome, tsc --noEmit, unit + keyless e2e tests, live tests (skip cleanly), the report, bun audit, actionlint when installed, and gitleaks dir. Fixtures are regenerated with bun run fixtures.
Not affiliated
Not affiliated with, endorsed by, or sponsored by Google LLC or the US National Weather Service. Google Maps, Google Maps Platform, Solar API, Weather API and Gemini are trademarks of Google LLC.
Built by SkilledDragon LLC. Questions: hello@skilleddragon.com. Apache-2.0, see LICENSE and NOTICE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Generate optimized work shift schedules from staffing demand, availability and fairness rules.
Field operations on a deterministic solver — run jobs, crews & fleet from Claude or ChatGPT.
Builds optimal 24/7 staff rosters with a real MILP solver: rest rules, skills, nights, fairness.
Screen US sites for solar: sun, grid distance, FEMA flood risk. Save sites, create PDF reports.
Related MCP Servers
- AlicenseCqualityDmaintenanceEnables AI clients to read and analyze Supershift schedules from exported calendar data, providing tools to list, filter, summarize shifts, detect conflicts, and estimate pay.35MIT

RoofMeasure MCPofficial
AlicenseAqualityBmaintenanceProvides free EagleView-style satellite roof measurements and modular Xactimate-style estimating from Google Solar API data, enabling contractors to generate reports and estimates from any address.3MIT- AlicenseNot gradedqualityCmaintenanceEnables users to check whether an Australian field-service job can be worked at a location on a given day for a specific trade, using live weather forecasts, daylight hours, and public holiday data.MIT
- FlicenseNot gradedqualityBmaintenanceEnables home solar energy planning by estimating PV output, validating consumption plans against generation and battery constraints, and automatically shifting flexible loads to ensure power availability during scheduled outages.-