bambu-printer-mcp 1.1.4 → 1.1.6

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/README.md CHANGED
@@ -771,7 +771,7 @@ Two transports are wired in, picked by `bambu_model`:
771
771
  - **TCP-on-6000** for **A1, A1 mini, P1S, P1P**. Native protocol per [OpenBambuAPI/video.md](https://github.com/Doridian/OpenBambuAPI/blob/main/video.md): TLS on port 6000, 80-byte auth packet (`bblp` + access token), repeating 16-byte frame header + JPEG payload.
772
772
  - **RTSP** for **X1, X1 Carbon, X1E, P2S** and **H2, H2S, H2D, H2C, H2D Pro**. Shells out to ffmpeg with `rtsps://bblp:<token>@<host>:322/streaming/live/1 -frames:v 1`. The H2 series wasn't documented in OpenBambuAPI's `video.md` but its firmware uses the same RTSP endpoint as X1 (verified live against an H2S, 2026-04-27).
773
773
 
774
- **Requires ffmpeg in PATH** for the RTSP path. Install with `brew install ffmpeg` on macOS. Override the binary location with the `ffmpeg_path` tool argument if it lives elsewhere. The TCP-on-6000 path uses native Node TLS and does not require ffmpeg.
774
+ **Requires ffmpeg in PATH** for the RTSP path. Install with `brew install ffmpeg` on macOS. Configure a trusted custom binary with the server-side `FFMPEG_PATH` environment variable, or set `MCP_ALLOW_EXECUTABLE_ARG=1` before using the `ffmpeg_path` tool argument. The TCP-on-6000 path uses native Node TLS and does not require ffmpeg.
775
775
 
776
776
  ```json
777
777
  {
@@ -1191,7 +1191,7 @@ Slice an STL or 3MF file using an external slicer and return the path to the out
1191
1191
 
1192
1192
  `slicer_type` options: `bambustudio`, `orcaslicer`, `prusaslicer`, `cura`, `slic3r`. When omitted, the value from the `SLICER_TYPE` environment variable is used (default: `bambustudio`).
1193
1193
 
1194
- `slicer_path` and `slicer_profile` fall back to the `SLICER_PATH` and `SLICER_PROFILE` environment variables when omitted.
1194
+ `slicer_path` and `slicer_profile` fall back to the `SLICER_PATH` and `SLICER_PROFILE` environment variables when omitted. Per-call `slicer_path` overrides require `MCP_ALLOW_EXECUTABLE_ARG=1`.
1195
1195
 
1196
1196
  You can provide either `template_3mf_path` or `template_name` when you want to slice from a saved template. `template_name` resolves through the local template registry directory configured for the server.
1197
1197
 
@@ -1257,7 +1257,7 @@ Send a set of named edit operations (remesh, boolean, decimate, etc.) to a Blend
1257
1257
 
1258
1258
  When `execute` is `false` (the default), the tool returns the payload that would be sent without running anything -- useful for previewing what would be dispatched.
1259
1259
 
1260
- When `execute` is `true`, the server invokes the configured bridge command with the payload as a JSON-encoded environment variable (`MCP_BLENDER_PAYLOAD`). The bridge command must be set via the `BLENDER_MCP_BRIDGE_COMMAND` environment variable or passed inline as `bridge_command`.
1260
+ When `execute` is `true`, the server invokes the configured bridge command with the payload as a JSON-encoded environment variable (`MCP_BLENDER_PAYLOAD`). Configure it with `BLENDER_MCP_BRIDGE_COMMAND`; per-call `bridge_command` overrides require `MCP_ALLOW_EXECUTABLE_ARG=1`.
1261
1261
 
1262
1262
  ```json
1263
1263
  {
package/dist/index.js CHANGED
@@ -498,6 +498,9 @@ function readRuntimeConfig() {
498
498
  enableJsonResponse: parseBooleanEnv(process.env.MCP_HTTP_JSON_RESPONSE, true),
499
499
  allowedOrigins: parseCsvEnv(process.env.MCP_HTTP_ALLOWED_ORIGINS),
500
500
  blenderBridgeCommand: process.env.BLENDER_MCP_BRIDGE_COMMAND?.trim() || undefined,
501
+ ffmpegPath: process.env.FFMPEG_PATH?.trim() || undefined,
502
+ allowExecutableArg: parseBooleanEnv(process.env.MCP_ALLOW_EXECUTABLE_ARG, false),
503
+ allowBridgeCommandArg: parseBooleanEnv(process.env.MCP_ALLOW_BRIDGE_COMMAND_ARG, false),
501
504
  };
502
505
  }
503
506
  function expandUserPath(rawPath) {
@@ -821,17 +824,44 @@ class BambuPrinterMCPServer {
821
824
  throw elicitError;
822
825
  }
823
826
  }
824
- bridgeOptionsFromArgs(args) {
827
+ bridgeOptionsFromArgs(args, toolName) {
825
828
  return {
826
- bridgeCommand: args?.bridge_command !== undefined ? String(args.bridge_command) : undefined,
829
+ bridgeCommand: this.resolveExecutableSelectorArg(args?.bridge_command, toolName, "bridge_command", true),
827
830
  configDir: args?.bambu_network_config_dir !== undefined ? String(args.bambu_network_config_dir) : undefined,
828
831
  countryCode: args?.country_code !== undefined ? String(args.country_code) : undefined,
829
832
  userInfo: args?.user_info !== undefined ? String(args.user_info) : undefined,
830
833
  timeoutMs: args?.timeout_ms !== undefined ? Number(args.timeout_ms) : undefined,
831
834
  };
832
835
  }
836
+ resolveExecutableSelectorArg(rawValue, toolName, argumentName, allowLegacyBridgeArg = false) {
837
+ if (rawValue === undefined) {
838
+ return undefined;
839
+ }
840
+ const value = String(rawValue);
841
+ if (value.length === 0) {
842
+ return undefined;
843
+ }
844
+ const isAllowed = this.runtimeConfig.allowExecutableArg ||
845
+ (allowLegacyBridgeArg && this.runtimeConfig.allowBridgeCommandArg);
846
+ if (!isAllowed) {
847
+ throw new Error(`${toolName}: the ${argumentName} argument cannot select an executable by default. ` +
848
+ "Executable paths and commands are read from server environment configuration. Set " +
849
+ `MCP_ALLOW_EXECUTABLE_ARG=1 to accept ${argumentName} for process execution.`);
850
+ }
851
+ return value;
852
+ }
853
+ resolveSlicerConfigFromArgs(args, toolName) {
854
+ const config = resolveSlicerConfig(args);
855
+ if (args?.slicer_path !== undefined) {
856
+ const resolvedSlicerPath = this.resolveExecutableSelectorArg(args.slicer_path, toolName, "slicer_path");
857
+ if (resolvedSlicerPath !== undefined) {
858
+ config.slicerPath = resolvedSlicerPath;
859
+ }
860
+ }
861
+ return config;
862
+ }
833
863
  async ensurePrintableThreeMFPath(args, printModel, printPreset, bedType) {
834
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args);
864
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(args, "print_3mf_bambu_network");
835
865
  let threeMFPath = String(args.three_mf_path);
836
866
  const JSZip = (await import('jszip')).default;
837
867
  const zipData = fs.readFileSync(threeMFPath);
@@ -993,7 +1023,7 @@ class BambuPrinterMCPServer {
993
1023
  task_ext_change_assist: args?.external_change_assist !== undefined ? Boolean(args.external_change_assist) : false,
994
1024
  try_emmc_print: args?.try_emmc_print !== undefined ? Boolean(args.try_emmc_print) : false,
995
1025
  };
996
- const bridgeResult = await this.bambuNetwork.callWithAgent(bridgeMethod, { client_job_id: clientJobId, params }, this.bridgeOptionsFromArgs(args));
1026
+ const bridgeResult = await this.bambuNetwork.callWithAgent(bridgeMethod, { client_job_id: clientJobId, params }, this.bridgeOptionsFromArgs(args, "print_3mf_bambu_network"));
997
1027
  if (typeof bridgeResult === "object" && bridgeResult !== null && bridgeResult.ok === false) {
998
1028
  throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String(bridgeResult.error || "unknown bridge error")}`);
999
1029
  }
@@ -1484,7 +1514,7 @@ class BambuPrinterMCPServer {
1484
1514
  enum: SLICER_SCHEMA_VALUES,
1485
1515
  description: "Type of slicer to use. Bambu-compatible choices (bambustudio, orcaslicer, orcaslicer-bambulab) export sliced 3MF; aliases such as fulu-orca and orca-studio are accepted."
1486
1516
  },
1487
- slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env)" },
1517
+ slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1488
1518
  slicer_profile: { type: "string", description: "Explicit slicer profile/config file. Overrides the named template only when provided in the tool call." },
1489
1519
  nozzle_diameter: { type: "string", description: "Nozzle diameter in mm (default: 0.4)" },
1490
1520
  bed_type: {
@@ -1530,7 +1560,7 @@ class BambuPrinterMCPServer {
1530
1560
  enum: SLICER_SCHEMA_VALUES,
1531
1561
  description: "Type of slicer to use. Bambu-compatible choices (bambustudio, orcaslicer, orcaslicer-bambulab) export sliced 3MF; aliases such as fulu-orca and orca-studio are accepted."
1532
1562
  },
1533
- slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env)" },
1563
+ slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1534
1564
  slicer_profile: { type: "string", description: "Path to the slicer profile/config file (optional, overrides bambu_model preset)" },
1535
1565
  template_3mf_path: { type: "string", description: "Optional template 3MF whose embedded Bambu slicer settings should be reused when slicing a new STL or 3MF." },
1536
1566
  template_name: { type: "string", description: "Optional named template from the local registry. Resolves to template_3mf_path automatically." },
@@ -1584,7 +1614,7 @@ class BambuPrinterMCPServer {
1584
1614
  type: "object",
1585
1615
  properties: {
1586
1616
  connect: { type: "boolean", description: "When true, start the bridge command and run a handshake plus agent initialization probe." },
1587
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
1617
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1588
1618
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the BambuNetwork agent; defaults to BAMBU_NETWORK_CONFIG_DIR or a user config directory." },
1589
1619
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the agent during startup." },
1590
1620
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string to pass to net.change_user after the agent starts." },
@@ -1601,7 +1631,7 @@ class BambuPrinterMCPServer {
1601
1631
  method: { type: "string", description: "FULU bridge method name, for example bridge.handshake, net.is_user_login, or net.get_user_selected_machine." },
1602
1632
  payload: { type: "object", description: "JSON payload passed to the bridge method." },
1603
1633
  with_agent: { type: "boolean", description: "When true, initialize a BambuNetwork agent and add its agent id to the payload before calling the method." },
1604
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
1634
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1605
1635
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the BambuNetwork agent; defaults to BAMBU_NETWORK_CONFIG_DIR or a user config directory." },
1606
1636
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the agent during startup." },
1607
1637
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string to pass to net.change_user after the agent starts." },
@@ -1637,13 +1667,13 @@ class BambuPrinterMCPServer {
1637
1667
  preset_name: { type: "string", description: "Optional preset name sent in FULU PrintParams; defaults to project plus one-based plate index." },
1638
1668
  task_name: { type: "string", description: "Optional BambuNetwork task name; defaults to the project name." },
1639
1669
  config_filename: { type: "string", description: "Optional config 3MF path for cloud print; defaults to the same 3MF path." },
1640
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
1670
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1641
1671
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the BambuNetwork agent; defaults to BAMBU_NETWORK_CONFIG_DIR or a user config directory." },
1642
1672
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the agent during startup." },
1643
1673
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string to pass to net.change_user after the agent starts." },
1644
1674
  timeout_ms: { type: "number", description: "Bridge request timeout in milliseconds." },
1645
1675
  slicer_type: { type: "string", enum: SLICER_SCHEMA_VALUES, description: "Slicer to use only if auto-slicing an unsliced 3MF; use orcaslicer-bambulab for FULU's fork." },
1646
- slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing; defaults to value from env or a platform default." },
1676
+ slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing; defaults to value from env or a platform default. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1647
1677
  slicer_profile: { type: "string", description: "Path to an optional slicer profile/config file for auto-slicing." },
1648
1678
  nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)." },
1649
1679
  use_ams: { type: "boolean", description: "Whether to use the AMS; defaults to auto-detect from the 3MF mapping." },
@@ -1679,7 +1709,7 @@ class BambuPrinterMCPServer {
1679
1709
  timeout_ms: { type: "number", description: "Max ms to wait for a full frame (default 8000). Camera may take a few seconds on cold start." },
1680
1710
  bambu_model: { type: "string", description: "Printer model. Used to route to the correct protocol or fail fast on unsupported models. Defaults to BAMBU_MODEL." },
1681
1711
  experimental: { type: "boolean", description: "Deprecated and ignored. Earlier this flag let callers probe H2 series via the A1/P1 TCP-on-6000 path; live testing on an H2S confirmed H2 uses RTSP instead, so the flag has no effect now." },
1682
- ffmpeg_path: { type: "string", description: "Override path to the ffmpeg binary used by the RTSP path. Defaults to ffmpeg via $PATH. Required only for the RTSP transport (X1, P2S, H2 series)." },
1712
+ ffmpeg_path: { type: "string", description: "Override path to the ffmpeg binary used by the RTSP path. Defaults to FFMPEG_PATH or ffmpeg via $PATH. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1. Required only for the RTSP transport (X1, P2S, H2 series)." },
1683
1713
  host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
1684
1714
  bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
1685
1715
  bambu_token: { type: "string", description: "Access token (default: value from env)" }
@@ -1997,7 +2027,7 @@ class BambuPrinterMCPServer {
1997
2027
  bambu_network_method: { type: "string", enum: BAMBU_NETWORK_PRINT_METHODS, description: "FULU print method when connection_mode is bambu_network; defaults to start_print for cloud and start_local_print for lan." },
1998
2028
  dev_id: { type: "string", description: "Bambu device id for FULU BambuNetwork printing; defaults to BAMBU_DEV_ID or BAMBU_SERIAL." },
1999
2029
  dev_ip: { type: "string", description: "Printer IP address for FULU BambuNetwork LAN/local print methods; defaults to host when provided." },
2000
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
2030
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2001
2031
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the FULU BambuNetwork agent." },
2002
2032
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the FULU bridge agent." },
2003
2033
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string passed to net.change_user for the FULU bridge." },
@@ -2015,7 +2045,7 @@ class BambuPrinterMCPServer {
2015
2045
  enum: SLICER_SCHEMA_VALUES,
2016
2046
  description: "Slicer to use only if auto-slicing an unsliced 3MF. Bambu-compatible slicer aliases such as fulu-orca and orca-studio are accepted."
2017
2047
  },
2018
- slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing (default: value from env or a platform default)" },
2048
+ slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing (default: value from env or a platform default). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2019
2049
  use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
2020
2050
  ams_mapping: {
2021
2051
  type: "array",
@@ -2122,7 +2152,7 @@ class BambuPrinterMCPServer {
2122
2152
  description: "Ordered edit operations for Blender (e.g. remesh, boolean, decimate)",
2123
2153
  items: { type: "string" }
2124
2154
  },
2125
- bridge_command: { type: "string", description: "Override command for invoking Blender MCP bridge" },
2155
+ bridge_command: { type: "string", description: "Override command for invoking Blender MCP bridge. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2126
2156
  execute: { type: "boolean", description: "Execute bridge command (true) or return payload only (false)" }
2127
2157
  },
2128
2158
  required: ["stl_path", "operations"]
@@ -2195,7 +2225,7 @@ class BambuPrinterMCPServer {
2195
2225
  break;
2196
2226
  case "bambu_network_bridge_status": {
2197
2227
  const bridgeArgs = args;
2198
- const options = this.bridgeOptionsFromArgs(bridgeArgs);
2228
+ const options = this.bridgeOptionsFromArgs(bridgeArgs, "bambu_network_bridge_status");
2199
2229
  result = this.bambuNetwork.getStatus(options);
2200
2230
  if (Boolean(bridgeArgs?.connect)) {
2201
2231
  const probe = await this.bambuNetwork.ensureAgent(options);
@@ -2216,7 +2246,7 @@ class BambuPrinterMCPServer {
2216
2246
  const payload = bridgeArgs.payload && typeof bridgeArgs.payload === "object"
2217
2247
  ? bridgeArgs.payload
2218
2248
  : {};
2219
- const options = this.bridgeOptionsFromArgs(bridgeArgs);
2249
+ const options = this.bridgeOptionsFromArgs(bridgeArgs, "bambu_network_call");
2220
2250
  result = bridgeArgs.with_agent === false
2221
2251
  ? await this.bambuNetwork.request(String(bridgeArgs.method), payload, options)
2222
2252
  : await this.bambuNetwork.callWithAgent(String(bridgeArgs.method), payload, options);
@@ -2233,7 +2263,7 @@ class BambuPrinterMCPServer {
2233
2263
  timeoutMs: args?.timeout_ms !== undefined ? Number(args.timeout_ms) : undefined,
2234
2264
  bambuModel: snapshotModel ? String(snapshotModel) : undefined,
2235
2265
  experimental: Boolean(args?.experimental),
2236
- ffmpegPath: args?.ffmpeg_path ? String(args.ffmpeg_path) : undefined,
2266
+ ffmpegPath: this.resolveExecutableSelectorArg(args?.ffmpeg_path, "camera_snapshot", "ffmpeg_path") ?? this.runtimeConfig.ffmpegPath,
2237
2267
  });
2238
2268
  break;
2239
2269
  }
@@ -2380,7 +2410,7 @@ class BambuPrinterMCPServer {
2380
2410
  if (!args?.template_name) {
2381
2411
  throw new Error("Missing required parameter: template_name");
2382
2412
  }
2383
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args);
2413
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(args, "slice_with_template");
2384
2414
  const sliceModel = await this.resolveBambuModel(args?.bambu_model);
2385
2415
  const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
2386
2416
  const activeSlicerProfile = await resolveTemplateFirstSlicerProfilePath(args, slicerProfile || undefined, resolveTemplatePathFromName(["json", "config", "3mf"]) || explicitTemplatePath || undefined, TEMP_DIR);
@@ -2456,7 +2486,7 @@ class BambuPrinterMCPServer {
2456
2486
  if (!args?.stl_path) {
2457
2487
  throw new Error("Missing required parameter: stl_path");
2458
2488
  }
2459
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args);
2489
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(args, "slice_stl");
2460
2490
  const sliceModel = await this.resolveBambuModel(args?.bambu_model);
2461
2491
  const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
2462
2492
  const activeSlicerProfile = await resolveSlicerProfilePath(slicerProfile || undefined, resolveTemplatePathFromName(["json", "config", "3mf"]) || explicitTemplatePath || undefined, TEMP_DIR);
@@ -2541,7 +2571,7 @@ class BambuPrinterMCPServer {
2541
2571
  if (!bambuSerial || !bambuToken) {
2542
2572
  throw new Error("Bambu serial number and access token are required for print_3mf.");
2543
2573
  }
2544
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args);
2574
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(args, "print_3mf");
2545
2575
  const printModel = await this.resolveBambuModel(args?.bambu_model);
2546
2576
  const printBedType = resolveBedType(args?.bed_type);
2547
2577
  const printNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
@@ -2689,7 +2719,7 @@ class BambuPrinterMCPServer {
2689
2719
  if (!bambuSerial || !bambuToken) {
2690
2720
  throw new Error("Bambu serial number and access token are required for print_collar_charm.");
2691
2721
  }
2692
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args);
2722
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(args, "print_collar_charm");
2693
2723
  const printModel = await this.resolveBambuModel(args?.bambu_model);
2694
2724
  const printBedType = resolveBedType(args?.bed_type);
2695
2725
  const printNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
@@ -2761,9 +2791,7 @@ class BambuPrinterMCPServer {
2761
2791
  stlPath: String(args.stl_path),
2762
2792
  operations: args.operations.map((entry) => String(entry)),
2763
2793
  execute: Boolean(args.execute ?? false),
2764
- bridgeCommand: args.bridge_command
2765
- ? String(args.bridge_command)
2766
- : this.runtimeConfig.blenderBridgeCommand,
2794
+ bridgeCommand: this.resolveExecutableSelectorArg(args.bridge_command, "blender_mcp_edit_model", "bridge_command", true) ?? this.runtimeConfig.blenderBridgeCommand,
2767
2795
  });
2768
2796
  break;
2769
2797
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bambu-printer-mcp",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "MCP server for Bambu Lab 3D printers with STL manipulation, slicing, and print management",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
package/src/index.ts CHANGED
@@ -601,6 +601,9 @@ type RuntimeConfig = {
601
601
  enableJsonResponse: boolean;
602
602
  allowedOrigins: Set<string>;
603
603
  blenderBridgeCommand?: string;
604
+ ffmpegPath?: string;
605
+ allowExecutableArg: boolean;
606
+ allowBridgeCommandArg: boolean;
604
607
  };
605
608
 
606
609
  function parseBooleanEnv(rawValue: string | undefined, fallback: boolean): boolean {
@@ -698,6 +701,9 @@ function readRuntimeConfig(): RuntimeConfig {
698
701
  enableJsonResponse: parseBooleanEnv(process.env.MCP_HTTP_JSON_RESPONSE, true),
699
702
  allowedOrigins: parseCsvEnv(process.env.MCP_HTTP_ALLOWED_ORIGINS),
700
703
  blenderBridgeCommand: process.env.BLENDER_MCP_BRIDGE_COMMAND?.trim() || undefined,
704
+ ffmpegPath: process.env.FFMPEG_PATH?.trim() || undefined,
705
+ allowExecutableArg: parseBooleanEnv(process.env.MCP_ALLOW_EXECUTABLE_ARG, false),
706
+ allowBridgeCommandArg: parseBooleanEnv(process.env.MCP_ALLOW_BRIDGE_COMMAND_ARG, false),
701
707
  };
702
708
  }
703
709
 
@@ -1113,9 +1119,17 @@ class BambuPrinterMCPServer {
1113
1119
  }
1114
1120
  }
1115
1121
 
1116
- private bridgeOptionsFromArgs(args: Record<string, any> | undefined): BambuNetworkBridgeOptions {
1122
+ private bridgeOptionsFromArgs(
1123
+ args: Record<string, any> | undefined,
1124
+ toolName: string
1125
+ ): BambuNetworkBridgeOptions {
1117
1126
  return {
1118
- bridgeCommand: args?.bridge_command !== undefined ? String(args.bridge_command) : undefined,
1127
+ bridgeCommand: this.resolveExecutableSelectorArg(
1128
+ args?.bridge_command,
1129
+ toolName,
1130
+ "bridge_command",
1131
+ true
1132
+ ),
1119
1133
  configDir: args?.bambu_network_config_dir !== undefined ? String(args.bambu_network_config_dir) : undefined,
1120
1134
  countryCode: args?.country_code !== undefined ? String(args.country_code) : undefined,
1121
1135
  userInfo: args?.user_info !== undefined ? String(args.user_info) : undefined,
@@ -1123,13 +1137,61 @@ class BambuPrinterMCPServer {
1123
1137
  };
1124
1138
  }
1125
1139
 
1140
+ private resolveExecutableSelectorArg(
1141
+ rawValue: unknown,
1142
+ toolName: string,
1143
+ argumentName: string,
1144
+ allowLegacyBridgeArg = false
1145
+ ): string | undefined {
1146
+ if (rawValue === undefined) {
1147
+ return undefined;
1148
+ }
1149
+
1150
+ const value = String(rawValue);
1151
+ if (value.length === 0) {
1152
+ return undefined;
1153
+ }
1154
+
1155
+ const isAllowed =
1156
+ this.runtimeConfig.allowExecutableArg ||
1157
+ (allowLegacyBridgeArg && this.runtimeConfig.allowBridgeCommandArg);
1158
+ if (!isAllowed) {
1159
+ throw new Error(
1160
+ `${toolName}: the ${argumentName} argument cannot select an executable by default. ` +
1161
+ "Executable paths and commands are read from server environment configuration. Set " +
1162
+ `MCP_ALLOW_EXECUTABLE_ARG=1 to accept ${argumentName} for process execution.`
1163
+ );
1164
+ }
1165
+
1166
+ return value;
1167
+ }
1168
+
1169
+ private resolveSlicerConfigFromArgs(
1170
+ args: Record<string, unknown> | undefined,
1171
+ toolName: string
1172
+ ): ReturnType<typeof resolveSlicerConfig> {
1173
+ const config = resolveSlicerConfig(args);
1174
+ if (args?.slicer_path !== undefined) {
1175
+ const resolvedSlicerPath = this.resolveExecutableSelectorArg(
1176
+ args.slicer_path,
1177
+ toolName,
1178
+ "slicer_path"
1179
+ );
1180
+ if (resolvedSlicerPath !== undefined) {
1181
+ config.slicerPath = resolvedSlicerPath;
1182
+ }
1183
+ }
1184
+ return config;
1185
+ }
1186
+
1126
1187
  private async ensurePrintableThreeMFPath(
1127
1188
  args: Record<string, any>,
1128
1189
  printModel: string,
1129
1190
  printPreset: string | undefined,
1130
1191
  bedType?: string
1131
1192
  ): Promise<{ threeMFPath: string; autoSliced: boolean }> {
1132
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args);
1193
+ const { slicerType, slicerPath, slicerProfile } =
1194
+ this.resolveSlicerConfigFromArgs(args, "print_3mf_bambu_network");
1133
1195
  let threeMFPath = String(args.three_mf_path);
1134
1196
 
1135
1197
  const JSZip = (await import('jszip')).default;
@@ -1366,7 +1428,7 @@ class BambuPrinterMCPServer {
1366
1428
  const bridgeResult = await this.bambuNetwork.callWithAgent(
1367
1429
  bridgeMethod,
1368
1430
  { client_job_id: clientJobId, params },
1369
- this.bridgeOptionsFromArgs(args)
1431
+ this.bridgeOptionsFromArgs(args, "print_3mf_bambu_network")
1370
1432
  );
1371
1433
 
1372
1434
  if (typeof bridgeResult === "object" && bridgeResult !== null && (bridgeResult as { ok?: unknown }).ok === false) {
@@ -1949,7 +2011,7 @@ class BambuPrinterMCPServer {
1949
2011
  enum: SLICER_SCHEMA_VALUES,
1950
2012
  description: "Type of slicer to use. Bambu-compatible choices (bambustudio, orcaslicer, orcaslicer-bambulab) export sliced 3MF; aliases such as fulu-orca and orca-studio are accepted."
1951
2013
  },
1952
- slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env)" },
2014
+ slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1953
2015
  slicer_profile: { type: "string", description: "Explicit slicer profile/config file. Overrides the named template only when provided in the tool call." },
1954
2016
  nozzle_diameter: { type: "string", description: "Nozzle diameter in mm (default: 0.4)" },
1955
2017
  bed_type: {
@@ -1995,7 +2057,7 @@ class BambuPrinterMCPServer {
1995
2057
  enum: SLICER_SCHEMA_VALUES,
1996
2058
  description: "Type of slicer to use. Bambu-compatible choices (bambustudio, orcaslicer, orcaslicer-bambulab) export sliced 3MF; aliases such as fulu-orca and orca-studio are accepted."
1997
2059
  },
1998
- slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env)" },
2060
+ slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
1999
2061
  slicer_profile: { type: "string", description: "Path to the slicer profile/config file (optional, overrides bambu_model preset)" },
2000
2062
  template_3mf_path: { type: "string", description: "Optional template 3MF whose embedded Bambu slicer settings should be reused when slicing a new STL or 3MF." },
2001
2063
  template_name: { type: "string", description: "Optional named template from the local registry. Resolves to template_3mf_path automatically." },
@@ -2049,7 +2111,7 @@ class BambuPrinterMCPServer {
2049
2111
  type: "object",
2050
2112
  properties: {
2051
2113
  connect: { type: "boolean", description: "When true, start the bridge command and run a handshake plus agent initialization probe." },
2052
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
2114
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2053
2115
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the BambuNetwork agent; defaults to BAMBU_NETWORK_CONFIG_DIR or a user config directory." },
2054
2116
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the agent during startup." },
2055
2117
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string to pass to net.change_user after the agent starts." },
@@ -2066,7 +2128,7 @@ class BambuPrinterMCPServer {
2066
2128
  method: { type: "string", description: "FULU bridge method name, for example bridge.handshake, net.is_user_login, or net.get_user_selected_machine." },
2067
2129
  payload: { type: "object", description: "JSON payload passed to the bridge method." },
2068
2130
  with_agent: { type: "boolean", description: "When true, initialize a BambuNetwork agent and add its agent id to the payload before calling the method." },
2069
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
2131
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2070
2132
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the BambuNetwork agent; defaults to BAMBU_NETWORK_CONFIG_DIR or a user config directory." },
2071
2133
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the agent during startup." },
2072
2134
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string to pass to net.change_user after the agent starts." },
@@ -2102,13 +2164,13 @@ class BambuPrinterMCPServer {
2102
2164
  preset_name: { type: "string", description: "Optional preset name sent in FULU PrintParams; defaults to project plus one-based plate index." },
2103
2165
  task_name: { type: "string", description: "Optional BambuNetwork task name; defaults to the project name." },
2104
2166
  config_filename: { type: "string", description: "Optional config 3MF path for cloud print; defaults to the same 3MF path." },
2105
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
2167
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2106
2168
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the BambuNetwork agent; defaults to BAMBU_NETWORK_CONFIG_DIR or a user config directory." },
2107
2169
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the agent during startup." },
2108
2170
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string to pass to net.change_user after the agent starts." },
2109
2171
  timeout_ms: { type: "number", description: "Bridge request timeout in milliseconds." },
2110
2172
  slicer_type: { type: "string", enum: SLICER_SCHEMA_VALUES, description: "Slicer to use only if auto-slicing an unsliced 3MF; use orcaslicer-bambulab for FULU's fork." },
2111
- slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing; defaults to value from env or a platform default." },
2173
+ slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing; defaults to value from env or a platform default. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2112
2174
  slicer_profile: { type: "string", description: "Path to an optional slicer profile/config file for auto-slicing." },
2113
2175
  nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)." },
2114
2176
  use_ams: { type: "boolean", description: "Whether to use the AMS; defaults to auto-detect from the 3MF mapping." },
@@ -2144,7 +2206,7 @@ class BambuPrinterMCPServer {
2144
2206
  timeout_ms: { type: "number", description: "Max ms to wait for a full frame (default 8000). Camera may take a few seconds on cold start." },
2145
2207
  bambu_model: { type: "string", description: "Printer model. Used to route to the correct protocol or fail fast on unsupported models. Defaults to BAMBU_MODEL." },
2146
2208
  experimental: { type: "boolean", description: "Deprecated and ignored. Earlier this flag let callers probe H2 series via the A1/P1 TCP-on-6000 path; live testing on an H2S confirmed H2 uses RTSP instead, so the flag has no effect now." },
2147
- ffmpeg_path: { type: "string", description: "Override path to the ffmpeg binary used by the RTSP path. Defaults to ffmpeg via $PATH. Required only for the RTSP transport (X1, P2S, H2 series)." },
2209
+ ffmpeg_path: { type: "string", description: "Override path to the ffmpeg binary used by the RTSP path. Defaults to FFMPEG_PATH or ffmpeg via $PATH. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1. Required only for the RTSP transport (X1, P2S, H2 series)." },
2148
2210
  host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
2149
2211
  bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
2150
2212
  bambu_token: { type: "string", description: "Access token (default: value from env)" }
@@ -2462,7 +2524,7 @@ class BambuPrinterMCPServer {
2462
2524
  bambu_network_method: { type: "string", enum: BAMBU_NETWORK_PRINT_METHODS, description: "FULU print method when connection_mode is bambu_network; defaults to start_print for cloud and start_local_print for lan." },
2463
2525
  dev_id: { type: "string", description: "Bambu device id for FULU BambuNetwork printing; defaults to BAMBU_DEV_ID or BAMBU_SERIAL." },
2464
2526
  dev_ip: { type: "string", description: "Printer IP address for FULU BambuNetwork LAN/local print methods; defaults to host when provided." },
2465
- bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND." },
2527
+ bridge_command: { type: "string", description: "Override command for the FULU bridge host or macOS/WSL wrapper; defaults to BAMBU_NETWORK_BRIDGE_COMMAND. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2466
2528
  bambu_network_config_dir: { type: "string", description: "Config/log directory used by the FULU BambuNetwork agent." },
2467
2529
  country_code: { type: "string", description: "BambuNetwork country code, such as US, used by the FULU bridge agent." },
2468
2530
  user_info: { type: "string", description: "Optional BambuNetwork user_info JSON string passed to net.change_user for the FULU bridge." },
@@ -2480,7 +2542,7 @@ class BambuPrinterMCPServer {
2480
2542
  enum: SLICER_SCHEMA_VALUES,
2481
2543
  description: "Slicer to use only if auto-slicing an unsliced 3MF. Bambu-compatible slicer aliases such as fulu-orca and orca-studio are accepted."
2482
2544
  },
2483
- slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing (default: value from env or a platform default)" },
2545
+ slicer_path: { type: "string", description: "Path to the slicer executable for auto-slicing (default: value from env or a platform default). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2484
2546
  use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
2485
2547
  ams_mapping: {
2486
2548
  type: "array",
@@ -2587,7 +2649,7 @@ class BambuPrinterMCPServer {
2587
2649
  description: "Ordered edit operations for Blender (e.g. remesh, boolean, decimate)",
2588
2650
  items: { type: "string" }
2589
2651
  },
2590
- bridge_command: { type: "string", description: "Override command for invoking Blender MCP bridge" },
2652
+ bridge_command: { type: "string", description: "Override command for invoking Blender MCP bridge. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
2591
2653
  execute: { type: "boolean", description: "Execute bridge command (true) or return payload only (false)" }
2592
2654
  },
2593
2655
  required: ["stl_path", "operations"]
@@ -2696,7 +2758,10 @@ class BambuPrinterMCPServer {
2696
2758
 
2697
2759
  case "bambu_network_bridge_status": {
2698
2760
  const bridgeArgs = args as Record<string, any> | undefined;
2699
- const options = this.bridgeOptionsFromArgs(bridgeArgs);
2761
+ const options = this.bridgeOptionsFromArgs(
2762
+ bridgeArgs,
2763
+ "bambu_network_bridge_status"
2764
+ );
2700
2765
  result = this.bambuNetwork.getStatus(options);
2701
2766
  if (Boolean(bridgeArgs?.connect)) {
2702
2767
  const probe = await this.bambuNetwork.ensureAgent(options);
@@ -2718,7 +2783,7 @@ class BambuPrinterMCPServer {
2718
2783
  const payload = bridgeArgs.payload && typeof bridgeArgs.payload === "object"
2719
2784
  ? bridgeArgs.payload as Record<string, unknown>
2720
2785
  : {};
2721
- const options = this.bridgeOptionsFromArgs(bridgeArgs);
2786
+ const options = this.bridgeOptionsFromArgs(bridgeArgs, "bambu_network_call");
2722
2787
  result = bridgeArgs.with_agent === false
2723
2788
  ? await this.bambuNetwork.request(String(bridgeArgs.method), payload, options)
2724
2789
  : await this.bambuNetwork.callWithAgent(String(bridgeArgs.method), payload, options);
@@ -2737,7 +2802,11 @@ class BambuPrinterMCPServer {
2737
2802
  timeoutMs: args?.timeout_ms !== undefined ? Number(args.timeout_ms) : undefined,
2738
2803
  bambuModel: snapshotModel ? String(snapshotModel) : undefined,
2739
2804
  experimental: Boolean(args?.experimental),
2740
- ffmpegPath: args?.ffmpeg_path ? String(args.ffmpeg_path) : undefined,
2805
+ ffmpegPath: this.resolveExecutableSelectorArg(
2806
+ args?.ffmpeg_path,
2807
+ "camera_snapshot",
2808
+ "ffmpeg_path"
2809
+ ) ?? this.runtimeConfig.ffmpegPath,
2741
2810
  });
2742
2811
  break;
2743
2812
  }
@@ -2959,7 +3028,10 @@ class BambuPrinterMCPServer {
2959
3028
  if (!args?.template_name) {
2960
3029
  throw new Error("Missing required parameter: template_name");
2961
3030
  }
2962
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args as Record<string, unknown> | undefined);
3031
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(
3032
+ args as Record<string, unknown> | undefined,
3033
+ "slice_with_template"
3034
+ );
2963
3035
  const sliceModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
2964
3036
  const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
2965
3037
  const activeSlicerProfile = await resolveTemplateFirstSlicerProfilePath(
@@ -3041,7 +3113,10 @@ class BambuPrinterMCPServer {
3041
3113
  if (!args?.stl_path) {
3042
3114
  throw new Error("Missing required parameter: stl_path");
3043
3115
  }
3044
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args as Record<string, unknown> | undefined);
3116
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(
3117
+ args as Record<string, unknown> | undefined,
3118
+ "slice_stl"
3119
+ );
3045
3120
  const sliceModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
3046
3121
  const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
3047
3122
  const activeSlicerProfile = await resolveSlicerProfilePath(
@@ -3131,7 +3206,10 @@ class BambuPrinterMCPServer {
3131
3206
  throw new Error("Bambu serial number and access token are required for print_3mf.");
3132
3207
  }
3133
3208
 
3134
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args as Record<string, unknown> | undefined);
3209
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(
3210
+ args as Record<string, unknown> | undefined,
3211
+ "print_3mf"
3212
+ );
3135
3213
  const printModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
3136
3214
  const printBedType = resolveBedType(args?.bed_type as string | undefined);
3137
3215
  const printNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
@@ -3320,7 +3398,10 @@ class BambuPrinterMCPServer {
3320
3398
  throw new Error("Bambu serial number and access token are required for print_collar_charm.");
3321
3399
  }
3322
3400
 
3323
- const { slicerType, slicerPath, slicerProfile } = resolveSlicerConfig(args as Record<string, unknown> | undefined);
3401
+ const { slicerType, slicerPath, slicerProfile } = this.resolveSlicerConfigFromArgs(
3402
+ args as Record<string, unknown> | undefined,
3403
+ "print_collar_charm"
3404
+ );
3324
3405
  const printModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
3325
3406
  const printBedType = resolveBedType(args?.bed_type as string | undefined);
3326
3407
  const printNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
@@ -3423,9 +3504,12 @@ class BambuPrinterMCPServer {
3423
3504
  stlPath: String(args.stl_path),
3424
3505
  operations: args.operations.map((entry: any) => String(entry)),
3425
3506
  execute: Boolean(args.execute ?? false),
3426
- bridgeCommand: args.bridge_command
3427
- ? String(args.bridge_command)
3428
- : this.runtimeConfig.blenderBridgeCommand,
3507
+ bridgeCommand: this.resolveExecutableSelectorArg(
3508
+ args.bridge_command,
3509
+ "blender_mcp_edit_model",
3510
+ "bridge_command",
3511
+ true
3512
+ ) ?? this.runtimeConfig.blenderBridgeCommand,
3429
3513
  });
3430
3514
  break;
3431
3515