Skip to main content
Glama

solar-plan-mcp

An agent that answers one everyday question: will I get through tomorrow's set of consumers on my own generation, and if not — what should I reschedule?

A roof with panels, a battery, an inverter, a known outage schedule. The agent takes the weather forecast from a ready-made MCP server, calculates expected generation by hour on its own MCP server, checks the plan against physical rules, and if it doesn't pass — reschedules flexible loads and proves with numbers that things got better.

Two MCP connections:

Server

Role

Ready-made

mschneider82/mcp-openweather, commit e032683

forecast: sky class and temperature for every 3 hours

Own

solar_mcp (this repository)

4 meaningful domain tools + forecast text parsing

Documentation: tool contracts · design rationale · demo scenario

What you need

What for

Note

Python 3.13

agent and own server

no admin rights needed

Go 1.24+

only to build the weather server

the project doesn't publish ready binaries; go.mod requires 1.24, although the README there says 1.20

OpenWeather key

weather server

free, openweathermap.org/api; takes up to a few hours to activate

claude CLI + model access

only the agent; the own server and tests don't need it

Claude Agent SDK spawns this CLI as a child process — see Model access

Node + npx

optional — MCP Inspector

npx @modelcontextprotocol/inspector

The PVGIS dataset is already in the repository (data/pvgis_kyiv_5kwp.csv, 1.1 MB), so the own server works without a network. Nothing needs to be downloaded.

Installation

git clone <цей-репозиторій>
cd solar-plan-mcp

python -m venv .venv                       # або: uv venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt

Windows, and this isn't cosmetic: all commands below are in PowerShell, because && in PowerShell 5.1 isn't an operator at all. From here on, everywhere .venv\Scripts\python.exe.

Two encoding variables, and they're different. PYTHONUTF8=1 tells Python to write UTF-8; [Console]::OutputEncoding tells PowerShell to read it the same way. Without the second one, Ukrainian output turns into ╨▓╨╗╨░╤ü╨╜╨╕╨╣ — measured, and precisely in the pipe (| Tee-Object, | Select-String), because there PowerShell decodes bytes with the console code page. So in every new window:

[Console]::OutputEncoding = [Text.Encoding]::UTF8
$env:PYTHONUTF8 = "1"

Build the weather server

Go installs into the user profile, without an administrator and without registry changes:

# 1. портативний Go у профіль (один раз). curl.exe є у Windows 10 1803+
curl.exe -Lo go.zip https://go.dev/dl/go1.27.0.windows-amd64.zip
Expand-Archive go.zip -DestinationPath "$env:LOCALAPPDATA\Programs"

# 2. клон і збірка. GOROOT і PATH живуть лише в цьому вікні — так і треба
$env:GOROOT = "$env:LOCALAPPDATA\Programs\go"
$env:PATH = "$env:GOROOT\bin;$env:PATH"
New-Item -ItemType Directory -Force vendor | Out-Null
cd vendor
git clone https://github.com/mschneider82/mcp-openweather.git
cd mcp-openweather
git checkout e032683574a0723591445462ef7104d360ad0889
go build -o mcp-weather.exe .
cd ..\..

The agent looks for the ready binary at vendor\mcp-openweather\mcp-weather.exe. If yours is elsewhere — don't move it, but point a variable at it: $env:WEATHER_MCP_BINARY = "…\mcp-weather.exe" (see env.example). The agent checks for the file's existence before starting the session and refuses with a sentence, not a traceback from inside the SDK.

vendor/ is in .gitignore: someone else's git history and a 13 MB binary have nothing to do with this repository. The commit is pinned — the contract documentation is written against exactly that commit.

If the course pins a different mcp-openweather commit — take it and record it here; the contract description in docs/TOOLS.md was written from main.go at e032683.

Key

Secrets never get into the repository: .env and .env.* are in .gitignore, and the sample sits in env.example without a single value.

The demo needs three terminals, and $env: lives in only one, so the key is worth setting at the user level — no admin rights needed for that:

# так ключ не потрапляє ні в скролбек, ні в історію PSReadLine
$s = Read-Host "OWM_API_KEY" -AsSecureString
[Environment]::SetEnvironmentVariable("OWM_API_KEY",
  [Runtime.InteropServices.Marshal]::PtrToStringBSTR(
    [Runtime.InteropServices.Marshal]::SecureStringToBSTR($s)), "User")

Only new terminals will see the new value. A check that it arrived, without revealing the key: .venv/Scripts/python.exe -c "import os; print(len(os.environ.get('OWM_API_KEY','')))" — should be 32. Don't run dir env: on camera: it prints the key.

The one-off session form $env:OWM_API_KEY = "…" also works, but has exactly one proper use here — clearing the key in a separate window for the failure scenario: $env:OWM_API_KEY = "".

The key is read only from the environment — it's not in the code or in .mcp.json.example; there's a ${OWM_API_KEY} substitution there. The .env file is read by no one: the code only has os.environ.get, so copying env.example to .env is a no-op.

Model access

The own server and all 57 tests work without any Anthropic credentials — these are different things and shouldn't be confused. The model is needed by exactly one file, agent/run.py.

Claude Agent SDK doesn't call the API itself: it spawns the claude CLI as a child process, and that CLI is what looks for authorization. So two things are needed:

  1. claude in PATH. Check: (Get-Command claude).Source. Installation — per the official instructions; in this project it was installed via WinGet and sits in %LOCALAPPDATA%\Microsoft\WinGet\Links\claude.exe.

  2. Authorization — one of two paths, and the CLI takes whichever it finds:

    • claude login — interactive sign-in; the CLI puts the token in ~/.claude/.credentials.json. This is exactly the path used here: the process environment has no ANTHROPIC_* variable, and the credentials file exists. The recorded run on August 25, 2026 passed this way.

    • ANTHROPIC_API_KEY in the environment — a key from console.anthropic.com. Set it the same way as OWM_API_KEY above, and likewise it never gets into the repository.

What's pinned in the code: the model claude-opus-5 (agent/run.py) and claude-agent-sdk==0.2.144 (requirements.txt). If you have different access and this model id doesn't resolve — replace it in run.py with an available one and record here which one; the rest of the run doesn't depend on the id.

This code reads no credential and passes none anywhere: agent/run.py touches neither ANTHROPIC_API_KEY nor the credentials file — the CLI handles that. There are no secrets in the repository, and env.example sits empty.

External API limits

The free OpenWeather plan gives 60 calls per minute (documentation). One agent run makes one call to the weather tool; inside, the weather server turns it into two HTTP requests (current weather + 5-day forecast). That is, there's three orders of magnitude of headroom below the ceiling even with continuous rehearsals.

The code has no polling loop, no retry on error, and no background refresh: the weather is requested exactly when the model calls the tool. The own server doesn't touch the network at all — its dataset sits in data/, so any number of estimate_pv_generation, validate_energy_plan, and other runs create zero external requests.

Running: two independent processes

The own server comes up separately from the agent and knows nothing about the agent.

Terminal 1 — own MCP server:

$env:PYTHONUTF8 = "1"
.venv\Scripts\python.exe -m solar_mcp --transport streamable-http --port 8931

Terminal 2 — agent:

[Console]::OutputEncoding = [Text.Encoding]::UTF8
$env:PYTHONUTF8 = "1"
.venv\Scripts\python.exe agent\run.py

Without --date the agent plans tomorrow's day: the product question is precisely about tomorrow, and the OpenWeather forecast only covers now … +5 days, so today's day is already half outside the horizon. A date outside this window will give NO_FORECAST_FOR_DATE, not silent zeros.

The agent starts the weather server itself, over stdio — that's how the connection is configured. The own server can also be run over stdio (python -m solar_mcp, that's the default) — that's how clients like Claude Code expect it, and that's the variant described in .mcp.json.example. For the demo, HTTP is better: then it's visible that the server really is a separate process.

Useful agent flags:

--plan boiler:18:2 --plan aircon:18:3    # свій план замість дефолтного (можна кілька разів)
--date YYYY-MM-DD                        # інша доба; вт/чт/пт — без відключень, сб/нд — вечірнє вікно
--objective maximize_outage_reserve      # інша цільова функція
--city Lviv                              # інше місто
--width 120                              # скільки символів сліду друкувати

--date accepts only a day within the forecast horizontomorrow … today + 5. A date outside it will give NO_FORECAST_FOR_DATE, and the presence of an outage window in the schedule doesn't save it: the schedule sits in the repository and knows any date, while the forecast lives five days. Check before running: scripts/call_weather.py --city Kyiv --covers YYYY-MM-DD.

The dates in the outage schedule have a weekly pattern and provenance — see data/outage_windows.json: the windows for August 22–26 were entered from the public schedule, then the same pattern is repeated forward so the demo doesn't depend on the recording date.

Checking that everything is alive

# 4 інструменти домену + 1 допоміжний, зі схемами входу І виходу
.venv\Scripts\python.exe scripts\inspect_tools.py
.venv\Scripts\python.exe scripts\inspect_tools.py --url http://127.0.0.1:8931/mcp --schemas

# сервер погоди напряму: сирий текст і те, що з нього вийшло
.venv\Scripts\python.exe scripts\call_weather.py --city Kyiv

# 57 тестів: фізика, правила домену, планувальник, контракт через MCP-клієнта
$env:PYTHONUTF8 = "1"; $env:PYTHONPATH = "."
.venv\Scripts\python.exe -m pytest tests\ -q

The tests need neither a network, nor an OpenWeather key, nor model access: the dataset sits in the repository, and the third-party server's responses are recorded in tests/fixtures/.

What lives where

solar_mcp/            власний MCP-сервер (окремий процес)
  server.py           інструменти й ресурс — увесь контракт
  models.py           схеми входу й виходу (Pydantic → справжні inputSchema/outputSchema)
  errors.py           закритий перелік кодів; помилка ≠ порожній результат
  pv.py               огинаюча ясного неба × прозорість × температурний дерейтинг
  rules.py            симуляція балансу, порушення, планувальник, порівняння
  forecast.py         розбір плоского тексту сервера погоди
  dataset.py store.py читання датасету; реєстр виданих оцінок
agent/run.py          Claude Agent SDK, дві MCP-конекції, слід викликів
scripts/              inspect_tools.py — контракт; call_weather.py — чужий сервер напряму
data/                 датасет + fetch_pvgis.py (провенанс)
tests/                57 тестів; у fixtures/ — три записані відповіді сервера погоди й одна синтетична
docs/                 TOOLS.md · DESIGN.md · DEMO.md

Two of these directories have their own README, and those are exactly what to look for under "data source" and "fixtures": data/README.md — where the PVGIS row, the tariff, and the outage schedule came from; tests/fixtures/README.md — what exactly was recorded from the third-party server, when, and with what.

One observation on which half the design stands

The weather server doesn't distinguish a failure from an empty response. Without a key it returns is_error: false and text with zeros and an empty city name — recorded verbatim in tests/fixtures/owm_no_api_key.txt, even though its README promises "FATAL: OWM_API_KEY environment variable not set".

That's why the own server is built the opposite way: a closed list of error codes, a field pointing at the offending field, and separately — a reason where empty is legitimate (night, no violations). Details: DESIGN.md, TOOLS.md.

-
license - not tested
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Unofficial integration! ## ✨ Key Features ### 💰 Financial Intelligence - **Smart Charging Cost An…

  • One-call installer quote review plus energy incentives, estimates, scores, and routing for agents.

  • Personalized timing intelligence for AI agents — ask 'should I do X on this date?'

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prasolantoncp-bot/solar-plan-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server