balenamcp

command module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: MIT Imports: 5 Imported by: 0

README

BalenaMCP

CI Security OpenSSF Scorecard Go Report Card Go Reference Latest Release Renovate Conventional Commits

A Model Context Protocol server that wraps the balena CLI so MCP-aware agents (Claude Code, Claude Desktop, Cursor, Continue, Cline, Goose, etc.) can list fleets, inspect devices, manage tags and env vars, pin releases, reboot devices, and more.

It exposes all three MCP primitives: tools (direct balena actions), prompts (guided multi-step workflows like canary rollouts and device diagnosis), and resources (read-only fleet/device/release state you attach as context). See the Tools, Prompts, and Resources sections below.

This is a personal fork of klutchell/balenamcp brought up to date with the current balena CLI and the current mark3labs/mcp-go.

Prerequisites

  • balena CLI on PATH
  • An MCP-aware agent or IDE (Claude Code, Claude Desktop, Cursor, …)
  • Go 1.23+ (only if you're building from source — pre-built binaries don't require it)

Install

Three options, pick whichever fits.

Grab the archive for your OS/arch from Releases:

OS Arch Archive
Linux x86_64 balenamcp_<version>_Linux_x86_64.tar.gz
Linux arm64 balenamcp_<version>_Linux_arm64.tar.gz
macOS x86_64 (Intel) balenamcp_<version>_Darwin_x86_64.tar.gz
macOS arm64 (Apple Silicon) balenamcp_<version>_Darwin_arm64.tar.gz
Windows x86_64 balenamcp_<version>_Windows_x86_64.zip

Each release also publishes:

  • checksums.txt — SHA-256 of every artifact
  • checksums.txt.sigstore.json — cosign signature over checksums.txt
  • <archive>.sbom.cdx.json — CycloneDX Software Bill of Materials per archive
  • <archive>.sbom.cdx.json.sigstore.json — cosign signature over each SBOM

The release is signed with cosign using Sigstore keyless signing — no public-key juggling needed. Install cosign, then:

# 1. Verify the signature on checksums.txt
cosign verify-blob \
  --bundle checksums.txt.sigstore.json \
  --certificate-identity-regexp \
    'https://github.com/schubydoo/balenamcp/.github/workflows/release.yml@.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  checksums.txt

# 2. Verify your archive against the (now-trusted) checksums file
sha256sum --check checksums.txt --ignore-missing

If both commands succeed, you've cryptographically verified that the archive was built by this repo's release workflow (not a typo-squatter, not tampered with in transit).

Extract and install
tar -xzf balenamcp_<version>_Linux_x86_64.tar.gz
sudo install balenamcp /usr/local/bin/
balenamcp --help   # or just `balenamcp` to start serving over stdio
2. go install (Go developers)
go install github.com/schubydoo/balenamcp@latest

Resolves to the highest semver tag, builds locally, and drops the binary in $GOBIN (default $GOPATH/bin or ~/go/bin). For docs and package info: https://pkg.go.dev/github.com/schubydoo/balenamcp.

@latest follows the most recent release; pin a specific version with go install github.com/schubydoo/balenamcp@v0.1.0 if you'd rather not auto-update.

3. Build from source
git clone https://github.com/schubydoo/balenamcp.git
cd balenamcp
go mod download
go build -o bin/balenamcp

Cross-compile examples:

GOOS=linux   GOARCH=amd64 go build -o bin/balenamcp-linux-amd64
GOOS=windows GOARCH=amd64 go build -o bin/balenamcp-windows-amd64.exe
GOOS=darwin  GOARCH=arm64 go build -o bin/balenamcp-darwin-arm64

For a versioned source build (matches release builds):

go build -ldflags='-s -w -X github.com/schubydoo/balenamcp/server.Version=v0.1.0' \
  -trimpath -o bin/balenamcp .

Without the -X ldflag, the server reports dev as its version.

A -dry-run flag is available — the server prints the balena command it would run instead of executing it. Useful for testing/debugging.

Environment variables
Variable Default Purpose
BALENAMCP_EXEC_TIMEOUT 60 (seconds) Wall-clock cap on any single balena CLI subprocess. Prevents device-logs --tail and similar long-running commands from blocking the MCP transport indefinitely. Set to a higher integer for slow networks; the server logs a warning and falls back to default if the value is non-positive or non-numeric.
BALENAMCP_REQUIRE_CONFIRM unset (off) When set to 1/true, every destructive tool refuses to run unless the call carries confirm: true in its arguments. A belt-and-suspenders safety net for MCP clients that ignore the destructiveHint annotation. Off by default — Claude Desktop and other compliant clients already prompt before invoking destructive tools, so the gate is redundant there.

Authenticate

Before using any tool that touches balenaCloud, log in once on the host:

balena login

The MCP server inherits that session by shelling out to the same balena binary.

MCP client setup

The server speaks stdio JSON-RPC — any MCP-compliant client wires it up the same way: point at the binary, optionally pass args, restart the client. Specifics below.

Claude Code

CLI (recommended):

claude mcp add balena /absolute/path/to/balenamcp

That edits ~/.claude.json for you. Use claude mcp add balena --scope project ... to scope it to the current repo (.mcp.json in repo root) instead.

Or edit ~/.claude.json by hand:

{
  "mcpServers": {
    "balena": {
      "command": "/absolute/path/to/balenamcp",
      "args": []
    }
  }
}

The tools appear in the next Claude Code session. Run /mcp to confirm.

Claude Desktop

Add to claude_desktop_config.json:

OS Path
macOS ~/Library/Application Support/Claude/claude_desktop_config.json
Windows %APPDATA%\Claude\claude_desktop_config.json
Linux* ~/.config/Claude/claude_desktop_config.json

* Claude Desktop on Linux is unofficial; the path mirrors the XDG location that community builds use.

{
  "mcpServers": {
    "balena": {
      "command": "/absolute/path/to/balenamcp",
      "args": []
    }
  }
}

Restart Claude Desktop. The tools appear under the balena server.

Other MCP clients

Setup is similar — point the client at the binary as a stdio MCP server. Consult each tool's docs for the exact config file / command:

  • Cursor — Settings → MCP → Add server, or ~/.cursor/mcp.json
  • ContinuemcpServers block in ~/.continue/config.yaml
  • Cline — VS Code settings under cline.mcpServers
  • Goosegoose configure (interactive) or ~/.config/goose/config.yaml
  • LibreChatmcpServers: in librechat.yaml

If your client supports MCP via stdio, the binary will work — there's nothing balenamcp-specific about the wiring.

Tools

⚠️ Destructive tools — read this first

12 of the 29 tools change state on real devices or in balenaCloud. A reboot or device-purge can't be undone from inside the model. Every destructive tool is flagged with destructiveHint: true in its MCP annotation, and Claude Desktop (and other compliant MCP clients) prompts you for confirmation before running them.

Tool Effect Reversible?
device-reboot Remote reboot yes (device comes back up)
device-restart Restart containers (no reboot) yes
device-shutdown Remote shutdown — manual power cycle to recover requires physical access
device-purge Wipe /data on the device no — data is gone
device-pin Pin a device to a specific release yes (device-track-fleet or re-pin)
device-track-fleet Drop a device's pin and resume tracking the fleet's release yes (device-pin again)
fleet-pin Pin a fleet to a specific release yes
release-finalize Promote a draft release to final no — finals can't be un-finalized
tag-set Create or update a tag yes (tag-rm)
tag-rm Remove a tag yes (tag-set)
env-set Set/update an env or config variable yes (env-rm or env-set again)
env-rm Delete an env or config variable (needs yes: true to bypass the CLI's confirm prompt) yes (env-set again)

Belt-and-suspenders gate: set BALENAMCP_REQUIRE_CONFIRM=1 and every destructive tool will refuse to run unless the call carries confirm: true in its arguments. Useful with MCP clients that don't honor destructiveHint.

Read-only

The remaining 17 tools are read-only — they shell out to balena with no state change. Safe to call without confirmation.

Tool Purpose
version balena CLI version
whoami Current user / org / device session info
fleet-list List all accessible fleets
fleet-info Detailed info for one fleet
device-list List devices, optionally filtered by fleet
device-info Detailed info for one device
device-logs Recent historical logs from a device, optionally per-service. Streaming (--tail) is not supported over MCP — run the balena CLI directly for continuous monitoring. Note: this tool's identifier arg is named device (accepts UUID, IP, or .local address) while every other device-* tool uses uuid. Kept this way to match the broader argument shape balena's CLI accepts here.
device-type-list Supported balena device types
release-list Releases of a fleet
release-info Metadata or composition of one release
release-asset-list Binary assets attached to a release
tag-list Tags on a fleet or device or release
env-list Env/config variables on a fleet or device, optionally per-service
os-versions Available balenaOS versions for a device type
organization-list Organizations the user belongs to
ssh-key-list SSH keys registered in balenaCloud
api-key-list balenaCloud API keys
Argument constraints

tag-list / tag-set / tag-rm require exactly one of fleet / device / release. env-list / env-set require exactly one of fleet / device.

Prompts

Beyond tools, the server exposes MCP prompts — guided, multi-step workflows you invoke from your client (in Claude Desktop they appear in the prompt/slash picker). A prompt runs no balena commands itself; it returns a runbook that tells the model which tools to call and in what order, encoding the sequencing and safety ordering an experienced operator would follow. Destructive steps still go through the same destructiveHint / BALENAMCP_REQUIRE_CONFIRM guards as any other tool call.

Prompt Arguments What it walks the model through
diagnose-device uuid Pull status, logs, env, tags, and pin state for one device, then summarize a health verdict and likely root cause. Read-only.
fleet-health-report fleet Tally device status across a fleet, compare each device against the fleet's target release, flag what needs attention. Read-only.
safe-release-rollout fleet, release Canary-first rollout: record the rollback target, pin one device, verify it, then roll out fleet-wide — pausing for approval before each state change.
rollback-device uuid Identify a previously known-good release and roll a single device back to it, after confirming the target.
audit-config fleet Compare device-level env/config variables against fleet defaults; surface drift, secret-shaped values (never echoed), and orphaned overrides. Read-only.
compare-releases release_a, release_b Diff two releases: per-service image-size deltas (how much bigger/smaller), composition changes, and asset differences. Read-only.
replicate-config source, target Copy env/config variables from one fleet/device to another, with a masked plan and an approval step before any write.
bulk-tag fleet, key, value (optional) Apply a tag to many devices in a fleet at once, with an approval step before any write.

Resources

The server also exposes read-only balena state as MCP resources under the balena:// URI scheme. Where a tool is a single CLI call the model invokes, a resource is context you attach to the conversation — and each one composes several CLI calls into one JSON document, so a single read gives the model a coherent picture instead of forcing several separate tool calls. Composition degrades gracefully: if a sub-call fails (e.g. logs for an offline device) the document still returns the sections that succeeded and records the rest under an "errors" object with "partial": true.

Resource URI Type Aggregates
balena://account static whoami + organizations
balena://account/keys static registered SSH public keys + API key names (no secrets)
balena://fleets static all accessible fleets
balena://device-types static supported device types
balena://device/{uuid} template device status + recent logs + env/config + tags
balena://fleet/{org}/{fleet} template fleet metadata + devices + env/config + releases
balena://fleet/{org}/{fleet}/releases template the fleet's release history
balena://release/{id} template release metadata + docker-compose composition + assets
balena://os-versions/{type} template available balenaOS versions (stable + ESR + draft) for a device type

Fleet slugs are org/fleet, so the fleet templates take the two parts as separate path segments (e.g. balena://fleet/myorg/myfleet).

Development

go build -o bin/balenamcp           # build
go test ./...                       # unit tests (in-process MCP client, dry-run)

For a live end-to-end sweep against real balenaCloud (requires balena login and a sacrificial device):

BALENA_LIVE_FLEET=myorg/myfleet \
BALENA_LIVE_DEVICE=<uuid> \
BALENA_LIVE_RELEASE=<commit> \
BALENA_LIVE_RELEASE_ALT=<other-commit> \
  go test -tags=integration -v -count=1 -run TestLiveSweep .

Irreversible sub-tests (device-purge, device-shutdown, release-finalize) gate on additional BALENA_LIVE_ALLOW_* opt-in env vars and skip by default.

Layout:

  • main.go — entry point, flag parsing, stdio transport
  • server/setup.go — all tool definitions
  • main_test.go — in-process MCP client driving every tool in dry-run mode
  • livetest_test.go — build-tagged (integration) end-to-end sweep against real balenaCloud, opt-in via env vars
Release flow

Releases are automated via release-please

  1. Commits to main use Conventional Commits (feat:, fix:, perf:, chore:, docs:, etc.). The PR title gate enforces this on every PR.
  2. release-please watches main and opens a "chore(main): release vX.Y.Z" PR that bumps .release-please-manifest.json and rewrites CHANGELOG.md based on the conventional-commit history since the last tag.
  3. Merging that PR pushes a vX.Y.Z tag.
  4. goreleaser (in release.yml) fires on the tag push: cross-compiles for 5 targets, generates CycloneDX SBOMs via syft, signs checksums.txt and SBOMs with cosign keyless, uploads everything to the GitHub Release release-please already created.

End-to-end, a release looks like:

$ git commit -m "feat: add foo tool"     # conventional commit
$ git push                                # to a branch + PR + merge
                                          # ... release-please opens release PR ...
$ # merge the release PR ...
$ # ... goreleaser publishes binaries + SBOMs + signatures to GH Releases

Troubleshooting

Where to find the server's logs

Logs land wherever the MCP host writes them:

Client Path
Claude Desktop, macOS ~/Library/Logs/Claude/mcp-server-balena.log
Claude Desktop, Windows %APPDATA%\Claude\logs\mcp-server-balena.log
Claude Desktop, Linux ~/.config/Claude/logs/mcp-server-balena.log
Claude Code claude --mcp-debug enables verbose MCP logging to the current session; persistent logs live in ~/.claude/projects/<encoded-cwd>/
Cursor / Continue / others Check each client's own log dir; the server itself writes only its startup line to stderr
Common errors
  • balena CLI error: exec: not found — the balena binary isn't on PATH for whatever user the MCP host runs the server as. For desktop apps that's the GUI's PATH, not your shell's. Add a symlink to /usr/local/bin/balena or set the absolute path in the client's MCP config under env / command.
  • Auth errorsbalena login again on the host where the server runs.
  • We need to slow down your requests temporarily. — balenaCloud rate-limits fast sequential calls. The server doesn't queue or back off; it surfaces the message as the tool result. Wait a few seconds and retry, or pace the agent's calls. Most noticeable when sweeping many tools in a row (e.g. validation runs).
  • Tool annotated as destructive but the client didn't prompt — set BALENAMCP_REQUIRE_CONFIRM=1 in the server's env (via the client's MCP env: block) so the server itself refuses destructive calls without confirm: true in the arguments. Documented above.

License

MIT

Documentation

Overview

Command balenamcp is a Model Context Protocol (MCP) server that wraps the balena CLI, letting MCP-aware AI assistants (Claude Desktop, Claude Code, Cursor, Cline, Continue, and others) drive balenaCloud through a structured tool interface instead of free-form shell invocation.

Usage

balenamcp [-dry-run]

The server speaks MCP over stdio. It is normally launched by an MCP client via that client's server-configuration mechanism (for example, "claude mcp add balena /usr/local/bin/balenamcp"), not run directly.

The -dry-run flag swaps real command execution for stubbed responses that report the argv that would have been invoked. Useful for tool development and CI smoke tests without a live balena login.

Prerequisites

The balena CLI must be installed and on PATH, and the invoking user must be authenticated (run "balena login" once). balenamcp shells out to the CLI for every operation; it does not talk to the balenaCloud API directly.

Tool surface

The server exposes 29 tools split into two categories. Read-only tools (version, whoami, fleet-list, fleet-info, device-list, device-info, device-logs, device-type-list, release-list, release-info, release-asset-list, tag-list, env-list, os-versions, organization-list, ssh-key-list, api-key-list) carry the MCP readOnlyHint and are safe to invoke without confirmation.

Mutating tools (device-reboot, device-restart, device-shutdown, device-purge, device-pin, device-track-fleet, fleet-pin, release-finalize, tag-set, tag-rm, env-set, env-rm) carry the destructiveHint so compliant clients can prompt the user before running them.

Every tool's identifier arguments are flag-shape guarded: arguments beginning with "-" are rejected server-side to prevent argv injection where an agent passes "--help" or similar as a UUID. Free-form values (tag values, env values) are intentionally exempt.

Configuration

All configuration lives in the invoking client's MCP server definition. There are no environment variables specific to balenamcp itself — the balena CLI's own authentication state (typically ~/.balena/token) is inherited from the launching shell.

Verifying a release

Release archives are signed via Sigstore cosign keyless signing and published with CycloneDX SBOMs. See the project README's "Install" section for the cosign verify-blob invocation.

Source

https://github.com/schubydoo/balenamcp

Directories

Path Synopsis
Package server implements the MCP server for balenamcp — its tools, prompts, and resources.
Package server implements the MCP server for balenamcp — its tools, prompts, and resources.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL