DEV Community

Cover image for I Wrote an MCP Server for My 3D Printer
Nic Lydon
Nic Lydon

Posted on

I Wrote an MCP Server for My 3D Printer

I’m writing this on a Sunday afternoon. The 3D printer on my kitchen counter has been printing for 19 hours and 12 minutes. I know this because I just asked it.

Not by walking into the kitchen. By calling a tool in a Claude conversation:

// kiln_progress
{
  "status": "printing",
  "file": "looki_l1_tests.gcode",
  "layer": 257,
  "target_layer": 315,
  "progress": 0.9112598299980164,
  "print_duration_s": 69192
}
Enter fullscreen mode Exit fullscreen mode

The printer is a Flashforge Adventurer 5M. It’s named “kiln” because everything in my home lab gets a fire-themed name and I’ve already used the good ones (Furnace runs the GPUs, Forge is the inference gateway). It sits next to the cutting board. I bought it on a whim a few weeks ago and I have no idea what I’m doing with 3D printing as a hobby.

But I do know how to wrap an API in MCP tools, and the printer has two of them. So now I have 16 MCP tools for a machine I barely understand.

This post is the receipts.

The two APIs

The AD5M ships with firmware that exposes two ways in:

  • An HTTP API on port 8898 that returns JSON for things like /detail (status, fans, temps, current job). This is what the FlashForge mobile app talks to.
  • A legacy TCP port on 8899 that speaks G-code over a length-prefixed wire format. You send M115 and you get the firmware version back. Send M114 and you get the current head position.

The HTTP API is comfortable. The TCP port is from a more savage era. Both are running on the same printer.

kiln-mcp is a small TypeScript MCP server that wraps both. Read-only G-codes go through the TCP port. State-changing operations like kiln_print and kiln_control (pause/resume/cancel) go through the HTTP API. All calls carry a check code so the printer trusts them.

I also do not trust any LLM with a hot nozzle and an open command channel.

That shaped the design more than anything else. Read-only telemetry is permissive. Stateful operations are constrained. The kiln_mcode tool only accepts read-only M-codes because the first version of this server had that gate softer, and I tightened it after the second time I caught a tool call trying to send M104 (set extruder temp) inside what I thought was a status query.

Here’s what they look like in practice.

The read-only side

kiln_info just dumps firmware and build volume. Under the hood it calls M115:

Machine Type: Flashforge Adventurer 5M
Machine Name: kiln
Firmware: v3.2.7
SN: [redacted]
X: 220 Y: 220 Z: 220
Tool Count: 1
Enter fullscreen mode Exit fullscreen mode

kiln_temps is more useful. As of the moment I’m writing this paragraph:

{
  "nozzle": { "temp": 219.67, "target": 220 },
  "bed": { "temp": 59.53, "target": 60 },
  "chamber": { "temp": 0, "target": 0 }
}
Enter fullscreen mode Exit fullscreen mode

219.67 / 220 is the nozzle holding steady on PLA. 59.53 / 60 is the bed. The chamber slot exists for a heated enclosure I don’t have.

Claude Mobile and 3D Printer

kiln_files lists what’s on the printer’s storage:

looki_l1_tests.gcode
looki05.gcode
looki04.gcode
looki03.gcode
looki02.gcode
looki01.gcode
nameplate_batch_3_13-14_PLA_020mm.gcode
nameplate_batch_2_7-12_PLA_020mm.gcode
nameplate_batch_1x6_PLA_020mm.gcode
plate_1.gcode
Enter fullscreen mode Exit fullscreen mode

I’m iterating on a mount for a wearable AI camera I use, hence looki_l1_tests.gcode being on its fifth revision. The nameplate batches were for a friend. The file names are a journal.

The state-changing side

kiln_print takes one of those file names and starts the job:

kiln_print({ file_name: "looki05.gcode", level: true })
Enter fullscreen mode Exit fullscreen mode

kiln_control does pause / resume / stop. The stop is destructive in the obvious way: cancel an 18-hour print at hour 17, you have an 18-hour-old failed extrusion blob on the bed.

I don’t let Claude call kiln_control casually.

The weird one

The tool I actually built this server for is kiln_image2mesh.

I wanted the shortest possible path from “that would make a neat print” to an STL on the printer.

You hand it an image. It hands you back an STL ready to slice. It runs entirely on the iGPU of one of my mini PCs.

Under the hood:

  • A FastAPI service called Modly that auto-spawns on first use (it isn’t running right now, which is fine).
  • rembg strips the background from the image.
  • TripoSG, an image-to-3D diffusion model from VAST AI, generates the mesh.
  • A marching-cubes octree turns the implicit field into triangles.
  • Mesh simplification brings the face count down to a printable target (default 80,000 faces).
  • The result is written as an STL next to the input image.

Kiln MCP

Total time: 5 to 15 minutes depending on diffusion steps. CFG, seed, foreground ratio, face count, and steps are all parameters. Defaults are tuned for “preview-grade,” which is what I want 95% of the time.

The point: there is no cloud STL service in this pipeline. The image goes onto disk on Furnace, Modly runs locally on the iGPU, the STL lands on the same disk, and then kiln_print ships it. The only thing leaving my network is the message I typed at Claude asking it to do all of that. (And sometimes I don't run that through Claude.)

The honest bits

While drafting this, kiln_status timed out:

timeout after 8000ms (path=/detail)
Enter fullscreen mode Exit fullscreen mode

The printer’s HTTP server gets cranky when it’s deep into a long job. The legacy TCP port answered fine the whole time. Both APIs, one machine, very different attitudes about life. The MCP server papers over this poorly. That’s a TODO.

kiln_modly_status reported api_up: false. The auto-spawn handles that on the next kiln_image2mesh call, but if I were writing this server today I’d add a –prewarm flag.

The ugly old TCP side of the printer has actually been more reliable than the modern JSON API. Which feels about right.

Why bother

I write MCP servers for things at a much higher rate than is reasonable. Most of them are useful in the obvious “I can ask Claude about my pipeline state” way. A few are useful in the less obvious way of “I now have a sharp picture of what the underlying system actually exposes.”

Wrapping the printer was the second category.

The AD5M’s two APIs disagree about a lot of things: units, retry behavior, what “ready” means. Wrapping them forced me to pick a model. The MCP surface is the cleanest description of that printer I have.

That’s the part of MCP work I think people miss. Once you expose a system through tools, you stop writing wrappers and start defining semantics. You have to decide what counts as state, what counts as safe, what operations deserve retries, and what an LLM should never be allowed to do.

That, and: I can now ask Claude to print me a thing.

Relaxed 3D Printing

The amount of glue between an LLM and a hot extruder is not zero, but it’s smaller than you’d think.

When I started writing this, the printer was at layer 257 of 315. I could check again.

I’m not going to.

Top comments (0)