An MCP (Model Context Protocol) server that gives AI assistants (Claude, etc.) full real-time control and visibility over 3D printers running Klipper firmware via the Fluidd / Moonraker API stack.
Also includes a standalone local monitor server (monitor_server.py) with a live
web UI and multi-channel alerting β push, SMS, email, and iMessage β that fires even
when the browser tab is closed.
| Tool | Description |
|---|---|
get_printer_status |
Full real-time status β state, temps, progress, ETA, position |
get_temperatures |
Hotend, bed, and all extra sensor temperatures |
get_print_job_status |
Current job file, layer, progress, elapsed & ETA |
get_klippy_status |
Klippy firmware health and error messages |
get_moonraker_status |
Moonraker API server health |
get_active_alerts |
All active alerts in one call |
get_printer_logs |
Recent Klippy log lines with error highlighting |
| Tool | Description |
|---|---|
check_failure_detection |
Heuristic checks for spaghetti, layer shifts, thermal anomalies, under-extrusion, and stalls |
get_active_alerts |
Continuous alert summary β thermal runaway, Klippy errors, clog detection |
Camera integration: Use
get_camera_snapshot_urlto retrieve live snapshot/stream URLs from Fluidd-configured webcams. For AI-powered visual spaghetti detection, pipe the snapshot URL to a vision model (e.g., Claude's image analysis).
| Tool | Description |
|---|---|
get_camera_snapshot_url |
Snapshot + stream URL(s) for all configured webcams |
| Tool | Description |
|---|---|
start_print |
Start printing a file from storage |
pause_print |
Pause the current print |
resume_print |
Resume a paused print |
cancel_print |
Cancel the current print |
emergency_stop |
Immediate M112 hardware stop |
| Tool | Description |
|---|---|
get_print_queue |
View the Moonraker job queue |
add_to_queue |
Add a file to the print queue |
remove_from_queue |
Remove a job by ID |
list_print_files |
Browse files on printer storage |
get_print_history |
Recent job history with durations and results |
| Tool | Description |
|---|---|
calculate_print_cost |
Calculate material + power cost and recommended sale price with configurable markup |
| Tool | Description |
|---|---|
set_temperature |
Set hotend or bed temperature |
send_gcode |
Send any raw G-code command |
restart_klippy |
Restart Klippy service |
restart_firmware |
Firmware restart |
- Docker Desktop with MCP Toolkit enabled
- A 3D printer running Klipper + Moonraker (exposed on your network)
- Fluidd or Mainsail as the front-end (optional β Moonraker API is the interface)
- Moonraker API key (if authentication is enabled)
- Python 3.7+ (for the monitor server β no extra packages needed)
Run the interactive setup script β it handles all steps below, asks for confirmation before making changes, and works on macOS, Linux, and Windows (Git Bash).
git clone https://github.com/block0ps/fluidd-klipper-mcp.git
cd fluidd-klipper-mcp
chmod +x setup.sh
./setup.shOr via one-liner (no clone needed):
curl -fsSL https://raw.githubusercontent.com/block0ps/fluidd-klipper-mcp/main/setup.sh | bashgit clone https://github.com/block0ps/fluidd-klipper-mcp.git
cd fluidd-klipper-mcp
docker build -t fluidd-klipper-mcp-server .docker mcp secret set PRINTER_HOST="http://192.168.1.100"
docker mcp secret set PRINTER_API_TOKEN="your-moonraker-api-key"
# Cost calculation config (optional β defaults shown)
docker mcp secret set FILAMENT_COST_PER_KG="25.0"
docker mcp secret set POWER_COST_PER_KWH="0.12"
docker mcp secret set PRINTER_WATTS="150.0"
docker mcp secret set MARKUP_PERCENTAGE="30.0"
docker mcp secret listmkdir -p ~/.docker/mcp/catalogs
cp custom.yaml ~/.docker/mcp/catalogs/custom.yamlAdd to ~/.docker/mcp/registry.yaml under the registry: key:
registry:
fluidd-klipper:
ref: ""Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"mcp-toolkit-gateway": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"-v", "/Users/YOUR_USERNAME/.docker/mcp:/mcp",
"docker/mcp-gateway",
"--catalog=/mcp/catalogs/docker-mcp.yaml",
"--catalog=/mcp/catalogs/custom.yaml",
"--config=/mcp/config.yaml",
"--registry=/mcp/registry.yaml",
"--tools-config=/mcp/tools.yaml",
"--transport=stdio"
]
}
}
}A standalone Python server that runs locally, serves a live web UI, and dispatches alerts across four channels when anomalies are detected. Polling runs on a background thread β alerts fire even when the browser tab is closed or your screen is locked.
Browsers block direct fetch() calls from file:// or https:// pages to local
http:// printer IPs (CORS + mixed content policy). monitor_server.py acts as a
local proxy: the browser talks to localhost, and Python forwards requests to
Moonraker server-side where CORS rules don't apply.
# Uses default printer at http://10.0.107.158, port 8484
python3 monitor_server.py
# Custom printer URL
python3 monitor_server.py http://192.168.1.100
# Custom printer URL and port
python3 monitor_server.py http://192.168.1.100 9000Then open http://localhost:8484 in your browser.
No dependencies beyond Python's standard library β nothing to pip install.
| Check | Threshold |
|---|---|
| π‘οΈ Thermal anomaly β hotend | Actual vs target divergence > 20Β°C |
| π‘οΈ Thermal anomaly β bed | Actual vs target divergence > 15Β°C |
| π΄ Klippy not ready | Any non-ready firmware state |
| π§΅ Clog / under-extrusion | < 5mm filament used after 5+ min printing |
| βΈοΈ Print stall | No progress after 10+ min printing |
| π Z position anomaly | Z < 0.1mm after 2+ min printing |
| π Print error / cancelled | State change to error or cancelled |
| π Print complete | State change to complete |
On first run, monitor_server.py creates monitor_config.json in the same
directory. Open it, set "enabled": true for each channel you want, fill in the
credentials, then restart the server.
{
"printer_host": "http://10.0.107.158",
"poll_interval_seconds": 1800,
"alert_on_warnings": true,
"ntfy": {
"enabled": false,
"topic": "my-printer-alerts",
"server": "https://ntfy.sh"
},
"twilio": {
"enabled": false,
"account_sid": "",
"auth_token": "",
"from_number": "+1XXXXXXXXXX",
"to_number": "+1XXXXXXXXXX"
},
"email": {
"enabled": false,
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"username": "you@gmail.com",
"password": "your-app-password",
"from_address": "you@gmail.com",
"to_address": "you@gmail.com"
},
"imessage": {
"enabled": false,
"to_number": "+1XXXXXXXXXX"
}
}ntfy.sh is a free, open-source push notification service.
- Install the ntfy app on your phone (iOS / Android)
- In the app, tap Subscribe to topic and enter a unique topic name (e.g.
mahdi-printer-9482) β treat it like a password, anyone who knows it can send to it - In
monitor_config.json:
"ntfy": {
"enabled": true,
"topic": "mahdi-printer-9482",
"server": "https://ntfy.sh"
}Alerts arrive instantly on your phone with priority levels β critical alerts are marked urgent and bypass Do Not Disturb.
Self-hosting: If you prefer to run your own ntfy server, change "server" to your instance URL.
- Sign up at twilio.com (free trial includes test credits)
- From the Twilio Console, copy your Account SID and Auth Token
- Get a Twilio phone number (free with trial)
- In
monitor_config.json:
"twilio": {
"enabled": true,
"account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"auth_token": "your_auth_token",
"from_number": "+15551234567",
"to_number": "+15559876543"
}Trial accounts: Twilio trial numbers can only send to verified phone numbers. Verify your number at Console β Phone Numbers β Verified Caller IDs.
- In your Google Account, go to Security β 2-Step Verification and enable it
- Then go to Security β App Passwords, create a new app password for "Mail"
- Copy the 16-character password (spaces don't matter)
- In
monitor_config.json:
"email": {
"enabled": true,
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"username": "you@gmail.com",
"password": "abcd efgh ijkl mnop",
"from_address": "you@gmail.com",
"to_address": "you@gmail.com"
}Other providers: Change smtp_host and smtp_port for Outlook (smtp.office365.com, 587), Yahoo (smtp.mail.yahoo.com, 587), or any other SMTP provider.
Uses macOS AppleScript to send iMessages via the Messages app already on your Mac.
The Mac running monitor_server.py must be signed into iMessage.
"imessage": {
"enabled": true,
"to_number": "+15551234567"
}to_number accepts a phone number (+15551234567) or an Apple ID email address (you@icloud.com).
Note: macOS may prompt you to grant Terminal (or your Python app) access to Messages the first time. Accept the permission request in System Settings β Privacy & Security β Automation.
Once configured, use the π Test Alerts button in the web UI to fire a test message across all enabled channels before walking away from the printer.
You can also trigger an immediate poll (without waiting for the interval) using the π Poll Now button.
Each unique alert fires once per print job β you won't get spammed with repeated SMS messages if a thermal anomaly persists across multiple poll cycles. The dedup set resets automatically when a new print job starts.
Change poll_interval_seconds in monitor_config.json and restart the server.
Recommended values:
| Print type | Suggested interval |
|---|---|
| Short print (< 2h) | 300 (5 min) |
| Overnight print | 900 (15 min) |
| Multi-day print | 1800 (30 min) |
Both the MCP server and the monitor support an unlimited number of printers. Each printer is polled on its own background thread; alerts, status, and camera feeds are scoped per printer.
Set PRINTER_HOSTS as a JSON array (instead of the single-printer PRINTER_HOST):
[
{"id": "voron", "name": "Voron 2.4", "host": "http://192.168.1.100", "api_token": ""},
{"id": "ender", "name": "Ender 5 Pro","host": "http://192.168.1.101", "api_token": ""},
{"id": "bambu", "name": "Bambu P1S", "host": "http://192.168.1.102", "api_token": ""}
]Via Docker MCP secrets:
docker mcp secret set PRINTER_HOSTS='[{"id":"voron","name":"Voron 2.4","host":"http://192.168.1.100"},{"id":"ender","name":"Ender 5 Pro","host":"http://192.168.1.101"}]'Once set, Claude can target any printer by name or URL in every tool:
"Check the status of the Voron"
"Pause the Ender"
"What's the bed temp on printer http://192.168.1.102?"
"List all my printers" β uses list_printers tool
The list_printers tool shows all configured printers with their IDs and hosts.
./setup.sh
# Step 2 will ask: "Add another printer?" after each oneStep 2 of setup.sh loops until you decline to add another printer. Each printer's name, host, and token is collected and written to both PRINTER_HOSTS (Docker MCP) and monitor_config.json.
Option A β interactive CLI (recommended):
python3 monitor_server.py add-printer
# β or via setup.sh shortcut β
./setup.sh add-printerYou'll be prompted for the printer name and host URL. The entry is written to monitor_config.json and a poll thread starts on the next server restart.
Option B β REST API (while server is running):
curl -X POST http://localhost:8484/api/printers \
-H "Content-Type: application/json" \
-d '{"name": "Bambu P1S", "host": "http://192.168.1.103", "api_token": ""}'The printer is registered immediately and starts polling β no restart required.
Option C β edit monitor_config.json directly:
{
"printers": [
{"id": "voron", "name": "Voron 2.4", "host": "http://192.168.1.100", "enabled": true, "api_token": ""},
{"id": "ender", "name": "Ender 5 Pro","host": "http://192.168.1.101", "enabled": true, "api_token": ""}
]
}Then restart monitor_server.py.
From the web UI:
- Open
http://localhost:8484 - Click β Manage next to the printer tabs
- Click β Edit on any printer to rename it, change its host URL, or update the API token
- Toggle the enable/disable switch to pause monitoring without removing the printer
From the CLI (while server is running):
# Rename
curl -X PATCH http://localhost:8484/api/printers/voron \
-H "Content-Type: application/json" \
-d '{"name": "Voron 2.4 β Garage"}'
# Change host
curl -X PATCH http://localhost:8484/api/printers/ender \
-H "Content-Type: application/json" \
-d '{"host": "http://10.0.0.55"}'
# Disable monitoring (pauses polling without removing)
curl -X PATCH http://localhost:8484/api/printers/bambu \
-H "Content-Type: application/json" \
-d '{"enabled": false}'Changes are written to monitor_config.json immediately and take effect live.
Direct JSON edit: Modify monitor_config.json and restart the server.
| Element | Description |
|---|---|
| Printer tabs | One tab per printer β click to switch the status card |
| Tab dot colour | π’ printing, π‘ paused, π΄ error, β« idle/standby |
| β Manage button | Opens the manage panel (edit, rename, enable/disable, add new) |
| Alert log | Combined across all printers; printer name shown on each entry |
| Camera button | Fetches snapshot from the currently selected printer's webcam |
Alerts from all printers are dispatched over the same channels (ntfy, SMS, email, iMessage). Each notification includes the printer name so you always know which machine fired:
[Voron 2.4] CRITICAL
Thermal anomaly β Bed: target 110Β°C actual 72.3Β°C
Once the MCP server is configured, ask Claude:
- "What's my printer doing right now?"
- "Are there any alerts or errors on the printer?"
- "Show me my print queue"
- "Start printing benchy.gcode"
- "Pause the current print"
- "Check for spaghetti or layer shifts"
- "What's the camera snapshot URL?"
- "How much did this print cost? What should I charge?"
- "Set the bed to 60Β°C"
- "Show me the last 20 log lines"
- "Restart Klippy"
The calculate_print_cost tool uses these configurable parameters:
| Env Var | Default | Description |
|---|---|---|
FILAMENT_COST_PER_KG |
25.0 |
USD per kilogram of filament |
POWER_COST_PER_KWH |
0.12 |
USD per kWh electricity |
PRINTER_WATTS |
150.0 |
Average power draw of your printer |
MARKUP_PERCENTAGE |
30.0 |
Profit margin % on top of costs |
The check_failure_detection and get_active_alerts tools use heuristics against
live Moonraker data (same logic as the monitor server):
| Detection | Method |
|---|---|
| Thermal anomaly | Heater actual vs target divergence > 15Β°C (bed) or 20Β°C (hotend) |
| Under-extrusion / clog | Very low filament usage after 5+ min of printing |
| Layer shift / stall | No Z progress after 10+ min of printing |
| Z position anomaly | Z < 0.1 mm after 2+ min |
| Spaghetti (visual) | Pull camera snapshot URL β analyze with vision AI |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Network β
β β
β Claude Desktop β
β β β
β βΌ β
β MCP Gateway (Docker) β
β β β
β βΌ β
β fluidd-klipper-mcp-server (Docker) β
β β βββββββββββββββββββ β
β β monitor_server.py βββββ€ Background β β
β β (localhost:8484) β Poll Thread β β
β β β ββββββββββ¬βββββββββ β
β β β Alerts β β
β β βββββ π± ntfy push β β
β β βββββ π¬ SMS (Twilio) β β
β β βββββ π§ Email (SMTP) β β
β β βββββ π¬ iMessage β β
β β β β
β ββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ HTTP/REST β
β Moonraker API (printer) β
β β β
β βΌ β
β Klipper Firmware + Fluidd β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export PRINTER_HOST="http://192.168.1.100"
export PRINTER_API_TOKEN="your-key"
python3 fluidd_klipper_server.py
# Test MCP protocol
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | python3 fluidd_klipper_server.py- Add a new
@mcp.tool()function tofluidd_klipper_server.py - Keep the docstring to a single line
- Default all parameters to
""(empty string) - Always return a string
- Add the tool name to
custom.yaml - Rebuild:
docker build -t fluidd-klipper-mcp-server .
- All secrets stored in Docker Desktop secrets β never hardcoded
- Container runs as non-root user (
mcpuser) - Sensitive values (API tokens) are never logged
monitor_config.jsonstores credentials locally β keep it out of version control (it's in.gitignore)- Emergency stop is intentionally available β treat chat access to this server with care
| Issue | Fix |
|---|---|
Connection refused on MCP server |
Verify PRINTER_HOST is reachable from Docker network |
401 Unauthorized on MCP server |
Set PRINTER_API_TOKEN via Docker secrets |
| Tools not appearing in Claude | Rebuild Docker image, restart Claude Desktop |
| Monitor shows "proxy error" | Ensure monitor_server.py is running (python3 monitor_server.py) |
| Push alerts not arriving | Check ntfy topic name matches app subscription; try π Test Alerts |
| SMS not sending | Verify Twilio trial number is verified; check account SID / auth token |
| Email not sending | Use an App Password (not your Gmail login password); ensure 2FA is on |
| iMessage permission denied | Grant Terminal access in System Settings β Privacy & Security β Automation |
| Alerts firing repeatedly | Dedup resets per print job β check if a new job started |
MIT