nakomis-dragonfruit-mcp
Run headless resin-print inspection and island detection with the DragonFruit engine: inspect STLs, find where supports are needed, and query the slicing engine—no GUI.
engine_info: report whichdragonfruit-cliis used, its version, output formats it can write, default slice settings, and warnings.mesh_info: report an STL's triangle/vertex count, bounding box and size (mm), and volume (mm3 and ml), with warnings for suspected unit errors or non-closed meshes (signed-volume caveat noted).find_islands: scan a model as positioned in the STL and detect unsupported layer regions that need supports; tunable vialayer_height,px_mm,support_buffer_mm,cluster_mm,min_area_mm2,max_islands; returns island centroids/areas, model bbox, plate contacts, raw detection count, total unsupported area, truncation flag, and timing.Per README, further tools are planned but not in this schema:
slice/list_printers,hollow/drill_holes,auto_support_and_slice,trim_islands,preview_layer/inspect_print.
Provides slicing to the Elegoo Mars 5 Ultra's .goo format via list_printers and slice tools, enabling resin print preparation for Elegoo printers.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@nakomis-dragonfruit-mcphollow this STL with 2mm walls and slice it for the Elegoo Mars 5 Ultra"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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:

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 mlfind_islands: where a model needs supportslist_printersandslice: slice to the printer's own format (.goofor the Elegoo Mars 5 Ultra,.nanodlpfor the Concepts3D Athena 8K, and anything else you add as a plugin)preview_layerandinspect_print: check the sliced output. They read.gooand ZIP-based files (.nanodlp); the Mars 5 Ultra's default.ctb(v5enc) is encrypted and cannot be read, so slice withformat=".goo"to preview ithollowanddrill_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 followhollowwithdrill_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 withclear_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 asslice(NDFM-8; see below). Keeps supports out of drilled holes: reads<stl>.holes.json(or aholeslist) and keeps contacts and shafts clear of each hole's radius + 1 mm (NDFM-16)trim_islands: remove every region of a sliced.goothat 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:
sliceandauto_support_and_slicerender the model into every.goothey 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 (forauto_support_and_slice) its supports and raft in blue, as Chitubox shows them.dragonfruit-mcp-tools renderdraws 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=Falseskips it; a render that fails is a warning, never a failed slice;previews_writtenreports 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-trimPick 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 | |
| about 237 s | 4.7-5.3 s |
| 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
Repository Layout
Path | Contents |
| The MCP server (Python, FastMCP) |
| pytest; integration tests run only when |
| Our Rust tool (hollowing, hole punching, overhang scan, preview rendering), linking DragonFruit's |
| Our TypeScript scripts, run under DragonFruit's tsx against its own modules (auto-supports, and a bounded voxel island detector) |
| DragonFruit, as a submodule pinned to upstream |
| Optional Rust backend for |
| Builds |
| Builds |
| Architecture diagram source ( |
| The logo; the other candidates live on the |
| 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-mcpPrinters
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 namedmy_screen.my_weird_printer.py: aPrintersubclass withnameandpreset_idorprofile, and any ofprepare,extra_slice_args,postprocessandwarningsoverridden (seeprinters/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 .githooksSupport
If you find this useful, please consider buying me a coffee:

Available Tools
3 toolsengine_infoA
Report which dragonfruit-cli is in use and the print formats it can write.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| formats | Yes | Output extensions the engine can write |
| version | Yes | |
| cli_path | Yes | |
| warnings | No | |
| slice_defaults | Yes | Engine defaults; never relied on for a real printer |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| px_mm | No | ||
| stl_path | Yes | ||
| cluster_mm | No | ||
| max_islands | No | ||
| layer_height | No | ||
| min_area_mm2 | No | ||
| support_buffer_mm | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| path | Yes | |
| px_mm | Yes | |
| layers | Yes | |
| islands | Yes | Largest first_area_mm2 first |
| bbox_max | Yes | |
| bbox_min | Yes | [x, y, z] mm of the model, to check positions against |
| warnings | No | |
| elapsed_s | Yes | |
| truncated | Yes | True when islands holds fewer than islands_total |
| islands_total | Yes | Islands found after clustering and filtering |
| plate_contacts | Yes | Layer-0 regions: on the plate, not islands |
| raw_detections | Yes | Off-plate islands the tracker reported, before clustering and filtering |
| total_area_mm2 | Yes | Unsupported footprint: first-layer areas of all islands_total islands (every merged detection), not just the ones listed |
| layer_height_mm | Yes | |
| support_buffer_mm | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| stl_path | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| path | Yes | |
| source | Yes | What the CLI read: stl, voxl or directory |
| size_mm | Yes | |
| bbox_max | Yes | |
| bbox_min | Yes | mm, in the file's own coordinate frame |
| vertices | Yes | Unwelded: three per triangle |
| warnings | No | |
| triangles | Yes | |
| volume_ml | Yes | volume_mm3 / 1000: the resin a solid print needs |
| volume_mm3 | Yes | Signed-volume integral, absolute value |
TDQS
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.
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.
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.
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.
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.
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.
3 tool updates
v0.1.0- First observed
engine_info - First observed
find_islands - First observed
mesh_info
TDQS
Scored across 3 tools
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.
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.
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.
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
Related MCP Connectors
Real 3D-print slicing, quoting, DFM, orientation & material/settings advisors. Free personal tier.
3D print farm management for AI. Monitor, queue, and control prints on your SimplyPrint account.
- OwlCADOAuthcom.owlcad
Parametric 3D CAD for AI agents: build print-ready parts, check them, export STL, 3MF or STEP.
LLM chat, text tools, image generation, editing, batch image jobs, and asynchronous video generation
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI agents to interact with OrcaSlicer to manage profiles, adjust printing settings, and perform slicing operations. It allows users to search settings, slice STL/3MF files, and analyze G-code metadata through natural language assistants.7AGPL 3.0
- FlicenseAqualityDmaintenanceAn 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.179-
- AlicenseBqualityDmaintenanceEnables AI agents to slice 3D models (STL files) headlessly using UltiMaker Cura's CuraEngine, returning ready-to-print G-code and estimated print time.12AGPL 3.0
- AlicenseCqualityBmaintenanceEnables 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.331MIT