scripts / setup.cjs
"use strict";
/**
* Setup script for the qemu-mcp LM Studio Plugin.
*
* NOTE: LM Studio does NOT run `postinstall` scripts when installing plugins
* from the Hub (for safety reasons). Therefore the pre-built binary is shipped
* in the `bin/` directory of the published package so the plugin works out of
* the box. This script is provided for local development and for users who
* want to rebuild from the upstream source tree.
*
* Responsibilities:
* [a] Clone the original qemu-mcp-server Rust source tree into ./qemu-mcp-server
* (only when it does not exist yet; otherwise it performs a `git pull --rebase`).
* [b] Build the Rust project with `cargo build --release`.
* [c] Copy the produced binary to bin/qemu-mcp-server (overwriting the shipped
* pre-built artifact so the local build is always up to date).
*
* This script uses only Node.js built-in modules so that no extra dependencies
* are required during plugin installation.
*
* The cloned Rust source tree is NOT part of this plugin's published artifact.
* It is kept for local development and future `git pull` updates from the
* upstream project.
*/
const { spawnSync, execSync } = require("child_process");
const { existsSync, readdirSync } = require("fs");
const { join, resolve } = require("path");
// Ensure the environment has the variables LM Studio's Node host sometimes
// strips (it runs `npm install` in a clean environment). cargo / rustc
// depend on PATH (to locate the `cc` linker) and HOME (for config lookup).
if (!process.env.PATH) {
process.env.PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
}
if (!process.env.HOME) {
process.env.HOME = resolve(__dirname, "..");
}
const REPO_URL = "https://github.com/nagy/qemu-mcp-server";
const TARGET_DIR = resolve(__dirname, "..", "qemu-mcp-server");
const BINARY_PATH = resolve(TARGET_DIR, "target", "release", "qemu-mcp-server");
function fail(message) {
// Messages must be visible to the user running `npm install`.
process.stderr.write("\n[qemu-mcp setup] ERROR: " + message + "\n\n");
process.exit(1);
}
function step(message) {
process.stderr.write("[qemu-mcp setup] " + message + "\n");
}
// ---------------------------------------------------------------------------
// 1. Verify that the Rust toolchain is available.
// ---------------------------------------------------------------------------
const cargoCheck = spawnSync("which", ["cargo"], {
cwd: resolve(__dirname, ".."),
stdio: ["pipe", "pipe", "ignore"],
});
if (cargoCheck.status !== 0 || !cargoCheck.stdout || !cargoCheck.stdout.toString().trim()) {
fail(
"The Rust toolchain (cargo) is required to build the qemu-mcp-server binary.\n" +
"Please install Rust first (https://rustup.rs), then run `npm install` again."
);
}
// ---------------------------------------------------------------------------
// 2. Clone or update the original source tree.
// ---------------------------------------------------------------------------
if (existsSync(TARGET_DIR)) {
step("Source directory already exists; updating with git pull --rebase ...");
try {
execSync("git pull --rebase", {
cwd: TARGET_DIR,
stdio: ["inherit", "pipe", "pipe"],
});
step("Source tree updated successfully.");
} catch (err) {
fail(
"Failed to update the qemu-mcp-server source tree. " +
"Please check your network connection and try again.\n" +
"Details: " + (err.message || err)
);
}
} else {
step("Cloning original repository (" + REPO_URL + ") ...");
try {
execSync('git clone "' + REPO_URL + '" "' + TARGET_DIR + '"', {
cwd: resolve(__dirname, ".."),
stdio: ["inherit", "pipe", "pipe"],
});
step("Source tree cloned successfully.");
} catch (err) {
fail(
"Failed to clone the qemu-mcp-server repository. " +
"Please check your network connection and try again.\n" +
"Details: " + (err.message || err)
);
}
}
// ---------------------------------------------------------------------------
// 3. Build the Rust project in release mode.
// ---------------------------------------------------------------------------
step("Building qemu-mcp-server with cargo (release) ...");
try {
execSync("cargo build --release", {
cwd: TARGET_DIR,
stdio: ["inherit", "pipe", "pipe"],
});
} catch (err) {
fail(
"Failed to build the qemu-mcp-server Rust project.\n" +
"Please check the error output above and try again.\n" +
"Details: " + (err.message || err)
);
}
// ---------------------------------------------------------------------------
// 4. Verify the produced binary.
// ---------------------------------------------------------------------------
if (!existsSync(BINARY_PATH)) {
fail(
"Build completed but the expected binary was not found at:\n" +
BINARY_PATH +
"\n" +
"The upstream project layout may have changed. Please report this issue."
);
}
const files = readdirSync(resolve(TARGET_DIR, "target", "release"));
step("Build complete. Available release binaries: " + files.join(", "));
// ---------------------------------------------------------------------------
// 5. Copy the binary to the shipped bin/ directory.
// ---------------------------------------------------------------------------
const BIN_DIR = resolve(__dirname, "..", "bin");
const SHIPPED_BINARY = resolve(BIN_DIR, "qemu-mcp-server");
if (!existsSync(BIN_DIR)) {
const { mkdirSync } = require("fs");
mkdirSync(BIN_DIR, { recursive: true });
}
const { copyFileSync, chmodSync } = require("fs");
copyFileSync(BINARY_PATH, SHIPPED_BINARY);
chmodSync(SHIPPED_BINARY, 0o755);
step("Binary copied to bin/qemu-mcp-server.");
step("qemu-mcp plugin is ready to use.");
scripts / setup.cjs
"use strict";
/**
* Setup script for the qemu-mcp LM Studio Plugin.
*
* NOTE: LM Studio does NOT run `postinstall` scripts when installing plugins
* from the Hub (for safety reasons). Therefore the pre-built binary is shipped
* in the `bin/` directory of the published package so the plugin works out of
* the box. This script is provided for local development and for users who
* want to rebuild from the upstream source tree.
*
* Responsibilities:
* [a] Clone the original qemu-mcp-server Rust source tree into ./qemu-mcp-server
* (only when it does not exist yet; otherwise it performs a `git pull --rebase`).
* [b] Build the Rust project with `cargo build --release`.
* [c] Copy the produced binary to bin/qemu-mcp-server (overwriting the shipped
* pre-built artifact so the local build is always up to date).
*
* This script uses only Node.js built-in modules so that no extra dependencies
* are required during plugin installation.
*
* The cloned Rust source tree is NOT part of this plugin's published artifact.
* It is kept for local development and future `git pull` updates from the
* upstream project.
*/
const { spawnSync, execSync } = require("child_process");
const { existsSync, readdirSync } = require("fs");
const { join, resolve } = require("path");
// Ensure the environment has the variables LM Studio's Node host sometimes
// strips (it runs `npm install` in a clean environment). cargo / rustc
// depend on PATH (to locate the `cc` linker) and HOME (for config lookup).
if (!process.env.PATH) {
process.env.PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
}
if (!process.env.HOME) {
process.env.HOME = resolve(__dirname, "..");
}
const REPO_URL = "https://github.com/nagy/qemu-mcp-server";
const TARGET_DIR = resolve(__dirname, "..", "qemu-mcp-server");
const BINARY_PATH = resolve(TARGET_DIR, "target", "release", "qemu-mcp-server");
function fail(message) {
// Messages must be visible to the user running `npm install`.
process.stderr.write("\n[qemu-mcp setup] ERROR: " + message + "\n\n");
process.exit(1);
}
function step(message) {
process.stderr.write("[qemu-mcp setup] " + message + "\n");
}
// ---------------------------------------------------------------------------
// 1. Verify that the Rust toolchain is available.
// ---------------------------------------------------------------------------
const cargoCheck = spawnSync("which", ["cargo"], {
cwd: resolve(__dirname, ".."),
stdio: ["pipe", "pipe", "ignore"],
});
if (cargoCheck.status !== 0 || !cargoCheck.stdout || !cargoCheck.stdout.toString().trim()) {
fail(
"The Rust toolchain (cargo) is required to build the qemu-mcp-server binary.\n" +
"Please install Rust first (https://rustup.rs), then run `npm install` again."
);
}
// ---------------------------------------------------------------------------
// 2. Clone or update the original source tree.
// ---------------------------------------------------------------------------
if (existsSync(TARGET_DIR)) {
step("Source directory already exists; updating with git pull --rebase ...");
try {
execSync("git pull --rebase", {
cwd: TARGET_DIR,
stdio: ["inherit", "pipe", "pipe"],
});
step("Source tree updated successfully.");
} catch (err) {
fail(
"Failed to update the qemu-mcp-server source tree. " +
"Please check your network connection and try again.\n" +
"Details: " + (err.message || err)
);
}
} else {
step("Cloning original repository (" + REPO_URL + ") ...");
try {
execSync('git clone "' + REPO_URL + '" "' + TARGET_DIR + '"', {
cwd: resolve(__dirname, ".."),
stdio: ["inherit", "pipe", "pipe"],
});
step("Source tree cloned successfully.");
} catch (err) {
fail(
"Failed to clone the qemu-mcp-server repository. " +
"Please check your network connection and try again.\n" +
"Details: " + (err.message || err)
);
}
}
// ---------------------------------------------------------------------------
// 3. Build the Rust project in release mode.
// ---------------------------------------------------------------------------
step("Building qemu-mcp-server with cargo (release) ...");
try {
execSync("cargo build --release", {
cwd: TARGET_DIR,
stdio: ["inherit", "pipe", "pipe"],
});
} catch (err) {
fail(
"Failed to build the qemu-mcp-server Rust project.\n" +
"Please check the error output above and try again.\n" +
"Details: " + (err.message || err)
);
}
// ---------------------------------------------------------------------------
// 4. Verify the produced binary.
// ---------------------------------------------------------------------------
if (!existsSync(BINARY_PATH)) {
fail(
"Build completed but the expected binary was not found at:\n" +
BINARY_PATH +
"\n" +
"The upstream project layout may have changed. Please report this issue."
);
}
const files = readdirSync(resolve(TARGET_DIR, "target", "release"));
step("Build complete. Available release binaries: " + files.join(", "));
// ---------------------------------------------------------------------------
// 5. Copy the binary to the shipped bin/ directory.
// ---------------------------------------------------------------------------
const BIN_DIR = resolve(__dirname, "..", "bin");
const SHIPPED_BINARY = resolve(BIN_DIR, "qemu-mcp-server");
if (!existsSync(BIN_DIR)) {
const { mkdirSync } = require("fs");
mkdirSync(BIN_DIR, { recursive: true });
}
const { copyFileSync, chmodSync } = require("fs");
copyFileSync(BINARY_PATH, SHIPPED_BINARY);
chmodSync(SHIPPED_BINARY, 0o755);
step("Binary copied to bin/qemu-mcp-server.");
step("qemu-mcp plugin is ready to use.");