plugins/src/expo/skills/maestro-mcp-setup/SKILL.md
Enable the Maestro CLI's built-in MCP server (`maestro mcp`, STDIO) for this machine, robustly. Detects the Maestro CLI and a usable Java runtime, installs/guides what is missing, and registers the MCP server at LOCAL (per-machine, uncommitted) scope with an absolute command path and injected JAVA_HOME/PATH so the spawn never dies on a non-login PATH. Use when an Expo project needs Maestro device-automation tools (list_devices, inspect_screen, take_screenshot, run flows). Never registers at project/committed scope — that reintroduces the fleet-wide "-32000" failure.
npx skillsauth add codyswanngt/lisa maestro-mcp-setupInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Wire the Maestro CLI's built-in MCP server (maestro mcp, STDIO) into the
current coding agent for this machine only, in a way that actually works.
The Maestro MCP server is intentionally NOT in the distributed plugin
.mcp.json. A static, committed entry always attempts to spawn, and coding
agents spawn stdio servers with a non-login PATH. So an always-on entry
fails visibly — Claude Code shows Failed to reconnect … -32000 every
session — on any machine missing the Maestro CLI or a resolvable Java
runtime, which is most of a typical fleet (CI runners, unattended QA/intake
agent boxes). That exact regression shipped once (expo plugin commit b12d5d3)
and was reverted (b1f3efd).
This skill fixes both failure modes without poisoning the fleet:
command
path plus injected JAVA_HOME / PATH env, so the server finds Java
regardless of the spawn's inherited PATH.Registration is always --scope local (per-machine, uncommitted). Never use
--scope project / a committed .mcp.json entry — that is the always-on form
that reds out every other machine.
MAESTRO_BIN="$(command -v maestro || true)"
[ -z "$MAESTRO_BIN" ] && [ -x "$HOME/.maestro/bin/maestro" ] && MAESTRO_BIN="$HOME/.maestro/bin/maestro"
echo "maestro: ${MAESTRO_BIN:-NOT FOUND}"
If not found, install it (official installer — confirm with the user first,
since it writes to ~/.maestro):
curl -fsSL "https://get.maestro.mobile.dev" | bash
MAESTRO_BIN="$HOME/.maestro/bin/maestro"
Resolve to an absolute path — a bare maestro in the registration relies on
the very PATH that is missing at spawn time.
Maestro needs a JDK (8+). Resolve JAVA_HOME honoring the project's version
manager — do not install a parallel global JDK when one is already managed:
# Prefer an already-active/managed Java before installing anything. Covers the
# common version managers (mise, asdf, SDKMAN) plus an ambient install.
if command -v mise >/dev/null 2>&1 && mise which java >/dev/null 2>&1; then
JAVA_BIN="$(mise which java)"
elif command -v asdf >/dev/null 2>&1 && asdf which java >/dev/null 2>&1; then
JAVA_BIN="$(asdf which java)"
elif [ -x "${SDKMAN_DIR:-$HOME/.sdkman}/candidates/java/current/bin/java" ]; then
# SDKMAN's `sdk` is a shell function absent in non-login shells; use its stable
# `candidates/java/current` symlink directly instead.
JAVA_BIN="${SDKMAN_DIR:-$HOME/.sdkman}/candidates/java/current/bin/java"
elif command -v java >/dev/null 2>&1; then
JAVA_BIN="$(command -v java)"
fi
echo "java: ${JAVA_BIN:-NOT FOUND}"
# Derive JAVA_HOME by asking the JVM itself — NOT `dirname "$JAVA_BIN"/..`, which
# is wrong when `java` is a mise/asdf shim or a symlink whose parent is not a JDK
# root. Fall back to the bin/.. parent only if the JVM query yields nothing usable.
if [ -n "$JAVA_BIN" ]; then
JAVA_HOME="$("$JAVA_BIN" -XshowSettings:properties -version 2>&1 \
| sed -n 's/^[[:space:]]*java\.home = //p' | head -n 1)"
{ [ -z "$JAVA_HOME" ] || [ ! -x "$JAVA_HOME/bin/java" ]; } &&
JAVA_HOME="$(cd "$(dirname "$JAVA_BIN")/.." && pwd)"
fi
If no Java is found: install through the project's version manager when present
(mise use -g java@21, asdf install java …, or sdk install java), or guide
the user to install a JDK. Prefer the managed path; only fall back to a global
install with explicit consent. Re-resolve JAVA_HOME afterward.
Build a PATH that puts the Maestro and Java bin dirs first, then register for
the current coding agent. $JAVA_BIN/$MAESTRO_BIN are absolute.
Claude Code:
# Refuse to register a broken server — both toolchain paths must resolve and be
# executable, or `claude mcp add` would create the exact -32000 entry this skill
# exists to avoid.
[ -n "$MAESTRO_BIN" ] && [ -x "$MAESTRO_BIN" ] || { echo "Maestro CLI unresolved — abort"; exit 1; }
[ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ] || { echo "Java runtime unresolved — abort"; exit 1; }
# Use the real JDK bin (from JAVA_HOME), not the shim dir, on PATH.
JBIN_DIR="$JAVA_HOME/bin"; MBIN_DIR="$(dirname "$MAESTRO_BIN")"
claude mcp add --scope local maestro \
--env "JAVA_HOME=$JAVA_HOME" \
--env "PATH=$JBIN_DIR:$MBIN_DIR:$PATH" \
-- "$MAESTRO_BIN" mcp
Parity — same intent on other harnesses (local/per-machine scope, absolute
command, injected JAVA_HOME/PATH); translate the registration verb only:
codex mcp add maestro --env JAVA_HOME=… --env PATH=… -- "$MAESTRO_BIN" mcp (writes ~/.codex/config.toml; keep it out of the committed project config).~/.cursor/mcp.json (not the project .cursor/mcp.json) with command, args: ["mcp"], and env: { JAVA_HOME, PATH }.~/.config/opencode/opencode.json) mcp block with the absolute command and environment.env; Copilot uses type: "local" for stdio servers.In every case: local/user scope, never the committed project file.
claude mcp list # or the harness equivalent
Confirm maestro shows connected. If it still fails, the usual cause is
JAVA_HOME pointing at a JRE without bin/java, or a Maestro path that moved —
re-resolve both and re-register. Do not "fix" it by dropping to a bare
maestro mcp command; that reintroduces the PATH-less failure.
--scope project or editing
a committed .mcp.json, stop — that is the reverted fleet-wide regression.tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.