PySolarMCP wraps solar-cell device simulation behind two surfaces:
solarcell_sim: a Python package for scripts, notebooks, optimizers, and tests.solarcell_sim_mcp: a thin MCP adapter that exposes selected core API calls to agents.
P0 focuses on SCAPS through a baseline .scaps definition plus a generated script. The
runner and artifacts are intentionally designed so a later release can generate .scaps
definition files directly without changing the public API.
Use the package directly from scripts, notebooks, optimizers, or tests. The runtime
configuration is loaded from .env, environment variables, and optional config files.
from pathlib import Path
from solarcell_sim import run_case
from solarcell_sim.storage import read_json
case = read_json(Path("examples/pin_baseline.json"))
case.pop("backendOptions", None) # use .env / project config
result = run_case(case)
print(result.status)
print(result.metrics)
print(result.execution)SCAPS/Wine can return a non-zero process code after writing a valid output file. In that
case result.status is partial, parsed metrics are still returned when available, and
the raw process details remain available under result.execution. The generated SCAPS
output text is available as result.output.result_text.
SCAPS is not distributed with this project. Install or copy it on the host machine, then point PySolarMCP at that installation. The recommended shared-machine layout is:
/opt/scaps/Scaps3309/scaps3310.exe
/opt/scaps/Scaps3309/Scapsdll.dll
/opt/scaps/Scaps3309/def/baseline.scaps
/opt/scaps/ScapsInstallation/bin/dp/Scaps3310.msiKeep /opt/scaps read-only for normal users. Keep writable state such as runs/ and
wineprefix/ per user or per project. Do not share one writable Wine prefix between
multiple users unless you also control concurrent access and permissions.
Create a local config file:
cp .env.example .envFor a host-side local run, the important values are:
SCAPS_EXECUTABLE_PATH=/opt/scaps/Scaps3309/scaps3310.exe
SCAPS_DEFINITION_PATH=/opt/scaps/Scaps3309/def/baseline.scaps
SCAPS_WORKDIR=./runs
SCAPS_RUNTIME_STRATEGY=workspace_copy
SCAPS_TIMEOUT_SECONDS=120
WINE_BIN=wine
WINEPREFIX=./wineprefix
WINEARCH=win32
SOLARCELL_SIM_XVFB=1To choose a SCAPS definition by the case architecture, switch the definition source to template mode and provide one definition file per architecture:
SCAPS_DEFINITION_SOURCE=template
SCAPS_TEMPLATE_NAME=auto
SCAPS_TEMPLATE_PIN_PATH=/opt/scaps/Scaps3309/def/pin.scaps
SCAPS_TEMPLATE_NIP_PATH=/opt/scaps/Scaps3309/def/nip.scapsWith SCAPS_TEMPLATE_NAME=auto, device.architecture: "p-i-n" loads the p-i-n
definition and device.architecture: "n-i-p" loads the n-i-p definition. A fixed
SCAPS_TEMPLATE_NAME must match the case architecture.
Use a 32-bit Wine prefix. WINEARCH=win32 only matters when a prefix is first created;
it will not convert an existing 64-bit prefix. Initialize the prefix and install the SCAPS
MSI into it:
uv run python scripts/setup_scaps_wine.py --installer /opt/scaps/ScapsInstallation/bin/dp/Scaps3310.msiPassing /opt/scaps/ScapsInstallation/setup.exe is also supported; the helper looks for
bin/dp/*.msi next to it first. The direct MSI path is preferred because the SCAPS
setup.exe bootstrapper can hang under Wine. The MSI installs runtime dependencies such
as the NI LabWindows/CVI runtime (cvirte.dll).
Run a prepare-only check first. This verifies config resolution, SCAPS definition copying, and script generation without launching Wine:
uv run python scripts/smoke_scaps_runner.py --prepare-onlyThen run SCAPS:
uv run python scripts/smoke_scaps_runner.py --timeout-seconds 90The smoke script reads .env, prepares examples/pin_baseline.json, runs the configured
backend, and prints raw stdout/stderr, parsed diagnostics, execution metadata, metrics,
and artifact paths. Generated scripts follow the SCAPS manual's scripting guidance:
messages are directed to SCAPSErrorLogFile.log with set errorhandling.overwritefile;
if that log is written, it is archived under raw/scaps_logs/ and surfaced as a
diagnostic.
Runner timeouts are enforced by killing the SCAPS process tree. Set
SCAPS_TIMEOUT_SECONDS in .env or the service environment to cap each simulation run.
For SCAPS_RUNTIME_STRATEGY=workspace_copy, each run copies the SCAPS runtime into a
per-run workspace, then removes runtime/ after parsing. Long-lived artifacts remain in:
runs/<run-id>/input/
runs/<run-id>/raw/scaps_inputs/
runs/<run-id>/raw/scaps_outputs/
runs/<run-id>/raw/scaps_logs/
runs/<run-id>/parsed/
runs/<run-id>/manifest.json
Some SCAPS/Wine combinations return a non-zero process code after writing a valid output
file. The SCAPS manual does not define these process return codes, so parseable output is
returned in output.resultText while the process code remains available as
execution.returnCode. The smoke script exits 0 for parseable partial results by
default. Add --strict-exit-code to make partial results fail the smoke command.
Host-side local runtime variables:
SCAPS_EXECUTABLE_PATH: host path toscaps3310.exeor another SCAPS executable.SCAPS_DEFINITION_PATH: host path to a baseline.scapsfile.SCAPS_DEFINITION_SOURCE:baseline_fileortemplate; defaults to baseline mode whenSCAPS_DEFINITION_PATHis set.SCAPS_TEMPLATE_NAME:auto,p-i-n, orn-i-p;autoselects fromdevice.architecture.SCAPS_TEMPLATE_PIN_PATH,SCAPS_TEMPLATE_NIP_PATH: architecture-specific SCAPS definition files used in template mode.SCAPS_WORKDIR: persistent run directory.SCAPS_RUNTIME_STRATEGY:workspace_copy,workspace_link, orin_place;workspace_copyis recommended for reproducible runs and Docker.SCAPS_TIMEOUT_SECONDS: maximum seconds for each SCAPS run before the process tree is killed.WINE_BIN: Wine executable, defaultwine.WINEPREFIX: writable Wine prefix containing SCAPS runtime dependencies.WINEARCH: normallywin32for SCAPS.SOLARCELL_SIM_XVFB: set to1to wrap Wine inxvfb-runon headless machines.
Docker Compose also reads .env. Host path variables such as SCAPS_HOST_DIR are used
for volume mounts; Docker-specific variables such as SCAPS_DOCKER_EXECUTABLE_PATH must
stay as container paths.
The Docker image now runs the MCP server as an always-on Streamable HTTP service by default. The stdio transport is still available for local process-based MCP clients, but remote deployments should use HTTP.
Follow the local SCAPS/Wine setup above on the machine that will run Docker. At minimum:
cp .env.example .env
uv run python scripts/setup_scaps_wine.py --installer /opt/scaps/ScapsInstallation/bin/dp/Scaps3310.msi
uv run python scripts/smoke_scaps_runner.py --timeout-seconds 90Then confirm the Docker-specific .env values match the host layout and the container
mount points:
SCAPS_HOST_DIR=/opt/scaps/Scaps3309
SCAPS_DEFINITIONS_HOST_DIR=/opt/scaps/Scaps3309/def
SCAPS_RUNS_HOST_DIR=./runs
SCAPS_WINEPREFIX_HOST_DIR=./wineprefix
SCAPS_DOCKER_EXECUTABLE_PATH=/scaps/scaps3310.exe
SCAPS_DOCKER_DEFINITION_PATH=/definitions/baseline.scaps
SCAPS_DOCKER_TEMPLATE_PIN_PATH=/definitions/pin.scaps
SCAPS_DOCKER_TEMPLATE_NIP_PATH=/definitions/nip.scaps
SCAPS_DOCKER_WORKDIR=/runs
SCAPS_DOCKER_TIMEOUT_SECONDS=120
WINE_DOCKER_PREFIX=/wineprefix
WINE_DOCKER_ARCH=win32
SOLARCELL_SIM_DOCKER_XVFB=1
DOCKER_UID=1000
DOCKER_GID=1000
DOCKER_HOME=/tmp
MCP_HOST_PORT=31335
MCP_PORT=31335
MCP_PATH=/mcpThe container mounts SCAPS_WINEPREFIX_HOST_DIR at /wineprefix, so SCAPS can find
installed runtime DLLs such as cvirte.dll. Wine requires the prefix to be owned by the
user running Wine. Set DOCKER_UID and DOCKER_GID in .env to the host user that owns
runs/ and wineprefix/:
id -u
id -g
mkdir -p runs wineprefix
sudo chown -R "$(id -u):$(id -g)" runs wineprefixdocker compose build
docker compose config
docker compose up -d solarcell-sim-mcpThe service listens on:
http://<server-host>:31335/mcp
You can validate construction without starting the long-running HTTP server:
docker compose run --rm solarcell-sim-mcp --check --transport streamable-http --host 0.0.0.0 --port 31335 --path /mcpCheck logs after starting the service:
docker compose logs -f solarcell-sim-mcpTo stop it:
docker compose downUse a URL-based remote MCP configuration. The exact file location depends on your MCP client, but the server entry should look like this:
{
"mcpServers": {
"pysolar-mcp": {
"url": "http://SERVER_HOST_OR_IP:31335/mcp"
}
}
}Replace SERVER_HOST_OR_IP with the machine running Docker. If the server is not on a
trusted private network, put it behind SSH tunneling, a VPN, or a reverse proxy with TLS
and authentication before exposing it. The current app-level server does not add its own
authentication layer.
For local testing against the same machine:
{
"mcpServers": {
"pysolar-mcp": {
"url": "http://127.0.0.1:31335/mcp"
}
}
}MCP clients should treat backend runtime configuration as server-owned. Do not send
backendOptions in MCP requests. Select the simulator with the tool's backend
argument, for example scaps, and send only the physical simulation case:
{
"backend": "scaps",
"case": {
"name": "pin-baseline-jv",
"device": {
"architecture": "p-i-n",
"layers": [
{
"name": "absorber",
"role": "absorber",
"thicknessNm": 600,
"material": {
"bandgapEv": 1.55,
"electronAffinityEv": 3.9,
"relativePermittivity": 25
}
}
]
},
"conditions": {
"temperatureK": 300,
"illumination": "AM1.5G",
"incidentSide": "front",
"voltageScan": {"startV": 0, "stopV": 1.2, "stepV": 0.05}
},
"measurements": ["JV"]
}
}The remote server loads SCAPS executable paths, baseline definitions, Wine prefix,
workdir, and runtime strategy from its .env, Docker environment, or server config.
Recommended MCP flow for agents:
- Call
solarcell_get_baseline_runwitharchitectureset ton-i-porp-i-nwhen you need a server-safe baseline request to edit. - Call
solarcell_validate_inputfirst. - Call
solarcell_run_caseonly when validation has no errors. Warnings are diagnostic signals, not hard blockers. - Do not immediately retry the same request after
mcp.run_busy,runner.timeout, orschema.validation_error; adjust the input or wait for the active run to finish.
The MCP server accepts one SCAPS run at a time by default. Additional run requests return
mcp.run_busy immediately instead of queueing behind a long calculation.
MCP tool responses include runPolicy.maxConcurrentRuns: 1 and
runPolicy.busyDiagnosticCode: "mcp.run_busy" so clients can make this scheduling rule
machine-readable.
For clients that still need process-based stdio, override the Docker command:
{
"mcpServers": {
"pysolar-mcp-stdio": {
"command": "docker",
"args": [
"compose",
"run",
"--rm",
"-i",
"solarcell-sim-mcp",
"--transport",
"stdio"
]
}
}
}For remote deployment, prefer the HTTP URL configuration above instead of stdio.
SCAPS_RUNTIME_STRATEGY=workspace_copyis recommended for remote runs; generated runtime directories are cleaned after parsing while raw inputs, raw outputs, parsed CSV/JSON, and manifests are kept.- A SCAPS/Wine process may return a non-zero process code after writing a valid output
file. The API returns parseable output as
partial, withexecution.returnCodeand diagnostics preserved. - If you see
cvirte.dllmissing, the mounted Wine prefix has not had the SCAPS MSI installed into it, or the wrongSCAPS_WINEPREFIX_HOST_DIRis mounted. - If Wine reports
/wineprefixis not owned by you, setDOCKER_UIDandDOCKER_GIDin.envto the host owner ofSCAPS_WINEPREFIX_HOST_DIR, then recreate the service. - If
xvfb-runreportsxauth command not found, rebuild the Docker image after updating this repository; the image must include bothxvfbandxauth. - If port
31335is already in use, changeMCP_HOST_PORTin.envand restart withdocker compose up -d. The client URL must use the host port.