Skip to main content
Glama
blanders2
by blanders2
README.md
# centauri-mcp

MCP server for the **Elegoo Centauri Carbon** 3D printer, built on the SDCP 3.0
protocol as documented by the [OpenCentauri](https://github.com/OpenCentauri/OpenCentauri)
project (`docs/software/api.md`). Works over the local network — no cloud.

## Features

**Read-only (no approval needed)**

| Tool | Description |
|---|---|
| `discover_printers` | UDP broadcast discovery (port 3000) |
| `get_status` | Temps, position, fans, lights, live print progress |
| `get_attributes` | Firmware, build volume, capabilities, storage |
| `list_files` | Files on `/local/` or `/usb/` storage |
| `get_print_history` | Past jobs with decoded failure reasons + fixes |
| `get_print_stats` | Success rate, print hours, failure-cause breakdown |
| `get_timelapse` | Per-job timelapse video URL / MP4 download |
| `get_recent_events` | Errors/notices pushed by the printer |
| `decode_error` | SDCP error code → cause + suggested fix |
| `get_monitor_status` | Monitor state and log |

**Control (annotated so MCP clients require approval)**

| Tool | Description |
|---|---|
| `start_print` | Start a file; `start_layer` resumes a failed print mid-way |
| `pause_print` / `resume_print` | Pause/resume the active job |
| `stop_print` | Cancel (irreversible; requires `confirm=true`) |
| `skip_preheating` / `stop_material_feeding` | Phase controls |
| `set_speed_profile` | silent/balanced/sport/ludicrous or a raw % |
| `set_fan_speeds` | Part-cooling / auxiliary / chamber fans individually |
| `set_timelapse` | Enable/disable timelapse recording |
| `set_printer_name` | Rename the printer |
| `upload_gcode` / `send_and_print` | MD5-verified HTTP upload (± auto-start) |
| `delete_files` | Permanent delete (requires `confirm=true`) |
| `get_snapshot` | Chamber-camera JPEG (toggles the single video stream slot) |
| `start_monitor` / `stop_monitor` | Start/stop the detached watcher (`stop_after_print` for one-shot) |
| `set_pushover_credentials` | Save Pushover keys for the watcher |
| `set_notification_settings` | Global priority and sound for all alerts |
| `get_notification_settings` | Current settings + all valid options (read-only) |
| `send_test_notification` | Verify the notification path end to end |

## Notification priority and sound

One global setting covers every alert the watcher sends — there is no
per-event configuration:

```
set_notification_settings(priority=1, sound="cosmic")
```

Either argument may be given alone; the other is left unchanged. Settings are
re-read on every send, so a running watcher picks up changes without a
restart.

| Priority | Behavior |
|---|---|
| -2 | Lowest — no notification, badge only |
| -1 | Low — no sound or vibration |
| 0 | Normal — default alert |
| 1 | High — bypasses quiet hours |
| 2 | Emergency — repeats until acknowledged (`retry`/`expire` added automatically) |

Sounds: `pushover`, `bike`, `bugle`, `cashregister`, `classical`, `cosmic`,
`falling`, `gamelan`, `incoming`, `intermission`, `magic`, `mechanical`,
`pianobar`, `siren`, `spacealarm`, `tugboat`, `alien`, `climb`, `persistent`,
`echo`, `updown`, `vibrate`, `none`.

With nothing set, priority falls back to per-event defaults (pause and printer
errors high, stop and complete normal) and the sound is your Pushover account
default. Setting a global priority overrides those defaults everywhere.

Environment equivalents: `PUSHOVER_PRIORITY`, `PUSHOVER_SOUND` (the config
file wins if both are present).

## The watcher

`start_monitor` spawns **`centauri_mcp.watcher` as a detached process**. It
outlives the MCP server and your client session, so alerts still arrive hours
into a print with nothing open.

It holds a WebSocket to the printer and reacts to *every pushed status
message* rather than polling a cache — a brief `Complete -> Idle` transition
cannot slip between samples. Notifications fire on:

| Event | Pushover priority |
|---|---|
| Print paused (with reason when the printer reports one) | 1 |
| Print stopped | 0 |
| Print complete | 0 |
| Printer error pushed on `sdcp/error` | 1 |

Each carries filename, layer, percent complete, and a camera snapshot unless
started with `with_snapshots=false`.

### Continuous vs one-shot

By default the watcher is **continuous**: after notifying you it keeps
running, so subsequent prints are covered too. It stops only on
`stop_monitor`, the stop flag, a kill, or a reboot.

Pass `stop_after_print=true` for **one-shot** mode — it shuts itself down once
the print reaches Complete or Stopped, after the notification has been sent. A
pause does *not* end it, since the print can still resume.

```
start_monitor(stop_after_print=True)
```

Run it standalone (e.g. from Task Scheduler at logon, so it covers prints
started from the printer's own screen):

```
python -m centauri_mcp.watcher --ip 192.168.1.50
```

```
python -m centauri_mcp.watcher --ip 192.168.1.50 --exit-on-complete
```

State lives in `~/.centauri-mcp/`:

| File | Purpose |
|---|---|
| `watcher_state.json` | Live state + heartbeat, read by `get_monitor_status` |
| `watcher.log` | Human-readable event log |
| `watcher.pid` | Liveness check / stop target |
| `watcher.stop` | Touch to request a graceful exit |
| `pushover.json` | Credentials, when not supplied via env |

Override the directory with `CENTAURI_MCP_HOME`. It deliberately avoids
`%LOCALAPPDATA%`: the Microsoft Store build of Python virtualizes AppData into
a per-package sandbox, which hides these files from you and from any other
interpreter.

## Install

```
git clone https://github.com/blanders2/centauri-mcp.git
cd centauri-mcp
pip install -e .
```

Register with Claude Code:

```
claude mcp add centauri-carbon -- python -m centauri_mcp.server
```

## Configuration (environment variables, all optional)

- `CENTAURI_IP` — printer IP; skips discovery (recommended if your printer has
  a DHCP reservation). Without it, the first discovered printer is used.
- `PUSHOVER_TOKEN` / `PUSHOVER_USER` — enables phone notifications from the
  watcher (print complete / paused / stopped / errors, with camera snapshot
  attached). The detached watcher inherits these from the MCP server; to run
  it independently at boot, use `set_pushover_credentials` instead, which
  writes `~/.centauri-mcp/pushover.json`.
- `CENTAURI_MCP_HOME` — override the watcher's state directory.

Example registration with env vars:

```
claude mcp add centauri-carbon -e CENTAURI_IP=192.168.1.50 -e PUSHOVER_TOKEN=xxx -e PUSHOVER_USER=yyy -- python -m centauri_mcp.server
```

## Safety design

- Tools that can affect a running print carry MCP annotations
  (`readOnlyHint: false`, and `destructiveHint: true` for stop/delete), so
  clients prompt for approval before running them.
- `stop_print` and `delete_files` additionally require an explicit
  `confirm=true` argument — a bare call refuses and explains why.
- The dangerous reverse-engineered config G-codes (`M8803`/`M8807`, which can
  brick the printer) are deliberately **not** exposed.
- The printer allows only **one** concurrent MJPEG stream; `get_snapshot`
  enables the stream, grabs one frame, and releases it.

## Protocol notes

- WebSocket JSON on `ws://<ip>:3030/websocket`; UDP discovery `M99999` on
  port 3000; MJPEG camera on port 3031; HTTP multipart upload with MD5 check.
- Several SDCP field names are misspelled in the protocol itself
  (`CurrenCoord`, `RelaseFilmState`, ...) — the client handles both spellings.