Skip to content

The meho CLI

The meho CLI is a single static Go binary and the starting point for connecting to a backplane. It runs on a VPN-connected workstation, verifies TLS against your operating system's trust store, and authenticates with the OAuth 2.0 device-code flow — so it works on private networks with no browser redirect back to the client.

Install it and log in before wiring any MCP client: registering targets and secrets happens only through the CLI (and the operator console / REST), never through MCP tools, so a useful agent session depends on this step having happened first.

Install the signed binary

Releases ship as four platform tarballs plus a SHA256SUMS file at the releases page. Each artefact carries a matching .cosign.bundle sigstore bundle — signed keyless via cosign (the GitHub Actions OIDC token is exchanged at Fulcio for a short-lived signing cert), so there is no public key to distribute. The canonical operator recipe is in the CLI README § Verify signatures.

Download the tarball and its checksums file:

TAG=<the release tag matching your backplane, e.g. v0.27.0>
TARBALL=meho_${TAG#v}_linux_amd64.tar.gz   # or darwin_arm64, linux_arm64, darwin_amd64
BASE=https://github.com/evoila/meho/releases/download/${TAG}

curl -LO ${BASE}/${TARBALL}
curl -LO ${BASE}/${TARBALL}.cosign.bundle
curl -LO ${BASE}/SHA256SUMS
curl -LO ${BASE}/SHA256SUMS.cosign.bundle

Verify the signatures with cosign, then the checksums. The identity regex pins the signer to this repo's release workflow on a tag ref, so a bundle produced anywhere else fails the check:

IDENTITY='^https://github\.com/evoila/meho/\.github/workflows/cli-release\.yml@refs/tags/v.+$'
ISSUER='https://token.actions.githubusercontent.com'

# Verify the checksums file's signature once...
cosign verify-blob \
  --certificate-identity-regexp "$IDENTITY" \
  --certificate-oidc-issuer "$ISSUER" \
  --bundle SHA256SUMS.cosign.bundle \
  SHA256SUMS

# ...then trust every tarball it covers via the checksums.
sha256sum -c SHA256SUMS --ignore-missing

Unpack and install:

tar xzf ${TARBALL}
sudo install -m 0755 meho /usr/local/bin/meho
meho version

Keep the CLI in step with the backplane

Use a CLI release that is close to the backplane's version:

  • Supported: the same minor version as the backplane, or one minor version older. For backplane 0.35.x, use CLI v0.35.x or v0.34.x.
  • meho login and meho status check this. When the versions are too far apart they print one line on stderr, for example:
warning: CLI v0.12.0 vs backplane 0.35.12 — this CLI is too old for this backplane. Upgrade: https://github.com/evoila/meho/releases/tag/v0.35.12
  • A much older CLI can also show backplane returned OperationResult.status "…", which this CLI (…) does not know. This does not mean the operation failed. It may have run, or it may be waiting for approval. Check with meho approvals list before you run it again.

To upgrade, repeat the download, verify, and install steps above with TAG set to the backplane's version (the tag in the warning). Then check the result:

meho version
meho status

Trust the deployment's CA

Skip this if your backplane and Keycloak present publicly-trusted certificates.

If they are signed by an internal CA, install that CA into your workstation's operating-system trust store first. The CLI is a Go binary — on macOS it reads the system keychain and ignores the SSL_CERT_FILE environment variable entirely, so the env-var trick that works on the backplane does not carry over here. The per-platform commands are on TLS and ingress § Your workstation.

Verify from a fresh shell before logging in — meho login contacts both the backplane and Keycloak:

curl -sf https://meho.example.com/healthz
curl -sf https://keycloak.example.com/realms/<realm>/.well-known/openid-configuration

Log in

meho login https://meho.example.com

This runs the device-code flow: the CLI discovers the realm and the public meho-cli client id from the backplane's /api/v1/auth-config endpoint, prints a verification URL and code, you approve it in a browser as a realm user, and the resulting token is stored in your OS keyring (Keychain / Secret Service / Wincred).

The CLI also writes the backplane URL to ~/.config/meho/config.json ($XDG_CONFIG_HOME/meho/config.json when set) so later subcommands do not need the URL re-typed. That file holds only the backplane URL — no secret; the token lives in the keyring, or in a 0600-mode credentials.json sibling on headless hosts where no keyring is available.

How long the login lasts

meho login asks for a long login by default (the OIDC scope offline_access). The CLI renews it by itself, so you log in once. On MEHO's built-in login server it ends after 48 hours without use, or after 7 days at most. Then run meho login again.

  • meho login --offline=false asks for a short login instead. It ends after 30 minutes without use (the realm's default).
  • If the login server does not offer the long login (an older realm), meho login logs in without it and prints one line that says so. Ask your admin to fix the realm: see How long each login lasts.
  • The long login is stored like the token: in the OS keyring, or in the 0600 file. Treat it as a secret.

Two useful overrides:

  • meho login --client-id <id> / --issuer <url> skip or override the auto-discovered values — handy when a realm publishes several CLI clients (meho-cli-prod, meho-cli-staging).
  • MEHO_KEYRING_DISABLE=1 meho login … forces the file backend even where a keyring exists (see reading the raw token).

Prove the whole chain end to end:

meho status

meho status calls /api/v1/health with the stored token, so a clean result confirms CLI, token, ingress, and backplane all agree.

Known walls

Two workstation-side issues account for most first-login failures:

  • Internal-CA trust. meho login dying at its discovery probe with x509: certificate signed by unknown authority means the deployment's CA is not in your OS trust store — redo Trust the deployment's CA. This is the workstation twin of the backplane's own internal-CA trust problem.
  • Split DNS. If the backplane's hostname resolves to different addresses inside and outside the VPN, a login started off-VPN (or during a VPN-idle DNS flap) can blackhole the first lookup. Confirm the host resolves to its internal address from the machine you are logging in from; a temporary /etc/hosts pin to the internal VIP is a reliable workaround while you sort DNS out.

For realm-side login failures (unauthorized_client, invalid_audience, the missing_sub / basic-scope trap), the symptom-to-fix table is Keycloak realm setup § If login fails, and the deeper cross-wall walk is the troubleshooting page.

Reading the raw token

There is no meho ... --print-token verb — the CLI never prints the bearer token to stdout. When you genuinely need the raw JWT (to decode its claims, or to bake it into a shim — see Other MCP clients), force the file backend and read it from credentials.json:

MEHO_KEYRING_DISABLE=1 meho login https://meho.example.com

# The file holds one entry per backplane; select the one you logged
# into (extracting .entries[] unfiltered would print every backplane's
# token):
jq -r --arg url "https://meho.example.com" \
  '.entries[] | select(.backplane_url == $url) | .access_token' \
  "${XDG_CONFIG_HOME:-$HOME/.config}/meho/credentials.json"

Where next