Skip to main content
Glama
nakomis

nakomis-dragonfruit-mcp

by nakomis
README.md
<p align="center"><img src="docs/logo.png" alt="nakomis-dragonfruit-mcp logo" width="200"></p>

# nakomis-dragonfruit-mcp — headless resin print preparation and slicing with the DragonFruit engine

> **Unofficial.** Not affiliated with or endorsed by the Open Resin Alliance or the
> DragonFruit project.

An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant inspect,
hollow, check and slice resin prints using the engine of
[DragonFruit](https://github.com/Open-Resin-Alliance/DragonFruit), with no GUI.
It wraps `dragonfruit-cli` and a small Rust tool of its own.

## Support

If you find this useful, please consider buying me a coffee:

[![Donate with PayPal](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_SM.gif)](https://www.paypal.com/donate?hosted_button_id=Q3BESC73EWVNN&custom=nakomis-dragonfruit-mcp)

## Table of Contents

<!-- toc -->

- [Status](#status)
  * [Optional Rust backend for `trim_islands`](#optional-rust-backend-for-trim_islands)
- [Architecture Diagram](#architecture-diagram)
- [Repository Layout](#repository-layout)
- [Building and running](#building-and-running)
- [Printers](#printers)
- [Licence](#licence)
- [Architecture Diagrams](#architecture-diagrams)
- [Support](#support)

<!-- tocstop -->

## Status

Early days. The repository is scaffolded; the tools are being built. Planned tools:

- `mesh_info`: triangles, size in mm, volume in ml
- `find_islands`: where a model needs supports
- `list_printers` and `slice`: slice to the printer's own format (`.goo` for the
  Elegoo Mars 5 Ultra, `.nanodlp` for the Concepts3D Athena 8K, and anything else
  you add as a plugin)
- `preview_layer` and `inspect_print`: check the sliced output. They read `.goo` and ZIP-based
  files (`.nanodlp`); the Mars 5 Ultra's default `.ctb` (v5enc) is encrypted and cannot be read,
  so slice with `format=".goo"` to preview it
- `hollow` and `drill_holes`: hollow a model to save resin, then give each cavity a drain
  (suction relief) through the floor at its lowest point and a vent at its highest, so it
  can drain (always follow `hollow` with `drill_holes`). The vent takes the shortest
  straight route out from the top that never comes closer to the surface than to its own
  exit, so it cannot graze a flank and break out early (NDFM-19; `vent_route="vertical"`
  for straight up through the roof). The drain keeps clear of the model's lowest point,
  where the first supports land. Each hole starts inside the cavity far enough for its full
  diameter to open (a dome narrowing to an apex would otherwise give a slit), is checked by
  slicing the drilled mesh, and is recorded in `<output>.holes.json`. A cavity with only
  one opening warns, as does a hole under 2 mm across (3 mm with `clear_resin=True`: clear
  resin cures small holes shut)
- `auto_support_and_slice`: DragonFruit's own auto-supports and raft, sliced
  into the print, with the same printer, format and material options as `slice`
  (NDFM-8; see below). Keeps supports out of drilled holes: reads
  `<stl>.holes.json` (or a `holes` list) and keeps contacts and shafts clear of
  each hole's radius + 1 mm (NDFM-16)
- `trim_islands`: remove every region of a sliced `.goo` that has nothing cured
  beneath it, which would otherwise peel off as a flake in the vat. Open lattices
  and cut edges are full of tiny ones that mesh-level checks miss. Works on the
  print file's own layers (cured core touching the held core below), re-encodes
  changed layers byte-compatibly with DragonFruit, and verifies the result
  (NDFM-20)
- Preview pictures: `slice` and `auto_support_and_slice` render the model into
  every `.goo` they write, so the printer's screen and print servers show what
  the file holds rather than a placeholder: a three-quarter view, the model in
  magenta and (for `auto_support_and_slice`) its supports and raft in blue, as
  Chitubox shows them. `dragonfruit-mcp-tools render` draws it with a software
  rasteriser (no display or GPU, so it works on a headless box) in under a
  second for a few million triangles. `previews=False` skips it; a render that
  fails is a warning, never a failed slice; `previews_written` reports it
  (NDFM-14)

### Optional Rust backend for `trim_islands`

`accel/goo-trim` is a native port of `trim_islands` and its island check. It
writes the same file byte for byte, much faster. It is optional, separate from
`scripts/build.sh`, and needs only cargo:

```bash
scripts/build-accel.sh      # builds bin/goo-trim
```

Pick the backend with `NDFM_TRIM_BACKEND` in the server's environment: `python`
(the default), `rust`, or `auto` (rust when `bin/goo-trim` is built, python
otherwise). The tool's `backend` parameter overrides it for one call, and the
result's `backend` field says which ran. Asking for `rust` when the binary isn't
built is an error, not a silent fallback.

The binary also runs on its own:

```bash
bin/goo-trim trim print.goo print.trimmed.goo --verify   # JSON report on stdout
bin/goo-trim islands print.trimmed.goo                   # {"islands": [[layer, pixels], ...]}
```

Measured on the gyroid lantern (960 full-screen layers of 8520x4320, 337 MB,
678 layers changed) on a 16-core Mac:

| | Python | Rust |
|---|---|---|
| `trim` + verify | about 237 s | 4.7-5.3 s |
| `trim` alone | | 3.5 s |
| island check alone | 88 s | 1.2 s |

The Rust output was compared with `cmp` against the Python's on that file and on
the test fixtures: identical.

Supports: upstream has no headless command for them, so `ts/autosupport-slice.ts`
runs the app's own placement and support export under Node, and
`dragonfruit-mcp-tools overhangs` runs the app's overhang scan (a Tauri command
upstream). The third island family the app uses, mesh minima, isn't run, and
the overhang scan isn't fully deterministic upstream, so support counts can
differ slightly from the GUI's and between runs. Check a print before trusting it.
The voxel island family runs the app's own per-layer scan, but `ts/voxel-islands.ts`
joins the results: it holds the unsupported voxels as runs along each row rather
than upstream's one `Set` entry a voxel, which a large part overflows. Its
islands match upstream's (NDFM-23).

`ts/support-zones.ts` holds where supports may not go and where they came too
close (NDFM-28). Holes' keep-out cylinders and the height cap
(`max_support_height_mm`, measured from the model's own base) block contacts on
the model surface and remove any support that still enters them. A contact just
under the cap keeps its tip, whose cone can poke a fraction of a millimetre
above it. The clearance check samples the final support mesh every 0.25 mm and
measures each point's distance to the model with a BVH, ignoring the 3 mm round
each contact tip and anything below the model's base. Gaps under
`min_clearance_mm` (0.3 by default) are reported as grazes and warned about. On
Chi (a 50 mm cat, 245,000 samples), the check takes about 0.6 s. The sparse-support check (NDFM-30) looks the other way: overhang surface
that is further than 1.25 grid spacings from every contact (the grid being the
square root of the app's area per support), where each layer rests on the one
below but the edge between contacts can still sag or tear. If it finds any,
placement runs again at 1.5x and 2x the density and the least sparse run is
kept, restored from a snapshot of the support store so that the supports
sliced are the ones checked (`auto_densify`, on by default). Steep-wall contacts (NDFM-31): a support whose tips all
meet surface steeper than 60° from horizontal (averaged over 1.5 mm round the
tip), with no overhang within that 1.5 mm, holds up a wall that holds itself
up, and tears a strip out of it when it comes off a soft, fresh print. Such
supports are removed (`avoid_steep_contacts`, on by default), except where one
is all that holds an island; this runs inside each placement try, before the
sparse check, so densifying judges the supports that will print.

It also holds the overhang band (NDFM-22). `overhang_max_height_mm` limits
the overhang family alone to that height above the model's base, for a lifted
self-supporting lattice: the base gets the overhang grid, the lattice above
gets none, and voxel islands are supported wherever they are.
`clipOverhangRegion` cuts each overhang region at the band's top before it
becomes an island, because the scanner can return a whole lattice as one
region. Nothing is blocked or removed. The result's `overhang_band` reports
the highest overhang contact, and counts any that land more than 0.5 mm above
the band.

Anything that names model triangles to placement (the overhang regions, the
support blockers) counts them in the BVH's order, `positionsInFaceOrder`.
three-mesh-bvh gives an STL's unindexed triangles an index and sorts it, and
placement's raycasts report faces in that order. Before NDFM-22 we counted them
in file order, so the overhang grid sampled the wrong triangles and scattered
contacts up a lattice, and the blockers blocked the wrong triangles.

Previews can also be rendered by hand:

```bash
bin/dragonfruit-mcp-tools render --stl print.goo.supported.stl --out preview.png \
    --split <model_triangles>     # first N triangles magenta, the rest blue
```

`--azimuth` (default -35, from the front-left), `--elevation` (28), `--fov`
(25; 0 is orthographic), `--size` (580), `--crease` (50 degrees) and the
`--model-rgb`/`--support-rgb`/`--background` colours adjust it.

## Architecture Diagram

![Architecture](docs/architecture/nakomis-dragonfruit-mcp.svg)

## Repository Layout

| Path | Contents |
|---|---|
| `nakomis_dragonfruit_mcp/` | The MCP server (Python, FastMCP) |
| `tests/` | pytest; integration tests run only when `bin/` is built |
| `rust/dragonfruit-mcp-tools/` | Our Rust tool (hollowing, hole punching, overhang scan, preview rendering), linking DragonFruit's `dragonfruit-mesh-repair` |
| `ts/` | Our TypeScript scripts, run under DragonFruit's tsx against its own modules (auto-supports, and a bounded voxel island detector) |
| `vendor/dragonfruit/` | DragonFruit, as a submodule pinned to upstream `dev` |
| `accel/goo-trim/` | Optional Rust backend for `trim_islands` (see above) |
| `scripts/build-accel.sh` | Builds `bin/goo-trim` |
| `scripts/build.sh` | Builds `bin/dragonfruit-cli` and `bin/dragonfruit-mcp-tools`, and installs DragonFruit's Node dependencies for `dragonfruit-ts-cli` |
| `docs/architecture/` | Architecture diagram source (`.drawio`) and generated SVG |
| `docs/logo.png` | The logo; the other candidates live on the `logo-candidates` branch |
| `.githooks/` | Pre-commit hook: regenerates diagram SVGs and the README table of contents |

## Building and running

```bash
git clone --recurse-submodules git@github.com:nakomis/nakomis-dragonfruit-mcp.git
cd nakomis-dragonfruit-mcp
scripts/build.sh            # needs cargo, Node, cmake and a C++ compiler; full log in /tmp/ndfm-build.log
uv run nakomis-dragonfruit-mcp
```

## Printers

A printer is a DragonFruit printer profile (an official preset id, a custom
profile JSON, or an app-exported bundle), optionally wrapped by a Python driver
for real overrides. `list_printers` shows what is available and which plugins
failed to load; `slice(stl_path, printer=..., format=..., ...)` slices with one.
Supports are not included yet.

Built in: `mars5ultra` (Elegoo Mars 5 Ultra) and `athena8k` (Concepts3D Athena 8K,
`.nanodlp`, a `.py` driver that writes a sidecar JSON of how the file was sliced).
`mars5ultra` defaults to `.goo`: that Mars 5 Ultra is proven to print `.goo`, and a `.goo`
can be inspected and previewed. `format=".ctb"` gives upstream's `.ctb` v5enc, which
DragonFruit encrypts and this server cannot read back.

`slice` centres the model on the plate in XY with its lowest point on z=0
(`place_on_plate=False` slices it as positioned), warns if it does not fit the build
volume, and writes `<print file>.ndfm.json` recording the printer, profile summary, layer
height, plate offset and so on. `export_plate_stl=True` also writes the model as it sits on
the plate. It runs in a worker thread, so a long slice does not block the server.

To add one, drop a file in `$NDFM_PRINTERS_DIR` or
`~/.config/nakomis-dragonfruit-mcp/printers/` (later directories win a name
clash, with a warning):

- `my_screen.json`: a DragonFruit profile or `{"presetId": "..."}`; the printer is named `my_screen`.
- `my_weird_printer.py`: a `Printer` subclass with `name` and `preset_id` or `profile`, and any of
  `prepare`, `extra_slice_args`, `postprocess` and `warnings` overridden (see `printers/base.py`).

The printer is chosen by the `printer` argument, then `$NDFM_PRINTER`, then
`printer = "..."` in `~/.config/nakomis-dragonfruit-mcp/config.toml`, then `mars5ultra`.

**A drop-in `.py` file is imported and runs with this server's privileges.** Only those
directories are scanned; put nothing there you would not run yourself. A plugin that fails
to load is skipped and reported by `list_printers`.

## Licence

[AGPL-3.0-or-later](LICENSE), the same licence as DragonFruit, whose crates this
project links.

## Architecture Diagrams

`docs/architecture/nakomis-dragonfruit-mcp.drawio` is the source for the diagram above.
The SVG is auto-regenerated on commit by the pre-commit hook in `.githooks/pre-commit`.

To activate the hook after cloning:

```bash
git config core.hooksPath .githooks
```

## Support

If you find this useful, please consider buying me a coffee:

[![Donate with PayPal](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_SM.gif)](https://www.paypal.com/donate?hosted_button_id=Q3BESC73EWVNN&custom=nakomis-dragonfruit-mcp)

TDQS

A3.9/5.0

Scored across 3 tools

Disambiguation5/5

The three tools have clearly distinct purposes: engine_info reports CLI environment, mesh_info reports mesh statistics, and find_islands detects unsupported regions. No overlap in function.

Naming Consistency4/5

All names use snake_case, but two follow a noun_info pattern (engine_info, mesh_info) while find_islands uses a verb_noun pattern. This is a minor inconsistency in verb style, though still readable.

Tool Count5/5

Three tools is appropriate for a focused analysis server; each tool addresses a distinct need (environment, mesh properties, island detection) and there is no redundancy.

Completeness4/5

The server covers environment info, mesh inspection, and island detection, which are core for resin print support analysis. However, it lacks any tool to generate or export supports, which may be a gap for a full workflow, though possibly out of scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues