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.
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.
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.
- "Prep
~/parts/bracket.stlfor the Form 4 in Black V5 and save it as~/jobs/bracket.form." - "Pack these five STLs in
~/parts/batchonto the Fuse 1+ in Nylon 12 and send them to Fuse-Loud-Otter." - "What materials can I use on the Form 4B?"
- "Find printers on my network."
- "Hollow
~/parts/bust.stlwith 2 mm walls, add drain holes, and estimate print time on the Form 3." - "Open
~/jobs/old.form, swap the part for~/parts/v2.stl, and re-estimate."
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.
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": "..."
}
}
| Variable | Default | What it does |
|---|---|---|
PREFORM_SERVER_PATH | auto-detected | Path to the PreFormServer executable if it lives somewhere unusual. |
PREFORM_SERVER_PORT | 44388 | Port PreFormServer listens on. |
PREFORM_SERVER_URL | http://127.0.0.1:44388 | Connect to a PreFormServer you run yourself instead of spawning one. |
PREFORM_SPAWN | 1 | Set to 0 to never start PreFormServer, only connect. |
PREFORM_STARTUP_TIMEOUT | 120 | Seconds to wait for PreFormServer to come up. |
PREFORM_POLL_TIMEOUT | 600 | Longest a single operation (supports, packing, upload) may take. |
PREFORM_TELEMETRY | 0 | PreFormServer telemetry is off when spawned; 1 allows it. |
FORMLABS_ALLOWED_PATHS | home directory | Directories Claude may read models from and write files to. Colon-separated (semicolon on Windows). |
FORMLABS_ALLOW_HIDDEN_PATHS | 0 | Allow paths through dot-directories like ~/.cache. |
FORMLABS_USERNAME, FORMLABS_PASSWORD | unset | Formlabs account for remote printing, Fleet Control and Dashboard printers. FORMLABS_ACCESS_TOKEN works too. |
FORMLABS_ALLOW_REMOTE_LOGIN | 0 | Allow login against a non-loopback PREFORM_SERVER_URL. |
PREFORM_REMOTE_HOST | unset | Run 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_SPAWN | 22, well-known paths, 1 | ssh port, PreFormServer path on the remote host, and whether to start it there or only tunnel. |
PREFORM_LAUNCHER | wine on Linux | Command prefix to start PreFormServer, e.g. xvfb-run -a wine. |
PREFORM_SERVER_PATH_STYLE | auto | wine 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_MAP | unset | local=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:
- Verified installs. The installer only downloads over HTTPS from
downloads.formlabs.comwith Formlabs' release path layout, refuses redirects elsewhere, scans the archive for path traversal before extracting, and checks the code signature before anything is moved into place: Developer ID teamKVPE3R79SRplus notarization on macOS, a valid Authenticode signature from Formlabs on Windows,osslsigncodeon Linux. A failed check leaves the previous install untouched. The one-line install scripts are pinned to a release and read from this site over HTTPS. - Path guard rails. Every file path Claude passes must be absolute, resolve (symlinks included) to somewhere under
FORMLABS_ALLOWED_PATHS(your home directory by default), avoid hidden directories such as~/.ssh, and carry the right extension: models in,.form/.png/.fpsout. A prompt injection cannot turnsave_forminto a way to write arbitrary files. - Credentials never enter the chat.
logintakes no arguments. It reads your Formlabs account from the environment and returns only your username, never tokens. - Loopback only. Credentials are refused for any PreFormServer that is not on this machine unless you opt in.
- Short-lived and quiet. PreFormServer runs only while Claude Code is connected, and its telemetry is switched off.
- Remote mode uses ssh keys only (no password prompts), validates the host string so it can never be parsed as an ssh option, binds the forward to 127.0.0.1, and sanitizes staged file names.
- Small, pinned supply chain. The server has two runtime dependencies and a lockfile; the plugin pins an exact npm version, published from CI with signed provenance; GitHub Actions are pinned to commit SHAs and CodeQL runs on every change. Tools declare MCP annotations, so clients can ask before
print_to_printer,save_form,install_preform_serveror any delete.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
No formlabs tools; /mcp shows the server failed | Node.js 20+ is missing or not on PATH. Install it, then restart Claude Code. |
health_check says PreFormServer is not installed | Run /formlabs:setup and say yes to the install, or run the one-line installer above. |
| First call hangs for a minute | Normal on the first run: npx fetches the server, then macOS verifies PreFormServer. Later runs are fast. |
INPUT_ERROR: Scene type not supported | That printer, material and layer height combination does not exist. Claude should call list_materials and use one of the listed settings. |
IMPORT_PRODUCED_EMPTY_SCENE | The 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 found | Run discovery again, check the printer is on the same network, or give its IP address. |
| Installer says the signature check failed | By 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 printing | Set 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.
- formlabs-claude-skills: the plugin, the skills, the install scripts and this site. CONTRIBUTING.
- formlabs-local-mcp: the MCP server, its 45 tools and the PreFormServer installer. CONTRIBUTING.
- Questions, ideas and "here is what I printed" go in Discussions on either repo. Security reports go through GitHub's private reporting, see each repo's
SECURITY.md.
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.