Print on your Formlabs from Claude Code

One plugin gives Claude the tools and the know-how to take STL files to a supported, validated job on your Form or Fuse printer. Install is one line; PreFormServer is fetched from Formlabs and signature-checked for you.

"Prep ~/parts/bracket.stl for the Form 4 in Black V5 and save it as ~/jobs/bracket.form."

See it work: spin the real supported part, replay the run, and compare seven materials, a six-up batch and a Fuse 1+, all from real PreForm runs on a downloaded bracket.

Install

You need Node.js 20+ and Claude Code. Then one line:

# macOS / Linux
curl -fsSL https://mkebiclioglu.github.io/formlabs-claude-skills/install.sh | sh
# Windows (PowerShell)
irm https://mkebiclioglu.github.io/formlabs-claude-skills/install.ps1 | iex

The script adds the plugin to Claude Code and downloads PreFormServer (Formlabs' headless PreForm, about 170 MB) from Formlabs, verifying Formlabs' code signature before installing it into a folder you own. No sudo, no Python, nothing global. Read the script first if you like; it is short.

Or, inside Claude Code

/plugin marketplace add mkebiclioglu/formlabs-claude-skills
/plugin install formlabs@formlabs-claude-skills
/formlabs:setup

/formlabs:setup notices that PreFormServer is missing, asks you, and installs it through the install_preform_server tool. Same download, same signature check.

First run: the plugin's MCP server is fetched by npx the first time Claude Code starts it (a few seconds), and PreFormServer's first launch can take up to a minute while macOS verifies the app. After that both start in seconds. Already have PreFormServer.app in /Applications? It is used as is.

Use it

Just ask. Claude picks the right skill from plain language, or you can invoke one directly.

Every flow ends with a summary: printer, material and layer height, print time, resin or powder usage, validation warnings, and the files written. Sending a job to a printer always waits for your explicit yes.

No printer yet? PreFormServer ships a built-in virtual printer for every model (list_devices shows them as VIRTUAL: Form 4, Form 4L, Fuse 1+ and so on). "Send it to the virtual Form 4" runs the whole job upload without hardware and comes back with a job id, so a workflow can be rehearsed end to end. For the network path too, preform-linux's simulated printer is a fake Form 4 on the LAN that PreFormServer discovers by IP, lists with tank and cartridge, and uploads real jobs to. The virtual and simulated SLS printers refuse jobs in the current PreFormServer, so those dry runs are SLA only.

Fuse X1: PreFormServer 3.63.0 prepares jobs for it (FUSX-1-0, Nylon 12 GF FLP12G01 at 0.11 mm, the one setting it ships) but leaves the family out of its materials list; list_printer_types adds it with an unlisted note. No other material and no auto_pack yet; models stay where import_model places them.

What's inside

/formlabs:setup

Checks Node, the MCP server and PreFormServer; installs PreFormServer with your OK.

/formlabs:prep

Import, orient, support, lay out, validate, drain holes, estimate, save a .form plus a preview PNG.

/formlabs:print

The same pipeline, then uploads to your printer after you confirm.

Under the skills sits the formlabs-local-mcp server: 45 typed tools covering scenes, models, orientation, supports, layout and packing, hollowing, labels, drain holes, every validation check (cups, minima, supportedness, thin walls, interferences), print-time estimates, screenshots, printer discovery and printing, plus a verified PreFormServer installer. It tracks Formlabs Local API 0.9.30.

You │ "prep this STL for the Form 4" ▼ Claude Code ──► /formlabs:prep (skill: the workflow and the judgment calls) │ ▼ formlabs MCP server (typed tools, path guard rails) │ HTTP on localhost ▼ PreFormServer (Formlabs' headless PreForm, installed and started on demand) │ ▼ your printer

Configuration

None needed when PreFormServer is in /Applications. Everything else is an environment variable. For the plugin, put them in the env block of ~/.claude/settings.json; Claude Code passes that block to every subprocess, the MCP server included:

{
  "env": {
    "FORMLABS_ALLOWED_PATHS": "/Users/me/prints:/Volumes/Parts",
    "FORMLABS_USERNAME": "me@example.com",
    "FORMLABS_PASSWORD": "..."
  }
}
VariableDefaultWhat it does
PREFORM_SERVER_PATHauto-detectedPath to the PreFormServer executable if it lives somewhere unusual.
PREFORM_SERVER_PORT44388Port PreFormServer listens on.
PREFORM_SERVER_URLhttp://127.0.0.1:44388Connect to a PreFormServer you run yourself instead of spawning one.
PREFORM_SPAWN1Set to 0 to never start PreFormServer, only connect.
PREFORM_STARTUP_TIMEOUT120Seconds to wait for PreFormServer to come up.
PREFORM_POLL_TIMEOUT600Longest a single operation (supports, packing, upload) may take.
PREFORM_TELEMETRY0PreFormServer telemetry is off when spawned; 1 allows it.
FORMLABS_ALLOWED_PATHShome directoryDirectories Claude may read models from and write files to. Colon-separated (semicolon on Windows).
FORMLABS_ALLOW_HIDDEN_PATHS0Allow paths through dot-directories like ~/.cache.
FORMLABS_USERNAME, FORMLABS_PASSWORDunsetFormlabs account for remote printing, Fleet Control and Dashboard printers. FORMLABS_ACCESS_TOKEN works too.
FORMLABS_ALLOW_REMOTE_LOGIN0Allow login against a non-loopback PREFORM_SERVER_URL.
PREFORM_REMOTE_HOSTunsetRun PreFormServer on another machine over ssh, e.g. me@studio-mac.local. See Linux and remote.
PREFORM_REMOTE_PORT, PREFORM_REMOTE_SERVER_PATH, PREFORM_REMOTE_SPAWN22, well-known paths, 1ssh port, PreFormServer path on the remote host, and whether to start it there or only tunnel.
PREFORM_LAUNCHERwine on LinuxCommand prefix to start PreFormServer, e.g. xvfb-run -a wine.
PREFORM_SERVER_PATH_STYLEautowine writes file paths as Wine's Z:/... view of this machine; native sends them as-is. Auto picks wine when the server starts PreFormServer through Wine.
PREFORM_PATH_MAPunsetlocal=remote pairs, comma separated, for a PreFormServer that mounts your directories elsewhere, e.g. ~/jobs=Z:/jobs for the preform-linux container.

Other MCP clients

The MCP server works on its own in Claude Desktop, Cursor, VS Code, Windsurf and anything else that speaks MCP over stdio. You lose the skills but keep every tool. It only needs Node.js. Add this to the client's MCP config (claude_desktop_config.json, .cursor/mcp.json, and so on):

{
  "mcpServers": {
    "formlabs": {
      "command": "npx",
      "args": ["-y", "formlabs-local-mcp@1.0.8"]
    }
  }
}

Environment variables go in an env object next to args. For Claude Code without the plugin:

claude mcp add --scope user formlabs -- npx -y formlabs-local-mcp@1.0.8

Then ask for health_check; if PreFormServer is missing the server offers to install it. From a shell: npx -y formlabs-local-mcp@1.0.8 install-preform, and doctor instead of install-preform shows what is installed and whether Formlabs has a newer release. The package is published on npm with signed build provenance from GitHub Actions.

Linux and remote

Formlabs ships PreFormServer for macOS and Windows only. On Linux the server can talk to PreFormServer running under Wine, in a container or on the same machine, or drive one on a Mac or Windows box over ssh.

Container: preform-linux (recommended for servers and automation)

preform-linux runs the Windows build of PreFormServer under Wine and Xvfb in a container: headless, no GPU, MIT, prebuilt as ghcr.io/mkebiclioglu/preform-linux (the image holds no Formlabs software; PreFormServer is downloaded from Formlabs and signature-checked on first start). Printers are reached by IP address or through a Formlabs account, both set with environment variables:

git clone https://github.com/mkebiclioglu/preform-linux.git && cd preform-linux && mkdir jobs
PREFORM_PRINTERS=10.0.0.21,10.0.0.22 docker compose up   # FORMLABS_USERNAME/PASSWORD or a Docker secret for Fleet Control

Point the MCP server at it and tell it how the container sees your files:

PREFORM_SERVER_URL=http://127.0.0.1:44388
PREFORM_SERVER_PATH_STYLE=wine
PREFORM_PATH_MAP=/home/me/preform-linux/jobs=Z:/jobs

Files under the mapped directory are sent as Z:/jobs/...; every tool then works as on macOS. The container probes the listed printers as soon as it is up (and every ten minutes after) and logs in to your account, so list_devices shows them; print_to_printer takes a printer IP or, after login, a Fleet Control queue. LAN discovery by mDNS does not work under Wine, which is why printers are listed by address. A pipeline needs no MCP client at all: the same Local API answers on port 44388 for curl or any HTTP client, and examples/smoke.sh in that repository is a complete import, support, estimate, save run in bash.

Wine on this machine

install-preform fetches the Windows build and verifies its Authenticode signature with osslsigncode; the server starts it through wine with headless defaults and writes file paths as Z:/home/me/... automatically. Needs Wine 11.5 or newer from WineHQ (distro Wine 9.0 cannot load PreFormServer 3.63.0) and a display: PREFORM_LAUNCHER="xvfb-run -a wine" on a headless box. A weekly CI job runs the smoke test this way.

Remote mode

Run PreFormServer on any Mac or Windows machine on your network and let the MCP server on Linux drive it over ssh:

PREFORM_REMOTE_HOST=me@studio-mac.local

One ssh session, keys only, forwards a loopback port and starts PreFormServer on the remote machine so it stops when the MCP server does. Input files are copied over with scp into a per-session staging folder under the remote user's home; .form files and screenshots are copied back. The remote host needs PreFormServer installed (run install-preform there) and a POSIX shell over ssh. For a Windows remote host set PREFORM_REMOTE_SPAWN=0 and start PreFormServer yourself. Remote mode also works from a Mac to another Mac, for instance a Mac mini next to the printers.

Security

PreFormServer is a plain-HTTP server with no authentication that reads and writes files as you. The MCP server is built to keep that surface small:

One thing to know: PreFormServer itself listens on all network interfaces, not only loopback, and offers no option to change that. On a shared or untrusted network keep your OS firewall on (macOS: System Settings, Network, Firewall) so other machines cannot reach port 44388.

Troubleshooting

SymptomCause and fix
No formlabs tools; /mcp shows the server failedNode.js 20+ is missing or not on PATH. Install it, then restart Claude Code.
health_check says PreFormServer is not installedRun /formlabs:setup and say yes to the install, or run the one-line installer above.
First call hangs for a minuteNormal on the first run: npx fetches the server, then macOS verifies PreFormServer. Later runs are fast.
INPUT_ERROR: Scene type not supportedThat printer, material and layer height combination does not exist. Claude should call list_materials and use one of the listed settings.
IMPORT_PRODUCED_EMPTY_SCENEThe mesh could not be parsed even with repair. Open it in PreForm to see why.
Path "is outside the allowed directories"By design. Move the file under your home directory or extend FORMLABS_ALLOWED_PATHS.
Printer not foundRun discovery again, check the printer is on the same network, or give its IP address.
Installer says the signature check failedBy design it refuses to install. Report it; do not bypass. Formlabs may have changed signing identity, or the download was tampered with.
401 on remote printingSet FORMLABS_USERNAME and FORMLABS_PASSWORD, then ask Claude to log in.

Still stuck? Run /formlabs:setup, or open an issue on formlabs-claude-skills (plugin and skills) or formlabs-local-mcp (tools and PreFormServer).

Contribute

Both repositories are MIT and take issues and pull requests. The most useful things you can send are a transcript of a skill doing something wrong, and a note on which printer, material and OS you used, so behaviour can be fixed where it matters.

Updating

/plugin update formlabs@formlabs-claude-skills picks up new skills and the MCP server version the plugin pins. For a new PreFormServer release, ask Claude to run install_preform_server again (or run install-preform from a shell): it is a no-op when you already have the latest.