chore(deps): update dependency @supabase/mcp-server-supabase to v0.5.… #327
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | name: Build MCP Server Containers | |
| on: | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - 'npx/**/*.yaml' | |
| - 'uvx/**/*.yaml' | |
| - 'go/**/*.yaml' | |
| - 'cmd/dockhand/**' | |
| - 'go.mod' | |
| - 'go.sum' | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - 'npx/**/*.yaml' | |
| - 'uvx/**/*.yaml' | |
| - 'go/**/*.yaml' | |
| - 'cmd/dockhand/**' | |
| - 'go.mod' | |
| - 'go.sum' | |
| workflow_dispatch: | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| discover-configs: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| configs: ${{ steps.find-configs.outputs.configs }} | |
| changed-configs: ${{ steps.find-configs.outputs.changed-configs }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 0 # Need full history for change detection | |
| - name: Find configuration files to build | |
| id: find-configs | |
| run: | | |
| # Find all spec.yaml files in protocol directories | |
| all_configs=$(find npx uvx go -name "spec.yaml" -type f 2>/dev/null | sort) | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| # For manual triggers, build all configs | |
| configs_to_build="$all_configs" | |
| echo "Manual trigger - building all configurations" | |
| elif [ "${{ github.event_name }}" == "pull_request" ]; then | |
| # For PRs, build configs that changed compared to target branch | |
| changed_files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| configs_to_build="" | |
| # Check if dockhand source, go.mod, or go.sum changed (rebuild all) | |
| if echo "$changed_files" | grep -E "(cmd/dockhand/|go\.mod|go\.sum)"; then | |
| echo "Core files changed - building all configurations" | |
| configs_to_build="$all_configs" | |
| else | |
| # Only build configs whose spec.yaml files changed or their parent directories changed | |
| for config in $all_configs; do | |
| config_dir=$(dirname "$config") | |
| # Check if spec.yaml itself changed or any file in its directory changed | |
| if echo "$changed_files" | grep -q "^$config$" || echo "$changed_files" | grep -q "^$config_dir/"; then | |
| configs_to_build="$configs_to_build$config"$'\n' | |
| fi | |
| done | |
| fi | |
| else | |
| # For pushes to main, build configs that changed in this push | |
| changed_files=$(git diff --name-only HEAD~1..HEAD) | |
| configs_to_build="" | |
| # Check if dockhand source, go.mod, or go.sum changed (rebuild all) | |
| if echo "$changed_files" | grep -E "(cmd/dockhand/|go\.mod|go\.sum)"; then | |
| echo "Core files changed - building all configurations" | |
| configs_to_build="$all_configs" | |
| else | |
| # Only build configs whose spec.yaml files changed or their parent directories changed | |
| for config in $all_configs; do | |
| config_dir=$(dirname "$config") | |
| # Check if spec.yaml itself changed or any file in its directory changed | |
| if echo "$changed_files" | grep -q "^$config$" || echo "$changed_files" | grep -q "^$config_dir/"; then | |
| configs_to_build="$configs_to_build$config"$'\n' | |
| fi | |
| done | |
| fi | |
| fi | |
| # Convert to JSON array, filtering out empty lines | |
| configs_json=$(echo "$configs_to_build" | grep -v '^$' | jq -R -s -c 'split("\n")[:-1]') | |
| all_configs_json=$(echo "$all_configs" | jq -R -s -c 'split("\n")[:-1]') | |
| echo "configs=$all_configs_json" >> $GITHUB_OUTPUT | |
| echo "changed-configs=$configs_json" >> $GITHUB_OUTPUT | |
| echo "All configurations: $all_configs_json" | |
| echo "Configurations to build: $configs_json" | |
| verify-provenance: | |
| needs: discover-configs | |
| runs-on: ubuntu-latest | |
| # Verify package provenance when we have configs to build | |
| if: ${{ needs.discover-configs.outputs.changed-configs != '[]' }} | |
| strategy: | |
| matrix: | |
| config: ${{ fromJson(needs.discover-configs.outputs.changed-configs) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| - name: Set up Go | |
| uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Extract metadata from config | |
| id: meta | |
| run: | | |
| config_file="${{ matrix.config }}" | |
| protocol=$(echo "$config_file" | cut -d'/' -f1) | |
| server_name=$(echo "$config_file" | cut -d'/' -f2) | |
| echo "config_file=$config_file" >> $GITHUB_OUTPUT | |
| echo "protocol=$protocol" >> $GITHUB_OUTPUT | |
| echo "server_name=$server_name" >> $GITHUB_OUTPUT | |
| - name: Build dockhand | |
| run: go build -o /tmp/dockhand ./cmd/dockhand | |
| - name: Verify package provenance | |
| id: provenance | |
| run: | | |
| echo "🔍 Verifying package provenance for ${{ steps.meta.outputs.server_name }}" | |
| # Run provenance verification | |
| if /tmp/dockhand verify-provenance -c "${{ matrix.config }}" > /tmp/provenance-${{ steps.meta.outputs.server_name }}.txt 2>&1; then | |
| echo "provenance_passed=true" >> $GITHUB_OUTPUT | |
| echo "✅ Provenance verification passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "provenance_passed=false" >> $GITHUB_OUTPUT | |
| echo "⚠️ Provenance verification failed or package has no provenance" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Display results | |
| cat /tmp/provenance-${{ steps.meta.outputs.server_name }}.txt | |
| # Save for artifact | |
| cp /tmp/provenance-${{ steps.meta.outputs.server_name }}.txt . | |
| - name: Upload provenance verification results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: provenance-${{ steps.meta.outputs.server_name }} | |
| path: provenance-${{ steps.meta.outputs.server_name }}.txt | |
| retention-days: 30 | |
| mcp-security-scan: | |
| needs: [discover-configs, verify-provenance] | |
| runs-on: ubuntu-latest | |
| # Always run security scans when we have configs to build | |
| if: ${{ needs.discover-configs.outputs.changed-configs != '[]' }} | |
| strategy: | |
| matrix: | |
| config: ${{ fromJson(needs.discover-configs.outputs.changed-configs) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| - name: Set up Python | |
| uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 | |
| with: | |
| python-version: '3.13' | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@2ddd2b9cb38ad8efd50337e8ab201519a34c9f24 # v7 | |
| with: | |
| enable-cache: true | |
| - name: Install dependencies | |
| run: | | |
| # Install mcp-scan | |
| uv tool install mcp-scan | |
| # Install Python dependencies for scripts | |
| uv pip install --system pyyaml | |
| # Verify installation (mcp-scan doesn't have --version, so we use help) | |
| uv tool run mcp-scan --help || true | |
| - name: Extract metadata from config | |
| id: meta | |
| run: | | |
| config_file="${{ matrix.config }}" | |
| # Extract protocol and server name from path like "npx/context7/spec.yaml" | |
| protocol=$(echo "$config_file" | cut -d'/' -f1) | |
| server_name=$(echo "$config_file" | cut -d'/' -f2) | |
| echo "config_file=$config_file" >> $GITHUB_OUTPUT | |
| echo "protocol=$protocol" >> $GITHUB_OUTPUT | |
| echo "server_name=$server_name" >> $GITHUB_OUTPUT | |
| - name: Run MCP Security Scan | |
| id: scan | |
| run: | | |
| echo "🔍 Scanning MCP server: ${{ steps.meta.outputs.server_name }}" | |
| # Generate MCP configuration | |
| python3 scripts/mcp-scan/generate_mcp_config.py \ | |
| "${{ matrix.config }}" \ | |
| "${{ steps.meta.outputs.protocol }}" \ | |
| "${{ steps.meta.outputs.server_name }}" \ | |
| > "/tmp/${{ steps.meta.outputs.server_name }}-mcp-config.json" | |
| # Run mcp-scan | |
| scan_output="/tmp/mcp-scan-${{ steps.meta.outputs.server_name }}.json" | |
| if uv tool run mcp-scan scan "/tmp/${{ steps.meta.outputs.server_name }}-mcp-config.json" \ | |
| --json \ | |
| --storage-file "/tmp/mcp-scan-storage" \ | |
| --server-timeout 30 \ | |
| --suppress-mcpserver-io true \ | |
| > "$scan_output" 2>&1; then | |
| echo "scan_passed=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "scan_passed=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Process results (pass the config file to check for allowed issues) | |
| python3 scripts/mcp-scan/process_scan_results.py \ | |
| "$scan_output" \ | |
| "${{ steps.meta.outputs.server_name }}" \ | |
| "${{ matrix.config }}" \ | |
| > scan-summary.json | |
| # Copy scan output to current directory for artifact upload | |
| cp "$scan_output" "mcp-scan-${{ steps.meta.outputs.server_name }}.json" | |
| - name: Upload scan results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: mcp-scan-${{ steps.meta.outputs.server_name }} | |
| path: | | |
| mcp-scan-${{ steps.meta.outputs.server_name }}.json | |
| scan-summary.json | |
| retention-days: 30 | |
| build-containers: | |
| needs: [discover-configs, verify-provenance, mcp-security-scan] | |
| runs-on: ubuntu-latest | |
| # Only proceed if security scans passed (provenance check is informational only) | |
| if: ${{ needs.discover-configs.outputs.changed-configs != '[]' && needs.mcp-security-scan.result == 'success' }} | |
| strategy: | |
| matrix: | |
| config: ${{ fromJson(needs.discover-configs.outputs.changed-configs) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write # Needed for OIDC token (sigstore) | |
| attestations: write # Needed for attestations | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| - name: Set up Go | |
| uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 | |
| - name: Install Cosign | |
| uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 | |
| - name: Install yq | |
| uses: mikefarah/yq@0ecdce24e83f0fa127940334be98c86b07b0c488 # v4.48.1 | |
| - name: Log in to Container Registry | |
| # Only login when we're going to push (main branch or manual trigger) | |
| if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' | |
| uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata from config | |
| id: meta | |
| run: | | |
| config_file="${{ matrix.config }}" | |
| echo "config_file=$config_file" >> $GITHUB_OUTPUT | |
| # Extract protocol from directory name (first part of path) | |
| protocol=$(echo "$config_file" | cut -d'/' -f1) | |
| echo "protocol=$protocol" >> $GITHUB_OUTPUT | |
| # Extract server name from directory name (second part of path) | |
| server_name=$(echo "$config_file" | cut -d'/' -f2) | |
| echo "server_name=$server_name" >> $GITHUB_OUTPUT | |
| # Extract version from YAML file (spec.version) | |
| spec_version=$(yq '.spec.version' "$config_file" 2>/dev/null || echo "") | |
| # Use spec.version if available, otherwise "latest" | |
| if [ -n "$spec_version" ]; then | |
| version="$spec_version" | |
| else | |
| version="latest" | |
| fi | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| # Generate image name | |
| image_name="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${protocol}/${server_name}" | |
| echo "image_name=$image_name" >> $GITHUB_OUTPUT | |
| - name: Generate Dockerfile | |
| id: dockerfile | |
| run: | | |
| echo "Generating Dockerfile for ${{ steps.meta.outputs.config_file }}" | |
| # Create a temporary directory for the Dockerfile | |
| dockerfile_dir=$(mktemp -d) | |
| dockerfile_path="${dockerfile_dir}/Dockerfile" | |
| # Build and run dockhand to generate the Dockerfile | |
| go build -o /tmp/dockhand ./cmd/dockhand | |
| /tmp/dockhand build --config "${{ steps.meta.outputs.config_file }}" --output "${dockerfile_path}" | |
| echo "dockerfile_dir=$dockerfile_dir" >> $GITHUB_OUTPUT | |
| echo "dockerfile_path=$dockerfile_path" >> $GITHUB_OUTPUT | |
| # Display the generated Dockerfile for debugging | |
| echo "Generated Dockerfile:" | |
| cat "${dockerfile_path}" | |
| - name: Build and push Docker image | |
| id: build | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 | |
| with: | |
| context: ${{ steps.dockerfile.outputs.dockerfile_dir }} | |
| file: ${{ steps.dockerfile.outputs.dockerfile_path }} | |
| platforms: linux/amd64,linux/arm64 | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: | | |
| ${{ steps.meta.outputs.image_name }}:${{ steps.meta.outputs.version }} | |
| ${{ steps.meta.outputs.image_name }}:latest | |
| labels: | | |
| org.opencontainers.image.title=${{ steps.meta.outputs.server_name }} | |
| org.opencontainers.image.description=MCP server for ${{ steps.meta.outputs.server_name }} | |
| org.opencontainers.image.vendor=Stacklok | |
| org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.version=${{ steps.meta.outputs.version }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| sbom: true | |
| provenance: true | |
| - name: Sign container images with Cosign | |
| if: github.event_name != 'pull_request' | |
| env: | |
| DIGEST: ${{ steps.build.outputs.digest }} | |
| run: | | |
| echo "Signing image ${{ steps.meta.outputs.image_name }}@${DIGEST}" | |
| cosign sign --yes ${{ steps.meta.outputs.image_name }}@${DIGEST} | |
| # Also sign the tagged versions for better UX | |
| cosign sign --yes ${{ steps.meta.outputs.image_name }}:${{ steps.meta.outputs.version }} | |
| cosign sign --yes ${{ steps.meta.outputs.image_name }}:latest | |
| echo "✅ Images signed with Sigstore/Cosign" >> $GITHUB_STEP_SUMMARY | |
| - name: Download MCP security scan results | |
| id: download-scan | |
| if: github.event_name != 'pull_request' | |
| uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 | |
| with: | |
| name: mcp-scan-${{ steps.meta.outputs.server_name }} | |
| path: /tmp/scan-results | |
| continue-on-error: false # Fail if scan results are missing | |
| - name: Create security scan attestation | |
| if: github.event_name != 'pull_request' | |
| env: | |
| DIGEST: ${{ steps.build.outputs.digest }} | |
| run: | | |
| echo "Creating security scan attestation for ${{ steps.meta.outputs.image_name }}@${DIGEST}" | |
| # Read the scan summary | |
| SCAN_SUMMARY=$(cat /tmp/scan-results/scan-summary.json) | |
| SCAN_STATUS=$(echo "$SCAN_SUMMARY" | jq -r '.status') | |
| # Create custom attestation for security scan | |
| cat > /tmp/security-attestation.json <<EOF | |
| { | |
| "_type": "https://in-toto.io/Statement/v0.1", | |
| "predicateType": "https://github.com/stacklok/dockyard/mcp-security-scan/v1", | |
| "subject": [ | |
| { | |
| "name": "${{ steps.meta.outputs.image_name }}", | |
| "digest": { | |
| "sha256": "${DIGEST#sha256:}" | |
| } | |
| } | |
| ], | |
| "predicate": { | |
| "scanTool": "mcp-scan", | |
| "scanDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", | |
| "scanResult": $SCAN_SUMMARY, | |
| "repository": "${{ github.repository }}", | |
| "commitSha": "${{ github.sha }}", | |
| "workflowRun": "${{ github.run_id }}", | |
| "configFile": "${{ matrix.config }}" | |
| } | |
| } | |
| EOF | |
| # Attest the security scan results | |
| cosign attest --yes \ | |
| --predicate /tmp/security-attestation.json \ | |
| --type https://github.com/stacklok/dockyard/mcp-security-scan/v1 \ | |
| ${{ steps.meta.outputs.image_name }}@${DIGEST} | |
| echo "✅ Security scan attestation created" >> $GITHUB_STEP_SUMMARY | |
| # Clean up | |
| rm -f /tmp/security-attestation.json | |
| - name: Generate image summary | |
| run: | | |
| echo "## Container Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Config**: ${{ steps.meta.outputs.config_file }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Protocol**: ${{ steps.meta.outputs.protocol }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Server**: ${{ steps.meta.outputs.server_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Image**: ${{ steps.meta.outputs.image_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version**: ${{ steps.meta.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Platforms**: linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ github.event_name }}" != "pull_request" ]; then | |
| echo "- **SBOM**: ✅ Attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Build Provenance**: ✅ Attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Security Scan**: ✅ Attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Signatures**: ✅ Signed with Sigstore/Cosign" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Status**: ✅ Built, pushed, signed, and attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY | |
| echo " - ${{ steps.meta.outputs.image_name }}:${{ steps.meta.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo " - ${{ steps.meta.outputs.image_name }}:latest" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Digest**: ${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- **Status**: ✅ Built (not pushed - PR)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| mcp-scan-report: | |
| needs: mcp-security-scan | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' && always() | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Download all scan results | |
| uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 | |
| with: | |
| pattern: mcp-scan-* | |
| path: scan-artifacts | |
| - name: Comment PR with scan results | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| let comment = '## 🔒 MCP Security Scan Results\n\n'; | |
| let hasAnyIssues = false; | |
| let totalServersScanned = 0; | |
| let totalVulnerabilities = 0; | |
| // Find all scan summary files in the scan-artifacts directory | |
| const summaryFiles = []; | |
| const artifactsDir = 'scan-artifacts'; | |
| // Debug: List all files in current directory | |
| console.log('Current directory contents:', fs.readdirSync('.')); | |
| if (fs.existsSync(artifactsDir)) { | |
| console.log('Artifacts directory exists'); | |
| const artifactDirs = fs.readdirSync(artifactsDir); | |
| console.log('Artifact directories:', artifactDirs); | |
| // Check each item in the artifacts directory | |
| for (const item of artifactDirs) { | |
| const itemPath = path.join(artifactsDir, item); | |
| const stat = fs.statSync(itemPath); | |
| if (stat.isDirectory()) { | |
| // Look for scan-summary.json in subdirectory | |
| const summaryFile = path.join(itemPath, 'scan-summary.json'); | |
| if (fs.existsSync(summaryFile)) { | |
| summaryFiles.push(summaryFile); | |
| console.log('Found summary file in directory:', summaryFile); | |
| } else { | |
| console.log(`No scan-summary.json in ${itemPath}, contents:`, fs.readdirSync(itemPath)); | |
| } | |
| } else if (stat.isFile() && item === 'scan-summary.json') { | |
| // The file is directly in the artifacts directory | |
| summaryFiles.push(itemPath); | |
| console.log('Found summary file directly:', itemPath); | |
| } | |
| } | |
| } else { | |
| console.log('Artifacts directory does not exist'); | |
| } | |
| console.log('Total summary files found:', summaryFiles.length); | |
| if (summaryFiles.length === 0) { | |
| comment += '⚠️ No MCP servers were scanned in this PR.\n'; | |
| } else { | |
| for (const file of summaryFiles) { | |
| try { | |
| const summary = JSON.parse(fs.readFileSync(file, 'utf8')); | |
| totalServersScanned++; | |
| if (summary.status === 'passed') { | |
| comment += `### ✅ ${summary.server}\n`; | |
| comment += `- **Status**: Passed\n`; | |
| comment += `- **Tools scanned**: ${summary.tools_scanned || 0}\n`; | |
| comment += `- **Result**: No security issues detected\n\n`; | |
| } else if (summary.status === 'failed') { | |
| hasAnyIssues = true; | |
| totalVulnerabilities += summary.blocking_count || 0; | |
| comment += `### ❌ ${summary.server}\n`; | |
| comment += `- **Status**: Failed\n`; | |
| comment += `- **Tools scanned**: ${summary.tools_scanned || 0}\n`; | |
| comment += `- **Vulnerabilities found**: ${summary.blocking_count || 0}\n`; | |
| comment += '\n**Security issues detected:**\n'; | |
| if (summary.blocking_issues) { | |
| summary.blocking_issues.forEach(vuln => { | |
| comment += `- **[${vuln.code}]** ${vuln.message}\n`; | |
| }); | |
| } | |
| // Also show allowed issues if any | |
| if (summary.allowed_issues && summary.allowed_issues.length > 0) { | |
| comment += '\n**Allowed issues (not blocking):**\n'; | |
| summary.allowed_issues.forEach(vuln => { | |
| comment += `- **[${vuln.code}]** ${vuln.message} _(Allowed: ${vuln.allowed_reason})_\n`; | |
| }); | |
| } | |
| comment += '\n'; | |
| } else if (summary.status === 'warning') { | |
| comment += `### ⚠️ ${summary.server}\n`; | |
| comment += `- **Status**: Warning\n`; | |
| comment += `- **Message**: ${summary.message}\n\n`; | |
| } else { | |
| comment += `### ⚠️ ${summary.server}\n`; | |
| comment += `- **Status**: Error\n`; | |
| comment += `- **Message**: ${summary.message || 'Unknown error'}\n\n`; | |
| } | |
| } catch (error) { | |
| console.error(`Error parsing ${file}:`, error); | |
| comment += `### ⚠️ Error parsing scan results\n`; | |
| comment += `Could not parse ${file}: ${error.message}\n\n`; | |
| } | |
| } | |
| // Add summary | |
| if (totalServersScanned > 0) { | |
| comment += '---\n'; | |
| comment += `**Summary**: Scanned ${totalServersScanned} MCP server(s)`; | |
| if (hasAnyIssues) { | |
| comment += `, found ${totalVulnerabilities} security issue(s).\n\n`; | |
| comment += '⚠️ **Action Required**: Security issues were detected. Please review and address them before merging.\n'; | |
| } else { | |
| comment += ', all passed security checks. ✅\n'; | |
| } | |
| } | |
| } | |
| // Find and update or create comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && comment.body.includes('MCP Security Scan Results') | |
| ); | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: comment | |
| }); | |
| console.log(`Updated existing comment #${botComment.id}`); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: comment | |
| }); | |
| console.log('Created new comment'); | |
| } | |
| summary: | |
| needs: [discover-configs, verify-provenance, mcp-security-scan, build-containers] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Build Summary | |
| run: | | |
| echo "## Dockyard Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Trigger**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Ref**: ${{ github.ref }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Total Configs**: ${{ needs.discover-configs.outputs.configs }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Changed Configs**: ${{ needs.discover-configs.outputs.changed-configs }}" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.verify-provenance.result }}" == "success" ]; then | |
| echo "- **Provenance Verification**: ✅ All packages verified" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.verify-provenance.result }}" == "failure" ]; then | |
| echo "- **Provenance Verification**: ⚠️ Some packages have provenance issues (non-blocking)" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.verify-provenance.result }}" == "skipped" ]; then | |
| echo "- **Provenance Verification**: ⏭️ Skipped" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "${{ needs.mcp-security-scan.result }}" == "success" ]; then | |
| echo "- **Security Scan**: ✅ All MCP servers passed security scanning" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.mcp-security-scan.result }}" == "failure" ]; then | |
| echo "- **Security Scan**: ❌ Some MCP servers have security issues" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.mcp-security-scan.result }}" == "skipped" ]; then | |
| echo "- **Security Scan**: ⏭️ Skipped" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "${{ needs.build-containers.result }}" == "success" ]; then | |
| echo "- **Build Status**: ✅ All changed containers built successfully" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Features**:" >> $GITHUB_STEP_SUMMARY | |
| echo " - 🏗️ Multi-architecture support (amd64, arm64)" >> $GITHUB_STEP_SUMMARY | |
| echo " - 📦 SBOM (Software Bill of Materials) included" >> $GITHUB_STEP_SUMMARY | |
| echo " - 🔐 Provenance attestation for supply chain security" >> $GITHUB_STEP_SUMMARY | |
| echo " - ✍️ Sigstore/Cosign signatures for image verification" >> $GITHUB_STEP_SUMMARY | |
| echo " - 🚀 GitHub Actions cache for faster builds" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.build-containers.result }}" == "failure" ]; then | |
| echo "- **Build Status**: ❌ Some containers failed to build" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.build-containers.result }}" == "skipped" ]; then | |
| echo "- **Build Status**: ⏭️ No configuration changes detected" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- **Build Status**: ⚠️ Build status unknown" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Add efficiency note | |
| changed_count=$(echo '${{ needs.discover-configs.outputs.changed-configs }}' | jq length) | |
| total_count=$(echo '${{ needs.discover-configs.outputs.configs }}' | jq length) | |
| echo "- **Efficiency**: Built $changed_count out of $total_count configurations" >> $GITHUB_STEP_SUMMARY |