Skip to main content
Glama
nakomis

nakomis-dragonfruit-mcp

by nakomis

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 server that lets an AI assistant inspect, hollow, check and slice resin prints using the engine of 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

Related MCP server: PrusaMCP

Table of Contents

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:

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:

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:

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

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

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, 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:

git config core.hooksPath .githooks

Support

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

Donate with PayPal

Available Tools

3 tools
engine_infoA

Report which dragonfruit-cli is in use and the print formats it can write.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
formatsYesOutput extensions the engine can write
versionYes
cli_pathYes
warningsNo
slice_defaultsYesEngine defaults; never relied on for a real printer

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. 'Report' implies a passive, non-mutating lookup, which is useful signal, and it hints at the returned content (available print formats). However it says nothing about permissions, cost, or side effects, leaving real gaps for a tool with no annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler or redundancy. Every clause adds information about what the tool reports.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description need not explain return values, and with zero parameters there is no input surface to document. It is nearly complete, missing only guidance on when this diagnostic is relevant versus other info tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate. The 100% schema coverage and empty properties object make this a baseline case where no compensating text is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Report') and resource ('which dragonfruit-cli is in use and the print formats it can write'), so an agent knows exactly what it returns. It does not differentiate itself from siblings mesh_info or find_islands, but those cover unrelated domains so confusion is unlikely.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to call this tool, no prerequisites, and no reference to any alternative. The agent must infer usage entirely from the name and the one-line purpose.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_islandsA

Find islands: regions that appear in a layer with nothing solid beneath them.

These are where a resin print needs supports. The model is scanned as it sits in the STL: layer 0 is its lowest point, as if lying on the plate, and no supports are considered. Positions are in the STL's own X/Y/Z (mm). Layers are sliced layer_height apart on a px_mm grid; a region counts as supported if solid lies within support_buffer_mm of it in the layer below. Detections that start within cluster_mm (XY distance from the cluster's first detection) and ten layers of each other are merged into one entry; 0 disables that. Entries whose first_area_mm2 is under min_area_mm2 are then dropped, and the max_islands largest by that area are returned, with truncated set if there were more.

ParametersJSON Schema
NameRequiredDescriptionDefault
px_mmNo
stl_pathYes
cluster_mmNo
max_islandsNo
layer_heightNo
min_area_mm2No
support_buffer_mmNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
pathYes
px_mmYes
layersYes
islandsYesLargest first_area_mm2 first
bbox_maxYes
bbox_minYes[x, y, z] mm of the model, to check positions against
warningsNo
elapsed_sYes
truncatedYesTrue when islands holds fewer than islands_total
islands_totalYesIslands found after clustering and filtering
plate_contactsYesLayer-0 regions: on the plate, not islands
raw_detectionsYesOff-plate islands the tracker reported, before clustering and filtering
total_area_mm2YesUnsupported footprint: first-layer areas of all islands_total islands (every merged detection), not just the ones listed
layer_height_mmYes
support_buffer_mmYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so well: it explains that the model is scanned as it sits in the STL with layer 0 at the plate and no supports considered, that positions are in the STL's own mm frame, and that results may be merged, area-filtered and truncated. It omits operational traits like compute cost or whether the call is purely read-only, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and caveats are front-loaded in the first two sentences, and every clause carries information. The final paragraph is a single very long sentence packing clustering, filtering and truncation together, which could have been broken up, but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be detailed, yet the description still flags the important ones (area-based ordering, max_islands cap, truncated flag). Combined with full parameter explanation and the algorithmic model, an agent has everything needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate, and it explains essentially every parameter: px_mm (grid), layer_height (slice spacing), support_buffer_mm (support proximity test), cluster_mm (XY merge distance, with '0 disables'), min_area_mm2 (drop threshold) and max_islands (largest-N selection plus the truncated flag). This adds real meaning beyond the bare schema names.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource ('Find islands') and immediately defines the term operationally: 'regions that appear in a layer with nothing solid beneath them.' It also grounds the output in a concrete use case (resin print supports), which clearly separates it from the informational siblings engine_info and mesh_info.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: 'These are where a resin print needs supports' hints at why you'd call it, but there is no explicit when-to-use/when-not, no prerequisite for preparing the STL, and no mention of alternatives. Adequate context, no routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

mesh_infoA

Report an STL's triangle count, bounding box, size (mm) and volume (mm3 and ml).

Warns about sizes that suggest the wrong units and volumes that suggest the mesh is not a closed solid. Volume is the CLI's signed-volume sum, which cannot tell a closed mesh from an open one that happens to integrate sensibly; it does not report watertightness itself. The absence of the fill-ratio warning therefore proves nothing about whether the mesh is closed.

ParametersJSON Schema
NameRequiredDescriptionDefault
stl_pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
pathYes
sourceYesWhat the CLI read: stl, voxl or directory
size_mmYes
bbox_maxYes
bbox_minYesmm, in the file's own coordinate frame
verticesYesUnwelded: three per triangle
warningsNo
trianglesYes
volume_mlYesvolume_mm3 / 1000: the resin a solid print needs
volume_mm3YesSigned-volume integral, absolute value

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it does substantial work: it explains the warning conditions, states the volume is a signed-volume sum, and explicitly cautions that it does not report watertightness and that a missing fill-ratio warning proves nothing. What it omits is behavior on bad input (non-STL file, missing path) and permission/side-effect posture, though a reporting tool implies a safe read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The first sentence front-loads the four reported values, then the caveats follow. The three-sentence caveat block is a bit dense, but each sentence carries distinct, non-redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not have been explained, yet the description usefully clarifies their interpretation. For a single-parameter read tool with no annotations, it is nearly complete, lacking only input-failure behavior and an explicit usage routing against siblings.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for the single stl_path parameter, which is undocumented in the schema. The description implies the input is an STL file ('an STL's triangle count') but adds no path format, resolution, or error-handling semantics, so it only partially compensates.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource: reporting triangle count, bounding box, size, and volume for an STL. It is immediately distinguishable from siblings like engine_info or find_islands, but it never explicitly contrasts itself with them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied — an agent infers it inspects a mesh before other geometry operations. There is no explicit when-to-use statement, no prerequisites, and no pointer to the sibling tools that might be the better choice for related queries.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedengine_info
    • First observedfind_islands
    • First observedmesh_info

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

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    An intelligent 3D printing assistant that enables users to analyze meshes, generate optimal print profiles, and automate PrusaSlicer operations through an MCP client. It provides a comprehensive suite of tools for geometric analysis, printability checks, filament recommendations, and post-print diagnostics.
    17
    9
    -
  • A
    license
    B
    quality
    D
    maintenance
    Enables AI agents to slice 3D models (STL files) headlessly using UltiMaker Cura's CuraEngine, returning ready-to-print G-code and estimated print time.
    12
    AGPL 3.0
  • A
    license
    C
    quality
    B
    maintenance
    Enables AI agents to perform reproducible Snapmaker U1 headless slicing with Snapmaker Orca, including model/profile discovery, STL/3MF-to-G-code slicing, G-code analysis, controlled overrides, and slice or orientation comparisons without GUI automation or printer control.
    33
    1
    MIT