Turned the bambuddy self-host into a full print control plane on telep-mainframe: a server-side Bambu Studio slicer-api sidecar, four material pipelines (PLA/PETG/ABS/PC-FR), the built-in maintenance tracker wired to the H2S, Telegram notifications, and a tailnet-reachable MCP endpoint so the Aperture ai node can drive the printer. All deployed and verified end-to-end on 2026-09-01. This replaced the standalone OrcaSlicer KasmVNC sidecar (see 2026-09-01-orcaslicer-tailnet-deploy — now retired) with bambuddy’s built-in Slicer API.

For Agents — quick facts

  • bambuddy = maziggy/bambuddy, host-networked, UI at 127.0.0.2:8000, node bambuddy.taild4189d.ts.net
  • Slicer API sidecar = bambu-studio-api (ghcr.io/maziggy/bambu-studio-api:latest), bridge-published 127.0.0.2:3001:3000, in /home/levander/slicer-api/
  • MCP endpoint for Aperture = https://bambuddy.taild4189d.ts.net/mcp (streamable-HTTP; SSE at /sse) — path-mounted on the existing bambuddy node, no new node
  • Everything binds 127.0.0.2 (loopback hardening per 2026-08-31-tailnet-plaintext-port-hardening); tailnet + tag:telep ACL is the gate
  • Printer = Bambu Lab H2S (bambuddy printer id 1, DevName 3DP-093-310, LAN 192.168.1.202 reserved as bambu.lan)
  • No secrets in this note: the bambuddy API key, the Telegram bot token, and the chat id are stored in .env files (chmod-600) — never committed
  • REST base is http://127.0.0.2:8000/api/v1/

1. Slicer API — server-side slicing

Deployed the Bambu Studio slicer-api sidecar so bambuddy can slice on the box instead of relying on a desktop Studio/OrcaSlicer session.

  • docker compose in /home/levander/slicer-api/, image ghcr.io/maziggy/bambu-studio-api:latest, container bambu-studio-api.
  • It is a normal bridge container, NOT host-networked — published 127.0.0.2:3001:3000 (internal port 3000). No --shm-size needed. Healthcheck curl :3000/health.
  • Bound to 127.0.0.2 only (loopback hardening; the sidecar itself has no auth).

Port collision — the ESP32 already owns 127.0.0.1:3001

An ESP32 device already occupies 127.0.0.1:3001. That is exactly why the slicer sidecar is published on 127.0.0.2:3001 (a distinct loopback address, same port number) and bambuddy is pointed at 127.0.0.2. Do not move it back to 127.0.0.1.

Wiring bambuddy → sidecar (REST)

PATCH /api/v1/settings/
  use_slicer_api        = true
  bambu_studio_api_url  = http://127.0.0.2:3001
  (preferred_slicer already "bambu_studio", slice_engine "sidecar")

Port map — bambuddy reserves 3000/3002 for its virtual printer

Ports 3000 and 3002 are reserved by bambuddy’s own virtual-printer. Slicer sidecars use 3001 (Bambu Studio) and 3003 (Orca — not deployed).

Remaining manual affordance: “Save as pipeline” is a UI action, but we created all pipelines via the API (below).

2. Slicer pipelines — PLA / PETG / ABS / PC-FR

Created 4 pipelines via POST /api/v1/slicer-pipelines/ (schema SlicerPipelineCreate: name, description, printer_preset, process_preset, filament_presets[], bed_type; presets are PresetRef {source:"standard", id:"<exact bundled preset name>"}). All returned 201 (ids 1–4).

PipelineFilament preset (@BBL H2S)id
PLABambu PLA Basic @BBL H2S1
PETGBambu PETG Basic @BBL H2S2
ABSBambu ABS @BBL H2S3
PC-FRBambu PC FR @BBL H2S4

Shared across all four:

  • Printer presetBambu Lab H2S 0.4 nozzle
  • Process preset0.20mm Standard @BBL H2S
  • Bed typeTextured PEI Plate

Bambu ships H2S-specific @BBL H2S profiles

The temps / chamber / cooling values live inside Bambu’s @BBL H2S profiles, not in the pipeline. The 0.4 mm ladder: 0.08 / 0.12 / 0.16 High Quality, 0.16 / 0.20 / 0.24 Standard, 0.20 High Quality.

Per-material operational guidance (baked into each pipeline description)

Material cheat sheet

  • PLA — chamber OPEN/vented (heat-creep in an enclosure), bed 35–45 °C, no glue, full cooling.
  • PETG — bed 70 °C + glue (for release), chamber ambient / no active heat, keep dry (AMS 2 Pro 65 °C ok).
  • ABS — chamber CLOSED ~60 °C, low cooling, bed 90–100 °C + glue + brim, ventilate. Drying needs 80–100 °C (AMS 2 Pro too cool for this).
  • PC-FR — chamber CLOSED ~60 °C, bed 90–110 °C + glue + brim, drying CRITICAL (80–100 °C; AMS insufficient), ventilate, brass nozzle fine (unfilled).

Do not co-print PLA with ABS / PC-FR

The printer runs the chamber at the highest requested chamber temp, so mixing PLA with a high-chamber material makes the PLA heat-creep. Also keep PLA out of the AMS during high-temp drying cycles.

Running a pipeline

  • File Manager ⋮ → Run with pipeline, or Archives → Reprint with pipeline.
  • Pipelines require the slicer-api sidecar from §1 to be up.

3. Maintenance tracker

Assigned bambuddy’s built-in maintenance types to the H2S (printer id 1, DevName 3DP-093-310) via POST /api/v1/maintenance/printers/1/assign/{type_id}. 6 H2S-appropriate tasks are now tracked against print hours:

  • Lubricate Linear Rails — currently DUE
  • Clean Linear Rails
  • Clean Nozzle / Hotend
  • Check Belt Tension
  • Clean Build Plate
  • Check PTFE Tube

The 400 on Steel/Carbon-Rod types is correct, not a failure

The default Steel / Carbon-Rod maintenance types are meant for A1 / P1-class printers. bambuddy rejects (HTTP 400) assigning them to an H2S — that is expected validation, not an error.

Endpoints:

  • GET /api/v1/maintenance/summary and GET .../overview — due state.
  • POST /api/v1/maintenance/items/{id}/perform — reset a task after doing it.

4. Telegram notifications

Created a notification provider via POST /api/v1/notifications/ (provider_type telegram, config {bot_token, chat_id} — both strings; keys confirmed against bambuddy’s source schemas/notification.py).

  • Target = the existing “Telephely biztonsági riasztások” supergroup (the chat_id is a negative supergroup id). Test message sent successfully.
  • Events enabled: on_print_complete / on_print_failed / on_print_stopped, on_printer_offline / on_printer_error, on_filament_low, on_maintenance_due, on_ams_humidity_high, on_ai_failure_detection.

Secrets + open API

The bot token and chat id are NOT recorded here — they live in the provider config (and .env). Also note the provider config / notifications API is currently open (no bambuddy auth enabled).

5. Bambuddy MCP on the tailnet (for Aperture)

Goal: let Aperture (the ai node, https://ai.taild4189d.ts.net) drive the printer. bambuddy-mcp (github MrMebelMan/bambuddy-mcp) is a stdio-only Python MCP that dynamically exposes bambuddy’s REST API (731 endpoints) through meta-tools: list_categories / search_tools / execute_tool / find_printer.

Why we self-host it behind a bridge

Aperture is not self-hosted, so it can only consume a network MCP endpoint. bambuddy-mcp only speaks stdio, so we self-host it on the mainframe wrapped in an stdio → streamable-HTTP bridge.

The bridge container

  • Container bambuddy-mcp-bridge in /home/levander/bambuddy-mcp/.
  • Dockerfile: python:3.12-slim, pip install uv mcp-proxy==0.12.0 mcp==1.29.1, then uv tool install --with mcp==1.29.1 bambuddy-mcp.
  • --network host, ENTRYPOINT:
    mcp-proxy --host 127.0.0.2 --port 8091 --pass-environment -- bambuddy-mcp
    
  • Env: BAMBUDDY_URL=http://127.0.0.2:8000 + BAMBUDDY_API_KEY (full-scope key minus cloud, stored in a chmod-600 .env).
  • Bound 127.0.0.2:8091 loopback-only.

Version-pin gotcha — pin mcp==1.29.1 in BOTH envs

The current mcp SDK 2.x removed mcp.server.lowlevel.server.request_ctx (breaks mcp-proxy) and the low-level Server.list_tools decorator (breaks bambuddy-mcp). Both tools are written against mcp 1.x, so pin mcp==1.29.1 in both the proxy pip env and bambuddy-mcp’s uv-tool env. Same class of SDK-drift trap as the FastMCP allowed_hosts 421 in 2026-08-31-telep-kb-mcp-server.

Exposure — path mount on the existing bambuddy node (no new node)

Rather than spin up a dedicated sidecar node, the MCP is path-mounted onto the existing bambuddy tailscale node:

tailscale --socket=/run/tailscale-bambuddy/tailscaled.sock serve --bg \
  --https=443 --set-path=/mcp http://127.0.0.2:8091/mcp

serve status shows both / (the UI, preserved) and /mcp.

Prefix-strip behaviour on tailscale 1.102.2

On tailscale 1.102.2 the mount strips the prefix and joins the remainder onto the target, so external /mcp maps to backend /mcp (which is why the target URL includes /mcp).

Endpoint + verification

Endpoint for Aperture: https://bambuddy.taild4189d.ts.net/mcp (streamable-HTTP; SSE also at /sse).

Verified end-to-end from a tailnet client (personal-mac): initialize returns a session, tools/list returns the meta-tools.

The mainframe can't self-hairpin — test from another node

The mainframe runs a userspace tailscaled (TUN:false) and cannot hairpin to its own tailnet HTTPS, so end-to-end reachability must be proven from a different node (we used personal-mac). Same hairpin caveat noted in 2026-08-31-nextcloud-drive-code-server-runbook.

Security posture — tailnet ACL is the only gate

The /mcp endpoint has no auth of its own (mcp-proxy adds none; the bambuddy API key is server-side). Tailnet membership + tag:telep ACL is the only gate, and execute_tool can control the printer. bambuddy-mcp masks access_code / serial by default. Tighten the ACL if owner-only access is desired. Do NOT expose via Funnel (standing rule, cf. 2026-08-31-nextcloud-drive-code-server-runbook).