bambu-printer-mcp 1.1.27 → 1.1.29
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 +7 -1
- package/README.md +29 -10
- package/dist/ams-mapping.d.ts +18 -2
- package/dist/ams-mapping.js +36 -5
- package/dist/bambu-native.d.ts +55 -0
- package/dist/bambu-native.js +212 -13
- package/dist/index.js +154 -71
- package/dist/printers/bambu.d.ts +29 -2
- package/dist/printers/bambu.js +59 -28
- package/dist/safety/hidden-hms.d.ts +32 -0
- package/dist/safety/hidden-hms.js +405 -0
- package/dist/safety/limits.d.ts +6 -0
- package/dist/safety/limits.js +9 -0
- package/dist/safety/print-file.d.ts +2 -0
- package/dist/safety/print-file.js +83 -7
- package/dist/safety/printer-state.d.ts +11 -0
- package/dist/safety/printer-state.js +115 -22
- package/dist/slicer/profile-flatten.js +25 -0
- package/dist/slicer/remote-slicer.d.ts +12 -0
- package/dist/slicer/remote-slicer.js +240 -0
- package/dist/stl/stl-manipulator.d.ts +2 -1
- package/dist/stl/stl-manipulator.js +71 -42
- package/native/bambu-native-print.cpp +100 -26
- package/package.json +7 -5
- package/scripts/build-bambu-native.sh +53 -0
- package/scripts/setup-bambu-native.mjs +372 -0
- package/src/ams-mapping.ts +38 -5
- package/src/bambu-native.ts +229 -14
- package/src/index.ts +156 -67
- package/src/printers/bambu.ts +65 -25
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/limits.ts +9 -0
- package/src/safety/print-file.ts +71 -7
- package/src/safety/printer-state.ts +117 -22
- package/src/slicer/profile-flatten.ts +32 -0
- package/src/slicer/remote-slicer.ts +216 -0
- package/src/stl/stl-manipulator.ts +70 -43
- package/scripts/build-bambu-native.zsh +0 -33
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
#include <sstream>
|
|
15
15
|
#include <string>
|
|
16
16
|
#include <sys/stat.h>
|
|
17
|
+
#if !defined(__APPLE__)
|
|
18
|
+
#include <pwd.h>
|
|
19
|
+
#include <unistd.h>
|
|
20
|
+
#endif
|
|
17
21
|
#include <thread>
|
|
18
22
|
|
|
19
23
|
// This small adapter intentionally mirrors the public C++ ABI declarations
|
|
@@ -111,13 +115,37 @@ void outputLine(const std::string &line) {
|
|
|
111
115
|
std::cout << line << std::endl;
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
// End a one-shot run once the plug-in's own cleanup (destroy_agent) is done.
|
|
119
|
+
// Its worker threads can outlive that cleanup; process-exit destructors and
|
|
120
|
+
// dlclose then crashed successful command, upload and probe runs with SIGSEGV
|
|
121
|
+
// on Linux. Flush the protocol output and let the OS reclaim the rest.
|
|
122
|
+
[[noreturn]] void exitNow(int code) {
|
|
123
|
+
std::cout.flush();
|
|
124
|
+
std::cerr.flush();
|
|
125
|
+
std::_Exit(code);
|
|
126
|
+
}
|
|
127
|
+
|
|
114
128
|
std::string envOr(const char *name, const std::string &fallback = {}) {
|
|
115
129
|
const char *value = std::getenv(name);
|
|
116
130
|
return value && *value ? std::string(value) : fallback;
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
std::string homeDir() {
|
|
134
|
+
#if defined(__APPLE__)
|
|
120
135
|
return envOr("HOME", "/Users/Shared");
|
|
136
|
+
#else
|
|
137
|
+
// Match Node's os.homedir() so the server and helper agree without HOME.
|
|
138
|
+
const std::string home = envOr("HOME");
|
|
139
|
+
if (!home.empty()) return home;
|
|
140
|
+
const passwd *entry = getpwuid(getuid());
|
|
141
|
+
return entry && entry->pw_dir ? std::string(entry->pw_dir) : std::string("/");
|
|
142
|
+
#endif
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#if defined(__APPLE__)
|
|
146
|
+
bool isRegularFile(const std::string &path) {
|
|
147
|
+
struct stat fileStat {};
|
|
148
|
+
return stat(path.c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode);
|
|
121
149
|
}
|
|
122
150
|
|
|
123
151
|
std::string defaultPluginPath() {
|
|
@@ -125,13 +153,43 @@ std::string defaultPluginPath() {
|
|
|
125
153
|
// present; older Bambu Studio installations remain a compatible fallback.
|
|
126
154
|
const std::string connectPlugin =
|
|
127
155
|
"/Applications/Bambu Connect.app/Contents/Resources/app.asar.unpacked/plugins/plugins-mac/libbambu_networking.dylib";
|
|
128
|
-
|
|
129
|
-
if (stat(connectPlugin.c_str(), &connectStat) == 0 && S_ISREG(connectStat.st_mode)) {
|
|
130
|
-
return connectPlugin;
|
|
131
|
-
}
|
|
156
|
+
if (isRegularFile(connectPlugin)) return connectPlugin;
|
|
132
157
|
return homeDir() + "/Library/Application Support/BambuStudio/plugins/libbambu_networking.dylib";
|
|
133
158
|
}
|
|
134
159
|
|
|
160
|
+
std::string defaultConfigDir() {
|
|
161
|
+
return envOr("BAMBU_NATIVE_CONFIG_DIR", homeDir() + "/Library/Application Support/BambuStudio");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
std::string defaultCertDir() {
|
|
165
|
+
return envOr("BAMBU_NATIVE_CERT_DIR", "/Applications/BambuStudio.app/Contents/Resources/cert");
|
|
166
|
+
}
|
|
167
|
+
#else
|
|
168
|
+
// Linux has no application bundle to borrow from. The setup script installs a
|
|
169
|
+
// pinned plug-in and certificates under one private directory. The server
|
|
170
|
+
// passes the resolved paths explicitly; these defaults only serve direct use
|
|
171
|
+
// and mirror bambuNativeManagedPaths() in src/bambu-native.ts.
|
|
172
|
+
std::string nativeHomeDir() {
|
|
173
|
+
const std::string configured = envOr("BAMBU_NATIVE_HOME");
|
|
174
|
+
if (!configured.empty()) return configured;
|
|
175
|
+
return envOr("XDG_CONFIG_HOME", homeDir() + "/.config") + "/bambu-printer-mcp/bambu-native";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
std::string defaultPluginPath() {
|
|
179
|
+
// Only the pinned plug-in: Bambu Studio's own copy auto-updates and its
|
|
180
|
+
// PrintParams layout is not checked against this helper.
|
|
181
|
+
return nativeHomeDir() + "/plugins/libbambu_networking.so";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
std::string defaultConfigDir() {
|
|
185
|
+
return envOr("BAMBU_NATIVE_CONFIG_DIR", nativeHomeDir() + "/config");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
std::string defaultCertDir() {
|
|
189
|
+
return envOr("BAMBU_NATIVE_CERT_DIR", nativeHomeDir() + "/cert");
|
|
190
|
+
}
|
|
191
|
+
#endif
|
|
192
|
+
|
|
135
193
|
void *symbol(void *handle, const char *name) {
|
|
136
194
|
return dlsym(handle, name);
|
|
137
195
|
}
|
|
@@ -238,10 +296,8 @@ void runProbe(NativeApi &api) {
|
|
|
238
296
|
|
|
239
297
|
// Avoid calling the C++ string-returning symbol in the probe. The tunnel
|
|
240
298
|
// API is C ABI and is the only part needed to verify the X2D route.
|
|
241
|
-
const std::string configDir =
|
|
242
|
-
|
|
243
|
-
const std::string certDir = envOr("BAMBU_NATIVE_CERT_DIR",
|
|
244
|
-
"/Applications/BambuStudio.app/Contents/Resources/cert");
|
|
299
|
+
const std::string configDir = defaultConfigDir();
|
|
300
|
+
const std::string certDir = defaultCertDir();
|
|
245
301
|
const std::string certFile = envOr("BAMBU_NATIVE_CERT_FILE", "slicer_base64.cer");
|
|
246
302
|
void *agent = api.createAgent(configDir);
|
|
247
303
|
if (!agent) throw std::runtime_error("bambu_network_create_agent returned null in probe");
|
|
@@ -256,7 +312,8 @@ void runProbe(NativeApi &api) {
|
|
|
256
312
|
const FtErr createResult = api.ftTunnelCreate(localUrl(host, token).c_str(), &tunnel);
|
|
257
313
|
if (createResult != 0 || !tunnel) {
|
|
258
314
|
outputLine("probe=failed stage=tunnel_create result=" + std::to_string(createResult));
|
|
259
|
-
|
|
315
|
+
api.destroyAgent(agent);
|
|
316
|
+
exitNow(22);
|
|
260
317
|
}
|
|
261
318
|
|
|
262
319
|
const FtErr connectResult = api.ftTunnelSyncConnect(tunnel);
|
|
@@ -264,6 +321,7 @@ void runProbe(NativeApi &api) {
|
|
|
264
321
|
api.destroyAgent(agent);
|
|
265
322
|
outputLine("probe=local_tunnel version=" + version + " ft_abi=" + std::to_string(abi) +
|
|
266
323
|
" connect_result=" + std::to_string(connectResult));
|
|
324
|
+
if (connectResult != 0) exitNow(22);
|
|
267
325
|
}
|
|
268
326
|
|
|
269
327
|
void runMqttProbe(NativeApi &api) {
|
|
@@ -274,10 +332,8 @@ void runMqttProbe(NativeApi &api) {
|
|
|
274
332
|
throw std::runtime_error("BAMBU_NATIVE_HOST, BAMBU_NATIVE_ACCESS_CODE, and BAMBU_NATIVE_SERIAL are required for --probe-mqtt");
|
|
275
333
|
}
|
|
276
334
|
|
|
277
|
-
const std::string configDir =
|
|
278
|
-
|
|
279
|
-
const std::string certDir = envOr("BAMBU_NATIVE_CERT_DIR",
|
|
280
|
-
"/Applications/BambuStudio.app/Contents/Resources/cert");
|
|
335
|
+
const std::string configDir = defaultConfigDir();
|
|
336
|
+
const std::string certDir = defaultCertDir();
|
|
281
337
|
const std::string certFile = envOr("BAMBU_NATIVE_CERT_FILE", "slicer_base64.cer");
|
|
282
338
|
void *agent = api.createAgent(configDir);
|
|
283
339
|
if (!agent) throw std::runtime_error("bambu_network_create_agent returned null in MQTT probe");
|
|
@@ -384,7 +440,7 @@ void runMqttProbe(NativeApi &api) {
|
|
|
384
440
|
destroyAgent();
|
|
385
441
|
outputLine("probe=mqtt status=" + std::to_string(status) +
|
|
386
442
|
(message.empty() ? std::string{} : " message=" + message));
|
|
387
|
-
|
|
443
|
+
exitNow(ok ? 0 : 21);
|
|
388
444
|
}
|
|
389
445
|
|
|
390
446
|
std::string bedTypeForNative(const std::string &bedType) {
|
|
@@ -475,10 +531,8 @@ void runOperation(NativeApi &api, NativeOperation operation) {
|
|
|
475
531
|
" file is not readable: " + file);
|
|
476
532
|
}
|
|
477
533
|
|
|
478
|
-
const std::string configDir =
|
|
479
|
-
|
|
480
|
-
const std::string certDir = envOr("BAMBU_NATIVE_CERT_DIR",
|
|
481
|
-
"/Applications/BambuStudio.app/Contents/Resources/cert");
|
|
534
|
+
const std::string configDir = defaultConfigDir();
|
|
535
|
+
const std::string certDir = defaultCertDir();
|
|
482
536
|
const std::string certFile = envOr("BAMBU_NATIVE_CERT_FILE", "slicer_base64.cer");
|
|
483
537
|
const std::string country = envOr("BAMBU_NATIVE_COUNTRY", "US");
|
|
484
538
|
|
|
@@ -653,8 +707,7 @@ void runOperation(NativeApi &api, NativeOperation operation) {
|
|
|
653
707
|
}
|
|
654
708
|
outputLine("native_command result=" + std::to_string(result));
|
|
655
709
|
destroyAgent();
|
|
656
|
-
|
|
657
|
-
return;
|
|
710
|
+
exitNow(result == 0 ? 0 : 20);
|
|
658
711
|
}
|
|
659
712
|
|
|
660
713
|
std::this_thread::sleep_for(std::chrono::seconds(3));
|
|
@@ -729,35 +782,56 @@ void runOperation(NativeApi &api, NativeOperation operation) {
|
|
|
729
782
|
// crash after acceptance; let the OS reclaim it without changing that result.
|
|
730
783
|
// outputLine flushes the receipt before exiting. Upload/control paths retain
|
|
731
784
|
// their existing cleanup, and no dispatched print is replayed here.
|
|
732
|
-
if (!uploadOnly)
|
|
785
|
+
if (!uploadOnly) exitNow(result == 0 ? 0 : 20);
|
|
733
786
|
destroyAgent();
|
|
734
|
-
|
|
787
|
+
exitNow(result == 0 ? 0 : 20);
|
|
735
788
|
}
|
|
736
789
|
|
|
737
790
|
} // namespace
|
|
738
791
|
|
|
792
|
+
// Load the plug-in and resolve every required symbol without any network
|
|
793
|
+
// access. On Linux also load the file-tunnel library the plug-in opens from its
|
|
794
|
+
// own directory, so missing system libraries surface at setup time.
|
|
795
|
+
void runPluginCheck(const NativeApi &) {
|
|
796
|
+
#if !defined(__APPLE__)
|
|
797
|
+
const std::string plugin = envOr("BAMBU_NATIVE_PLUGIN", defaultPluginPath());
|
|
798
|
+
// Resolve beside the plug-in, never through the system library path.
|
|
799
|
+
const std::string::size_type slash = plugin.find_last_of('/');
|
|
800
|
+
const std::string source = (slash == std::string::npos ? std::string("./") : plugin.substr(0, slash + 1)) + "libBambuSource.so";
|
|
801
|
+
void *handle = dlopen(source.c_str(), RTLD_NOW | RTLD_LOCAL);
|
|
802
|
+
if (!handle) {
|
|
803
|
+
const char *error = dlerror();
|
|
804
|
+
throw std::runtime_error("could not load Bambu tunnel library: " + source + (error ? std::string(": ") + error : std::string()));
|
|
805
|
+
}
|
|
806
|
+
dlclose(handle);
|
|
807
|
+
#endif
|
|
808
|
+
outputLine("plugin=ok");
|
|
809
|
+
}
|
|
810
|
+
|
|
739
811
|
int main(int argc, char **argv) {
|
|
740
812
|
try {
|
|
813
|
+
const bool pluginCheck = argc == 2 && std::strcmp(argv[1], "--check-plugin") == 0;
|
|
741
814
|
const bool probe = argc == 2 && std::strcmp(argv[1], "--probe") == 0;
|
|
742
815
|
const bool mqttProbe = argc == 2 && std::strcmp(argv[1], "--probe-mqtt") == 0;
|
|
743
816
|
const bool print = argc == 2 && std::strcmp(argv[1], "--print-authorized") == 0;
|
|
744
817
|
const bool upload = argc == 2 && std::strcmp(argv[1], "--upload") == 0;
|
|
745
818
|
const bool command = argc == 2 && std::strcmp(argv[1], "--command") == 0;
|
|
746
819
|
const bool authorizedCommand = argc == 2 && std::strcmp(argv[1], "--command-authorized") == 0;
|
|
747
|
-
if (!probe && !mqttProbe && !print && !upload && !command && !authorizedCommand) {
|
|
748
|
-
std::cerr << "usage: bambu-native-print --probe|--probe-mqtt|--print-authorized|--upload|--command|--command-authorized" << std::endl;
|
|
820
|
+
if (!pluginCheck && !probe && !mqttProbe && !print && !upload && !command && !authorizedCommand) {
|
|
821
|
+
std::cerr << "usage: bambu-native-print --check-plugin|--probe|--probe-mqtt|--print-authorized|--upload|--command|--command-authorized" << std::endl;
|
|
749
822
|
return 2;
|
|
750
823
|
}
|
|
751
824
|
NativeApi api = loadApi();
|
|
825
|
+
if (pluginCheck) runPluginCheck(api);
|
|
752
826
|
if (probe) runProbe(api);
|
|
753
827
|
if (mqttProbe) runMqttProbe(api);
|
|
754
828
|
if (print) runOperation(api, NativeOperation::Print);
|
|
755
829
|
if (upload) runOperation(api, NativeOperation::Upload);
|
|
756
830
|
if (command) runOperation(api, NativeOperation::Command);
|
|
757
831
|
if (authorizedCommand) runOperation(api, NativeOperation::AuthorizedCommand);
|
|
758
|
-
|
|
832
|
+
exitNow(0);
|
|
759
833
|
} catch (const std::exception &error) {
|
|
760
834
|
std::cerr << "native_error=" << error.what() << std::endl;
|
|
761
|
-
|
|
835
|
+
exitNow(1);
|
|
762
836
|
}
|
|
763
837
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bambu-printer-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.29",
|
|
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": {
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
"package:mcpb": "npm run build && node scripts/package-mcpb.mjs",
|
|
18
18
|
"version": "node scripts/sync-manifest-version.mjs",
|
|
19
19
|
"postinstall": "node scripts/install-patches.mjs",
|
|
20
|
-
"build:native": "scripts/build-bambu-native.
|
|
20
|
+
"build:native": "sh scripts/build-bambu-native.sh"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
|
-
"bambu-printer-mcp": "dist/index.js"
|
|
23
|
+
"bambu-printer-mcp": "dist/index.js",
|
|
24
|
+
"bambu-printer-mcp-native-setup": "scripts/setup-bambu-native.mjs"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"dist",
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"LICENSE",
|
|
33
34
|
"native/*.cpp",
|
|
34
35
|
"native/*.h",
|
|
35
|
-
"scripts/build-bambu-native.
|
|
36
|
+
"scripts/build-bambu-native.sh",
|
|
37
|
+
"scripts/setup-bambu-native.mjs"
|
|
36
38
|
],
|
|
37
39
|
"keywords": [
|
|
38
40
|
"mcp",
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
50
52
|
"bambu-js": "^2.2.2",
|
|
51
53
|
"bambu-node": "3.22.21",
|
|
52
|
-
"basic-ftp": "^6.2.
|
|
54
|
+
"basic-ftp": "^6.2.3",
|
|
53
55
|
"dotenv": "^16.3.1",
|
|
54
56
|
"jszip": "^3.10.2",
|
|
55
57
|
"mqtt": "^5.16.0",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Build the optional adapter that calls Bambu's networking plug-in at runtime.
|
|
4
|
+
# The plug-in itself is not bundled here. Supported hosts: macOS and Linux.
|
|
5
|
+
# Usage: build-bambu-native.sh [--output PATH]
|
|
6
|
+
set -eu
|
|
7
|
+
|
|
8
|
+
case "$(uname -s)" in
|
|
9
|
+
Darwin) default_cxx=clang++ ;;
|
|
10
|
+
Linux) default_cxx=c++ ;;
|
|
11
|
+
*)
|
|
12
|
+
echo "The Bambu native helper supports macOS and Linux only." >&2
|
|
13
|
+
exit 1
|
|
14
|
+
;;
|
|
15
|
+
esac
|
|
16
|
+
|
|
17
|
+
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
18
|
+
project_root=$(dirname -- "$script_dir")
|
|
19
|
+
source_file="$project_root/native/bambu-native-print.cpp"
|
|
20
|
+
output="$project_root/native/bambu-native-print"
|
|
21
|
+
|
|
22
|
+
while [ "$#" -gt 0 ]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--output)
|
|
25
|
+
[ "$#" -ge 2 ] && [ -n "$2" ] || { echo "--output requires a path" >&2; exit 2; }
|
|
26
|
+
output=$2
|
|
27
|
+
shift 2
|
|
28
|
+
;;
|
|
29
|
+
*)
|
|
30
|
+
echo "usage: $0 [--output PATH]" >&2
|
|
31
|
+
exit 2
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
cxx_bin=${CXX:-$default_cxx}
|
|
37
|
+
if ! command -v "$cxx_bin" >/dev/null 2>&1; then
|
|
38
|
+
echo "C++ compiler not found: $cxx_bin (install clang++ or g++, or set CXX)" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
mkdir -p "$(dirname -- "$output")"
|
|
43
|
+
# Compile beside the target and rename so a running server never executes a
|
|
44
|
+
# partially written helper.
|
|
45
|
+
temporary="$output.tmp.$$"
|
|
46
|
+
trap 'rm -f "$temporary"' EXIT
|
|
47
|
+
# dash skips EXIT traps on signals; clean up a killed build explicitly.
|
|
48
|
+
trap 'rm -f "$temporary"; exit 1' HUP INT TERM
|
|
49
|
+
"$cxx_bin" -std=c++17 -O2 -Wall -Wextra -pthread -o "$temporary" "$source_file" -ldl
|
|
50
|
+
chmod 755 "$temporary"
|
|
51
|
+
mv -f "$temporary" "$output"
|
|
52
|
+
trap - EXIT HUP INT TERM
|
|
53
|
+
echo "Built $output"
|