beagleprint-mcp
README.md
# beagleprint-mcp
[MCP](https://modelcontextprotocol.io) server for 3D printers connected to a **Mintion Beagle** ("Beagle Print")
print server. The printer (Marlin firmware) is attached to the Beagle over USB, and this server talks to the
Beagle's own local API: no cloud, no OctoPrint or Moonraker needed.
Any MCP client can use it to check the printer, look through the camera, query the firmware, upload G-code and
start, pause, resume or stop prints.
Unofficial project, not affiliated with or endorsed by Mintion.
## Tools
| Tool | Kind | What it does |
|---|---|---|
| `get_status` | read | Connection and print state (printing, idle, paused, finished), heating, fan, filament sensor |
| `get_progress` | read | Current or last job: file, percent, time left and elapsed, layer, height |
| `get_temperatures` | read | Current and target nozzle and bed temperatures |
| `list_files` | read | G-code files stored on the Beagle |
| `camera_snapshot` | read | Current camera picture (JPEG image) |
| `query_gcode` | read | Sends a **query** G-code (`M115`, `M503`, `M105`, `M114`, `M420 V`, `M851`...) and returns the firmware reply |
| `send_gcode` | **destructive** | Sends any G-code line (motion, heating, calibration, `M500`...) and returns the reply |
| `upload_file` | action | Uploads a local `.gcode` file to the Beagle (does not print it) |
| `start_print` | **destructive** | Starts printing a file stored on the Beagle |
| `pause_print` | action | Pauses the current print |
| `resume_print` | action | Resumes a paused print |
| `stop_print` | **destructive** | Cancels the current print |
Safety rules built into the server:
- `query_gcode` only accepts a fixed list of Marlin query commands with no arguments that change anything
(see [`gcode.py`](src/beagleprint_mcp/gcode.py)). Everything else must go through `send_gcode`, which is marked
as destructive so clients can ask for confirmation.
- While the printer is printing, only `get_*`, `list_files`, `camera_snapshot`, `pause_print` and `stop_print`
work. The console, uploads and starting another print are refused.
- Tools carry MCP annotations (`readOnlyHint`, `destructiveHint`), so a client can auto-approve the read
tools and ask before everything else.
## Requirements
- A Mintion Beagle on your network with the printer connected over USB.
- [uv](https://docs.astral.sh/uv/getting-started/installation/) (or Python 3.10+ and pip).
## Configuration
| Variable | Required | Default | Meaning |
|---|---|---|---|
| `BEAGLE_HOST` | yes | — | IP address or host name of the Beagle (shown in the Beagleprint app or your router) |
| `BEAGLE_USER` | no | `admin` | Beagle user (factory default) |
| `BEAGLE_PASSWORD` | no | `admin` | Beagle password (factory default) |
Check the connection:
```bash
BEAGLE_HOST=192.168.1.50 uvx --from git+https://github.com/ingenioustoad/beagleprint-mcp beagleprint-mcp --check
```
## Installation
### Claude Code
```bash
claude mcp add beagleprint -s user -e BEAGLE_HOST=192.168.1.50 -- uvx --from git+https://github.com/ingenioustoad/beagleprint-mcp beagleprint-mcp
```
To let the read tools run without asking while every action still asks, add this to `permissions.allow` in
`~/.claude/settings.json`:
```json
"mcp__beagleprint__get_status",
"mcp__beagleprint__get_progress",
"mcp__beagleprint__get_temperatures",
"mcp__beagleprint__list_files",
"mcp__beagleprint__camera_snapshot",
"mcp__beagleprint__query_gcode"
```
### Claude Desktop, Cursor and other clients
Add the server to the client's MCP configuration (`claude_desktop_config.json`, `.cursor/mcp.json`...):
```json
{
"mcpServers": {
"beagleprint": {
"command": "uvx",
"args": ["--from", "git+https://github.com/ingenioustoad/beagleprint-mcp", "beagleprint-mcp"],
"env": { "BEAGLE_HOST": "192.168.1.50" }
}
}
}
```
Add `BEAGLE_USER` and `BEAGLE_PASSWORD` to `env` if you changed them on the Beagle.
## How it talks to the Beagle
The Beagle runs a small web server on port 80. This server uses the same endpoints as the Beagle's web page:
- **Commands**: `POST /set3DPiCmd` with a JSON body such as
`{"pro": "get_prconnectstate", "cmd": 312, "user": "admin", "pwd": "admin"}`. The reply is JSON in the
response body; `result` is `0` on success.
| cmd | pro | Purpose |
|---|---|---|
| 312 | `get_prconnectstate` | State: `connect_state`, `print_state` (101 printing, 102 idle, 103/104 paused, 105 finished), `filament_open` |
| 318 | `get_prgresp` | Progress: `file_name`, `progress`, `time_left`, `time_cost`, `layerIndex`, `printingHeight` |
| 302 | `get_temp` | Temperatures: `tempture_noz`, `tempture_bed`, `des_tempture_noz`, `des_tempture_bed` |
| 320 | `pri_file` + `operate_cmd: 120` | File list (`filesList`: `name`, `len`, `time`) |
| 313 | `pr_start` + `filename` | Start a print |
| 314 / 315 / 317 | `pr_pause` / `pr_resume` / `pr_off` | Pause / resume / stop |
| 270 | `set_gcode_cmd` + `gcodeCmd` | Send one G-code line to the printer |
- **Upload**: `POST /WebUploadFormFile`, multipart with a single `file` field.
- **Camera**: `GET /images/snapshot0.jpg`.
- **Console**: a websocket on port **7685** with subprotocol `lws-serial-terminal` mirrors the serial line:
`Send: ...` for what the Beagle sends to the printer and `Recv: ...` for the printer's answers. Command 270
only acknowledges the request, so to read a reply the server opens the console first, sends the G-code,
waits for its `Send:` echo and collects the `Recv:` lines up to `ok`. It skips the replies to the `M105`
the Beagle sends every couple of seconds to poll temperatures.
## Development
```bash
uv sync
uv run pytest
```
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues