roofgeometry
Integrates with Google Solar API and Google Geocoding API to retrieve roof geometry for a building from an address or coordinates, then package per-segment pitch, azimuth, area, height, sunshine, shading, complexity, and installer-ready roof facts.
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., "@roofgeometryget roof geometry and usable solar area for 123 Main St"
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.
roofgeometry-prospect
A small MCP server and CLI that turns the Google Solar API roof data for one building into an installer-ready roof geometry packet: per-segment pitch (degrees and rise/12), azimuth, facing, area, plane height and sunshine quantiles, plus derived facts an installer or a sales agent asks first: how much usable south/west area there is, whether the roof is steep (fall protection, extra labour), how complex it is, and a shading proxy. Output is compact JSON, Markdown and a simple SVG roof sketch.
It can also ask for the Solar API's pre-GA roof geometry option (experiments=ROOF_GEOMETRY_INSIGHTS +
additionalInsights=ROOF_GEOMETRY), which adds facet polygons and rooftop obstacles. That option is opt-in and
labelled pre-GA everywhere; see What we checked.
The problem
buildingInsights:findClosest already returns roof segments with pitch, azimuth, area, height and sunshine, but as
raw numbers an agent has to interpret: which faces are worth panels, which are too small, which are steep, how
many planes a crew will deal with. Installers want that as a one-page answer before they roll a truck. This lab
does the packaging in one tool call and labels the source of every threshold.
Related MCP server: RoofMeasure MCP
Quickstart
bun install --frozen-lockfile
# Keyless demo: synthetic buildings in the documented response shape (fictional addresses, open-ocean points)
bun run src/cli.ts roof --address "100 Example Ave, Sampletown" --fixtures
bun run src/cli.ts roof --address "100 Example Ave, Sampletown" --roof-geometry --svg roof.svg --fixtures
bun run src/cli.ts roof --address "9 Gable Row, Steepville" --format json --fixtures
bun run src/cli.ts roof --address "7 Sample Plaza, Calmtown" --min-quality MEDIUM --fixtures
# Live: put GOOGLE_MAPS_API_KEY in .env (see .env.example), then
bun run src/cli.ts roof --address "<address>" [--roof-geometry] [--steep-pitch 35] [--svg roof.svg]
bun run src/cli.ts roof --at=37.4,-122.1 --min-quality MEDIUM # negative numbers need --at=...
# Methodology (formulas, thresholds, pre-GA status, references)
bun run src/cli.ts method
# MCP server (stdio)
bun run src/cli.ts serve # live
bun run src/cli.ts serve --fixtures # keyless demoMCP client config (Claude Code .mcp.json, Cursor mcp.json):
{
"mcpServers": {
"roofgeometry": {
"command": "bun",
"args": ["run", "/path/to/roofgeometry-prospect/src/cli.ts", "serve"],
"env": { "GOOGLE_MAPS_API_KEY": "${GOOGLE_MAPS_API_KEY}", "ROOFGEO_BUDGET": "10" }
}
}
}The key's Cloud project must have the Solar API (solar.googleapis.com) enabled with billing on, plus the
Geocoding API for address lookups. Restrict the key to those APIs.
Tools
Tool | What it does | Google calls |
|
| 1 Geocoding v4 call for an address, plus 1 |
| Formulas, thresholds and their sources, what is verified about the pre-GA option, attribution, references | none |
| Budget used/remaining, refused calls | none |
CLI: roofgeo roof | method | serve. Exit codes: 0 ok, 2 error, 64 usage.
What the packet contains
Per segment (copied from roofSegmentStats[i] unless marked derived):
Field | Meaning |
| Pitch, and derived |
| Azimuth (dropped for flat segments: Google sets it to 0 there); derived 8-point facing or |
| Roof area (tilt included) and footprint |
| Plane height at the segment center, meters above sea level; derived difference from the lowest segment |
| min / p25 / median / p75 / max, interpolated from |
| Derived: median vs |
| Derived flags (rules below) |
| Panels on this segment in the API's maximum layout ( |
| Pre-GA only: rings, holes and ground footprint of |
Summary: primeAreaM2, usableAreaM2 + usableSegments, flatAreaM2, areaByFacing, steepRoof +
steepSegments + steepAreaShare, complexity (score, tier), shading, obstacles (pre-GA), heightRangeM,
maxArrayPanelsCount, panel size. Plus building id, imagery quality and date, distance from the query point,
roofGeometry (requested / returned / pre-GA / fallback), thresholds with sources, notes, attribution.
Rules and their sources
Rule | Default | Source |
steep = pitch > threshold | 18.43° (slope > 4 in 12) | OSHA 29 CFR 1926.500(b) "steep roof"; override with |
| any usable segment is steep (any segment if none is usable) | lab rule |
flat = pitch < threshold | 10° | lab default |
prime = non-flat, facing within 90° of 225° (north of the equator: S, SW, W) or of 315° (south of it: N, NW, W) | lab rule | |
usable = (prime or flat) and area >= N x panel height x panel width | N = 4 panels, panel size from the response | lab default |
shading proxy = share of area below X x | X = 0.8 | lab default |
complexity score = segments + obstacles (obstacles only with pre-GA geometry) | tiers: simple <= 2, moderate <= 4, complex <= 8, else very-complex | lab default |
far building note | center > 30 m from the query point | lab default |
The SVG sketch draws the building and segment bounding boxes (dashed) or, with pre-GA geometry, the facet polygons (solid, holes cut out) and obstacles, on a local meter grid with north up, a scale bar, downslope arrows, hatching for steep faces and the attribution line. It is a schematic, not a map.
Measured
bun run report (keyless): the real MCP server through an MCP client, against synthetic fixtures (invented
buildings, not real data). Byte counts are JSON/Markdown/SVG string lengths.
Scenario | Input | Calls | Imagery | Segments | Prime m² | Usable m² | Steep (threshold deg) | Max pitch | Complexity | Shaded share | Obstacles | Polygons | Fallback | Solar response bytes | Packet JSON bytes | Markdown bytes | SVG bytes | MCP = service |
hip roof, GA fields | address | 2 | HIGH | 6 | 90.6 | 124.6 | yes (> 18.43, osha-4-in-12) | 26.8 | complex (6) | 55% | - | false | false | 8332 | 5763 | 2313 | 4409 | true |
hip roof, pre-GA roof geometry | address, roof_geometry | 2 | HIGH | 6 | 90.6 | 124.6 | yes (> 18.43, osha-4-in-12) | 26.8 | complex (8) | 55% | 2 | true | false | 9926 | 5969 | 2475 | 4642 | true |
hip roof, steep at 35 deg | address, steep_pitch_deg 35 | 2 | HIGH | 6 | 90.6 | 124.6 | no (> 35, user) | 26.8 | complex (6) | 55% | - | false | false | 8332 | 5746 | 2230 | 4013 | true |
steep cut-up roof | address | 2 | HIGH | 9 | 105.8 | 95.4 | yes (> 18.43, osha-4-in-12) | 50.0 | very-complex (9) | 71% | - | false | false | 6498 | 7313 | 2628 | 6210 | true |
southern hemisphere gable | address | 2 | HIGH | 3 | 76.8 | 76.8 | yes (> 18.43, osha-4-in-12) | 22.0 | moderate (3) | 59% | - | false | false | 4135 | 4155 | 1990 | 3107 | true |
flat commercial, MEDIUM | address, min_quality MEDIUM | 2 | MEDIUM | 2 | 0.0 | 480.1 | no (> 18.43, osha-4-in-12) | 1.1 | simple (2) | 7% | - | false | false | 19220 | 3705 | 1893 | 2332 | true |
pre-GA rejected, GA fallback | lat/lng, roof_geometry | 2 | HIGH | 2 | 46.2 | 46.2 | yes (> 18.43, osha-4-in-12) | 30.0 | simple (2) | 54% | - | false | true | 2923 | 3794 | 2254 | 2750 | true |
Pre-GA polygons: ground footprint from
polygonGeojson(shoelace) vs the segment'sgroundAreaMeters2: max difference 0% on the synthetic hip roof (a round-trip check of our projection and shoelace code, since the fixture is drawn with the same projection; not evidence about real Google polygons).Flat-roof address at the default
min_qualityHIGH: google_NOT_FOUND (synthetic 404); with MEDIUM it answers (row above).403 SERVICE_DISABLED (synthetic copy of the error shape): google_PERMISSION_DENIED / SERVICE_DISABLED; with
roof_geometrythe same error, no fallback retry (SERVICE_DISABLED).Pre-GA rejected with
fallback: false: google_INVALID_ARGUMENT.Point with no building: google_NOT_FOUND.
Budget 3, two address lookups (2 calls each): ok, budget_exceeded.
Live status (2026-10-21)
With a Google Maps Platform key from our test project (bun test tests/live, bun run record):
Geocoding v4: answered (the address resolves to the expected area).
Solar API
findClosest, GA request and pre-GA roof geometry request: both HTTP 403 PERMISSION_DENIED, reason SERVICE_DISABLED (the Solar API is not enabled in that project, and we did not enable it). The live test prints askipped:line for this case.
So no real Solar response has been seen by this lab, with or without roof geometry. The packet logic runs only on synthetic fixtures shaped from the docs.
What we checked in Google's docs
Checked on 2026-10-21.
Item | What we found | Source |
HTML reference: | Lists only | findClosest, Experiment (both last updated 2026-05-12) |
HTML reference: | Lists only | same |
Discovery document |
| discovery doc, revision 20261019 |
When it appeared | The Wayback Machine copy of the discovery doc captured 2026-03-12 (revision 20260310) has neither value. The "Oct 2 2026" date in the idea could not be verified; we only know it is between those revisions | |
Release notes | Newest entry May 11, 2026 (detected arrays GA); nothing about roof geometry | release notes (last updated 2026-10-20) |
Pre-GA terms | Experimental: no SLA or support obligation, not intended for production, may change in backward-incompatible ways. Preview: no SLA, test environments | launch stages (last updated 2026-10-20) |
GA roof segment fields used |
| findClosest reference + discovery doc |
Imagery quality | Discovery enum: | discovery doc |
Attribution | "Source: Includes solar data from Google" next to Solar data; "Includes data from Google Maps" for content created from it | Solar API policies (last updated 2026-10-20) |
Caching | "Content pre-fetching, caching, or storage is generally restricted" (place IDs exempt) | same |
Attribution and usage requirements
Attribution. Every JSON packet carries
attribution("Source: Includes solar data from Google") andderivedAttribution("Includes data from Google Maps"); every Markdown packet and SVG sketch ends with both. Keep them next to the numbers wherever you show them.No caching. This server keeps nothing between calls and the repo commits no Google responses. Store packets only as your agreement with Google allows.
Maps. If you show Solar results on a map, the policies require a Google Map; the SVG is a schematic without any basemap.
Pre-GA.
roof_geometry: trueuses an experimental option under Google's pre-GA terms. Treat it as test-only.
Limits
Unverified live. See Live status: every number in Measured comes from synthetic fixtures. Real responses may differ in fields, value formats or polygon semantics (for example, the third GeoJSON coordinate: the docs' example shows
0, its meaning is not documented; we ignore it).Pre-GA option unverified. The roof geometry request and fields come only from the discovery document. We do not know whether
ROOF_GEOMETRYworks without the experiment flag, whether projects need allow-listing, or how it is billed. If Google rejects the request (400, or 403 other than API disabled/blocked), the tool retries once without it and says so.Not a site survey or safety assessment. Roof data comes from aerial/satellite imagery. The steep flag uses the OSHA steep-roof slope as a planning signal; it says nothing about the safety of a roof. Labour cost is not estimated.
Heights are above sea level; there is no ground elevation in the response, so "height above ground" and storey count are not given.
heightAboveLowestSegmentMis relative only.Shading proxy comes from the sunshine quantiles, not a shade study; it cannot tell tree shade from self-shading or orientation.
Complexity tiers and other lab defaults are our choices, not an industry scale; they are labelled in every output and can be overridden where it matters (
steep_pitch_deg,flat_pitch_deg,shade_threshold,min_panels).Closest building. The API returns the building nearest the point; a bad geocode still gives a confident answer.
Budget scope. One upstream call budget per process / MCP connection (default 10), not per key or per day.
Keyless mode answers only the demo inputs in
fixtures/synthetic/INDEX.json; anything else returnsgoogle_NO_FIXTURE.Not included: the
dataLayersendpoint (DSM / mask GeoTIFFs, which would give real 3D outlines), batch address lists, detected arrays (seesolarscope-detected-arrays), financial analysis (seesolarscope-mcp).
Development
bun run check # install, skills layout, strict YAML, biome, tsc, unit + e2e tests, live tests (skip
# cleanly), report, bun audit, actionlint (if installed), gitleaks
bun test tests/unit tests/e2e
bun test tests/live # needs GOOGLE_MAPS_API_KEY; prints "skipped: ..." otherwise (ROOFGEO_SKIP_LIVE=1 forces skip)
bun run fixtures:synthetic # regenerate fixtures/synthetic from src/synthetic.ts
bun run record # live: save real responses to gitignored .recorded-local/ for the shape checkCredits: builds on the SkilledDragonCorp minilabs solarscope-mcp and solarscope-detected-arrays (see NOTICE).
Not affiliated
Not affiliated with, endorsed by, or sponsored by Google LLC or OSHA. Google Maps Platform, Solar API and Geocoding API 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
Rooftop solar potential, RGE installer search and quote requests. France only, no API key.
Screen US sites for solar: sun, grid distance, FEMA flood risk. Save sites, create PDF reports.
Indicative commercial rooftop solar assessments for Australian buildings, from open map data.
UK solar PV and heat pump calculators that follow the published MCS methods. Open, no key.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables solar energy potential assessment by converting parcel centroid and acreage into annual/monthly generation estimates using the NREL PVWatts v8 API, suitable for revenue modeling and infographic headlines.-

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- AlicenseAqualityBmaintenanceGenerates deterministic solar and energy storage system preliminary proposal calculations from structured customer inputs.1MIT
- AlicenseNot gradedqualityCmaintenanceEnables rooftop solar installers to turn a CSV of tomorrow's booked jobs into a ranked crew schedule in Markdown and JSON, joining per-address geocoding, roof pitch/azimuth from the Google Solar API, hourly forecast checks against crew-safety thresholds, and estimated plane-of-array irradiance. Each job is returned as KEEP, MOVE (with a new same-day start) or SLIP, together with the specific rule that decided it.Apache 2.0