mcp-3d-printer-server 1.2.31 → 1.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.32] – 2026-11-04
4
+
5
+ ### Added
6
+
7
+ - `counterpart_printer_info` reports `capabilities.slicing`: whether `slice_stl` can apply supports, copies (`repetitions`) and auto-orient with the slicer this server uses for that printer. The Bambu-compatible CLI route takes all three, generic OrcaSlicer takes supports, and PrusaSlicer, Slic3r and Cura take none. For a Bambu printer on OrcaSlicer, that assumes the call passes the printer's `bambu_model`. Clients such as Counterpart offer those choices from this instead of knowing which slicers accept what. Same field as bambu-printer-mcp 1.1.58. Checked with mocked transports only.
8
+
9
+ ### Changed
10
+
11
+ - `slice_stl` with `repetitions` above 1 on a slicer that can't make copies (anything but the Bambu-compatible CLI route) now says so and slices nothing; it used to slice one copy without a word. Checked with stand-in slicers only.
12
+
3
13
  ## [1.2.31] – 2026-11-04
4
14
 
5
15
  ### Added
package/README.md CHANGED
@@ -210,6 +210,7 @@ The optional FULU **BambuNetwork bridge** is a separate runtime. With `BAMBU_DEF
210
210
  - Per-call `type`, `host`, `port`, and `api_key` arguments for working with more than one printer
211
211
  - Printer status, file listing, G-code upload from inline content or a local path, starting a stored file, pausing, resuming, cancelling a job, and setting bed or nozzle temperatures
212
212
  - The `counterpart/1` tools shared with [bambu-printer-mcp](https://github.com/DMontgomery40/bambu-printer-mcp): one progress shape for every firmware (state, job, progress, time left, layers, temperatures, problems), detected printer capabilities, camera snapshots, and the loaded filament (Spoolman on Moonraker)
213
+ - `counterpart_printer_info` reports `capabilities.slicing` (`supports`, `copies`, `orient`) for the slicer route this server uses for that printer (for a Bambu printer on OrcaSlicer, when the call passes its `bambu_model`), so a client can offer those choices without knowing the slicer.
213
214
  - Errors carry their details in `structuredContent` (`status`, `retryable`, `suggestion`, `message`); a printer refusal on `print_3mf` also carries a stable `code`, `printer-command-rejected` or `printer-not-ready`, so a client can act on the code instead of the wording.
214
215
  - STL tools: inspect dimensions, scale, rotate, translate, extend the base, merge vertices, center, lay flat, transform one section of a model, and render multi-angle SVG previews
215
216
  - A print and heating safety gate on every backend: the exact G-code is inspected, every heater target is checked against hardware and material ceilings, and the printer's state is checked. There is no confirmation prompt and no bed check: your client's own print action is the approval. Heater-off, pause, and cancel are never gated; resume only continues a paused job this server inspected and started, and asks a person only when the printer cannot hand the file back for re-reading
@@ -55,6 +55,13 @@ export interface CounterpartCapabilities {
55
55
  cancel: boolean;
56
56
  print3mf: boolean;
57
57
  gcode: boolean;
58
+ /** What slice_stl can apply with this server's slicer for this printer (src/slicer/capabilities.ts). For a Bambu printer on
59
+ * OrcaSlicer this assumes the call passes the printer's bambu_model, as Counterpart does; without one, OrcaSlicer slices generically. */
60
+ slicing?: {
61
+ supports: boolean;
62
+ copies: boolean;
63
+ orient: boolean;
64
+ };
58
65
  }
59
66
  export interface PrinterIdentity {
60
67
  model?: string;
@@ -172,6 +179,21 @@ export declare const PRINTER_INFO_OUTPUT_SCHEMA: {
172
179
  readonly gcode: {
173
180
  readonly type: "boolean";
174
181
  };
182
+ readonly slicing: {
183
+ readonly type: "object";
184
+ readonly required: readonly ["supports", "copies", "orient"];
185
+ readonly properties: {
186
+ readonly supports: {
187
+ readonly type: "boolean";
188
+ };
189
+ readonly copies: {
190
+ readonly type: "boolean";
191
+ };
192
+ readonly orient: {
193
+ readonly type: "boolean";
194
+ };
195
+ };
196
+ };
175
197
  };
176
198
  };
177
199
  };
@@ -59,6 +59,11 @@ export const PRINTER_INFO_OUTPUT_SCHEMA = {
59
59
  cancel: { type: "boolean" },
60
60
  print3mf: { type: "boolean" },
61
61
  gcode: { type: "boolean" },
62
+ slicing: {
63
+ type: "object",
64
+ required: ["supports", "copies", "orient"],
65
+ properties: { supports: { type: "boolean" }, copies: { type: "boolean" }, orient: { type: "boolean" } },
66
+ },
62
67
  },
63
68
  },
64
69
  },
@@ -3,6 +3,7 @@
3
3
  * adapters supply firmware reads; this module turns them into the shared
4
4
  * results, detects capabilities, and fetches camera snapshots.
5
5
  */
6
+ import { sliceCapabilities } from "../slicer/capabilities.js";
6
7
  import fs from "node:fs/promises";
7
8
  import path from "node:path";
8
9
  import { isAxiosError } from "axios";
@@ -83,6 +84,7 @@ export class CounterpartService {
83
84
  capabilities = {
84
85
  progress: true, layers: true, camera: overrideSnapshotUrl() ? "http-snapshot" : "none", filaments: "ams",
85
86
  pause: true, resume: true, cancel: true, print3mf: true, gcode: true,
87
+ slicing: sliceCapabilities(this.bambuProfileSource().slicerType ?? "bambustudio", true),
86
88
  };
87
89
  }
88
90
  else {
@@ -96,6 +98,7 @@ export class CounterpartService {
96
98
  capabilities = {
97
99
  progress: true, layers, camera, filaments: "single",
98
100
  pause: pauseResume, resume: pauseResume, cancel: true, print3mf: false, gcode: true,
101
+ slicing: sliceCapabilities(this.bambuProfileSource().slicerType ?? "prusaslicer", false),
99
102
  };
100
103
  }
101
104
  const streamCandidates = await this.cameraCandidates(target).catch(() => []);
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { STLManipulator, SUPPORT_TYPES } from "./stl/stl-manipulator.js";
21
21
  import { parsePauseLayers, readPlateGcode, pausedLayers, machinePauseGcode } from "./slicer/layer-pauses.js";
22
22
  import { PRINT_QUALITY_PROPERTIES, withPrintQuality } from "./slicer/print-quality.js";
23
23
  import { PrinterRefusal } from "./refusal.js";
24
+ import { isBambuProjectSlicer, usesBambuCli } from "./slicer/capabilities.js";
24
25
  import { CounterpartService } from "./counterpart/service.js";
25
26
  import { CAMERA_STREAM_TOOLS } from "./counterpart/camera-tools.js";
26
27
  import { annotateTools } from "./tool-annotations.js";
@@ -101,9 +102,6 @@ function normalizeSlicerType(rawValue) {
101
102
  }
102
103
  return resolved;
103
104
  }
104
- function isBambuProjectSlicer(slicerType) {
105
- return slicerType === "orcaslicer-bambulab" || slicerType === "bambustudio";
106
- }
107
105
  function firstExistingPath(candidates) {
108
106
  return candidates.find((candidate) => fs.existsSync(candidate));
109
107
  }
@@ -756,10 +754,14 @@ class ThreeDPrinterMCPServer {
756
754
  const explicitModel = typeof args.bambu_model === "string" && args.bambu_model.trim() ? args.bambu_model : undefined;
757
755
  // Generic OrcaSlicer keeps its user-supplied machine profile unless the
758
756
  // call explicitly targets a Bambu model.
759
- const bambuCli = isBambuProjectSlicer(slicerType) || (slicerType === "orcaslicer" && explicitModel !== undefined);
757
+ const bambuCli = usesBambuCli(slicerType, explicitModel !== undefined);
760
758
  if (!bambuCli && parsePauseLayers(args.pause_at_layers)) {
761
759
  throw new Error(`Pausing at a layer needs the Bambu Studio or OrcaSlicer CLI, not ${slicerType}.`);
762
760
  }
761
+ // Copies only come from the Bambu-compatible CLI; elsewhere one copy would be sliced without a word.
762
+ if (!bambuCli && args.repetitions !== undefined && Number(args.repetitions) > 1) {
763
+ throw new Error(`Copies (repetitions) need the Bambu Studio or OrcaSlicer CLI with a Bambu model, not ${slicerType}; nothing was sliced.`);
764
+ }
763
765
  let printerPreset;
764
766
  let options;
765
767
  if (bambuCli) {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What a slice from this server can do with the slicer it runs (counterpart_printer_info
3
+ * capabilities.slicing). Clients such as Counterpart offer supports, copies and auto-orient only
4
+ * when this says so, instead of knowing which slicer accepts what.
5
+ *
6
+ * The Bambu-compatible CLI route (Bambu Studio, FULU OrcaSlicer-bambulab, or OrcaSlicer given a
7
+ * Bambu model) takes all three. Generic OrcaSlicer takes the support keys over its process profile.
8
+ * PrusaSlicer, Slic3r and Cura take none of them through this server. For a Bambu printer on OrcaSlicer, the
9
+ * Bambu route needs the call's bambu_model; clients that report these to the person pass it.
10
+ */
11
+ export interface SliceCapabilities {
12
+ supports: boolean;
13
+ copies: boolean;
14
+ orient: boolean;
15
+ }
16
+ export declare function isBambuProjectSlicer(slicerType: string): boolean;
17
+ /** True when slice_stl uses the Bambu-compatible CLI route for this slicer and target. */
18
+ export declare function usesBambuCli(slicerType: string, bambuModel: boolean): boolean;
19
+ export declare function sliceCapabilities(slicerType: string, bambuModel: boolean): SliceCapabilities;
@@ -0,0 +1,11 @@
1
+ export function isBambuProjectSlicer(slicerType) {
2
+ return slicerType === "orcaslicer-bambulab" || slicerType === "bambustudio";
3
+ }
4
+ /** True when slice_stl uses the Bambu-compatible CLI route for this slicer and target. */
5
+ export function usesBambuCli(slicerType, bambuModel) {
6
+ return isBambuProjectSlicer(slicerType) || (slicerType === "orcaslicer" && bambuModel);
7
+ }
8
+ export function sliceCapabilities(slicerType, bambuModel) {
9
+ const bambuCli = usesBambuCli(slicerType, bambuModel);
10
+ return { supports: bambuCli || slicerType === "orcaslicer", copies: bambuCli, orient: bambuCli };
11
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-3d-printer-server",
3
3
  "mcpName": "io.github.DMontgomery40/mcp-3d-printer-server",
4
- "version": "1.2.31",
4
+ "version": "1.2.32",
5
5
  "description": "MCP server for connecting Claude with 3D printer management systems",
6
6
  "main": "dist/index.js",
7
7
  "repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/DMontgomery40/mcp-3D-printer-server",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.2.31",
10
+ "version": "1.2.32",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "mcp-3d-printer-server",
15
- "version": "1.2.31",
15
+ "version": "1.2.32",
16
16
  "runtimeHint": "npx",
17
17
  "transport": {
18
18
  "type": "stdio"
@@ -63,6 +63,9 @@ export interface CounterpartCapabilities {
63
63
  cancel: boolean;
64
64
  print3mf: boolean;
65
65
  gcode: boolean;
66
+ /** What slice_stl can apply with this server's slicer for this printer (src/slicer/capabilities.ts). For a Bambu printer on
67
+ * OrcaSlicer this assumes the call passes the printer's bambu_model, as Counterpart does; without one, OrcaSlicer slices generically. */
68
+ slicing?: { supports: boolean; copies: boolean; orient: boolean };
66
69
  }
67
70
 
68
71
  export interface PrinterIdentity {
@@ -153,6 +156,11 @@ export const PRINTER_INFO_OUTPUT_SCHEMA = {
153
156
  cancel: { type: "boolean" },
154
157
  print3mf: { type: "boolean" },
155
158
  gcode: { type: "boolean" },
159
+ slicing: {
160
+ type: "object",
161
+ required: ["supports", "copies", "orient"],
162
+ properties: { supports: { type: "boolean" }, copies: { type: "boolean" }, orient: { type: "boolean" } },
163
+ },
156
164
  },
157
165
  },
158
166
  },
@@ -3,6 +3,7 @@
3
3
  * adapters supply firmware reads; this module turns them into the shared
4
4
  * results, detects capabilities, and fetches camera snapshots.
5
5
  */
6
+ import { sliceCapabilities } from "../slicer/capabilities.js";
6
7
  import fs from "node:fs/promises";
7
8
  import path from "node:path";
8
9
  import { isAxiosError, type AxiosInstance } from "axios";
@@ -132,6 +133,7 @@ export class CounterpartService {
132
133
  capabilities = {
133
134
  progress: true, layers: true, camera: overrideSnapshotUrl() ? "http-snapshot" : "none", filaments: "ams",
134
135
  pause: true, resume: true, cancel: true, print3mf: true, gcode: true,
136
+ slicing: sliceCapabilities(this.bambuProfileSource().slicerType ?? "bambustudio", true),
135
137
  };
136
138
  } else {
137
139
  const { host, port, apiKey } = target;
@@ -144,6 +146,7 @@ export class CounterpartService {
144
146
  capabilities = {
145
147
  progress: true, layers, camera, filaments: "single",
146
148
  pause: pauseResume, resume: pauseResume, cancel: true, print3mf: false, gcode: true,
149
+ slicing: sliceCapabilities(this.bambuProfileSource().slicerType ?? "prusaslicer", false),
147
150
  };
148
151
  }
149
152
  const streamCandidates = await this.cameraCandidates(target).catch(() => []);
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ import { STLManipulator, SUPPORT_TYPES, type BambuSliceOptions, type ProcessOver
38
38
  import { parsePauseLayers, readPlateGcode, pausedLayers, machinePauseGcode } from "./slicer/layer-pauses.js";
39
39
  import { PRINT_QUALITY_PROPERTIES, withPrintQuality } from "./slicer/print-quality.js";
40
40
  import { PrinterRefusal } from "./refusal.js";
41
+ import { isBambuProjectSlicer, usesBambuCli } from "./slicer/capabilities.js";
41
42
  import { CounterpartService, type BambuControlRoute, type PrinterTarget } from "./counterpart/service.js";
42
43
  import {CAMERA_STREAM_TOOLS} from "./counterpart/camera-tools.js";
43
44
  import { annotateTools } from "./tool-annotations.js";
@@ -153,9 +154,6 @@ function normalizeSlicerType(rawValue: string | undefined): SlicerType {
153
154
  return resolved;
154
155
  }
155
156
 
156
- function isBambuProjectSlicer(slicerType: SlicerType): boolean {
157
- return slicerType === "orcaslicer-bambulab" || slicerType === "bambustudio";
158
- }
159
157
 
160
158
  function firstExistingPath(candidates: string[]): string | undefined {
161
159
  return candidates.find((candidate) => fs.existsSync(candidate));
@@ -915,10 +913,14 @@ class ThreeDPrinterMCPServer {
915
913
  typeof args.bambu_model === "string" && args.bambu_model.trim() ? args.bambu_model : undefined;
916
914
  // Generic OrcaSlicer keeps its user-supplied machine profile unless the
917
915
  // call explicitly targets a Bambu model.
918
- const bambuCli = isBambuProjectSlicer(slicerType) || (slicerType === "orcaslicer" && explicitModel !== undefined);
916
+ const bambuCli = usesBambuCli(slicerType, explicitModel !== undefined);
919
917
  if (!bambuCli && parsePauseLayers(args.pause_at_layers)) {
920
918
  throw new Error(`Pausing at a layer needs the Bambu Studio or OrcaSlicer CLI, not ${slicerType}.`);
921
919
  }
920
+ // Copies only come from the Bambu-compatible CLI; elsewhere one copy would be sliced without a word.
921
+ if (!bambuCli && args.repetitions !== undefined && Number(args.repetitions) > 1) {
922
+ throw new Error(`Copies (repetitions) need the Bambu Studio or OrcaSlicer CLI with a Bambu model, not ${slicerType}; nothing was sliced.`);
923
+ }
922
924
 
923
925
  let printerPreset: string | undefined;
924
926
  let options: BambuSliceOptions | undefined;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What a slice from this server can do with the slicer it runs (counterpart_printer_info
3
+ * capabilities.slicing). Clients such as Counterpart offer supports, copies and auto-orient only
4
+ * when this says so, instead of knowing which slicer accepts what.
5
+ *
6
+ * The Bambu-compatible CLI route (Bambu Studio, FULU OrcaSlicer-bambulab, or OrcaSlicer given a
7
+ * Bambu model) takes all three. Generic OrcaSlicer takes the support keys over its process profile.
8
+ * PrusaSlicer, Slic3r and Cura take none of them through this server. For a Bambu printer on OrcaSlicer, the
9
+ * Bambu route needs the call's bambu_model; clients that report these to the person pass it.
10
+ */
11
+ export interface SliceCapabilities {
12
+ supports: boolean;
13
+ copies: boolean;
14
+ orient: boolean;
15
+ }
16
+
17
+ export function isBambuProjectSlicer(slicerType: string): boolean {
18
+ return slicerType === "orcaslicer-bambulab" || slicerType === "bambustudio";
19
+ }
20
+
21
+ /** True when slice_stl uses the Bambu-compatible CLI route for this slicer and target. */
22
+ export function usesBambuCli(slicerType: string, bambuModel: boolean): boolean {
23
+ return isBambuProjectSlicer(slicerType) || (slicerType === "orcaslicer" && bambuModel);
24
+ }
25
+
26
+ export function sliceCapabilities(slicerType: string, bambuModel: boolean): SliceCapabilities {
27
+ const bambuCli = usesBambuCli(slicerType, bambuModel);
28
+ return { supports: bambuCli || slicerType === "orcaslicer", copies: bambuCli, orient: bambuCli };
29
+ }