Solarscope
Uses Google Maps Platform APIs—Geocoding for address lookup and the Solar API's buildingInsights:findClosest—to retrieve roof geometry, solar panel layouts, energy estimates, and optional financial analyses for an address or coordinates.
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., "@Solarscoperun a solar proposal for 123 Main St, Austin TX at $0.14/kWh and $2.80/W"
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-mcp
A small MCP server and CLI that answers "is this roof worth solar?" for an address. It geocodes the address,
asks the Google Solar API (buildingInsights:findClosest) what fits on the closest building's roof, and turns
that into a transparent proposal: a few system sizes with yearly energy, cost, incentives, year-1 savings and
payback computed from your tariff, install cost per watt and incentives. When the API returns its own
financialAnalyses, they are shown side by side with yours, never mixed in.
The problem
Solar installers and homeowners want a fast, credible first answer for one roof: how big a system fits, how much it produces, and whether it pays back at local prices. The Solar API has the roof data (panel layouts, yearly DC energy per layout, roof segments) and, in some areas, its own financial analysis, but an agent still has to geocode, pick layouts, convert DC to AC, apply prices and keep track of which numbers are assumptions. This lab does that in one tool call and labels the source of every input.
Related MCP server: Solar Sathi MCP Server
Quickstart
bun install --frozen-lockfile
# Keyless demo: synthetic building in the documented response shape (fictional address, invented values)
bun run src/cli.ts roof --address "100 Example Ave, Sampletown" --example-inputs --fixtures
bun run src/cli.ts roof --address "100 Example Ave, Sampletown" --tariff 0.25 --cost-per-watt 2.8 \
--incentive-pct 10 --monthly-bill 120 --format json --fixtures
bun run src/cli.ts roof --address "7 Sample Plaza, Calmtown" --min-quality MEDIUM --example-inputs --fixtures
# Live: put GOOGLE_MAPS_API_KEY in .env (see .env.example), then
bun run src/cli.ts roof --address "<address>" --tariff <your price per kWh> --cost-per-watt <installer quote per W> \
[--incentive-pct <percent>] [--incentive-amount <rebate>] [--annual-kwh <usage> | --monthly-bill <bill>]
bun run src/cli.ts roof --at=37.4,-122.1 --min-quality MEDIUM # negative numbers need --at=...
# Methodology (formulas, constants, example inputs, 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": {
"solarscope": {
"command": "bun",
"args": ["run", "/path/to/solarscope-mcp/src/cli.ts", "serve"],
"env": { "GOOGLE_MAPS_API_KEY": "${GOOGLE_MAPS_API_KEY}", "SOLARSCOPE_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, input sources, Google's US method constants, the example inputs, verdict rule, attribution, reference URLs | none |
| Budget used/remaining, refused calls | none |
CLI: solarscope roof | method | serve. Exit codes: 0 ok, 2 error, 64 usage.
How the proposal is computed
Layouts. From
solarPotential.solarPanelConfigs(ordered by panel count; config N uses the first N panels): 25%, 50% and 75% of the largest layout, the largest, the largest layout whose first-year AC output stays within your yearly usage (if given), the layout the API's own analysis picked for its default bill (if any), and anypanel_countsyou ask for.Size and energy. kW =
panelsCount x panelCapacityWatts / 1000. Year-1 AC kWh =yearlyEnergyDcKwh x 0.85. Output declines by factor 0.995 per year.Cost.
watts x install_cost_per_watt; incentives =cost x incentive_percent / 100 + incentive_amount, capped at the cost.Savings. Year n = (self-consumed kWh x tariff + exported kWh x export rate) x 1.022^(n-1). Without usage, every kWh is valued at the tariff. With usage and no export rate, exports are credited at the tariff (labelled as an assumption).
Payback. First (fractional) year when cumulative savings reach the net cost; simple payback = net cost / year-1 savings; NPV discounts year n by 1.04^(n-1). Recommended option = highest NPV.
Verdict on the recommended option:
pays-back-early(within half the lifetime),pays-back-late(within the lifetime),does-not-pay-back,needs-prices(no tariff or install cost),no-usable-layout(the API returned no layouts: fewer than 4 panels fit).
The constants 0.85 (DC to AC derate), 0.995 (yearly output factor), 1.022 (yearly price increase), 1.04 (discount
rate) and the 20-year lifespan are the values Google's
US costs and savings page lists for
its own US analysis. They are labelled google-us-method in every output and can be overridden. The lifetime
defaults to the response's panelLifetimeYears when present.
No invented prices
Tariff, install cost and incentives have no hidden defaults. Without them you get sizes and energy and the
verdict needs-prices. use_example_inputs / --example-inputs fills the missing prices with round placeholder
values (0.15 per kWh, 3.00 per W, no incentive) that this lab chose for demos; every such input carries
source: "example" and the note "EXAMPLE INPUT: placeholder chosen by this lab, not a market price or quote", and
the verdict headline starts with "On EXAMPLE inputs (not real prices)".
Output
JSON (proposal): location, building (name, imagery quality and dates, distance from the query point), roof
(max panels and kW, panel model, sunshine, segments with facing/pitch/area), inputs (each {value, source, note}),
options[] (size, DC/AC energy, kWh/kW, CO2 offset from carbonOffsetFactorKgPerMwh, self-consumed/exported kWh,
finance), recommended, verdict, apiFinancials (compacted analyses per bill: cash, financed, lease,
incentives, implied price per kWh and per watt; plus a comparison table for one bill), assumptions, notes,
attribution, references, source. Markdown: verdict, roof table, options table, the API comparison, an inputs
table with sources, and the attribution line. A full sample is in
reports/solarscope-mcp.md.
Measured
Keyless run of the real MCP server (in-memory MCP client) over fixtures/synthetic/: invented buildings and
invented API financial analyses in the response shape documented in Google's API reference. The numbers show what
the tool does with that input, not anything about real roofs or prices. "MCP = service" checks that the tool's
JSON and Markdown equal the service output for the same input. Produced by bun run report; full output in
reports/solarscope-mcp.md and reports/solarscope-mcp.json.
Scenario | Input | Calls | Imagery | Layouts | Options | Recommended | Yr-1 AC kWh | Net cost | Payback (yrs) | API payback, same bill (yrs) | Verdict | Solar response bytes | Proposal JSON bytes | Markdown bytes | MCP = service |
sizes only | address, no prices | 2 | HIGH | 33 | 5 | 36 panels / 14.4 kW | 14,680 | - | - | 11.3 (20 panels) | needs-prices | 37402 | 14922 | 5702 | true |
example inputs | address, use_example_inputs | 2 | HIGH | 33 | 5 | 9 panels / 3.6 kW | 4,208 | 10,800 | 15.2 | 11.3 (20 panels) | pays-back-late | 37402 | 16616 | 6181 | true |
typed prices | address, 0.25/kWh, 2.80/W, 10% | 2 | HIGH | 33 | 5 | 36 panels / 14.4 kW | 14,680 | 36,288 | 9.2 | 11.3 (20 panels) | pays-back-early | 37402 | 16218 | 5789 | true |
typed prices + usage | same + 6000 kWh/yr, export 0.05 | 2 | HIGH | 33 | 6 | 12 panels / 4.8 kW | 5,572 | 12,096 | 8.2 | 11.3 (20 panels) | pays-back-early | 37402 | 16635 | 5686 | true |
flat roof, MEDIUM imagery | address, min_quality MEDIUM, example inputs | 2 | MEDIUM | 15 | 4 | 18 panels / 7.2 kW | 9,360 | 21,600 | 13.8 | - | pays-back-late | 7971 | 7912 | 4495 | true |
shed (3 panels fit) | lat/lng, typed prices | 1 | HIGH | 0 | 0 | - | - | - | - | - | no-usable-layout | 1726 | 4190 | 3177 | true |
Google's US method page says the 20-year cost without solar is
204.35 x monthlyBill;src/finance.tswith the page's constants (1.022 / 1.04, 20 years) gives 204.35.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): tool error google_PERMISSION_DENIED, reason SERVICE_DISABLED.
Point with no building: google_NOT_FOUND.
Budget 3, two address checks (2 calls each): ok, budget_exceeded.
"example inputs" rows: 4 inputs carry
source: "example"; "typed prices" rows: 0.
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 Solar API response has been observed by this lab. The Solar path is tested only 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 |
Endpoint |
| findClosest reference (last updated 2026-05-12) |
Parameters |
| same |
Imagery quality |
| |
Response |
| findClosest reference |
|
| same |
| per | same |
US method constants | DC to AC 0.85, efficiency depreciation 0.995, cost increase 1.022, discount 1.04, 20-year lifespan; cost without solar = 204.35 x monthlyBill (reproduced in | |
Attribution | "Source: Includes solar data from Google" next to the data; "Includes data from Google Maps" for new content created from Environment API data | |
"Installers lack fast, credible rooftop proposals" | Not verified: the idea's market claim, not measured | none |
Doc inconsistencies we found: the building insights guide's TypeScript sample types imageryQuality as
'HIGH' | 'MEDIUM' | 'BASE' and sends required_quality: 'BASE', while the reference enum lists LOW. This lab
follows the reference (LOW); we could not test which spelling the live API accepts. The US method page's
LifetimeProductionAcKwh formula multiplies by dcToAcDerate although initialAcKwhPerYear already includes it;
this lab applies the derate once. The guide's examples pass the key as key=; this lab sends X-Goog-Api-Key
instead (the Solar API answered SERVICE_DISABLED for our project with that header, so it identified the project,
but we have not seen a successful response with it).
Attribution and usage requirements
From the Solar API policies:
Attribution. If you use data or imagery from the Solar API you must show "Source: Includes solar data from Google" on or next to it. Content you create from Environment API data uses "Includes data from Google Maps" (text attribution rules: font, size, color, placement,
translate="no"in HTML). Every JSON proposal carries both (attribution,derivedAttribution) and every Markdown proposal ends with both. Keep them next to the numbers wherever you show them.No caching. Pre-fetching, caching or storing Solar content is generally restricted (place IDs are exempt). This server keeps nothing between calls, and the repo commits no Google responses.
Maps. If you show Solar results on a map, it must be a Google Map.
Terms. Your app needs public Terms of Use and a Privacy Policy that meet Google's requirements; use is governed by your agreement with Google. EEA billing addresses get different terms, and the docs say certain Solar API content is no longer returned for them.
Billing. Building insights is the "Solar API Building Insights" SKU (Essentials category) per the usage and billing page. No prices are quoted here; set Cloud quotas and budget alerts.
Limits
Solar responses unverified live. See Live status. Synthetic fixtures follow the reference field by field (
src/types.ts, strict check insrc/schema.ts), but real responses may carry fields or value formats we have not seen.Not a quote, not financial advice. The proposal is arithmetic on the API's energy figures and your inputs: one flat tariff, one cost per watt, your incentives. No time-of-use rates, tiered tariffs, export caps, loans, maintenance, inverter replacement, taxes, shading changes or panel model choice. The API's own analysis uses its own data and models; differences between the two columns are expected.
No tariff data. The idea mentioned a local tariff table; there is no verified open data source for it in this MVP, so the tariff is a user input.
No weather. The idea's original title mentions the Weather API. It is not enabled in our project and is not needed for yearly energy (the Solar API already returns yearly DC kWh per layout), so it is left out.
Closest building. The API returns the building nearest the point; the output reports the distance and adds a note above 30 m. Geocoding returns one point per address, which can land on the wrong building.
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. Fixture output is never a real building and says so in the Markdown footer.Not included (separate ideas): detected arrays (
additionalInsights=DETECTED_ARRAYS), roof geometry views, thedataLayersendpoint (imagery GeoTIFFs), the pre-GAEXPANDED_COVERAGEexperiment, batch address lists.findClosestUrlalready acceptsadditionalInsightsandexperiments, andsrc/types.tstypesdetectedArrays, so these can be added as new tools.
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 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.
Free independent solar proposal review tool for California homeowners. Audit your solar quote for pr
Screen US sites for solar: sun, grid distance, FEMA flood risk. Save sites, create PDF reports.
UK solar PV and heat pump calculators that follow the published MCS methods. Open, no key.
Related MCP Servers
- FlicenseAqualityDmaintenanceEnables solar energy feasibility analysis and ROI calculation for Indian users, including irradiance lookup, system sizing, cost estimation, subsidy calculation, and environmental impact assessment.8-
- FlicenseNot gradedqualityDmaintenanceEnables solar energy feasibility analysis and ROI calculation for Indian users, including irradiance lookup, capacity estimation, subsidy calculation, and environmental impact assessment.-
- 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