SolarScope Arrays
Integrates with Google Maps Platform's Solar API and Geocoding API to geocode addresses, query building insights, detect existing rooftop PV arrays, retrieve imagery capture dates and solar potential, and support ranked solar prospecting runs.
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., "@SolarScope ArraysProspect these addresses and rank roofs without existing solar panels"
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.
solarscope-detected-arrays
An MCP server and CLI for solar prospecting with the Google Solar API detected arrays insight. Give it a CSV of addresses, a list of points or a bounding box. For each building it reports whether rooftop PV is already detected, the capture date of that imagery and its age, and the roof's solar potential. It then writes a ranked prospect list (CSV + Markdown + JSON): roofs with no PV detected, recent imagery and good potential come first, roofs that already have panels are skipped, and stale or missing evidence is flagged for review. Every run has a call budget and a list-price cost estimate.
SolarScope v2: it extends solarscope-mcp (one roof, one payback proposal) and reuses its Google client, transport, schema check and fixture pattern.
The problem
Solar installers and retrofit sellers waste outreach on homes that already have panels. Since May 11, 2026 the Solar
API's buildingInsights:findClosest can return detectedArrays (whether PV was detected on the building, and when
that imagery was captured), but an agent still has to geocode a list, call the API once per building, read the
detection status, check the capture date's age, drop duplicates and rank what is left. This lab does that in one tool
call, under a budget, and states Google's terms next to the output.
Related MCP server: google-maps-leads
Quickstart
bun install --frozen-lockfile
# Keyless demo: synthetic buildings, fictional addresses (coordinates in the open Atlantic)
bun run src/cli.ts prospect --csv examples/addresses.csv --as-of 2026-10-21 --fixtures --stdout md
bun run src/cli.ts prospect --bbox=30.1,-40.1,30.1008,-40.099 --step-m 40 --fixtures --stdout csv
bun run src/cli.ts check --address "14 Example Ave, Sampletown" --fixtures
# Before a live run: how many calls, how much at list price (no key, no Google call)
bun run src/cli.ts plan --csv my-addresses.csv --max-calls 200
# Live: put GOOGLE_MAPS_API_KEY in .env (see .env.example), then
bun run src/cli.ts prospect --csv my-addresses.csv --max-calls 200 --out prospects-out/today
bun run src/cli.ts prospect --points="37.40,-122.08;37.41,-122.09"
bun run src/cli.ts check --at=37.4,-122.1 --min-quality MEDIUM
# MCP server (stdio)
bun run src/cli.ts serve # live
bun run src/cli.ts serve --fixtures # keyless demoCSV input: a header row with address, or lat + lng (aliases latitude, lon, longitude), and an optional
id. Other columns are ignored and never echoed back. Outputs go to prospects-out/ (gitignored) as
prospects.csv, prospects.md and prospects.json.
MCP client config (Claude Code .mcp.json, Cursor mcp.json):
{
"mcpServers": {
"solarscope-arrays": {
"command": "bun",
"args": ["run", "/path/to/solarscope-detected-arrays/src/cli.ts", "serve"],
"env": { "GOOGLE_MAPS_API_KEY": "${GOOGLE_MAPS_API_KEY}", "SOLARSCOPE_BUDGET": "100" }
}
}
}The key's Cloud project needs the Solar API (solar.googleapis.com) with billing on, plus the Geocoding API for
address rows. Restrict the key to those two APIs.
Tools
Tool | What it does | Google calls |
| One building: | 2 for an address, 1 for a point |
|
| 1 Geocoding per unique address + 1 Solar per unique query, at most |
| Same inputs as | none |
| Rules, lab defaults, Google doc facts with URLs, prices, terms | none |
| Session budget used/remaining, successful calls per API, Solar 404s | none |
CLI: solarscope-arrays check | prospect | plan | method | serve. Exit codes: 0 ok, 2 error (or a run stopped by a
Google 401/403), 64 usage.
How a building is classified
Each unique query is one GET buildingInsights:findClosest?location...&requiredQuality=<min>&additionalInsights=DETECTED_ARRAYS
(after a Geocoding API v4 call for address rows).
Field used | Meaning in the output |
|
|
| PV evidence date; age = as-of date minus capture date, in days and months (days / 30.4375); month-only dates are marked approximate |
| Roof imagery date and quality, reported separately (Google: the two datasets use different imagery) |
| Max panels, max kW, sunshine hours, max-layout yearly DC kWh |
| Distance from the query point; |
Status, in this order: skip-existing-pv (PV detected) -> skip-low-potential (fewer than min_panels panels or
fewer than min_sunshine_hours sunshine hours) -> review (PV unknown, no capture date, capture older than
stale_months, or building center more than 30 m from the query point) -> prospect. Rows that repeat a query
reuse the first answer (no call); rows that land on an already-listed building become same-building; plus
no-building (Solar 404), not-geocoded, error, not-run (budget, or the run stopped).
Ranking: prospect, review, skip-low-potential, skip-existing-pv, same-building, no-building, not-geocoded, error, not-run. Inside prospect and review: most max-layout yearly DC kWh first, then the most recent PV capture.
Lab defaults (ours, not Google guidance; every output lists them with their source): stale_months 24,
min_panels 8, min_sunshine_hours 1000, far-from-point 30 m, max_calls 50 per run, grid step 40 m, 1,000 rows
and 400 grid points per run.
Budget and cost
plan/plan_prospect_runcounts the calls before anything is spent.A row runs only if all of its calls fit in what is left of the run budget (
--max-calls/max_calls) and the session budget (SOLARSCOPE_BUDGET, default 100). Rows that do not fit arenot-run, never half-run.A 401/403 from Google (API disabled, key blocked) stops the run so the other rows do not each spend a failing call.
Cost estimate = successful calls x list price; upper bound = every planned call at list price (see the price table below). Solar 404 answers are not charged; the monthly free caps are not applied.
Measured
Keyless run of the real MCP server (in-memory MCP client) over fixtures/synthetic/: invented buildings with
fictional addresses, in the response shape documented in Google's API reference, as of 2026-10-21. The numbers show
what the tool does with that input, not anything about real roofs. "MCP = service" checks that the tool's JSON,
Markdown and CSV equal the service output for the same input. Produced by bun run report; full output in
reports/solarscope-detected-arrays.md and .json.
Scenario | Input | Rows | Calls planned / used | Prospects | Review | Skip: PV | Skip: low potential | Same building | No building / not geocoded | Error / not run | Buildings | Prospects (rank order) | Est. USD (upper bound) | MCP = service |
address CSV | examples/addresses.csv (12 rows), defaults | 12 | 20 / 19 | 2 | 3 | 1 | 1 | 2 | 2 / 1 | 0 / 0 | 7 | A6, A1 | 0.125 (0.155) | true |
address CSV, MEDIUM imagery | same, min_quality MEDIUM | 12 | 20 / 19 | 3 | 3 | 1 | 1 | 2 | 1 / 1 | 0 / 0 | 8 | A6, A7, A1 | 0.135 (0.155) | true |
address CSV, 60-month window | same, stale_months 60 | 12 | 20 / 19 | 3 | 2 | 1 | 1 | 2 | 2 / 1 | 0 / 0 | 7 | A6, A1, A3 | 0.125 (0.155) | true |
address CSV, small budget | same, max_calls 8 | 12 | 20 / 8 | 1 | 2 | 1 | 0 | 1 | 0 / 0 | 0 / 7 | 4 | A1 | 0.060 (0.155) | true |
bounding box | 3 x 3 grid, 40 m | 9 | 9 / 9 | 2 | 1 | 1 | 0 | 3 | 2 / 0 | 0 / 0 | 4 | g3-2, g1-1 | 0.070 (0.090) | true |
Solar API disabled | 3 points, first answers 403 SERVICE_DISABLED | 3 | 3 / 1 | 0 | 0 | 0 | 0 | 0 | 0 / 0 | 1 / 2 | 0 | - | 0.000 (0.030) | true |
has_existing_pvon 14 Example Ave (synthetic): skip-existing-pv, PV detected, capture 2026-02-02 (8.6 months before 2026-10-21), 2 calls.plan_prospect_runon the CSV with max_calls 10: 11 unique queries, 20 planned calls, fits budget: false, upper bound USD 0.155; session calls after check + plan: 2.Est. USD = successful calls x list price (Solar API Building Insights USD 10.00 / 1,000, Geocoding USD 5.00 / 1,000, first paid tier, free caps not applied); Solar 404s are not charged. Upper bound = every planned call at list price.
Every output row carries
Source: Includes solar data from Googleand a delete-by date 30 days after the as-of date (2026-10-21 + 30 = 2026-11-20).
Live status (2026-10-21)
Calls made with our Maps Platform key in project dev-test-461117 while building this lab (bun test tests/live):
Call | Result |
Geocoding API v4 | HTTP 200, coordinates in the expected box |
Solar API | HTTP 403, reason |
So no real detected-arrays response has been observed by this lab. Everything Solar-side is tested against
synthetic responses built from the reference. When the API is enabled, bun run record saves one real response to
the gitignored .recorded-local/, and tests/live then checks it against src/schema.ts.
What we checked in Google's docs
Checked on 2026-10-21.
Item | What the docs say | Source |
GA date | Release note dated May 11, 2026: "Detected arrays data generally available"; the API "detects existing rooftop solar installations based on satellite imagery" | Solar API release notes (last updated 2026-10-20) |
Request |
| findClosest reference (last updated 2026-05-12) |
Response |
| same |
Imagery | "The satellite imagery used for building insights data and detected arrays data are different." Coverage: "see the coverage map" (no separate detected-arrays coverage list) | Building insights guide (last updated 2026-10-20) |
Price | "Solar API Building Insights" (Essentials, SKU 1856-4940-856A): free cap 10,000 events/month, then USD 10.00 per 1,000 up to 100,000 (lower tiers above). "Geocoding" (Essentials, SKU BAC8-4E68-E261): free cap 10,000, then USD 5.00 per 1,000 | Pricing list (last updated 2026-10-20) |
Billing event | Building Insights bills a "Successful request"; requests that return NOT_FOUND "aren't charged, but do count against your usage limits". Nothing on whether | SKU details (last updated 2026-10-20) |
Geocoding SKU | "Geocoding API requests are billed using the SKU for Geocoding"; no separate v4 SKU named | Geocoding usage and billing (last updated 2026-10-20) |
Caching | Service Specific Terms section 20.2: Building Insights and Data Layers may be cached "for up to 30 consecutive calendar days", then deleted, except Solar Data incorporated into fixed media (feasibility study, proposal, marketing materials) for a Downstream Transaction. Policies page: caching "generally restricted"; place IDs may be stored indefinitely. Geocoding lat/lng: up to 30 days (section 6.3.1) | Service Specific Terms (version listed as last modified 2026-10-15), Solar API policies (last updated 2026-10-20) |
Permitted use | Section 20.1: only to determine the feasibility of installing energy systems for an address or geographic area, to design or install one, or for a Downstream Transaction; marketing materials count only "where a user has opted-in to receiving such materials". Nothing specific to detected arrays | Service Specific Terms |
Attribution | "Source: Includes solar data from Google"; "Includes data from Google Maps" for new content created from it | Solar API policies |
Doc inconsistencies we found:
The release note and the guide say to set
additionalInsights"in your request body", but findClosest is a GET whose body must be empty, and the guide's own example passesadditionalInsights=DETECTED_ARRAYSas a query parameter. This lab sends it as a query parameter.The guide's detected-arrays example sends
required_quality=BASE, and a September 4, 2025 release note announces aBASEquality tier, but the ImageryQuality reference (last updated 2025-08-27) lists onlyHIGH,MEDIUM,LOW. This lab accepts all four and has not testedBASE.
Terms and data handling
What this tool keeps: nothing between runs. Inside one run, a repeated query reuses the first answer and rows on the same building are merged. The MCP server holds no state except call counters.
What you keep: the CSV/Markdown/JSON files contain Solar API data. Each output carries a
solar_data_delete_bydate (as-of date + 30 days, per section 20.2) and the attribution line on every row. Delete the files by then unless they became part of a feasibility study or proposal for a Downstream Transaction.prospects-out/and.recorded-local/are gitignored.Permitted use: a prospect list is a feasibility screen for an area or a list of addresses. Section 20.1 only counts marketing materials as a Downstream Transaction when the recipient opted in. This tool does not send anything, and it does not make outreach to the people at these addresses permitted. EEA billing addresses have different Solar API terms.
This is our reading of Google's pages, not legal advice. Your agreement with Google governs.
Limits
Detected arrays unverified live. See Live status: our project gets 403
SERVICE_DISABLED, so the realdetectedArraysshape, value formats (e.g. whetherlatestCaptureDateever omits the day) and coverage are untested. The synthetic fixtures follow the reference field by field and pass a strict schema check.Building-level only. The API says whether arrays were detected on a building and when the imagery was captured. It gives no array size, panel count, position or confidence, so the tool cannot say how much of the roof is covered or whether there is room for more. The docs do not say whether solar thermal collectors or ground-mounted arrays count as detected arrays.
Detection accuracy unknown. Google does not publish precision/recall for detected arrays; a
nocan be wrong. The stale flag only says the evidence is old, not that panels were added.Thresholds are ours. 24 months, 8 panels, 1000 sunshine hours and 30 m are lab defaults, adjustable per run.
Closest building. findClosest returns the building nearest the point; geocoding gives one point per address, which can land on a neighbour. Rows more than 30 m away go to review. Apartments at one address share a building.
Bounding boxes are sampled as a grid; buildings between grid points are missed, and one building can take several calls (merged in the output, but each call is spent). Use a step near the typical lot width.
Cost is an estimate at the first paid tier with no free cap; whether
DETECTED_ARRAYSchanges the Building Insights price is not documented. Geocoding answers with no result are counted as billable (conservative).Budget scope. One session budget per process / MCP connection, plus a per-run budget; not per key or per day. Use Cloud quotas and budget alerts.
Keyless mode answers only the demo inputs in
fixtures/synthetic/INDEX.json; anything else returnsgoogle_NO_FIXTURE. Fixture output is never a real building and says so.Not verified: the idea's claim that nobody packages detected arrays as an agent-ready prospecting tool.
Not included: contact data of any kind, CRM export, maps (Solar results shown on a map must use a Google Map),
dataLayers, financial analysis (see solarscope-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 (SOLARSCOPE_SKIP_LIVE=1 forces skip)
bun run fixtures:synthetic # regenerate fixtures/synthetic and examples/addresses.csv from src/synthetic.ts
bun run record # live: save real responses to gitignored .recorded-local/ for the shape checkNot affiliated
Not affiliated with, endorsed by, or sponsored by Google LLC. 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.
Count and buy mailing lists of homes that fit a home-service trade, by measured property signals.
Turn Maps queries into lead lists: businesses, phones, websites, and reviews with async job control.
Screen US sites for solar: sun, grid distance, FEMA flood risk. Save sites, create PDF reports.
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.-
- FlicenseNot gradedqualityBmaintenanceEnables turning Google Maps queries into structured lead lists, extracting business names, phone numbers, websites, and reviews with asynchronous job control.-

Leadbase MCP Serverofficial
FlicenseNot gradedqualityBmaintenanceEnables AI assistants to discover target companies, research decision-makers, enrich existing prospect sheets with sourced company and contact data, and manage editable, exportable research lists.-- 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