Loxberry Client
stdio MCP server exposing LoxBerry client tools (plugins, JSON-RPC, logs)
Ask AI about Loxberry Client
Powered by Claude · Grounded in docs
I know everything about Loxberry Client. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
loxberry-client-library
Releases to npm and the changelog are driven by semantic-release on main using Conventional Commits — see RELEASING.md and CONTRIBUTING.md. Beerware is an informal thank-you on top of MIT (below).
Monorepo
This repository is an npm workspaces monorepo: the root package loxberry-client-library (library + CLI) and packages/loxberry-client-mcp (loxberry-client-mcp on npm — MCP server). Each package has its own package.json, version line, and release notes; CI and Release workflows run from the repo root. See RELEASING.md for how the two packages are published.
TypeScript client for LoxBerry 3.x (3.x-first): JSON-RPC (/admin/system/jsonrpc.php), HTTP Basic on /admin (same as stock htmlauth/.htaccess), and helpers aligned with plugininstall.cgi (plugin list, upload with SecurePIN, uninstall). Includes a small CLI and an optional MCP server package.
Purpose
When you develop a LoxBerry plugin, validating each build usually means manual work: open the appliance in a browser, go to Plugin management, upload your .zip, wait for plugininstall.pl to finish, test, then uninstall — often logging in again for every iteration.
This project automates those same stock flows (multipart upload, install-log polling, uninstall confirmation) over HTTPS from Node.js, so you can wire repeatable pipelines on Windows, macOS, or Linux:
package.jsonscripts — e.g.build → zip → upload → wait → test → uninstallwithout touching the UI.- CI — run install/uninstall checks on a dev appliance from GitHub Actions or another runner.
- AI-assisted workflows — the optional
loxberry-client-mcpserver exposes LoxBerry operations as MCP tools so assistants (Cursor, VS Code, …) can drive the same steps with your approval.
Nothing replaces reading LoxBerry’s own docs for plugin packaging; this library only automates HTTP interaction with the existing admin and JSON-RPC endpoints.
Table of contents
- Purpose
- Ways to use this project
- Install
- Quick usage
- Architecture (overview)
- CLI
- Environment file
- Browser and global builds
- Plugin admin (stock LoxBerry)
- Plugin admin paths
- MCP server
- Development
- License
- Funding
Ways to use this project
You can integrate with LoxBerry in three complementary ways — often library + CLI for plugin development, or MCP when you want an AI assistant to drive the same operations.
| How | npm package | What it is for |
|---|---|---|
| Client library | loxberry-client-library | Automate JSON-RPC, plugin list / upload / uninstall, and install-log polling from Node.js or TypeScript — ideal for tests, CI, and plugin tooling (build a zip, upload, verify, tear down). |
| CLI | same package (binary loxberry-client) | Shell-friendly commands (plugins list, plugins deploy, plugins upload, plugins uninstall, jsonrpc call, …) without writing code. |
| MCP server | loxberry-client-mcp | Model Context Protocol stdio server for editors such as Cursor / VS Code — exposes LoxBerry operations as tools for assistants. |
Plugin development and testing: the library matches stock plugininstall.cgi behavior (multipart upload with SecurePIN, uninstall confirm flow, temp install log polling). That lets you repeat upload → install → uninstall cycles from scripts or tests instead of only manual clicks — see Development → live tests (test:live:full) for an end-to-end example.
Install locally (in another project)
Add the core package as a dependency so versions are pinned in package-lock.json:
npm install loxberry-client-library
Use npx loxberry-client … from that project for CLI commands, or import { … } from "loxberry-client-library" in code (Quick usage).
CLI: npx vs local vs global
-
npx loxberry-client(after a localnpm install loxberry-client-library) — runs the CLI from your project’snode_moduleswithout a global install. Good for CI and team repos. -
Global CLI — one install,
loxberry-clienton yourPATHeverywhere:npm install -g loxberry-client-library loxberry-client --helpUse when you want the same commands in any directory without a
package.json.
MCP server install
The MCP package is separate on npm (same GitHub monorepo). Install it like any other tool:
npm install loxberry-client-mcp
npx loxberry-client-mcp
or, globally: npm install -g loxberry-client-mcp then loxberry-client-mcp. Configuration (env vars, IDE mcp.json) is covered in MCP server and packages/loxberry-client-mcp/README.md.
Install
Core library + CLI (this repository’s main package):
npm install loxberry-client-library
MCP server (optional; separate package):
npm install loxberry-client-mcp
See Ways to use this project for when to use each and local vs global CLI.
Quick usage
import { LoxBerryClient, SessionAuth } from "loxberry-client-library";
const baseUrl = "https://loxberry.local";
const session = new SessionAuth();
// Default strategy "basic": same user/password as the browser admin (Apache Basic on /admin)
await session.login(baseUrl, "admin", "secret");
const client = new LoxBerryClient({ baseUrl, session });
const ms = await client.call("LBSystem::get_miniservers", []);
const plugins = await client.plugins.listInstalledPlugins();
From the shell, plugins deploy --project . is the usual plugin-author loop: it picks the newest dist/loxberry-plugin-*.zip, reads FOLDER= from plugin.cfg, uploads, waits for install + list row, and tolerates a known “md5 changed though something threw” quirk. Pair with plugins uninstall --name <folder> (folder or md5 — the CLI resolves folder → pid when needed).
MQTT broker hints (optional, JSON-RPC only)
fetchMqttConnectionDetails is exported from loxberry-client-library/mqtt. It calls LoxBerry JSON-RPC (mqtt_connectiondetails with fallbacks) and returns host/port/user/pass hints. This repo does not ship or depend on an MQTT client — connect with whatever library you use (mqtt, WebSocket, etc.).
import { fetchMqttConnectionDetails } from "loxberry-client-library/mqtt";
const hints = await fetchMqttConnectionDetails(client);
// e.g. build an URL from hints?.brokerhost, hints?.brokerport, …
Architecture (overview)
Stock LoxBerry 3.x serves the admin UI and APIs under /admin behind HTTP Basic (Apache htmlauth). This library sends the same credentials on each request and talks to jsonrpc.php, plugininstall.cgi, and related endpoints.
flowchart LR
subgraph dev["Your machine"]
CLI["loxberry-client CLI"]
LIB["LoxBerryClient"]
MCP["loxberry-client-mcp"]
CLI --> LIB
MCP --> LIB
end
subgraph lb["LoxBerry appliance"]
ADM["/admin htmlauth"]
RPC["jsonrpc.php"]
PLG["plugininstall.cgi"]
LOG["logfile.cgi / install log"]
ADM --> RPC
ADM --> PLG
ADM --> LOG
end
LIB -->|"HTTPS + Basic / session"| ADM
After plugins upload, the stock UI polls a temp install log URL; the library exposes followPluginInstallTempLog() for the same behavior before waitForPluginFolder().
CLI
The CLI reads process.env only (no bundled .env loader). After build, or via npx once published, run loxberry-client --help.
To load a file locally, use Node 20.6+ --env-file, for example:
node --env-file=.env ./node_modules/loxberry-client-library/dist/cli.cjs plugins list
(or set LOXBERRY_* in your shell / CI secrets).
Auto-generated from src/cli-reference.ts — run npm run docs:sync-cli after changing commands.
Global flags
| Flag | Description |
|---|---|
--help / -h | Print help and exit. |
--baseUrl <url> | LoxBerry base URL (overrides LOXBERRY_BASE_URL). |
--user <name> | Admin user (overrides LOXBERRY_USERNAME). |
--password <secret> | Password (overrides LOXBERRY_PASSWORD). |
--file <path> | Path to a .zip — plugins upload (required), or plugins deploy (optional: newest dist/loxberry-plugin-*.zip if omitted). |
--project <dir> | Plugin project root (contains plugin.cfg + dist/). Default: current directory. Used by plugins deploy. |
--name <id> | Used by plugins uninstall — 32-char md5 (pid) or the FOLDER= name; the latter is resolved via plugins list. |
--securePin <pin> | Used by plugins upload — overrides LOXBERRY_SECURE_PIN. |
--wait-install | Used by plugins upload — after POST, follow the temp logfile.cgi install log, then (with --plugin-folder) wait until that folder appears in plugins list (same as stock UI). |
--plugin-folder <name> | With --wait-install: poll the installed-plugins list until this FOLDER from plugin.cfg exists. |
--follow | Used by logs install — poll generic install log until completion. |
--params '<json>' | Used by jsonrpc call — JSON-RPC params (default []). |
Commands
| Command | Description |
|---|---|
loxberry-client plugins list | Print installed plugins (JSON) from plugin admin list URL. |
loxberry-client plugins upload --file ./plugin.zip [--wait-install] [--plugin-folder myplugin] | POST multipart upload to stock plugininstall.cgi (set LOXBERRY_SECURE_PIN for install). Add --wait-install to poll the temp install log and (recommended) --plugin-folder to wait until the plugin row exists. |
loxberry-client plugins deploy [--project .] [--file ./dist/....zip] [--plugin-folder myplugin] | Plugin developer shortcut: from --project, read FOLDER= in plugin.cfg, pick the newest dist/loxberry-plugin-*.zip, then upload with --wait-install and wait for the folder; if the main flow throws but the installed md5 changes, still exits 0 (LoxBerry quirk). |
loxberry-client plugins uninstall --name <md5-or-folder> | Two-step GET uninstall (confirm + answer=1), same as the web UI. --name can be the plugin folder (from plugin.cfg); the CLI resolves the pid (md5) from plugins list when the value is not 32 hex chars. |
loxberry-client logs install | Read getInstallLog() (generic path; not the per-upload tempfile).Add --follow to poll until a completion phrase appears. |
loxberry-client jsonrpc call <method> [--params '[]'] | Call /admin/system/jsonrpc.php with session/Basic headers. |
Environment
| Variable | Purpose |
|---|---|
LOXBERRY_BASE_URL | e.g. https://loxberry.local |
LOXBERRY_USERNAME / LOXBERRY_PASSWORD | Web admin; sent as HTTP Basic on /admin (stock) |
LOXBERRY_HTTP_BASIC_* | Optional separate Basic layer (see .env.example) |
LOXBERRY_AUTH_STRATEGY | basic (default) or form |
LOXBERRY_LOGIN_PATH | Form-login path if form |
LOXBERRY_SECURE_PIN | Required for plugin install via upload API / MCP |
Examples
npx loxberry-client plugins list --baseUrl https://loxberry.local --user admin --password "$LOX_PASS"
npx loxberry-client plugins upload --file ./dist/myplugin.zip --wait-install --plugin-folder myplugin
npx loxberry-client plugins deploy --project .
npx loxberry-client plugins uninstall --name <md5-or-folder>
npx loxberry-client logs install --follow
npx loxberry-client jsonrpc call LBSystem::get_miniservers --params '[]'
Environment file
.env.example— committed template; copy to.envand fill in values..envis gitignored and is never committed.test/live/loxberry-live.test.tsloads.envfrom the repo root with a tiny test helper (nodotenvpackage) when you runnpm run test:live*.- Live runs are gated by
LOXBERRY_LIVE_TESTS=1, which only thetest:live*npm scripts set. KeepingLOXBERRY_BASE_URLin.envfor the CLI will not makenpm testcall your LoxBerry.
npm lifecycle scripts
.npmrc sets ignore-scripts=true so dependency postinstall (and similar) scripts do not run during npm install. This library has no runtime npm dependencies (only devDependencies for building and testing). To run install scripts once (e.g. debugging a native addon), use npm install --ignore-scripts=false or temporarily remove that line.
Browser and global builds
- ESM / CJS:
dist/index.jsanddist/index.cjs(seepackage.jsonexports). - IIFE (global):
dist/loxberry-client.browser.global.jsexposes the default export aswindow.LoxBerryClient(namespace object).
Calling a real LoxBerry from the browser usually hits CORS; prefer Node for automation or proxy requests through your dev server.
Plugin admin (stock LoxBerry)
Defaults match upstream: list/upload/uninstall go to /admin/system/plugininstall.cgi. Upload requires your SecurePIN (same as the web UI):
await client.plugins.uploadPluginZip(buf, "plugin.zip", { securePin: "1234" });
// Install runs in the background on the appliance; poll until the plugin row exists:
await client.plugins.waitForPluginFolder("myplugin", { title: "My Plugin", timeoutMs: 120_000 });
Uninstall uses the same flow as the UI: GET do=uninstall&pid=<md5> then confirm with answer=1.
Override paths only if your image differs:
Plugin admin paths
Override via LoxBerryClient options when needed:
new LoxBerryClient({
baseUrl,
session,
pluginPaths: {
list: "/your/path/plugins.php",
upload: "/your/path/upload.php",
uninstall: "/your/path/uninstall.php",
installLog: "/your/path/log.php",
},
});
Capture real URLs from your browser devtools and add fixtures under test/fixtures/ to lock behavior (TDD).
MCP server
See packages/loxberry-client-mcp/README.md. Build with npm run build:all.
Testing the MCP server
- Build:
npm run build:all(ornpm run build:mcpif the core library is already built). - Automated smoke (CI + local): after a build (
npm run build:mcporbuild:all), from the repo root — MCP SDK client over stdio:tools/listplustools/call→plugins_listagainst a dead port (expects failure, proves the tool path works without a real LoxBerry):npm run test:mcp - Real LoxBerry: set env (see below) and use Inspector or Cursor; or point the server at your unit and invoke tools there.
- Env (when actually calling tools): set
LOXBERRY_BASE_URL(and credentials) for the server process — same variables as.env.example. - Manual / IDE:
@modelcontextprotocol/inspector:npx @modelcontextprotocol/inspector node packages/loxberry-client-mcp/dist/server.js - Cursor / Copilot: use a stdio server; pass
LOXBERRY_*inenvinmcp.json(Cursor does not load your.envfor MCP). Examples: localnode+ path todist/server.js, or afternpm i -g loxberry-client-mcpusecommand:loxberry-client-mcp(see packages/loxberry-client-mcp/README.md).
Publishing loxberry-client-mcp as its own npm package (alongside loxberry-client-library) is described in RELEASING.md.
Development
npm install
cp .env.example .env # then edit; PowerShell: Copy-Item .env.example .env
npm test
npm run build
For maintainers: AGENTS.md summarizes architecture and conventions for AI-assisted work. Conventional commits and devDependency rationale: CONTRIBUTING.md. Releases and npm: RELEASING.md.
Before the first push to main (or merge that lands on main): run the full local gate and npm run release:dry-run so CI and semantic-release match your expectations—see the First push checklist in RELEASING.md.
Live tests (real LoxBerry)
Default npm test never registers the live suite (no appliance required). The test:live* scripts set LOXBERRY_LIVE_TESTS=1 so Mocha runs test/live/loxberry-live.test.ts against your unit.
| Script | What it does |
|---|---|
npm run test:live | Read-only style checks: login, JSON-RPC LBSystem::get_miniservers, listInstalledPlugins. |
npm run test:live:upload | Same + uploads the tiny fixture under test/fixtures/e2e-plugin/ (zipped in memory). Installs plugin folder loxberryclie2e. |
npm run test:live:full | Same as upload, then uninstalls that plugin — only if title matches E2E Client Lib (safety check). Use only on a dev LoxBerry you control. Verifies list → upload/install → list → uninstall → list. |
npm run test:live:debug | Same as test:live:upload, plus LOXBERRY_LIVE_DEBUG=1: writes tmp/loxberry-live-debug/ (upload-response.html, list-latest.html, trace.log) and stderr traces. Upload only (no uninstall). |
npm run test:live:debug:full | test:live:full + LOXBERRY_LIVE_DEBUG=1: full install and uninstall cycle with the same debug artifacts, plus uninstall-response.html after confirm. Best single command to prove list/upload/uninstall end-to-end. |
How long should upload + install take?
- HTTP POST (upload zip): usually 1–15 seconds to the Pi.
- Background
plugininstall.plfor the tiny E2E plugin: often ~20–90 seconds on a normal SD card; under load, updates, or slow I/O it can reach 2–4 minutes. The test polls the plugin list untilLOXBERRY_LIVE_INSTALL_TIMEOUT_MS(default 120000 = 2 minutes) or Mocha’s slow-suite timeout (install wait + 60s headroom, max 10 minutes). If installs routinely exceed 2 minutes, raiseLOXBERRY_LIVE_INSTALL_TIMEOUT_MS(e.g.300000).
The listInstalledPlugins live test is quick (~1s) and fails fast if the HTML parser finds zero plugins (misconfigured base URL or non-stock markup). The upload / uninstall tests live in a nested describe with a long Mocha timeout so only that block waits for plugininstall.pl on the Pi.
Debugging (logs + artifacts)
- Run
npm run test:live:debug(upload + artifacts) ornpm run test:live:debug:full(adds uninstall +uninstall-response.html). Opentmp/loxberry-live-debug/upload-response.html— if it is not the install log viewer, the POST did not start an install (PIN, lock, validation). - Open
list-latest.html— raw last plugin list GET body; afterdebug:full, refresh mentally: row should appear after install, disappear after uninstall. - Open
uninstall-response.html(debug:fullonly ) — HTML from the confirmed uninstall GET. - Read
trace.log(and stderr): install log polls, list polls, uninstall steps. - For any app using
LoxBerryClient(CLI, scripts), setLOXBERRY_CLIENT_DEBUG=1to print the same plugin-step lines to stderr without writing files.
Upload POST matches the stock browser form: saveformdata, empty archiveurl, uploadfile as application/zip, securepin, btnsubmit. The client checks that the response looks like the install log progress page (not an error/lock HTML).
After upload, the stock UI polls logfile.cgi?logfile=<random>.log until plugininstall.pl finishes. The library does the same via followPluginInstallTempLog() (tempfile from extractInstallLogTempfileFromHtml() on the POST body) before waiting for the new plugin row — polling only the plugin list is not enough.
Manual install check (same zip as the live test)
Stock plugininstall.pl always runs cp …/icons/*. A zip without icons/icon_64.png (and siblings) makes that step fail; the plugin may never show in the UI. The fixture now includes minimal PNGs.
Build a zip on disk and upload it in the browser (Plugin management → Install):
npm run build:e2e-zip
Default output: tmp/loxberryclie2e-manual-upload.zip (override: npx tsx scripts/build-e2e-plugin-zip.ts path/to/out.zip). If that works in the UI but the automated test does not, compare SecurePIN and credentials; if the zip fails in the UI too, open the installation log on the LoxBerry page.
You can put LOXBERRY_LIVE_UPLOAD / LOXBERRY_LIVE_UNINSTALL in .env if you like; LOXBERRY_LIVE_TESTS should come only from the npm scripts. Uninstall is gated by LOXBERRY_LIVE_UNINSTALL=1 (test:live:full sets it).
Upload tests need LOXBERRY_SECURE_PIN in .env (the same SecurePIN you enter on the plugin install page in the browser). Without it, test:live:upload / test:live:full fails with a clear error.
If your LoxBerry uses different plugin admin URLs than the library defaults, live tests will fail until you align pluginPaths (extend the live test client options if needed).
Auth: basic vs form
Stock LoxBerry does not use /admin/index.php for login — the whole htmlauth tree is AuthType Basic (.htaccess). The client default strategy: "basic" sends Authorization: Basic … with your web UI user/password on every request (and probes /admin/system/index.cgi).
If you use a custom HTML form login instead, set LOXBERRY_AUTH_STRATEGY=form and LOXBERRY_LOGIN_PATH as needed.
Troubleshooting
- 401: Wrong username/password for HTTP Basic (same as first browser prompt on
/admin). - 404 on “login”: You were on the old form-login path; with defaults, auth probes
/admin/system/index.cgi. FixLOXBERRY_BASE_URL(scheme/host/port). - JSON-RPC uses the same
Authorization(and cookies, if any) as other/adminrequests — nothing extra to enable (wiki — JsonRpc). - Optional:
LOXBERRY_HTTP_BASIC_*if the Basic user/password differ fromLOXBERRY_USERNAME/LOXBERRY_PASSWORD.
License
MIT — see LICENSE.
Beerware: If we meet someday and you found this useful, you can buy me a beer. (This is a social request, not a legal condition of the MIT license.)
Funding
package.json includes an npm funding field; run npm fund in this repo to see the URL.
