mirror of
https://github.com/jmagar/unraid-mcp
synced 2026-04-21 13:37:53 +00:00
Sweep remaining references to the old scripts/ directory that were missed when scripts/ was renamed to bin/ in 1.3.6. Fixes active config (ci.yml, .pre-commit-config.yaml, Justfile) and documentation (CHECKLIST, INVENTORY, MARKETPLACE, SCRIPTS, REPO). hooks/scripts/ and skills/unraid/scripts/ are intentional and unchanged.
171 lines
6.7 KiB
Makefile
171 lines
6.7 KiB
Makefile
# Unraid MCP Server — Justfile
|
|
# Run `just` to list available recipes
|
|
|
|
default:
|
|
@just --list
|
|
|
|
# ── Development ───────────────────────────────────────────────────────────────
|
|
|
|
# Start development server (hot-reload via watchfiles)
|
|
dev:
|
|
uv run python -m unraid_mcp
|
|
|
|
# Run tests
|
|
test:
|
|
uv run pytest tests/ -v
|
|
|
|
# Run linter (ruff)
|
|
lint:
|
|
uv run ruff check .
|
|
|
|
# Format code (ruff)
|
|
fmt:
|
|
uv run ruff format .
|
|
|
|
# Type-check (pyright / mypy)
|
|
typecheck:
|
|
uv run pyright unraid_mcp/ || uv run mypy unraid_mcp/
|
|
|
|
# Validate skills SKILL.md files exist and are non-empty
|
|
validate-skills:
|
|
@echo "=== Validating skills ==="
|
|
@for f in skills/*/SKILL.md; do \
|
|
if [ -f "$$f" ]; then \
|
|
echo " ✓ $$f"; \
|
|
else \
|
|
echo " ✗ SKILL.md not found in skills/"; \
|
|
exit 1; \
|
|
fi; \
|
|
done
|
|
@echo "Skills validation passed"
|
|
|
|
# Build Docker image
|
|
build:
|
|
docker build -t unraid-mcp .
|
|
|
|
# ── Docker Compose ────────────────────────────────────────────────────────────
|
|
|
|
# Start containers
|
|
up:
|
|
docker compose up -d
|
|
|
|
# Stop containers
|
|
down:
|
|
docker compose down
|
|
|
|
# Restart containers
|
|
restart:
|
|
docker compose restart
|
|
|
|
# Tail container logs
|
|
logs:
|
|
docker compose logs -f
|
|
|
|
# Check /health endpoint
|
|
health:
|
|
@PORT=$$(grep -E '^UNRAID_MCP_PORT=' .env 2>/dev/null | cut -d= -f2 || echo 6970); \
|
|
curl -sf "http://localhost:$$PORT/health" | python3 -m json.tool || echo "Health check failed"
|
|
|
|
# ── Live Testing ──────────────────────────────────────────────────────────────
|
|
|
|
# Run live integration tests against a running server
|
|
test-live:
|
|
uv run pytest tests/ -v -m live
|
|
|
|
# Run HTTP end-to-end smoke-test against the local server (auto-reads token from ~/.unraid-mcp/.env)
|
|
test-http:
|
|
bash tests/mcporter/test-http.sh
|
|
|
|
# Run HTTP e2e test with auth disabled (for gateway-protected deployments)
|
|
test-http-no-auth:
|
|
bash tests/mcporter/test-http.sh --skip-auth
|
|
|
|
# Run HTTP e2e test against a remote URL
|
|
# Usage: just test-http-remote https://unraid.tootie.tv/mcp
|
|
test-http-remote url:
|
|
bash tests/mcporter/test-http.sh --url {{url}} --skip-auth
|
|
|
|
# ── Setup ─────────────────────────────────────────────────────────────────────
|
|
|
|
# Create .env from .env.example if missing
|
|
setup:
|
|
@if [ ! -f .env ]; then \
|
|
cp .env.example .env && chmod 600 .env; \
|
|
echo "Created .env from .env.example — fill in your credentials"; \
|
|
else \
|
|
echo ".env already exists"; \
|
|
fi
|
|
|
|
# Generate a secure bearer token for UNRAID_MCP_TOKEN
|
|
gen-token:
|
|
@python3 -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
|
|
# ── Quality Gates ─────────────────────────────────────────────────────────────
|
|
|
|
# Run docker security checks
|
|
check-contract:
|
|
bash bin/check-docker-security.sh
|
|
bash bin/check-no-baked-env.sh
|
|
bash bin/ensure-ignore-files.sh --check
|
|
|
|
# ── CLI Generation ────────────────────────────────────────────────────────────
|
|
|
|
# Generate a standalone CLI for this server (requires running server)
|
|
generate-cli:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
PORT="${UNRAID_MCP_PORT:-6970}"
|
|
echo "⚠ Server must be running on port ${PORT} (run 'just dev' first)"
|
|
echo "⚠ Generated CLI embeds your OAuth token — do not commit or share"
|
|
mkdir -p dist dist/.cache
|
|
current_hash=$(timeout 10 curl -sf \
|
|
-H "Authorization: Bearer $MCP_TOKEN" \
|
|
-H "Accept: application/json, text/event-stream" \
|
|
"http://localhost:${PORT}/mcp/tools/list" 2>/dev/null | sha256sum | cut -d' ' -f1 || echo "nohash")
|
|
cache_file="dist/.cache/unraid-mcp-cli.schema_hash"
|
|
if [[ -f "$cache_file" ]] && [[ "$(cat "$cache_file")" == "$current_hash" ]] && [[ -f "dist/unraid-mcp-cli" ]]; then
|
|
echo "SKIP: unraid-mcp tool schema unchanged — use existing dist/unraid-mcp-cli"
|
|
exit 0
|
|
fi
|
|
timeout 30 mcporter generate-cli \
|
|
--command "http://localhost:${PORT}/mcp" \
|
|
--header "Authorization: Bearer $MCP_TOKEN" \
|
|
--name unraid-mcp-cli \
|
|
--output dist/unraid-mcp-cli
|
|
printf '%s' "$current_hash" > "$cache_file"
|
|
echo "✓ Generated dist/unraid-mcp-cli (requires bun at runtime)"
|
|
|
|
# ── Cleanup ───────────────────────────────────────────────────────────────────
|
|
|
|
# Remove build artifacts, caches, and compiled files
|
|
clean:
|
|
rm -rf dist/ build/ .pytest_cache/ .ruff_cache/ .mypy_cache/ htmlcov/ .coverage coverage.xml
|
|
find . -type d -name __pycache__ -not -path './.venv/*' -exec rm -rf {} + 2>/dev/null || true
|
|
find . -name '*.pyc' -not -path './.venv/*' -delete 2>/dev/null || true
|
|
@echo "Cleaned build artifacts"
|
|
|
|
# Publish: bump version, tag, push (triggers PyPI + Docker publish)
|
|
publish bump="patch":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
[ "$(git branch --show-current)" = "main" ] || { echo "Switch to main first"; exit 1; }
|
|
[ -z "$(git status --porcelain)" ] || { echo "Commit or stash changes first"; exit 1; }
|
|
git pull origin main
|
|
CURRENT=$(grep -m1 "^version" pyproject.toml | sed "s/.*\"\(.*\)\".*/\1/")
|
|
IFS="." read -r major minor patch <<< "$CURRENT"
|
|
case "{{bump}}" in
|
|
major) major=$((major+1)); minor=0; patch=0 ;;
|
|
minor) minor=$((minor+1)); patch=0 ;;
|
|
patch) patch=$((patch+1)) ;;
|
|
*) echo "Usage: just publish [major|minor|patch]"; exit 1 ;;
|
|
esac
|
|
NEW="${major}.${minor}.${patch}"
|
|
echo "Version: ${CURRENT} → ${NEW}"
|
|
sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW}\"/" pyproject.toml
|
|
for f in .claude-plugin/plugin.json .codex-plugin/plugin.json gemini-extension.json; do
|
|
[ -f "$f" ] && python3 -c "import json; d=json.load(open(\"$f\")); d[\"version\"]=\"${NEW}\"; json.dump(d,open(\"$f\",\"w\"),indent=2); open(\"$f\",\"a\").write(\"
|
|
\")"
|
|
done
|
|
git add -A && git commit -m "release: v${NEW}" && git tag "v${NEW}" && git push origin main --tags
|
|
echo "Tagged v${NEW} — publish workflow will run automatically"
|
|
|