Skip to content

chore(deps): update dependency @supabase/mcp-server-supabase to v0.5.… #327

chore(deps): update dependency @supabase/mcp-server-supabase to v0.5.…

chore(deps): update dependency @supabase/mcp-server-supabase to v0.5.… #327

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