From 5d6b8d0033100032e1804a6d80b1c8b9d26f261f Mon Sep 17 00:00:00 2001 From: yctimlin Date: Fri, 31 Oct 2025 17:00:48 +0000 Subject: [PATCH] Add comprehensive GitHub Actions CI/CD workflows and update documentation --- .github/workflows/ci.yml | 72 +++++++++++++++ .github/workflows/docker.yml | 140 ++++++++++++++++++++++++++++++ .github/workflows/npm-publish.yml | 106 ++++++++++++++++++++++ README.md | 64 +++++++++----- 4 files changed, 360 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/npm-publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f1cf9e7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + build-and-test: + name: Build and Type Check + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x, 18.x, 20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run TypeScript type check + run: npm run type-check + + - name: Build project + run: npm run build + + - name: Check build artifacts + run: | + echo "Checking if build artifacts exist..." + test -f dist/index.js || (echo "dist/index.js not found" && exit 1) + test -f dist/server.js || (echo "dist/server.js not found" && exit 1) + test -d dist/frontend || (echo "dist/frontend not found" && exit 1) + echo "All build artifacts present!" + + - name: Upload build artifacts + if: matrix.node-version == '20.x' + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: | + dist/ + retention-days: 7 + + lint-check: + name: Lint Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Check for TypeScript errors + run: npm run type-check diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..1a21b13 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,140 @@ +name: Docker Build & Push + +on: + push: + branches: [ main ] + tags: + - 'v*.*.*' + pull_request: + branches: [ main ] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME_MCP: ${{ github.repository }} + IMAGE_NAME_CANVAS: ${{ github.repository }}-canvas + +jobs: + build-and-push-mcp: + name: Build and Push MCP Server Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for MCP Server + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MCP }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push MCP Server image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + + build-and-push-canvas: + name: Build and Push Canvas Server Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Canvas Server + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_CANVAS }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Canvas Server image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.canvas + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + + test-docker-images: + name: Test Docker Images + needs: [build-and-push-mcp, build-and-push-canvas] + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + + steps: + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Test Canvas Server image + run: | + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_CANVAS }}:${{ github.ref_name }} + docker run -d -p 3000:3000 --name test-canvas ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_CANVAS }}:${{ github.ref_name }} + sleep 10 + curl -f http://localhost:3000/health || exit 1 + docker logs test-canvas + docker stop test-canvas + + - name: Test MCP Server image + run: | + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MCP }}:${{ github.ref_name }} + echo "MCP Server image pulled successfully" diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..18237f4 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,106 @@ +name: Publish to NPM + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Tag to publish (e.g., latest, beta, next)' + required: true + default: 'latest' + +jobs: + publish: + name: Publish to NPM Registry + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run type check + run: npm run type-check + + - name: Build project + run: npm run build + + - name: Verify build artifacts + run: | + echo "Verifying build artifacts..." + test -f dist/index.js || (echo "ERROR: dist/index.js not found" && exit 1) + test -f dist/server.js || (echo "ERROR: dist/server.js not found" && exit 1) + test -d dist/frontend || (echo "ERROR: dist/frontend not found" && exit 1) + echo "All required artifacts present!" + + - name: Get package version + id: package-version + run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: Check if version exists on NPM + id: check-version + run: | + if npm view mcp-excalidraw-server@${{ steps.package-version.outputs.version }} version 2>/dev/null; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "Version ${{ steps.package-version.outputs.version }} already exists on NPM" + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "Version ${{ steps.package-version.outputs.version }} does not exist on NPM" + fi + + - name: Publish to NPM (Release) + if: github.event_name == 'release' && steps.check-version.outputs.exists == 'false' + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish to NPM (Manual) + if: github.event_name == 'workflow_dispatch' && steps.check-version.outputs.exists == 'false' + run: npm publish --tag ${{ github.event.inputs.tag }} --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Skip publishing (version exists) + if: steps.check-version.outputs.exists == 'true' + run: | + echo "⚠️ Skipping publish - version ${{ steps.package-version.outputs.version }} already exists on NPM" + echo "Please bump the version in package.json before publishing" + + - name: Create GitHub Release Assets + if: github.event_name == 'release' + run: | + tar -czf mcp-excalidraw-server-${{ steps.package-version.outputs.version }}.tar.gz dist/ + + - name: Upload Release Assets + if: github.event_name == 'release' + uses: softprops/action-gh-release@v1 + with: + files: | + mcp-excalidraw-server-${{ steps.package-version.outputs.version }}.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + notify: + name: Publish Notification + needs: publish + runs-on: ubuntu-latest + if: success() + + steps: + - name: Success notification + run: | + echo "✅ Package successfully published to NPM!" + echo "View at: https://www.npmjs.com/package/mcp-excalidraw-server" diff --git a/README.md b/README.md index 3632640..5bcaa73 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # MCP Excalidraw Server: Advanced Live Visual Diagramming with AI Integration +[![CI](https://github.com/yctimlin/mcp_excalidraw/actions/workflows/ci.yml/badge.svg)](https://github.com/yctimlin/mcp_excalidraw/actions/workflows/ci.yml) +[![Docker Build & Push](https://github.com/yctimlin/mcp_excalidraw/actions/workflows/docker.yml/badge.svg)](https://github.com/yctimlin/mcp_excalidraw/actions/workflows/docker.yml) +[![NPM Version](https://img.shields.io/npm/v/mcp-excalidraw-server)](https://www.npmjs.com/package/mcp-excalidraw-server) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + A comprehensive **TypeScript-based** system that combines **Excalidraw's powerful drawing capabilities** with **Model Context Protocol (MCP)** integration, enabling AI agents to create and manipulate diagrams in real-time on a live canvas. ## 🚦 Current Status & Version Information @@ -149,15 +154,17 @@ http://localhost:3000 #### **Option B: Docker Canvas Server** -1. **Build Docker Image** +**Option B1: Use Pre-built Image from GHCR** (Recommended) +```bash +docker pull ghcr.io/yctimlin/mcp_excalidraw-canvas:latest +docker run -d -p 3000:3000 --name mcp-excalidraw-canvas ghcr.io/yctimlin/mcp_excalidraw-canvas:latest +``` + +**Option B2: Build Locally** ```bash git clone https://github.com/yctimlin/mcp_excalidraw.git cd mcp_excalidraw docker build -f Dockerfile.canvas -t mcp-excalidraw-canvas . -``` - -2. **Run Canvas Container** -```bash docker run -d -p 3000:3000 --name mcp-excalidraw-canvas mcp-excalidraw-canvas ``` @@ -324,13 +331,7 @@ Edit your `claude_desktop_config.json` file: ### **Format 2: Docker MCP Server** ✅ Fully Working -First, build the MCP Docker image: -```bash -cd mcp_excalidraw -docker build -f Dockerfile -t mcp-excalidraw . -``` - -Then configure: +**Using Pre-built Image from GHCR** (Recommended): ```json { "mcpServers": { @@ -343,13 +344,21 @@ Then configure: "--network", "host", "-e", "EXPRESS_SERVER_URL=http://localhost:3000", "-e", "ENABLE_CANVAS_SYNC=true", - "mcp-excalidraw" + "ghcr.io/yctimlin/mcp_excalidraw:latest" ] } } } ``` +**OR Build Locally**: +```bash +cd mcp_excalidraw +docker build -f Dockerfile -t mcp-excalidraw . +``` + +Then use `mcp-excalidraw` as the image name in the configuration above. + --- ## **Configuration for Claude Code** @@ -377,13 +386,7 @@ Create or edit `.mcp.json` in your project root: ### **Format 2: Docker MCP Server** ✅ Fully Working -First, build the MCP Docker image: -```bash -cd mcp_excalidraw -docker build -f Dockerfile -t mcp-excalidraw . -``` - -Then configure: +**Using Pre-built Image from GHCR** (Recommended): ```json { "mcpServers": { @@ -396,13 +399,21 @@ Then configure: "--network", "host", "-e", "EXPRESS_SERVER_URL=http://localhost:3000", "-e", "ENABLE_CANVAS_SYNC=true", - "mcp-excalidraw" + "ghcr.io/yctimlin/mcp_excalidraw:latest" ] } } } ``` +**OR Build Locally**: +```bash +cd mcp_excalidraw +docker build -f Dockerfile -t mcp-excalidraw . +``` + +Then use `mcp-excalidraw` as the image name in the configuration above. + ### **Alternative: Using Claude CLI** ```bash @@ -446,6 +457,7 @@ Edit `.cursor/mcp.json`: ### **Format 2: Docker MCP Server** ✅ Fully Working +**Using Pre-built Image from GHCR** (Recommended): ```json { "mcpServers": { @@ -458,13 +470,21 @@ Edit `.cursor/mcp.json`: "--network", "host", "-e", "EXPRESS_SERVER_URL=http://localhost:3000", "-e", "ENABLE_CANVAS_SYNC=true", - "mcp-excalidraw" + "ghcr.io/yctimlin/mcp_excalidraw:latest" ] } } } ``` +**OR Build Locally**: +```bash +cd mcp_excalidraw +docker build -f Dockerfile -t mcp-excalidraw . +``` + +Then use `mcp-excalidraw` as the image name in the configuration above. + --- ## **Important Configuration Notes**