Skip to main content
Glama
RoofMeasure MCP

RoofMeasure MCP

Official
by thegimmieapp

RoofMeasure MCP

Free EagleView-style satellite roof measurements plus modular Xactimate-style estimating, packaged as an MCP server and CLI for any LLM or agent.

What it measures (from Google Solar API 0.1 m elevation data):

  • Total roof area (sq ft and squares) from fitted roof planes, per-facet area, pitch and slope direction

  • Ridges, hips, valleys, rakes and eaves: every crease is the analytic intersection of two measured roof planes with its true sloped length; rakes are slope-corrected; eaves are level

  • Predominant pitch and areas-per-pitch table

  • EagleView-convention waste table (3/12 and steeper area only, columns around the suggested factor, squares rounded up to 1/3) and suggested waste factor calibrated to EagleView reports

  • Structure complexity (Simple / Normal / Complex), drip edge, steep-slope split

  • Tree-canopy confidence rating (HIGH / MEDIUM / LOW) and Google's own whole-roof area as an independent cross-check

  • Detached roofs nearby (garage, shed) reported, and measured as extra structures with include_outbuildings=true

Accuracy against six EagleView Premium Reports (Springfield, MO; primary structure): total area within 1% on simple roofs and +5 to +9% on complex ones; rakes within 1 to 9%; eaves within 4 to 8% (15% low where tree canopy hides a side); ridges+hips within 2 to 15% except where crest blur in the elevation model adds a facet (Fred Eagles: +24%). The engine follows EagleView's conventions: an upper roof's edge over a lower roof counts as an interior eave or rake, the wall junction as flashing / step flashing, waste on 3/12-and-steeper area only. Roofs bordered by tree canopy are flagged LOW confidence and are a lower bound. Our outline matches EagleView's drawn outline within about one foot on the canopy-free houses (verified by overlaying the vectors extracted from their PDFs, see tests/ev_extract.py).

Geometry rules (src/roofmeasure/regularize.py): measured planes are snapped to how US homes are framed before any line is drawn. Every pitched plane faces one of the building's four axis directions (45 degrees only for a bay-sized facet); pitch is a whole x/12 and planes within 1/12 of the wing's pitch take the wing's pitch; a strip between two parallel planes at different heights is a connector facet (hip + valley), a strip between planes at the same height is a blurred shoulder and dissolves. Hips and valleys between equal-pitch planes therefore run at exactly 45 degrees in plan and ridges sit centred, as framed. No gambrels: a same-facing pitch change with no wall between the two parts (break parallel to the ridge, flatter plane above) is one plane. Two same-facing, same-pitch neighbours offset by less than 0.6 m with a continuous surface across their boundary are one plane (a blurred shoulder); a jump in the elevation model there is a wall and stays a step. A side-by-side section at a different pitch on the same slope (Tony Russell's 7/12 insert on a 10/12 roof) is kept and its sides are walls (step flashing), as EagleView draws it.

Framing plan (src/roofmeasure/roofgraph.py, v0.5.0): the line generator for every report. Instead of intersecting fitted planes, it generates the ridge / hip / valley graph from the eave line the way a framer lays it out: every plane rises from its eave at one pitch, so hips run at 45 degrees from outside corners, valleys run from inside corners to the eaves, ridges sit centred over each wing, a narrower wing's ridge ends where its valleys meet and ties to the main ridge with a short hip (the straight skeleton of the eave polygon, computed with bpypolyskel). Every line ends where it meets another line, no line is drawn twice, and no hip starts anywhere but a corner, by construction. The elevation model supplies only what geometry cannot: the pitch (consensus of the fitted planes), gable ends, lower sections (porches, carports) and a fit check.

Gable ends are hypotheses, not verdicts. Two detectors propose them: the elevation profile one metre inside an eave (a tent whose flanks have the roof pitch; the flank fit gives the gable's width and position, so a gable can occupy part of a straight eave when a wing's end wall is flush with the eave beside it) and the fitted planes (a plane sloping along the edge is a rake). Every combination of the candidates is built and the plan the elevation data agrees with best is kept, with the plain hip plan always in the running. A flush gable produces the valley a framer would cut from the gable corner up to the ridge.

The plan is used when the measured planes support it: at least 70% of the framed ridge/hip/valley length lies within 1 m of a measured plane crease and the plan does not invent more than 1.6x the measured crease length (or the elevation residual is within 24 in at the 90th percentile). Its confidence is reported (confirmed, or MEDIUM with the residual). On a MEDIUM plan, where the measured planes contain more than 15% more crease length than the framed plan draws, the larger ridge/hip/valley totals are reported for ordering and the note says so; the diagram stays framed. Where the planes do not support the plan (several levels and pitches the single-pitch model does not describe), lines come from the measured plane intersections cleaned to the same construction rules (wireframe.sanitize_creases: duplicate near-parallel creases removed, collinear ridge pieces merged, hips moved to the nearest outline corner or dropped) and then the topology repair below. Dependency: bpypolyskel (GPL, pure Python, installed from GitHub). Blender's mathutils C extension is not required: roofmeasure ships the small subset bpypolyskel needs (src/roofmeasure/_mathutils.py, double precision), so no compiler is needed on macOS or in the Docker image.

Calibration (six EagleView reports, Springfield MO, measured values): Jenny R142/142 ft, hips 0/0, rakes 158/160; Joe hips 397/401, valleys 214/239; Tony ridge+hip 172/176, valleys 130/129; Fred and Andy are MEDIUM plans (Fred hips 278/216 after the ordering check, Andy 153/189); Lynda is canopy-bound and low confidence. 3669 W Maplewood (the report that prompted v0.5): framed plan confirmed within 20 in, 2 gables, R73 H161 V120, replacing a plane wireframe that drew duplicate hips and valleys.

Tree canopy (v0.5.1): the footprint follows the canopy where trees overhang the roof, and a tree line is never a roof line. An outline edge is untrusted when it runs off both building axes or when tall vegetation stands right outside it; each run of untrusted edges between two trusted edges is replaced by the rectilinear eave line (wireframe.complete_canopy_runs): extend the trusted eave, follow the side at the outermost extent a trusted corner establishes, return to the next trusted edge. A cut-off corner becomes the corner; a whole side pushed inward by canopy is pushed back out. The completed strip must be hidden by something standing above the ridge (a tree beside the house is not hidden roof), may not exceed 45% of the visible area, and its elevation is never used for pitch, gable profiles or the fit (generate_roof(known=...)); the plan-support test ignores lines under canopy. Any remaining off-axis edge is replaced by the two axis edges through the outside corner (roofgraph.rectilinearize). The report states how much plan area was recovered.

Topology rules (wireframe.enforce_topology): every ridge, hip and valley must end where it meets another line. A hip starts at an outline corner and ends at the point that begins a ridge; a valley starts at the eave and ends at a ridge or hip junction; a ridge ends at a hip pair, a valley pair or a gable end. A crease end that dangles inside the roof is extended along its own line (through roof pixels only) until it meets another crease or the outline, up to 3 m (4.5 m for a valley reaching for its eave); a short crease that still touches nothing is dropped. The report notes how many ends were repaired.

Eave and rake ordering margin: outline edges are the least certain lines in satellite elevation data (overhangs, canopy, eave-vs-rake classification at walls), and short drip edge or starter is the expensive error. Reported eave and rake totals are the measured lengths times model.EAVE_RAKE_SAFETY (1.15). The measured values are kept in eaves_measured_ft / rakes_measured_ft, printed on the Length Diagram and Report Summary, and the margin is stated in the notes. Diagram segment labels are measured lengths. Ridges, hips, valleys and areas carry no margin.

What it generates:

  • EagleView-layout measurement report PDF: cover, aerial, Length / Pitch / Area / Notes diagrams (EagleView colors, rotated segment labels), per- structure Report Summary (areas per pitch, complexity, waste table, lengths), notice page; plus a Markdown version

  • Xactimate-style insurance estimate (.docx) with line items, material sales tax, per-structure summaries, tax recap, and grand total. Fully modular: company name/logo, estimator, homeowner, claim info, pricing rules, tax rate, waste %, component counts, and O&P toggle are all configurable.

Setup

  1. Get a free Google Cloud API key with the Geocoding API and Solar API enabled (console.cloud.google.com). The $200/month free credit covers roughly 1,000 roof lookups.

  2. Install:

    pip install git+https://github.com/thegimmieapp/roofmeasureMCP.git
    # or from a local clone:
    pip install -e .
  3. Set your key:

    export GOOGLE_MAPS_API_KEY="your_key_here"

Related MCP server: EnergyAI MCP

Use as an MCP server (Claude Desktop, Claude Code, Cursor, etc.)

Add to your MCP config (e.g. claude_desktop_config.json):

{
  "mcpServers": {
    "roofmeasure": {
      "command": "roofmeasure-mcp",
      "env": {
        "GOOGLE_MAPS_API_KEY": "your_key_here",
        "ROOFMEASURE_OUT_DIR": "/path/for/reports",
        "ROOFMEASURE_LOGO": "/path/to/company_logo.png"
      }
    }
  }
}

Claude Code: claude mcp add roofmeasure -e GOOGLE_MAPS_API_KEY=your_key -- roofmeasure-mcp

Tools

Tool

Purpose

measure_roof(address, include_outbuildings=False)

Full JSON measurements

generate_roof_report(address, company, contact, phone, email, company_address, claim_number, include_outbuildings)

EagleView-layout PDF + Markdown report

generate_xactimate_estimate(address, homeowner, date_of_loss, ...)

Xactimate-style .docx estimate

The estimate tool enforces contractor intake: if homeowner name or date of loss are missing it returns needs_info so the agent asks the user before generating.

Use from any chat: run it as a remote connector

The plugin above only exists while Claude Desktop is open on the machine that has it installed. To use roof-measure from any claude.ai chat (web, desktop, mobile), run the same server over HTTPS and add it as a custom connector.

  1. Deploy (Fly.io shown; any host that runs a Docker container works):

    fly launch --no-deploy --copy-config --name roofmeasure-mcp
    fly volumes create reports --size 1 --region dfw
    fly secrets set GOOGLE_MAPS_API_KEY=... \
        ROOFMEASURE_PATH_SECRET=$(openssl rand -hex 16) \
        ROOFMEASURE_PUBLIC_URL=https://roofmeasure-mcp.fly.dev
    fly deploy

    fly secrets list shows the secret name; keep the value you generated. .github/workflows/fly-deploy.yml redeploys on every push to main once a FLY_API_TOKEN repository secret exists (fly tokens create deploy).

  2. In claude.ai: Settings > Connectors > Add custom connector, name it RoofMeasure and paste https://roofmeasure-mcp.fly.dev/<secret>/mcp. No OAuth is configured, so the unguessable path segment is the access control: anyone with the full URL can run measurements on your Google key. Rotate the secret (fly secrets set ROOFMEASURE_PATH_SECRET=...) if it leaks.

  3. Enable the connector in a chat. Tool results now carry report_pdf_url / estimate_url download links served from the same app (/<secret>/files/<name>), since the files are no longer on your own disk. GET /health is a public liveness check.

Entry point: roofmeasure-mcp-http (Streamable HTTP, stateless, JSON responses). Environment: PORT, GOOGLE_MAPS_API_KEY, ROOFMEASURE_PATH_SECRET, ROOFMEASURE_PUBLIC_URL, ROOFMEASURE_OUT_DIR.

LotMeasure: the lot for landscape design (v0.6.0)

Two more MCP tools on the same server, plus a JSON API for websites.

  • measure_lot(address, include_sun=True): lot line (county GIS parcel polygon) with area in sq ft and acres and the county's recorded size; the house footprint from RoofMeasure; front / back / left / right yard zones (left and right as seen from the street), each with area, surface breakdown (lawn and planting, hardscape, tree canopy, pool, bare soil or mulch, detached structure, unknown), the setback from the house wall to the lot line, the largest clear plantable rectangle and sun classes; trees with location, crown diameter, height and whether they are within 3 m of the house; totals; confidence notes.

  • generate_lot_plan(address, brand="Stronghouse Solutions", prepared_for=""): plan PNG + PDF (plan_png_url, plan_pdf_url). brand="Dream Landscaper" gives the neutral DL header.

Data sources, in order: county / state ArcGIS parcel layers (seeded and verified in src/roofmeasure/parcels_registry.json for Greene MO, Christian MO (City of Nixa area), Travis TX, Williamson TX, Santa Rosa and Escambia FL, plus the Florida statewide layer; other counties are discovered on ArcGIS Online and cached as unverified); Regrid when REGRID_API_KEY is set; otherwise lot_source: "none" and no lot size is reported. Street frontage: OpenStreetMap road centreline when Overpass answers, else the lot line with no neighbouring parcel across it (driveway breaks ties), else the line the driveway reaches. Sun: Solar API hourly shade (June and December) and annual flux; plantable ground is full sun at 6 h or more of June sun, part sun 3 to 6 h, shade under 3 h. Solar rasters are UTM (EPSG:326xx); geo.TileGeo maps lat/lng to pixels and back without pyproj.

JSON API (HTTP mode), header X-API-Key: <LOTMEASURE_API_KEY>:

GET /<secret>/api/lot?address=...&sun=1&plan=1&brand=Dream%20Landscaper&refresh=0
GET /<secret>/api/roof?address=...
GET /<secret>/api/health

Results are cached by address for 24 h on the volume. Set the key with fly secrets set LOTMEASURE_API_KEY=$(openssl rand -hex 24).

Calibration: GOOGLE_MAPS_API_KEY=... python tests/fetch_lot_cache.py <dir> caches raw GeoTIFFs; python tests/calibrate_lot.py <dir> [plan_dir] compares lot areas with the county record and draws plans.

Use from the command line

roofmeasure measure "3708 Ebony Hollow Pass, Austin, TX 78739"
roofmeasure report  "3708 Ebony Hollow Pass, Austin, TX 78739" -o report.md
roofmeasure estimate "3708 Ebony Hollow Pass, Austin, TX 78739"   # prompts for homeowner, date of loss, claim #

How it works

  1. Geocodes the address, pulls Google Solar API buildingInsights and dataLayers (0.1 m/px digital surface model + roof mask GeoTIFFs).

  2. Segments the DSM into planar facets (region growing on slope + aspect).

  3. Classifies every facet boundary: shared boundaries become ridges, hips, or valleys (by relative elevation and facing); perimeter boundaries become eaves or rakes (by edge direction vs the facet's downslope direction). Sloped edge lengths are slope-corrected in 3D.

  4. If high-resolution DSM data is not available for an address, it falls back to roof-segment statistics and clearly flags edge lengths as ESTIMATED.

Accuracy notes

This tool is free and uses the best publicly available elevation data. It is strong on areas, pitch, and squares; edge classification is measured from the DSM but can be affected by tree cover, imagery age, and resolution. Current calibration on five canopy-free EagleView reports (main structure, measured values before the eave/rake margin): area within -1% to +9%; hips within +5% to +9% and valleys within -3% to +25% on three of five; one roof (Fred Eagles) has a crest the elevation model blurs into extra facets, giving hips +30% and valleys +71%; eaves within -7% to +4%; rakes -22% to +7% (so the +15% margin still leaves one roof short on rakes). It does not carry an accuracy guarantee - field verify before ordering material, as you would with any measurement report.

Estimating defaults (all overridable)

  • Architectural laminated shingles + synthetic 30# felt only

  • 1:1 replacement of existing components

  • ~$550/SQ blended target across core roofing components (shingle unit price back-solved)

  • Steep-roof labor surcharges at 8/12-9/12 and 10/12+ tiers from the measured pitch table

  • Material sales tax applied to the material fraction of each line item

  • Overhead & Profit OFF by default (include_op: true to add 10/10)

MIT license.

Calibration against EagleView

tests/fetch_cache.py downloads the Solar API rasters for a set of addresses once (needs GOOGLE_MAPS_API_KEY); tests/calibrate.py then runs the engine offline and prints every measurement next to the EagleView value. Add a report's numbers to TRUTH in calibrate.py to extend the set. Synthetic roofs with analytic truth live in tests/synth.py (pytest).

Landscape scoping map contract

Website integrations can opt into GET /<secret>/api/lot?address=...&map=1&plan=0 with the existing X-API-Key authentication. map=1 adds scope_map (version 1), plus aerial_png_url when aerial imagery is available. The default API/MCP measurement responses remain unchanged.

Map coordinates are feet east and south from the cropped raster's top-left edge, with width_ft and height_ft. parcel, region and house are arrays of polygons; each polygon is an array of rings (outer ring, then holes); each ring contains [x, y] points. Zones include polygons and the largest clear rectangle; trees include centres and canopy radii in the same coordinate space. The PNG is exported without plot margins, so an image stretched to those map bounds aligns with every vector. Grid north is approximately true north.

A missing parcel produces an empty parcel, never a fabricated lot boundary. A withheld/uncertain house produces an empty house. region describes only the covered portion usable for scoping. Integrators must retain coverage, confidence and imagery notes, use the measurements only for approximate concept plans, and require on-site verification. The map is existing conditions, not an automatically approved landscape design or survey.

Copy the aerial to private application storage server-side. The connector's private path appears in generated asset URLs and must never be forwarded to a homeowner, lead record, client log or public page.

Available Tools

3 tools
generate_roof_reportA

Generate an EagleView-style roof measurement report (Markdown file) for an address. Returns JSON with the saved file path and a summary.

Args: address: Full property address. company: Company name stamped on the report. contact: Contact person shown on the report (optional). phone: Contact phone shown on the report (optional).

Inputs: address (required), phone, company, contact. Returns: result

generate_xactimate_estimateA

Generate an Xactimate-style insurance roofing estimate (.docx).

INTAKE: homeowner name and date_of_loss are REQUIRED for a complete estimate. If missing, this tool returns needs_info listing what to ask the user for; collect it and call again.

Args: address: Full property address (required). homeowner: Homeowner's full name (required; ask the user if unknown). homeowner_phone: Homeowner's phone number. date_of_loss: Date of loss, e.g. 5/14/2026 (required; ask the user). claim_number: Insurance claim number if available. policy_number: Insurance policy number if available. type_of_loss: e.g. Wind/Hail (default), Wind, Hail, Tree/Impact. tax_rate: Combined LOCAL sales tax rate as a decimal (e.g. 0.0825 for Austin TX). If 0, the agent should research the property's combined city/county/state rate and pass it in. price_list: Xactimate price list code, e.g. TXAU8X_JUL26. If empty, one is generated from the state and current month. waste_pct: Shingle waste percent. If 0, the suggested waste factor from the measurement's complexity analysis is used. components_json: JSON object of component counts, e.g. {"pipe_jacks": 3, "turtle_vents": 4, "ridge_vent_lf": 40, "exhaust_caps": 1, "skylights_flash": 1, "chimneys": 1}. company_name/company_tagline/company_city/estimator_name: Branding overrides so any contractor can use this tool. include_op: Include 10/10 Overhead & Profit (OFF by default). measurements_json: Optional pre-computed output of measure_roof to avoid re-measuring.

Inputs: address (required), tax_rate, homeowner, waste_pct, +13 more. Returns: result

measure_roofB

Measure a roof from satellite data. Returns full JSON measurements: total area (sq ft + squares), facet count and detail, predominant pitch, areas per pitch, ridges/hips/valleys/rakes/eaves lengths (ft), waste table (squares with 0-32% waste), suggested waste factor, and steep-slope split.

Args: address: Full property address (street, city, state, zip).

Inputs: address (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 3 tools

    Disambiguation5/5

    Each tool serves a distinct purpose: measuring roofs, generating markdown reports, and creating Xactimate estimates. There is no overlap or ambiguity.

    Naming Consistency5/5

    All tool names follow the snake_case verb_noun pattern consistently. Two start with 'generate_' and one with 'measure_', which is a clear and predictable convention.

    Tool Count4/5

    With only 3 tools, the server is lean but covers the essential functions for roof measurement and estimate generation. Each tool earns its place, though a few more complementary tools might be expected.

    Completeness5/5

    The tool set covers the full workflow: measure roof, generate a report, and create a detailed estimate. No critical gaps are apparent, and missing inputs are handled gracefully.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Radar-verified storm intelligence for the continental US — NOAA MRMS hail and wind swaths, per-address storm history, affected-property reports, area monitors with webhook + email alerts, and swath map images. Boots keyless; free API key (100 credits) at swathapi.com.
      9
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Provides free energy intelligence APIs for AI agents: solar production estimates, US clean-energy incentives by ZIP, home Energy Node Scores, contractor search, and consented installer routing.
      MIT
    • A
      license
      B
      quality
      B
      maintenance
      Enables multifamily renovation cost estimation, scope of work generation, contractor bid evaluation, ROI gating, and risk flagging for real estate intelligence agents.
      11
      Apache 2.0