rad-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:
rpictrenders, falsecolor luminance and illuminance maps, daylight glare probability (DGP) withevalglare, 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)
- Requirements
- Configure
- Install for other clients
- How it works
- A typical study
- How the annual method is chosen
- The tools
- The project folder
- Design rules
- The bundled skills
- Troubleshooting
- Known limits
- Licence and third-party material
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.objin 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
advisorieslist with a stable code, such asDEFAULTS_SUBSTITUTEDorDF_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_ROOTSbefore 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 reachoconv.
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_glarecomputes 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_timeand the other inline tools run synchronously. Onlyrun_annualis a background job.- Windows is not tested.
- Some advisory messages and code comments cite internal design notes (
docs/..., ruling numbers such asR864) 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.calis derived from Radiance 6.1a'slib/source.cal. It keeps its original copyright notice, and the Radiance licence is inLICENSES/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-workflowskill 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.