bambu-printer-mcp 1.1.12 → 1.1.13
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/CONTRIBUTORS.md +3 -1
- package/README.md +10 -4
- package/dist/bambu-network-bridge.d.ts +4 -0
- package/dist/bambu-network-bridge.js +3 -0
- package/dist/index.js +196 -85
- package/dist/printers/bambu.d.ts +36 -4
- package/dist/printers/bambu.js +315 -122
- package/dist/safety/archive.d.ts +11 -0
- package/dist/safety/archive.js +214 -0
- package/dist/safety/artifact.d.ts +6 -0
- package/dist/safety/artifact.js +66 -0
- package/dist/safety/limits.d.ts +29 -0
- package/dist/safety/limits.js +100 -0
- package/dist/safety/print-file.d.ts +25 -0
- package/dist/safety/print-file.js +514 -0
- package/dist/safety/printer-state.d.ts +43 -0
- package/dist/safety/printer-state.js +351 -0
- package/package.json +1 -1
- package/src/bambu-network-bridge.ts +7 -0
- package/src/index.ts +203 -93
- package/src/printers/bambu.ts +308 -152
- package/src/safety/archive.ts +181 -0
- package/src/safety/artifact.ts +58 -0
- package/src/safety/limits.ts +93 -0
- package/src/safety/print-file.ts +352 -0
- package/src/safety/printer-state.ts +323 -0
package/src/index.ts
CHANGED
|
@@ -30,6 +30,9 @@ import { hasAmsMappingInput, normalizeAmsMappingObject, normalizeBridgeAmsTrayVa
|
|
|
30
30
|
import { analyze3MFAmsRequirements, analyze3MFPlateObjects, analyzeCollarCharm3MF, extractBambuTemplateSettings, getCollarCharmRolePolicy, parse3MF } from './3mf_parser.js';
|
|
31
31
|
import type { ThreeMFAmsRequirements } from "./types.js";
|
|
32
32
|
import { BambuImplementation, assertDirectPrintSupported } from "./printers/bambu.js";
|
|
33
|
+
import { inspectPrintFile } from "./safety/print-file.js";
|
|
34
|
+
import { validatePrinterState } from "./safety/printer-state.js";
|
|
35
|
+
import { withPrinterOperation, withPrintSnapshot, uniquePrintName } from "./safety/artifact.js";
|
|
33
36
|
|
|
34
37
|
dotenv.config();
|
|
35
38
|
|
|
@@ -64,6 +67,29 @@ const DEFAULT_BAMBU_MODEL =
|
|
|
64
67
|
const DEFAULT_BED_TYPE = process.env.BED_TYPE?.trim().toLowerCase() || "textured_plate";
|
|
65
68
|
const DEFAULT_NOZZLE_DIAMETER = process.env.NOZZLE_DIAMETER?.trim() || "0.4";
|
|
66
69
|
|
|
70
|
+
function requestedPrintNozzles(args: Record<string, any>): number[] | undefined {
|
|
71
|
+
if (args.nozzle_diameters !== undefined && args.nozzle_diameter !== undefined) {
|
|
72
|
+
throw new Error("Supply nozzle_diameters or nozzle_diameter, not both.");
|
|
73
|
+
}
|
|
74
|
+
const raw = args.nozzle_diameters !== undefined ? args.nozzle_diameters :
|
|
75
|
+
args.nozzle_diameter !== undefined ? [args.nozzle_diameter] : undefined;
|
|
76
|
+
// A pre-sliced job already declares every nozzle; compare those with the
|
|
77
|
+
// printer rather than broadcasting the CLI's default diameter over them.
|
|
78
|
+
if (raw === undefined) return undefined;
|
|
79
|
+
if (!Array.isArray(raw) || !raw.length || raw.length > 2 || raw.some(value =>
|
|
80
|
+
(typeof value !== "number" && typeof value !== "string") || ![0.2, 0.4, 0.6, 0.8].includes(Number(value)))) {
|
|
81
|
+
throw new Error("Nozzle diameters must contain one or two supported sizes: 0.2, 0.4, 0.6, 0.8 mm.");
|
|
82
|
+
}
|
|
83
|
+
return raw.map(Number);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function assertUniformCliNozzles(args: Record<string, any>): void {
|
|
87
|
+
const nozzles = requestedPrintNozzles(args);
|
|
88
|
+
if (nozzles && new Set(nozzles).size > 1) {
|
|
89
|
+
throw new Error("Mixed nozzle diameters require a pre-sliced 3MF with complete per-nozzle metadata. Export it from the slicer GUI before printing.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
67
93
|
const VALID_BAMBU_MODELS = ["p1s", "p1p", "p2s", "x1c", "x1e", "a1", "a1mini", "h2d", "h2s", "h2c", "x2d"] as const;
|
|
68
94
|
type BambuModel = typeof VALID_BAMBU_MODELS[number];
|
|
69
95
|
const H2_BAMBU_MODELS = new Set<string>(["h2d", "h2s", "h2c"]);
|
|
@@ -1069,7 +1095,22 @@ class BambuPrinterMCPServer {
|
|
|
1069
1095
|
}
|
|
1070
1096
|
);
|
|
1071
1097
|
|
|
1072
|
-
this.bambu = new BambuImplementation(
|
|
1098
|
+
this.bambu = new BambuImplementation(async message => {
|
|
1099
|
+
try {
|
|
1100
|
+
const response = await this.server.elicitInput({
|
|
1101
|
+
mode: "form",
|
|
1102
|
+
message,
|
|
1103
|
+
requestedSchema: {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
properties: { confirmed: { type: "boolean", title: "I checked the printer and confirm this operation", default: false } },
|
|
1106
|
+
required: ["confirmed"],
|
|
1107
|
+
},
|
|
1108
|
+
});
|
|
1109
|
+
return response.action === "accept" && response.content?.confirmed === true;
|
|
1110
|
+
} catch {
|
|
1111
|
+
throw new Error("Human hardware confirmation requires an MCP client with elicitation support. For deliberately headless operation, BAMBU_REQUIRE_CONFIRMATION=0 disables ordinary print/heat prompts; finished-bed clearance and hardware-error confirmation still require elicitation.");
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1073
1114
|
this.bambuNetwork = new BambuNetworkBridge();
|
|
1074
1115
|
this.stlManipulator = new STLManipulator(TEMP_DIR);
|
|
1075
1116
|
|
|
@@ -1233,13 +1274,12 @@ class BambuPrinterMCPServer {
|
|
|
1233
1274
|
const JSZip = (await import('jszip')).default;
|
|
1234
1275
|
const zipData = fs.readFileSync(threeMFPath);
|
|
1235
1276
|
const zip = await JSZip.loadAsync(zipData);
|
|
1236
|
-
const hasGcode = Object.
|
|
1237
|
-
f => f.match(/Metadata\/plate_\d+\.gcode/i) || f.endsWith('.gcode')
|
|
1238
|
-
);
|
|
1277
|
+
const hasGcode = Object.values(zip.files).some(entry => !entry.dir && /\.gcode$/i.test(entry.name));
|
|
1239
1278
|
|
|
1240
1279
|
if (hasGcode) {
|
|
1241
1280
|
return { threeMFPath, autoSliced: false };
|
|
1242
1281
|
}
|
|
1282
|
+
assertUniformCliNozzles(args);
|
|
1243
1283
|
|
|
1244
1284
|
if (bedType === "supertack_plate") {
|
|
1245
1285
|
throw new Error(
|
|
@@ -1360,7 +1400,8 @@ class BambuPrinterMCPServer {
|
|
|
1360
1400
|
|
|
1361
1401
|
const printModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
|
|
1362
1402
|
const printBedType = resolveBedType(args?.bed_type as string | undefined);
|
|
1363
|
-
const
|
|
1403
|
+
const printNozzles = requestedPrintNozzles(args);
|
|
1404
|
+
const printNozzle = String(printNozzles?.[0] ?? DEFAULT_NOZZLE_DIAMETER);
|
|
1364
1405
|
const printPreset = BAMBU_MODEL_PRESETS[printModel]?.(printNozzle);
|
|
1365
1406
|
const plateIndex = args?.plate_index !== undefined ? Number(args.plate_index) : 0;
|
|
1366
1407
|
|
|
@@ -1395,6 +1436,13 @@ class BambuPrinterMCPServer {
|
|
|
1395
1436
|
throw new Error("bambu_token/access code is required for FULU BambuNetwork LAN/local print methods.");
|
|
1396
1437
|
}
|
|
1397
1438
|
|
|
1439
|
+
if (!bambuSerial || devId !== bambuSerial || !devIp || devIp === "localhost" || !(password || bambuToken)) {
|
|
1440
|
+
throw new Error("Bridge printing requires a matching bambu_serial/dev_id, printer host, and LAN access token for fresh MQTT safety verification, including cloud dispatch.");
|
|
1441
|
+
}
|
|
1442
|
+
for (const field of ["config_filename", "ftp_file", "ftp_file_md5", "ams_mapping_bridge", "ams_mapping2", "ams_mapping_info", "nozzle_mapping", "nozzles_info", "extra_options"]) {
|
|
1443
|
+
if (args[field] !== undefined && args[field] !== "") throw new Error(`${field} cannot override a verified print. Use the structured plate, nozzle, and AMS arguments.`);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1398
1446
|
const { threeMFPath, autoSliced } = await this.ensurePrintableThreeMFPath(
|
|
1399
1447
|
args,
|
|
1400
1448
|
printModel,
|
|
@@ -1411,89 +1459,127 @@ class BambuPrinterMCPServer {
|
|
|
1411
1459
|
printNozzle
|
|
1412
1460
|
);
|
|
1413
1461
|
const threeMfFilename = path.basename(threeMFPath);
|
|
1414
|
-
const projectName = String(args?.project_name || threeMfFilename.replace(/\.3mf$/i, ''));
|
|
1462
|
+
const projectName = String(args?.project_name || args?.task_name || threeMfFilename.replace(/\.3mf$/i, ''));
|
|
1415
1463
|
const presetName = String(args?.preset_name || `${projectName}_plate_${plateIndex + 1}`);
|
|
1416
1464
|
const clientJobId = args?.client_job_id !== undefined ? Number(args.client_job_id) : Date.now();
|
|
1417
1465
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1466
|
+
return withPrinterOperation(devIp, devId, assertActive => withPrintSnapshot(threeMFPath, async (snapshot, retainForDispatchedUpload) => {
|
|
1467
|
+
const inspection = await inspectPrintFile(snapshot, { model: printModel, nozzleDiameters: printNozzles, plateIndex, bedType: printBedType });
|
|
1468
|
+
let mapping = finalAmsMapping?.slice();
|
|
1469
|
+
if (finalAmsSlots) {
|
|
1470
|
+
if (finalAmsSlots.length !== inspection.usedFilamentPositions.length) throw new Error("ams_slots must supply exactly one physical tray per used filament.");
|
|
1471
|
+
mapping = Array(inspection.materials.length).fill(-1);
|
|
1472
|
+
inspection.usedFilamentPositions.forEach((position, index) => { mapping![position] = finalAmsSlots[index]; });
|
|
1473
|
+
}
|
|
1474
|
+
if (!useAMS) {
|
|
1475
|
+
mapping = Array(inspection.materials.length).fill(-1);
|
|
1476
|
+
inspection.usedFilamentPositions.forEach(position => { mapping![position] = 254; });
|
|
1477
|
+
}
|
|
1478
|
+
const requirements = { ...inspection, amsMapping: mapping, useAMS };
|
|
1479
|
+
const initialStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1480
|
+
validatePrinterState(initialStatus, requirements);
|
|
1481
|
+
const bedClearance = await this.bambu.confirmPrintPreflight(devId, initialStatus, inspection);
|
|
1482
|
+
const confirmedStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1483
|
+
validatePrinterState(confirmedStatus, requirements);
|
|
1484
|
+
this.bambu.assertBedClearance(confirmedStatus, bedClearance);
|
|
1485
|
+
const amsMapping = stringifyBridgeJson(mapping);
|
|
1486
|
+
const remoteFileName = uniquePrintName(threeMFPath);
|
|
1487
|
+
const params: Record<string, unknown> = {
|
|
1488
|
+
dev_id: devId,
|
|
1489
|
+
// Firmware can report the task name instead of the uploaded filename.
|
|
1490
|
+
// Bind either report to this inspected artifact for a later safe resume.
|
|
1491
|
+
task_name: remoteFileName.replace(/(?:\.gcode)?\.3mf$/i, ""),
|
|
1492
|
+
project_name: projectName,
|
|
1493
|
+
preset_name: presetName,
|
|
1494
|
+
filename: snapshot,
|
|
1495
|
+
config_filename: snapshot,
|
|
1496
|
+
plate_index: plateIndex + 1,
|
|
1497
|
+
ftp_folder: String(args?.ftp_folder || ""),
|
|
1498
|
+
ftp_file: String(args?.ftp_file || ""),
|
|
1499
|
+
ftp_file_md5: String(args?.ftp_file_md5 || ""),
|
|
1500
|
+
nozzle_mapping: stringifyBridgeJson(args?.nozzle_mapping) || "",
|
|
1501
|
+
ams_mapping: amsMapping || "",
|
|
1502
|
+
ams_mapping2: stringifyBridgeJson(args?.ams_mapping2) || "",
|
|
1503
|
+
ams_mapping_info: stringifyBridgeJson(args?.ams_mapping_info) || "",
|
|
1504
|
+
nozzles_info: stringifyBridgeJson(args?.nozzles_info) || "",
|
|
1505
|
+
connection_type: connectionType,
|
|
1506
|
+
comments: String(args?.comments || ""),
|
|
1507
|
+
origin_profile_id: args?.origin_profile_id !== undefined ? Number(args.origin_profile_id) : 0,
|
|
1508
|
+
stl_design_id: args?.stl_design_id !== undefined ? Number(args.stl_design_id) : 0,
|
|
1509
|
+
origin_model_id: String(args?.origin_model_id || ""),
|
|
1510
|
+
print_type: String(args?.print_type || "from_normal"),
|
|
1511
|
+
dst_file: remoteFileName,
|
|
1512
|
+
dev_name: String(args?.dev_name || ""),
|
|
1513
|
+
dev_ip: devIp,
|
|
1514
|
+
use_ssl_for_ftp: args?.use_ssl_for_ftp !== undefined ? Boolean(args.use_ssl_for_ftp) : true,
|
|
1515
|
+
use_ssl_for_mqtt: args?.use_ssl_for_mqtt !== undefined ? Boolean(args.use_ssl_for_mqtt) : true,
|
|
1516
|
+
username: String(args?.username || "bblp"),
|
|
1517
|
+
password,
|
|
1518
|
+
task_bed_leveling: args?.bed_leveling !== undefined ? Boolean(args.bed_leveling) : true,
|
|
1519
|
+
task_flow_cali: args?.flow_calibration !== undefined ? Boolean(args.flow_calibration) : true,
|
|
1520
|
+
task_vibration_cali: args?.vibration_calibration !== undefined ? Boolean(args.vibration_calibration) : true,
|
|
1521
|
+
task_layer_inspect: args?.layer_inspect !== undefined ? Boolean(args.layer_inspect) : false,
|
|
1522
|
+
task_record_timelapse: args?.timelapse !== undefined ? Boolean(args.timelapse) : false,
|
|
1523
|
+
task_use_ams: useAMS,
|
|
1524
|
+
task_bed_type: printBedType,
|
|
1525
|
+
extra_options: stringifyBridgeJson(args?.extra_options) || "",
|
|
1526
|
+
auto_bed_leveling: args?.auto_bed_leveling !== undefined ? Number(args.auto_bed_leveling) : 0,
|
|
1527
|
+
auto_flow_cali: args?.auto_flow_cali !== undefined ? Number(args.auto_flow_cali) : 0,
|
|
1528
|
+
auto_offset_cali: args?.auto_offset_cali !== undefined ? Number(args.auto_offset_cali) : 0,
|
|
1529
|
+
extruder_cali_manual_mode: args?.extruder_cali_manual_mode !== undefined ? Number(args.extruder_cali_manual_mode) : -1,
|
|
1530
|
+
task_ext_change_assist: args?.external_change_assist !== undefined ? Boolean(args.external_change_assist) : false,
|
|
1531
|
+
try_emmc_print: args?.try_emmc_print !== undefined ? Boolean(args.try_emmc_print) : false,
|
|
1532
|
+
};
|
|
1463
1533
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1534
|
+
assertActive();
|
|
1535
|
+
const bridgeResult = await this.bambuNetwork.callWithAgent(
|
|
1536
|
+
bridgeMethod,
|
|
1537
|
+
{ client_job_id: clientJobId, params },
|
|
1538
|
+
{
|
|
1539
|
+
...this.bridgeOptionsFromArgs(args, "print_3mf_bambu_network"),
|
|
1540
|
+
beforeDispatch: async method => {
|
|
1541
|
+
if (method !== bridgeMethod) return;
|
|
1542
|
+
assertActive();
|
|
1543
|
+
const dispatchStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1544
|
+
validatePrinterState(dispatchStatus, requirements);
|
|
1545
|
+
this.bambu.assertBedClearance(dispatchStatus, bedClearance);
|
|
1546
|
+
},
|
|
1547
|
+
assertDispatchAllowed: method => { if (method === bridgeMethod) assertActive(); },
|
|
1548
|
+
onDispatched: method => { if (method === bridgeMethod) retainForDispatchedUpload(); },
|
|
1549
|
+
}
|
|
1550
|
+
);
|
|
1469
1551
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1552
|
+
if (typeof bridgeResult === "object" && bridgeResult !== null && (bridgeResult as { ok?: unknown }).ok === false) {
|
|
1553
|
+
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String((bridgeResult as { error?: unknown }).error || "unknown bridge error")}`);
|
|
1554
|
+
}
|
|
1555
|
+
if (
|
|
1556
|
+
typeof bridgeResult === "object" &&
|
|
1557
|
+
bridgeResult !== null &&
|
|
1558
|
+
typeof (bridgeResult as { value?: unknown }).value === "number" &&
|
|
1559
|
+
(bridgeResult as { value: number }).value !== 0
|
|
1560
|
+
) {
|
|
1561
|
+
const value = (bridgeResult as { value: number }).value;
|
|
1562
|
+
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} returned non-zero result ${value}.`);
|
|
1563
|
+
}
|
|
1482
1564
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1565
|
+
assertActive();
|
|
1566
|
+
this.bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements);
|
|
1567
|
+
|
|
1568
|
+
return {
|
|
1569
|
+
status: "success",
|
|
1570
|
+
message: `FULU BambuNetwork ${bridgePrintMethod} command for ${threeMfFilename} sent successfully.`,
|
|
1571
|
+
bridgeMethod,
|
|
1572
|
+
bridgeResult,
|
|
1573
|
+
clientJobId,
|
|
1574
|
+
autoSliced,
|
|
1575
|
+
projectName,
|
|
1576
|
+
plateIndex,
|
|
1577
|
+
bridgePlateIndex: plateIndex + 1,
|
|
1578
|
+
useAMS,
|
|
1579
|
+
amsMapping: mapping,
|
|
1580
|
+
params: redactPrintParams(params),
|
|
1581
|
+
};
|
|
1582
|
+
}));
|
|
1497
1583
|
}
|
|
1498
1584
|
|
|
1499
1585
|
private async getResolvedPrinterFilamentInventory(
|
|
@@ -2200,7 +2286,7 @@ class BambuPrinterMCPServer {
|
|
|
2200
2286
|
plate_index: { type: "number", description: "Zero-based plate index to print from the sliced 3MF; converted to FULU's one-based PrintParams plate_index." },
|
|
2201
2287
|
project_name: { type: "string", description: "Optional project name sent in FULU PrintParams; defaults to the 3MF filename without extension." },
|
|
2202
2288
|
preset_name: { type: "string", description: "Optional preset name sent in FULU PrintParams; defaults to project plus one-based plate index." },
|
|
2203
|
-
task_name: { type: "string", description: "Optional
|
|
2289
|
+
task_name: { type: "string", description: "Optional project label when project_name is omitted. The submitted task name uses a unique inspected-job identity for safe resume." },
|
|
2204
2290
|
config_filename: { type: "string", description: "Optional config 3MF path for cloud print; defaults to the same 3MF path." },
|
|
2205
2291
|
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." },
|
|
2206
2292
|
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." },
|
|
@@ -2211,6 +2297,7 @@ class BambuPrinterMCPServer {
|
|
|
2211
2297
|
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." },
|
|
2212
2298
|
slicer_profile: { type: "string", description: "Path to an optional slicer profile/config file for auto-slicing." },
|
|
2213
2299
|
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)." },
|
|
2300
|
+
nozzle_diameters: { type: "array", minItems: 1, maxItems: 2, items: { type: "number", enum: [0.2, 0.4, 0.6, 0.8] }, description: "Complete per-nozzle diameters for pre-sliced jobs, e.g. [0.4, 0.6]. Omit to verify file metadata against every reported nozzle; cannot combine with nozzle_diameter." },
|
|
2214
2301
|
use_ams: { type: "boolean", description: "Whether to use the AMS; defaults to auto-detect from the 3MF mapping." },
|
|
2215
2302
|
ams_mapping: { type: "array", description: "AMS slot mapping array used by both local MCP printing and FULU PrintParams.", items: { type: "number" } },
|
|
2216
2303
|
ams_slots: { type: "array", description: "Per-used-filament AMS slot list, matching the local LAN print path.", items: { type: "number" } },
|
|
@@ -2274,13 +2361,14 @@ class BambuPrinterMCPServer {
|
|
|
2274
2361
|
},
|
|
2275
2362
|
{
|
|
2276
2363
|
name: "upload_gcode",
|
|
2277
|
-
description: "
|
|
2364
|
+
description: "Inspect a G-code file and upload a uniquely named copy without overwriting existing printer files.",
|
|
2278
2365
|
inputSchema: {
|
|
2279
2366
|
type: "object",
|
|
2280
2367
|
properties: {
|
|
2281
2368
|
filename: { type: "string", description: "Name for the file on the printer" },
|
|
2282
2369
|
gcode: { type: "string", description: "G-code content to upload, or a readable local .gcode path. Required unless gcode_path is provided. For large files, prefer gcode_path." },
|
|
2283
2370
|
gcode_path: { type: "string", description: "Local path to a .gcode file to upload. Required unless gcode is provided. This avoids sending large G-code bodies through the MCP request." },
|
|
2371
|
+
bambu_model: { type: "string", enum: [...VALID_BAMBU_MODELS], description: "Required printer model for inspecting the uploaded G-code, even when not starting it." },
|
|
2284
2372
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
2285
2373
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
2286
2374
|
bambu_token: { type: "string", description: "Access token (default: value from env)" }
|
|
@@ -2300,7 +2388,7 @@ class BambuPrinterMCPServer {
|
|
|
2300
2388
|
bambu_model: {
|
|
2301
2389
|
type: "string",
|
|
2302
2390
|
enum: [...VALID_BAMBU_MODELS],
|
|
2303
|
-
description: "Required
|
|
2391
|
+
description: "Required for every printable .gcode or .3mf upload, including upload-only operations."
|
|
2304
2392
|
},
|
|
2305
2393
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
2306
2394
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
@@ -2448,12 +2536,15 @@ class BambuPrinterMCPServer {
|
|
|
2448
2536
|
},
|
|
2449
2537
|
{
|
|
2450
2538
|
name: "set_temperature",
|
|
2451
|
-
description: "Set
|
|
2539
|
+
description: "Set a checked bed or nozzle temperature. Positive heating requires matching fresh printer telemetry; nozzle heating also requires declared material. Zero turns the heater off.",
|
|
2452
2540
|
inputSchema: {
|
|
2453
2541
|
type: "object",
|
|
2454
2542
|
properties: {
|
|
2455
2543
|
component: { type: "string", description: "Component to heat: bed, nozzle, or extruder" },
|
|
2456
|
-
temperature: { type: "number", description: "
|
|
2544
|
+
temperature: { type: "number", minimum: 0, description: "Finite target temperature in °C, checked against model/component and declared material limits" },
|
|
2545
|
+
bambu_model: { type: "string", enum: [...VALID_BAMBU_MODELS], description: "Printer model, required for positive heating unless configured in the environment" },
|
|
2546
|
+
material: { type: "string", description: "Loaded material, for example PLA or PETG. Required for positive nozzle heating, including non-RFID spools." },
|
|
2547
|
+
nozzle_diameter: { type: "number", enum: [0.2, 0.4, 0.6, 0.8], description: "Installed nozzle diameter to compare with printer-reported configuration (default 0.4)" },
|
|
2457
2548
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
2458
2549
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
2459
2550
|
bambu_token: { type: "string", description: "Access token (default: value from env)" }
|
|
@@ -2604,7 +2695,8 @@ class BambuPrinterMCPServer {
|
|
|
2604
2695
|
template_3mf_path: { type: "string", description: "Optional template 3MF whose embedded Bambu slicer settings should be reused when auto-slicing this print job." },
|
|
2605
2696
|
template_name: { type: "string", description: "Optional named template from the local registry. Resolves to template_3mf_path automatically." },
|
|
2606
2697
|
template_dir: { type: "string", description: "Optional template directory override when resolving template_name." },
|
|
2607
|
-
|
|
2698
|
+
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)" },
|
|
2699
|
+
nozzle_diameters: { type: "array", minItems: 1, maxItems: 2, items: { type: "number", enum: [0.2, 0.4, 0.6, 0.8] }, description: "Complete per-nozzle diameters for a pre-sliced job, e.g. [0.4, 0.6]. Omit to verify its declared diameters against live telemetry; cannot combine with nozzle_diameter." }
|
|
2608
2700
|
},
|
|
2609
2701
|
required: ["three_mf_path", "bambu_model"]
|
|
2610
2702
|
}
|
|
@@ -2852,6 +2944,10 @@ class BambuPrinterMCPServer {
|
|
|
2852
2944
|
throw new Error("Missing required parameter: method");
|
|
2853
2945
|
}
|
|
2854
2946
|
const bridgeArgs = args as Record<string, any>;
|
|
2947
|
+
const readMethods = new Set(["bridge.handshake", "net.is_user_login", "net.get_user_id", "net.get_user_name", "net.get_user_avatar", "net.get_user_nickanme"]);
|
|
2948
|
+
if (!readMethods.has(String(bridgeArgs.method))) {
|
|
2949
|
+
throw new Error("Raw bridge mutations and unknown methods are disabled. Use print_3mf_bambu_network for verified printing or a dedicated printer tool.");
|
|
2950
|
+
}
|
|
2855
2951
|
const payload = bridgeArgs.payload && typeof bridgeArgs.payload === "object"
|
|
2856
2952
|
? bridgeArgs.payload as Record<string, unknown>
|
|
2857
2953
|
: {};
|
|
@@ -2902,8 +2998,9 @@ class BambuPrinterMCPServer {
|
|
|
2902
2998
|
}
|
|
2903
2999
|
const uploadSource = resolveUploadGcodeSource(args as Record<string, unknown>);
|
|
2904
3000
|
try {
|
|
3001
|
+
const uploadModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
|
|
2905
3002
|
result = await this.bambu.uploadFile(
|
|
2906
|
-
host, bambuSerial, bambuToken, uploadSource.filePath, String(args.filename), false
|
|
3003
|
+
host, bambuSerial, bambuToken, uploadSource.filePath, String(args.filename), false, uploadModel
|
|
2907
3004
|
);
|
|
2908
3005
|
} finally {
|
|
2909
3006
|
if (uploadSource.cleanupDir) {
|
|
@@ -2918,7 +3015,8 @@ class BambuPrinterMCPServer {
|
|
|
2918
3015
|
throw new Error("Missing required parameters: file_path and filename");
|
|
2919
3016
|
}
|
|
2920
3017
|
const print = Boolean(args.print ?? false);
|
|
2921
|
-
const
|
|
3018
|
+
const printableUpload = /\.(?:gcode|3mf)$/i.test(String(args.file_path)) || /\.(?:gcode|3mf)$/i.test(String(args.filename));
|
|
3019
|
+
const uploadModel = print || printableUpload ? await this.resolveBambuModel(args?.bambu_model as string | undefined) : undefined;
|
|
2922
3020
|
if (print) assertDirectPrintSupported(uploadModel, bambuSerial);
|
|
2923
3021
|
result = await this.bambu.uploadFile(
|
|
2924
3022
|
host, bambuSerial, bambuToken,
|
|
@@ -2982,15 +3080,22 @@ class BambuPrinterMCPServer {
|
|
|
2982
3080
|
);
|
|
2983
3081
|
break;
|
|
2984
3082
|
|
|
2985
|
-
case "set_temperature":
|
|
3083
|
+
case "set_temperature": {
|
|
2986
3084
|
if (!args?.component || args?.temperature === undefined) {
|
|
2987
3085
|
throw new Error("Missing required parameters: component and temperature");
|
|
2988
3086
|
}
|
|
3087
|
+
if (typeof args.temperature !== "number" || !Number.isFinite(args.temperature) || args.temperature < 0) {
|
|
3088
|
+
throw new Error("Temperature must be a finite, non-negative number in °C.");
|
|
3089
|
+
}
|
|
2989
3090
|
result = await this.bambu.setTemperature(
|
|
2990
3091
|
host, bambuSerial, bambuToken,
|
|
2991
|
-
String(args.component),
|
|
3092
|
+
String(args.component), args.temperature,
|
|
3093
|
+
args.temperature === 0 ? undefined : await this.resolveBambuModel(args?.bambu_model as string | undefined),
|
|
3094
|
+
args?.material !== undefined ? String(args.material) : undefined,
|
|
3095
|
+
Number(args?.nozzle_diameter ?? DEFAULT_NOZZLE_DIAMETER)
|
|
2992
3096
|
);
|
|
2993
3097
|
break;
|
|
3098
|
+
}
|
|
2994
3099
|
|
|
2995
3100
|
case "set_fan_speed":
|
|
2996
3101
|
if (!args?.fan || args?.speed === undefined) {
|
|
@@ -3290,7 +3395,8 @@ class BambuPrinterMCPServer {
|
|
|
3290
3395
|
const printModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
|
|
3291
3396
|
assertDirectPrintSupported(printModel, bambuSerial);
|
|
3292
3397
|
const printBedType = resolveBedType(args?.bed_type as string | undefined);
|
|
3293
|
-
const
|
|
3398
|
+
const printNozzles = requestedPrintNozzles(args as Record<string, any>);
|
|
3399
|
+
const printNozzle = String(printNozzles?.[0] ?? DEFAULT_NOZZLE_DIAMETER);
|
|
3294
3400
|
const activeSlicerProfile = await resolveTemplateFirstSlicerProfilePath(
|
|
3295
3401
|
args,
|
|
3296
3402
|
slicerProfile || undefined,
|
|
@@ -3315,6 +3421,7 @@ class BambuPrinterMCPServer {
|
|
|
3315
3421
|
entry => !entry.dir && /\.gcode$/i.test(entry.name)
|
|
3316
3422
|
);
|
|
3317
3423
|
if (!hasGcode) {
|
|
3424
|
+
assertUniformCliNozzles(args as Record<string, any>);
|
|
3318
3425
|
if (printBedType === "supertack_plate") {
|
|
3319
3426
|
throw new Error(
|
|
3320
3427
|
'BambuStudio CLI SuperTack bed type is not verified; use a pre-sliced 3MF for SuperTack or choose textured_plate, cool_plate, engineering_plate, or hot_plate.'
|
|
@@ -3414,6 +3521,7 @@ class BambuPrinterMCPServer {
|
|
|
3414
3521
|
}
|
|
3415
3522
|
if (
|
|
3416
3523
|
isH2Print &&
|
|
3524
|
+
args?.use_ams !== false &&
|
|
3417
3525
|
!hasUserAmsMapping &&
|
|
3418
3526
|
!hasUserAmsSlots &&
|
|
3419
3527
|
args?.auto_match_ams !== true &&
|
|
@@ -3444,6 +3552,7 @@ class BambuPrinterMCPServer {
|
|
|
3444
3552
|
projectName,
|
|
3445
3553
|
filePath: threeMFPath,
|
|
3446
3554
|
bambuModel: printModel,
|
|
3555
|
+
nozzleDiameters: printNozzles,
|
|
3447
3556
|
plateIndex,
|
|
3448
3557
|
useAMS: useAMS,
|
|
3449
3558
|
amsMapping: finalAmsMapping,
|
|
@@ -3512,6 +3621,7 @@ class BambuPrinterMCPServer {
|
|
|
3512
3621
|
projectName,
|
|
3513
3622
|
filePath: preparedThreeMFPath,
|
|
3514
3623
|
bambuModel: printModel,
|
|
3624
|
+
nozzleDiameters: [Number(printNozzle)],
|
|
3515
3625
|
plateIndex: collarAnalysis.plateIndex,
|
|
3516
3626
|
useAMS: true,
|
|
3517
3627
|
amsSlots: collarAnalysis.amsSlots,
|