Skip to content

Headless & MCP (drive Studio from an agent)

The scripting API runs inside a browser. The headless engine runs that same API with no browser of your own - so an AI agent, a CI job, or a shell script can build, mesh, solve, query and see a model.

A headless Chromium runs the real Studio (same WASM solver, same THREE.js viewport, same window.CivilKit / window.__fem), so nothing can diverge from what a person sees - and the screenshot tool returns the actual 3D viewport as an image. Callers get eyes, not just numbers.

Why browser-backed, not pure Node

The solver, the section geometry and the renderer are the same code the app ships. Running them in a headless browser means the agent path is always identical to the user path - and it can take a screenshot of exactly what you'd see on screen.

Requirements

  • Node 18+
  • Playwright with a Chromium build: npm i -g playwright && npx playwright install chromium (or a local dependency).

From an MCP client (Claude Desktop, Cursor, Cline)

Add the server to your client's MCP config. For Claude Desktop that's claude_desktop_config.json:

json
{
  "mcpServers": {
    "civilkit": {
      "command": "node",
      "args": ["/ABS/PATH/TO/platform/mcp/server.mjs"],
      "env": { "NODE_PATH": "/ABS/PATH/TO/npm-global/node_modules" }
    }
  }
}

NODE_PATH lets the server find a globally-installed Playwright - set it to the output of npm root -g, or omit it if Playwright is a local dependency. Restart the client and the civilkit tools appear. The first tool call boots the headless browser (~5-10 s once); subsequent calls are fast.

The tools

ToolWhat it does
load_sample / load_modelLoad a built-in sample by key, or a full model object
add_node / add_member / set_supportBuild geometry + supports headlessly
mesh_allMesh every detected bay into plates (plane: all / floor / wall, elementSize in m)
solveRun the analysis; returns a results summary
get_reactions / summary / validateRead results / model stats / model-health issues
run_scriptEscape hatch: run arbitrary JS in the page (CivilKit + fem in scope, return a value)
screenshotPNG of the 3D viewport (optional renderMode: solid | lines) - the "eyes"

run_script is the full power: anything on window.CivilKit - including assignSection, windLoads, seismicLoads, loadCombinations and report - is callable from it, so the entire design workflow runs headlessly.

From a shell (no MCP client)

The same engine has a one-shot CLI:

sh
node platform/mcp/cli.mjs sample portalFrame          # load a sample, print summary
node platform/mcp/cli.mjs solve model.json            # load a model file, solve, print summary
node platform/mcp/cli.mjs mesh floor 3                # mesh all floor bays at 3 m elements
node platform/mcp/cli.mjs script "return CivilKit.summary()"
node platform/mcp/cli.mjs shot out.png lines          # screenshot the viewport to a PNG

Example: design a building headlessly

This is the office building from examples/office-building.mjs - generated, given real sections, loaded to AS/NZS, solved and checked, entirely through the engine's run_script (all one script):

js
// (inside run_script) - the model is already loaded
const F = window.__fem, C = window.CivilKit;
// classify + assign real catalogue sections
await C.assignSection(columnIds, '310UC158');
await C.assignSection(beamIds,   '610UB101');
// AS/NZS code loads + combinations
C.windLoads({ region: 'A', terrain: '3' });   // 1170.2
C.seismicLoads({ zone: '0.08', soil: 'De' });  // 1170.4
C.loadCombinations();                          // 7 AS/NZS 1170.0 combinations
const summary = await C.solve();               // solves every combination
return { checks: summary.memberCapacities, reportKB: Math.round(C.report().length / 1024) };

Then screenshot returns the rendered, solved building - real I-section profiles, coloured by the deflection contour.

Files

  • platform/mcp/engine.mjs - the headless-Studio driver (static file server + Playwright + tool functions + screenshot). Shared by both entry points.
  • platform/mcp/server.mjs - minimal MCP stdio server (newline-delimited JSON-RPC 2.0, no SDK dependency).
  • platform/mcp/cli.mjs - one-shot shell wrapper over the same engine.

Security

run_script runs arbitrary JavaScript

run_script executes any JS in the page - it's a local automation surface, the same trust level as devtools on your own model. Don't expose this server to untrusted callers as-is. For a multi-tenant or remote deployment, put it behind the version/live Worker's auth model (a master token plus per-project scoped JWTs) and consider disabling run_script for scoped tokens.