#!/bin/bash # Install lmcp on macOS via Homebrew. # # Idempotent. Pulls lua + luasocket from brew, copies lmcp library files # into brew's share/lua/5.4/ tree, mints a Bearer token (or reuses an # existing one at $(brew --prefix)/etc/lmcp/token), installs a LaunchAgent, # starts the service, and prints the token for Claude Code MCP config. # # Usage (from lmcp repo root): # ./scripts/lmcp-install-macos.sh # # Uninstall: # launchctl unload ~/Library/LaunchAgents/de.reauktion.marfrit.lmcp.plist # rm ~/Library/LaunchAgents/de.reauktion.marfrit.lmcp.plist # rm $(brew --prefix)/etc/lmcp/token set -euo pipefail PREFIX=$(brew --prefix) REPO=${REPO:-$(cd "$(dirname "$0")/.." && pwd)} LABEL="de.reauktion.marfrit.lmcp" PLIST="$HOME/Library/LaunchAgents/$LABEL.plist" TOKEN_FILE="$PREFIX/etc/lmcp/token" PORT="${LMCP_PORT:-8080}" NAME="${LMCP_NAME:-$(hostname -s)-tools}" for f in lmcp.lua json.lua server.lua example_server.lua; do [ -f "$REPO/$f" ] || { echo "error: $REPO/$f not found — run from lmcp repo root or set REPO="; exit 1; } done echo "==> brew install lua + luasocket" brew install --quiet lua luasocket LUA="$PREFIX/bin/lua" [ -x "$LUA" ] || { echo "error: $LUA not executable after brew install"; exit 1; } echo "==> install library files into $PREFIX/share/lua/5.4/" install -d "$PREFIX/share/lua/5.4" install -m 644 "$REPO/lmcp.lua" "$PREFIX/share/lua/5.4/lmcp.lua" install -m 644 "$REPO/json.lua" "$PREFIX/share/lua/5.4/json.lua" install -m 644 "$REPO/server.lua" "$PREFIX/share/lua/5.4/server.lua" install -m 755 "$REPO/example_server.lua" "$PREFIX/bin/lmcp-example" # Token: retain existing, otherwise mint 32 bytes of hex. if [ -r "$TOKEN_FILE" ]; then TOKEN=$(cat "$TOKEN_FILE") echo "==> reusing token from $TOKEN_FILE" else install -d -m 700 "$(dirname "$TOKEN_FILE")" TOKEN=$(openssl rand -hex 32) umask 077 printf '%s\n' "$TOKEN" > "$TOKEN_FILE" chmod 600 "$TOKEN_FILE" echo "==> minted new token, stored at $TOKEN_FILE (0600)" fi echo "==> write LaunchAgent $PLIST" mkdir -p "$HOME/Library/LaunchAgents" cat > "$PLIST" < Label $LABEL ProgramArguments $LUA $PREFIX/share/lua/5.4/server.lua EnvironmentVariables LMCP_PORT $PORT LMCP_NAME $NAME LMCP_TOKEN $TOKEN RunAtLoad KeepAlive StandardOutPath /tmp/lmcp.log StandardErrorPath /tmp/lmcp.err PLIST chmod 600 "$PLIST" # plist contains token echo "==> (re)load LaunchAgent" launchctl unload "$PLIST" 2>/dev/null || true launchctl load "$PLIST" sleep 1 echo "==> smoke test (unauth expected 401, Bearer expected 200)" unauth=$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:$PORT/mcp" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' || echo "000") auth=$(curl -s -X POST "http://127.0.0.1:$PORT/mcp" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' || true) if [ "$unauth" = "401" ] && echo "$auth" | grep -q '"tools"'; then echo "OK — lmcp listening on :$PORT as $NAME, Bearer-gated" else echo "smoke test failed (unauth=$unauth, auth body below)" echo "$auth" | head -c 500; echo tail -20 /tmp/lmcp.err 2>/dev/null || true exit 1 fi cat <