PluginWorld
Ra

rad-mcp

MCP

Enables AI assistants to run local Radiance lighting and daylighting simulations on user-owned models, including annual daylight, compliance metrics, renders, glare analysis and electric lighting studies, with reproducible exported scripts.

@akontadakis · MIT License · updated today

SECURITY

B

SCORE

60

STARS

▲ 0

PLUG IN

git clone https://github.com/akontadakis/rad-mcp.git

See the README to configure this MCP server

README

rad-mcp

License: MIT Python 3.11+ MCP

rad-mcp is a local Model Context Protocol (MCP) server for Radiance, the physically based lighting simulation engine. It lets an AI assistant such as Claude Code or Claude Desktop run real lighting and daylighting studies on your own models, on your own computer.

It is built for lighting professionals who know lighting but not every Radiance flag. You ask for a study in plain words. The assistant calls this server's tools. The server picks the method, runs Radiance, checks every intermediate file and reports the result together with the assumptions behind it.

  • Annual daylight: 2-phase daylight coefficients, the 3-phase and 5-phase matrix methods, Klems and tensor-tree BSDFs, operable blinds to IES LM-83.
  • Compliance: sDA, ASE and ASE_net to IES LM-83-12 or ANSI/IES LM-83-22; daylight provision, view out and sunlight exposure to EN 17037; electric-lighting uniformity to EN 12464-1; UDI.
  • Images and glare: rpict renders, falsecolor luminance and illuminance maps, daylight glare probability (DGP) with evalglare, the Unified Glare Rating (UGR) for luminaires.
  • Electric lighting: IES (LM-63) photometry import, luminaire placement, maintained illuminance.
  • Reproducibility: every result can be exported as the exact shell script and provenance report that built it.

Status: early (0.1.0). The tools are complete enough for real studies, but the interface may still change. Results support, and do not replace, the judgement of a qualified lighting or daylighting consultant.


Contents


Install in two lines (Claude Code)

/plugin marketplace add akontadakis/rad-mcp
/plugin install rad-mcp@rad-mcp

Then set RAD_MCP_ROOTS in your shell profile and restart Claude Code. The 21 tools are live, together with the two bundled skills.

The plugin starts the server with uvx, which downloads and runs it from this repository in an isolated environment. You need uv on your PATH and Radiance installed (see Requirements).


Requirements

What Version Why
Radiance developed and tested against 6.1a; older versions are untested The simulation engine. Not bundled: install it yourself from LBNL-ETA/Radiance releases.
Python 3.11 or later Runs the server. uv can install it for you.
uv any recent Runs the server with uvx. Install from docs.astral.sh/uv.
Operating system macOS or Linux Developed on macOS (Apple silicon). Linux should work. Windows is not tested.
An MCP client Claude Code, Claude Desktop or any stdio MCP client Talks to the server.

Radiance is found on your PATH first, then under /usr/local/radiance, /usr/local, /opt/radiance and /opt/homebrew. You can also pass its bin folder to the radiance_status tool. The server sets RAYPATH for every Radiance process it starts, so you do not have to.

Run the radiance_status tool first. It reports the Radiance version and which workflows your installation supports. A missing binary disables only the workflows that need it, not the whole server.


Configure

The server reads and writes files only inside folders you allow. Until you set RAD_MCP_ROOTS, it refuses every file path. An unconfigured server does nothing rather than everything.

Variable What it does If unset
RAD_MCP_ROOTS The folders the server may read and write. Absolute paths, separated by :. Every data path is refused.
RAD_MCP_DEFAULT_ROOT Where project(action="create") puts a new project when no root is given. Must be inside RAD_MCP_ROOTS. That one call needs an explicit root.

Add this to your shell profile (~/.zshrc or ~/.bashrc):

export RAD_MCP_ROOTS="$HOME/rad-projects"
export RAD_MCP_DEFAULT_ROOT="$HOME/rad-projects"
export PATH="/usr/local/radiance/bin:$PATH"   # if Radiance is not already on PATH

Paths are resolved before they are checked, so a symlink inside an allowed folder that points outside it is refused too.


Install for other clients

Claude Desktop

Claude Desktop does not read your shell profile, so pass the variables in its config file and use the absolute path to uvx (which uvx prints it).

Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, then restart the app:

{
  "mcpServers": {
    "rad-mcp": {
      "command": "/ABSOLUTE/PATH/TO/uvx",
      "args": ["--from", "git+https://github.com/akontadakis/rad-mcp", "rad-mcp"],
      "env": {
        "RAD_MCP_ROOTS": "/ABSOLUTE/PATH/TO/rad-projects",
        "RAD_MCP_DEFAULT_ROOT": "/ABSOLUTE/PATH/TO/rad-projects",
        "PATH": "/usr/local/radiance/bin:/usr/bin:/bin"
      }
    }
  }
}

Claude Code without the plugin

claude mcp add rad-mcp \
  -e RAD_MCP_ROOTS="$HOME/rad-projects" \
  -e RAD_MCP_DEFAULT_ROOT="$HOME/rad-projects" \
  -- uvx --from git+https://github.com/akontadakis/rad-mcp rad-mcp

This adds the tools only. To also get the skills, use the plugin or copy the folders under skills/ into ~/.claude/skills/.

Any other MCP client

rad-mcp speaks MCP over stdio. Configure your client to start this command, with the two environment variables set:

uvx --from git+https://github.com/akontadakis/rad-mcp rad-mcp

From a clone

git clone https://github.com/akontadakis/rad-mcp.git
cd rad-mcp
uv venv && uv pip install -e .

Then point your client at /ABSOLUTE/PATH/TO/rad-mcp/.venv/bin/rad-mcp.


How it works

flowchart LR
    U([You]) -- plain words --> C["AI client<br/>Claude Code, Claude Desktop, ..."]
    C -- "MCP over stdio" --> M

    subgraph S ["rad-mcp (this repository)"]
        direction TB
        M["<b>rad_mcp</b><br/>MCP tools: argument checks,<br/>path policy, advisories, jobs"]
        E["<b>rad_engine</b><br/>Radiance adapter: commands,<br/>processes, octrees, matrices"]
        K["<b>rad_core</b><br/>Pure logic: grids, standards,<br/>metrics, geometry, method choice"]
        M --> E
        M --> K
        E --> K
    end

    E -- "subprocesses<br/>oconv, rtrace, rpict, rcontrib,<br/>rfluxmtx, dctimestep, evalglare, ..." --> R[("Radiance<br/>installed separately")]
    E -- reads and writes --> P[("Your project folder<br/>inside RAD_MCP_ROOTS")]

The code is split into three Python packages with a strict layer rule:

Package Role May import
rad_core Pure logic: sensor grids, standards thresholds, metric arithmetic, geometry checks, method inference. No subprocesses, no file system beyond its arguments. nothing else here
rad_engine The Radiance adapter. Every Radiance command line, process group, timeout, octree and matrix lives here. rad_core
rad_mcp The MCP layer. Validates arguments, enforces the path policy, turns facts into advisories and shapes every result. rad_core, rad_engine

Short calls run inline and return their result directly. Annual simulations can take hours, so run_annual returns a job id at once. You poll it with the job tool, and a job survives a server restart.

sequenceDiagram
    participant C as AI client
    participant S as rad-mcp
    participant R as Radiance
    C->>S: run_annual(project, grid, metrics=["sda","ase"])
    S->>S: infer method, check inputs, plan steps
    S-->>C: job_id + chosen method + plan
    S->>R: build matrices in the background
    loop until done
        C->>S: job(action="status", job_id)
        S-->>C: running, step 4 of 9
    end
    C->>S: results(action="compliance", standard="lm83")
    S-->>C: sDA, ASE, verdict, advisories

A typical study

flowchart TD
    A["radiance_status<br/>check the install"] --> B["project create<br/>make the project folder"]
    B --> C["geometry add<br/>import .obj or .rad, validate the octree"]
    C --> D["materials set / apply_defaults<br/>optical properties with provenance"]
    D --> E["window_group define<br/>name the apertures"]
    E --> F{"Shading device<br/>or complex glazing?"}
    F -- yes --> G["bsdf inspect / generate<br/>window_group assign_bsdf"]
    F -- no --> H
    G --> H["sensor_grid create<br/>grid to EN 12464-1, EN 17037 or LM-83"]
    H --> I["weather load<br/>EPW to .wea, with its traps reported"]
    I --> J{"What do you need?"}
    J -- "one sky, one moment" --> K["run_point_in_time<br/>illuminance or daylight factor"]
    J -- "every hour of a year" --> L["run_annual  →  job  →  results"]
    J -- "a picture" --> M["run_render  →  image"]
    J -- "daylight glare" --> N["run_glare<br/>DGP from a fisheye HDR"]
    J -- "electric lighting" --> O["luminaire import / place<br/>run_electric, run_ugr"]
    J -- "EN 17037 view and sun" --> P["run_view_and_sunlight"]
    K & L & M & N & O & P --> Q["export<br/>scripts, provenance report, artefacts"]

A conversation might go like this:

"Create a project called office-east, import ~/rad-projects/office.obj in metres with this material map, make the east façade glazing one window group with the venetian blind BSDF, put an LM-83 grid on the floor, load the Athens EPW and tell me sDA and ASE."

The assistant calls the tools in the order above. The server infers the 5-phase method because ASE was asked for, runs the annual job, applies the LM-83 blind schedule and reports sDA300/50% and ASE1000,250 with every assumption it made.


How the annual method is chosen

You state the metrics. The server chooses the method, tells you why, and refuses an override that would make the result wrong.

flowchart TD
    S([run_annual]) --> A{"ASE requested?"}
    A -- yes --> P5["<b>5-phase</b><br/>ASE is defined on the direct beam;<br/>3-phase smears the sun over a sky patch"]
    A -- no --> B{"A light-redirecting or<br/>specular fenestration state?"}
    B -- yes --> P5b["<b>5-phase</b><br/>these systems respond to exact sun angles"]
    B -- no --> C{"Operable blinds<br/>and sDA requested?"}
    C -- yes --> P5c["<b>5-phase</b><br/>the blind schedule depends on direct sun"]
    C -- no --> D{"Any BSDF or<br/>non-specular shading?"}
    D -- yes --> P3["<b>3-phase</b><br/>the BSDF is the T matrix:<br/>swap glazing without re-running"]
    D -- no --> P2["<b>2-phase</b><br/>daylight coefficients:<br/>fastest for simple glazing"]

First match wins. Asking for any method other than 5-phase together with ASE is refused (METHOD_OVERRIDE_UNSAFE). Overriding the other rules is allowed and the reason is recorded.

The 3-phase method multiplies four matrices for every hour of the year:

flowchart LR
    V["<b>V</b><br/>view matrix<br/>sensors → window<br/><i>rfluxmtx</i>"] --> X(("×"))
    T["<b>T</b><br/>transmission<br/>BSDF, Klems 145×145"] --> X
    D["<b>D</b><br/>daylight matrix<br/>window → sky patches<br/><i>rfluxmtx</i>"] --> X
    SK["<b>S</b><br/>sky matrix<br/>8760 hourly skies<br/><i>gendaymtx</i>"] --> X
    X --> I["illuminance<br/>per sensor per hour<br/><i>dctimestep</i>"]

The 5-phase method adds a direct-sun correction: it removes the patch-averaged sun of the 3-phase result and adds back a sharp sun computed with real solar positions and a tensor-tree BSDF. Every matrix is cached in the project and reused when its inputs have not changed.


The tools

21 tools. Every tool that writes or deletes says so in its description and in its MCP annotations.

Set up

Tool Actions What it does
radiance_status – Reports the Radiance install and which workflows it supports. Run it first.
project create, list, describe, update, delete, archive Creates and inspects a project folder and its manifest. describe shows which results are still valid.
geometry add, list, remove, check Imports .obj (through obj2rad, with a JSON material map) or .rad, and proves the octree is complete. oconv can exit 0 after silently dropping an input; this tool catches it.
materials set, list, inspect, apply_defaults Sets optical properties. Every value carries a provenance: measured, assumed or default.
window_group define, list, assign_bsdf, bind_schedule, remove, make_secondary_source Names the apertures, groups them as LM-83 does and binds BSDF states and blind schedules.
bsdf inspect, validate, generate, list Checks a BSDF XML (Klems or tensor tree) or generates one with genBSDF.
sensor_grid plan, create, list, remove Plans and writes a calculation grid to EN 12464-1, EN 17037 or LM-83. The standard is required, because the wrong one gives a plausible grid of the wrong density.
weather load, describe, list, remove Registers an EPW, converts it to .wea and reports its traps, such as the sign of the longitude and a zero ground albedo.
luminaire import, place, list, remove Imports IES (LM-63) photometry with ies2rad and places luminaires by hand, as arrays or on OBJ faces.

Run

Tool What it does
run_point_in_time Illuminance or daylight factor at a sensor grid for one sky (a CIE sky or a Perez sky from the EPW), or under the placed luminaires.
run_annual Annual simulation. Infers the 2-, 3- or 5-phase method, applies LM-83 blinds, returns a job id.
run_render Photorealistic HDR renders and falsecolor luminance or illuminance maps with rpict, pfilt and falsecolor.
run_glare Daylight glare probability (DGP) from a 180° fisheye with evalglare. Refuses low-quality settings, which give a confident but wrong DGP.
run_electric Initial and maintained illuminance and Uo per zone, with the EN 12464-1 limits beside them.
run_ugr Unified Glare Rating (CIE 117) for the placed luminaires, measured from the built scene.
run_view_and_sunlight EN 17037 view out (5.2) or sunlight exposure (5.3), by casting rays through the window groups.

Read, check and hand over

Tool Actions What it does
job status, wait, list, cancel, logs Follows an annual run. A cancelled run keeps the matrices it already finished.
results summary, points, timeseries, compliance, metrics, blind_schedule, export Reads an annual result: statistics, per-point values, hourly series, CSV, and verdicts against LM-83 or EN 17037.
image inspect, falsecolor, tonemap, convert Post-processes an HDR you already have, without rendering again.
quality presets, explain, diagnose, estimate Explains Radiance parameters: which flag fixes a splotchy render, what each preset sets.
export scripts, report, artifacts, manifest Writes the exact shell script that rebuilds a result, and a provenance report of every input and assumption.

The project folder

Every study lives in one folder inside RAD_MCP_ROOTS:

my-project/
├── manifest.json        what was built, from what, with which Radiance version
├── 01_geometry/         imported and converted scene files
├── 02_materials/        materials written by the server
├── 03_views/            view files
├── 04_skies/            generated skies and sky matrices
├── 05_bsdf/             BSDF XML files
├── 06_octrees/          frozen octrees and ambient files
├── 07_matrices/         V, D, T, S and the 5-phase direct-sun matrices
├── 08_results/          illuminance results and glare reports
├── 09_images/           renders: hdr/ and ldr/
├── 10_jobs/             background job records and logs
├── 11_sensors/          sensor grid .pts files
├── 12_luminaires/       imported types/ and placements/
└── source.cal           photometric correction file, written when luminaires are imported

The manifest records how each artefact was built. When an input changes, the results built from it are marked stale, and a results read says so instead of returning an old number as if it were current.


Design rules

These rules hold across all tools:

  • No silent defaults. When the server substitutes a value, the result says so in an advisories list with a stable code, such as DEFAULTS_SUBSTITUTED or DF_SKY_FORCED. A default is an assumption about the building, not a fact about it.
  • No invented thresholds. Every limit cites the clause it comes from. Where a standard is silent, the server reports the number with no verdict rather than invent one.
  • Refuse rather than mislead. A wrong-but-plausible result is worse than an error. For example, ASE through 3-phase is refused, and so is DGP from a low-quality render.
  • Stale results are flagged. The manifest records what each result was built from. When an input such as a window group, a scene include or a blind schedule changes, a results read says the result is not current.
  • Local only. The server runs on your computer over stdio. It makes no network calls.
  • Path policy. Every file path is resolved and checked against RAD_MCP_ROOTS before it is opened.
  • Safe subprocesses. Radiance runs with an argument list, never through a shell. Scene files that contain ! command lines are screened before they reach oconv.

The bundled skills

The plugin ships two Claude Code skills. They give the assistant background knowledge so it can use the tools well.

Skill What it covers
radiance-simulation-workflow The Radiance command line: scene files, ambient parameters, the daylight-coefficient, three-phase and five-phase methods, BSDFs, falsecolor, evalglare and annual metrics. Chapters 9 and 10 adapt the command sequences of A. McNeil's LBNL three- and five-phase tutorials.
lighting-standards-compliance What the standards require: EN 12464-1 and -2, EN 17037, IES LM-83-12 and the changes in ANSI/IES LM-83-22, EN 13363, EN 15193. Metrics, thresholds, grids, reference planes and reporting rules, with clause citations.

The standards skill summarises requirements in its own words and cites clauses. It does not replace the standards. Buy them from your national standards body or from the IES.


Troubleshooting

Symptom Likely cause Fix
Every call is refused with a path error RAD_MCP_ROOTS is not set in the server's environment Set it in your shell profile and restart the client. For Claude Desktop, set it in the config's env.
RADIANCE_NOT_FOUND Radiance is not on the server's PATH Add Radiance's bin folder to PATH, or call radiance_status(bin_dir="/usr/local/radiance/bin").
The server closes at once (from a clone, macOS) The editable install's .pth file carries the macOS hidden flag, and Python 3.11 skips it chflags nohidden .venv/lib/python3.*/site-packages/*.pth
uvx: command not found uv is not installed or not on PATH Install uv, or use the absolute path to uvx in the client config.
Luminaires read about 1000× too dim when you run Radiance by hand Some Radiance builds' stock source.cal drops a photometric multiplier Inside the server this is handled: it writes its own corrected source.cal to the project.

Known limits

  • Annual glare (annual DGP) is not built. run_glare computes DGP for one view at one moment.
  • LM-83-22 is selectable with lm83_edition="LM-83-22", but a few of its rules are not applied yet, such as the 150 lux threshold for less critical tasks and the dirt and mullion factors. Every affected result lists the rules it did not apply.
  • run_point_in_time and the other inline tools run synchronously. Only run_annual is a background job.
  • Windows is not tested.
  • Some advisory messages and code comments cite internal design notes (docs/..., ruling numbers such as R864) that are not part of this repository. They are references, not instructions; the behaviour they describe is what the code does.

Licence and third-party material

rad-mcp is released under the MIT licence.

  • Radiance is not included. It is developed by Lawrence Berkeley National Laboratory and distributed under the Radiance Software License; install it yourself.
  • src/rad_engine/data/source.cal is derived from Radiance 6.1a's lib/source.cal. It keeps its original copyright notice, and the Radiance licence is in LICENSES/Radiance-License.txt.
  • Short quotations of Radiance manual pages in code comments are attributed in place and fall under the same Radiance licence. The quality presets follow Greg Ward's "rpict options" note in the Radiance documentation.
  • Standards. Requirements derived from EN 12464-1, EN 12464-2, EN 17037, EN 13363, EN 15193 and IES LM-83 are encoded as numbers with clause citations and short attributed quotations. No standard is reproduced. The MIT licence covers this project's code and text, not the standards themselves.
  • The radiance-simulation-workflow skill adapts command sequences from A. McNeil's LBNL tutorials The Three-Phase Method for Simulating Complex Fenestration with Radiance and the five-phase method tutorial, and credits them in place.

Made by Antonis Kontadakis. Issues and pull requests are welcome.

SIMILAR PLUGINS