name: Build and Deploy Container
on:
push:
branches:
- main
- master
workflow_dispatch:
env:
IMAGE_NAME: mcp-gitlab-mcp-server2
REGISTRY: us-central1-docker.pkg.dev
permissions:
contents: write
packages: write
attestations: write
id-token: write
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
attestations: write
id-token: write
steps:
- name: Checkout this repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Check if source code already imported
id: check-import
run: |
# Check if src/ or package.json exists (indicating source already imported)
if [ -f "package.json" ] || [ -f "pyproject.toml" ] || [ -f "Cargo.toml" ] || [ -f "go.mod" ] || [ -d "src" ]; then
echo "SOURCE_IMPORTED=true" >> $GITHUB_OUTPUT
echo "Source code already imported, skipping clone"
else
echo "SOURCE_IMPORTED=false" >> $GITHUB_OUTPUT
echo "Source code not yet imported, will clone from upstream"
fi
- name: Clone original source repository
if: steps.check-import.outputs.SOURCE_IMPORTED == 'false'
uses: actions/checkout@v4
with:
repository: FitoDomik/gitlab-mcp-server
ref: main
path: upstream-source
- name: Import source code to repository
if: steps.check-import.outputs.SOURCE_IMPORTED == 'false'
run: |
echo "=== Importing source code from FitoDomik/gitlab-mcp-server ==="
# Copy all source files (excluding .git and .github to avoid workflow permission issues)
cd upstream-source
find . -maxdepth 1 ! -name '.' ! -name '.git' ! -name '.github' -exec cp -r {} ../ \;
cd ..
# Remove the upstream-source directory
rm -rf upstream-source
# Configure git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Commit the source code
git add -A
git commit -m "feat: Import source code from FitoDomik/gitlab-mcp-server@main" -m "Source: https://github.com/FitoDomik/gitlab-mcp-server | Branch: main | Imported: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Push the commit
git push origin main
echo "=== Source code imported successfully ==="
- name: Detect Dockerfile or generate fallback
id: dockerfile
run: |
echo "=== Checking for Dockerfile ==="
if [ -f "Dockerfile" ]; then
echo "DOCKERFILE_SOURCE=existing" >> $GITHUB_OUTPUT
echo "Using existing Dockerfile from source repository"
cat Dockerfile
else
echo "DOCKERFILE_SOURCE=generated" >> $GITHUB_OUTPUT
echo "No Dockerfile found, generating fallback..."
if [ -f "package.json" ]; then
echo "Detected: Node.js project"
MAIN=$(jq -r '.main // "index.js"' package.json 2>/dev/null || echo "index.js")
HAS_BUILD=$(jq -r '.scripts.build // empty' package.json 2>/dev/null)
HAS_START=$(jq -r '.scripts.start // empty' package.json 2>/dev/null)
if [ -f "pnpm-lock.yaml" ]; then
PKG_MGR="pnpm"
{
echo "FROM node:20-alpine"
echo "WORKDIR /app"
echo "RUN corepack enable"
echo "COPY package.json pnpm-lock.yaml ./"
echo "RUN pnpm install --frozen-lockfile"
echo "COPY . ."
if [ -n "$HAS_BUILD" ]; then echo "RUN pnpm run build"; fi
if [ -n "$HAS_START" ]; then echo 'CMD ["pnpm", "start"]'
else echo "CMD [\"node\", \"$MAIN\"]"; fi
} > Dockerfile
elif [ -f "yarn.lock" ]; then
PKG_MGR="yarn"
# Yarn Berry (v2+) requires all files including .yarn dir for install
{
echo "FROM node:20-alpine"
echo "WORKDIR /app"
echo "RUN corepack enable"
echo "COPY . ."
echo "RUN yarn install --immutable"
if [ -n "$HAS_BUILD" ]; then echo "RUN yarn build"; fi
if [ -n "$HAS_START" ]; then echo 'CMD ["yarn", "start"]'
else echo "CMD [\"node\", \"$MAIN\"]"; fi
} > Dockerfile
elif [ -f "bun.lockb" ]; then
PKG_MGR="bun"
{
echo "FROM oven/bun:latest"
echo "WORKDIR /app"
echo "COPY package.json bun.lockb ./"
echo "RUN bun install --frozen-lockfile"
echo "COPY . ."
if [ -n "$HAS_BUILD" ]; then echo "RUN bun run build"; fi
if [ -n "$HAS_START" ]; then echo 'CMD ["bun", "start"]'
else echo "CMD [\"bun\", \"$MAIN\"]"; fi
} > Dockerfile
else
PKG_MGR="npm"
{
echo "FROM node:20-alpine"
echo "WORKDIR /app"
echo "COPY package*.json ./"
echo "RUN npm ci || npm install"
echo "COPY . ."
if [ -n "$HAS_BUILD" ]; then echo "RUN npm run build"; fi
if [ -n "$HAS_START" ]; then echo 'CMD ["npm", "start"]'
else echo "CMD [\"node\", \"$MAIN\"]"; fi
} > Dockerfile
fi
elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
echo "Detected: Python project"
if [ -f "main.py" ]; then ENTRY="main.py"
elif [ -f "app.py" ]; then ENTRY="app.py"
elif [ -f "server.py" ]; then ENTRY="server.py"
else ENTRY="main.py"; fi
{
echo "FROM python:3.11-slim"
echo "WORKDIR /app"
echo "COPY requirements.txt* pyproject.toml* ./"
echo "RUN pip install --no-cache-dir -r requirements.txt 2>/dev/null || pip install --no-cache-dir . 2>/dev/null || true"
echo "COPY . ."
echo "CMD [\"python\", \"$ENTRY\"]"
} > Dockerfile
elif [ -f "go.mod" ]; then
echo "Detected: Go project"
{
echo "FROM golang:1.21-alpine AS builder"
echo "WORKDIR /app"
echo "COPY go.mod go.sum* ./"
echo "RUN go mod download"
echo "COPY . ."
echo "RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ."
echo "FROM alpine:latest"
echo "WORKDIR /app"
echo 'COPY --from=builder /app/server .'
echo 'CMD ["./server"]'
} > Dockerfile
elif [ -f "Cargo.toml" ]; then
echo "Detected: Rust project"
{
echo "FROM rust:1.75-alpine AS builder"
echo "RUN apk add --no-cache musl-dev"
echo "WORKDIR /app"
echo "COPY . ."
echo "RUN cargo build --release"
echo "FROM alpine:latest"
echo "WORKDIR /app"
echo 'COPY --from=builder /app/target/release/* ./'
echo 'CMD ["./app"]'
} > Dockerfile
else
echo "Unknown project type, creating minimal Dockerfile"
{
echo "FROM alpine:latest"
echo "WORKDIR /app"
echo "COPY . ."
echo 'CMD ["/bin/sh", "-c", "echo Container started"]'
} > Dockerfile
fi
echo "=== Generated Dockerfile ==="
cat Dockerfile
fi
echo "=== Build ready ==="
- name: Wrap stdio server with HTTP/SSE transport (supergateway)
run: |
echo "=== Wrapping stdio MCP server with supergateway for HTTP/SSE transport ==="
if [ ! -f "Dockerfile" ]; then
echo "ERROR: No Dockerfile found to modify"
exit 1
fi
# Extract the original CMD from Dockerfile
ORIGINAL_CMD=$(grep -E "^CMD\\s" Dockerfile | tail -1 | sed 's/^CMD\\s*//')
if [ -z "$ORIGINAL_CMD" ]; then
echo "WARNING: No CMD found in Dockerfile, using default"
ORIGINAL_CMD='["node", "index.js"]'
fi
echo "Original CMD: $ORIGINAL_CMD"
# Convert CMD array to command string for supergateway
if echo "$ORIGINAL_CMD" | grep -q '^\\['; then
SHELL_CMD=$(echo "$ORIGINAL_CMD" | jq -r 'join(" ")' 2>/dev/null || echo "node index.js")
else
SHELL_CMD=$ORIGINAL_CMD
fi
echo "Shell command: $SHELL_CMD"
# Use pre-detected start command if available
WRAPPER_CMD="$SHELL_CMD"
echo "Wrapper will execute: $WRAPPER_CMD"
# Check if this is a Node.js project
if [ -f "package.json" ]; then
echo "Node.js project detected - will use npx supergateway"
# Remove the original CMD line
sed -i '/^CMD\\s/d' Dockerfile
# Add supergateway wrapper
echo "" >> Dockerfile
echo "# === Supergateway HTTP/SSE Wrapper ===" >> Dockerfile
echo "# This server uses stdio transport internally, wrapped with supergateway for HTTP/SSE" >> Dockerfile
echo "RUN npm install -g supergateway || yarn global add supergateway || true" >> Dockerfile
echo "ENV PORT=8080" >> Dockerfile
echo "CMD [\"sh\", \"-c\", \"npx -y supergateway --stdio '$WRAPPER_CMD' --port \\${PORT:-8080} --ssePath /sse --messagePath /message\"]" >> Dockerfile
elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
echo "Python project detected - will use mcp-proxy"
# Add mcp-proxy to requirements if needed
if [ -f "requirements.txt" ] && ! grep -q "mcp-proxy" requirements.txt; then
echo "mcp-proxy" >> requirements.txt
fi
sed -i '/^CMD\\s/d' Dockerfile
echo "" >> Dockerfile
echo "# === MCP-Proxy HTTP/SSE Wrapper ===" >> Dockerfile
echo "# This server uses stdio transport internally, wrapped with mcp-proxy for HTTP/SSE" >> Dockerfile
echo "RUN pip install mcp-proxy" >> Dockerfile
echo "ENV PORT=8080" >> Dockerfile
echo "CMD [\"sh\", \"-c\", \"mcp-proxy --port \\${PORT:-8080} $WRAPPER_CMD\"]" >> Dockerfile
else
echo "Unknown project type - using npx supergateway wrapper"
sed -i '/^CMD\\s/d' Dockerfile
echo "" >> Dockerfile
echo "# === Supergateway HTTP/SSE Wrapper ===" >> Dockerfile
echo "# Install Node.js for supergateway if not present" >> Dockerfile
echo "RUN apk add --no-cache nodejs npm 2>/dev/null || apt-get update && apt-get install -y nodejs npm 2>/dev/null || true" >> Dockerfile
echo "RUN npm install -g supergateway || true" >> Dockerfile
echo "ENV PORT=8080" >> Dockerfile
echo "CMD [\"sh\", \"-c\", \"npx -y supergateway --stdio '$WRAPPER_CMD' --port \\${PORT:-8080} --ssePath /sse --messagePath /message\"]" >> Dockerfile
fi
echo "=== Modified Dockerfile with HTTP wrapper ==="
cat Dockerfile
echo "=== Wrapper injection complete ==="
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/${{ secrets.GCP_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: mcp-deployer@biomindtalks.iam.gserviceaccount.com
project_id: biomindtalks
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: biomindtalks
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: us-central1-docker.pkg.dev/biomindtalks/docker-images/mcp-gitlab-mcp-server2
tags: |
type=ref,event=branch
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.title=gitlab-mcp-server
org.opencontainers.image.description=MCP Server: gitlab-mcp-server - Containerized deployment
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.licenses=MIT
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
GITLAB_ACCESS_TOKEN=${{ secrets.GITLAB_ACCESS_TOKEN }}
GITLAB_PROJECT_ID=${{ secrets.GITLAB_PROJECT_ID }}
GITLAB_URL=${{ secrets.GITLAB_URL }}
- name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v2
with:
service: mcp-gitlab-mcp-server
region: us-central1
image: us-central1-docker.pkg.dev/biomindtalks/docker-images/mcp-gitlab-mcp-server2:latest
flags: |
--port=8080
--memory=256Mi
--cpu=1
--max-instances=10
--min-instances=0
--allow-unauthenticated
env_vars: |
GITLAB_ACCESS_TOKEN=${{ secrets.GITLAB_ACCESS_TOKEN }}
GITLAB_PROJECT_ID=${{ secrets.GITLAB_PROJECT_ID }}
GITLAB_URL=${{ secrets.GITLAB_URL }}
- name: Show Cloud Run URL
run: |
echo "Service URL: ${{ steps.deploy.outputs.url }}"
echo "## Cloud Run Deployment" >> $GITHUB_STEP_SUMMARY
echo "**Service:** mcp-gitlab-mcp-server" >> $GITHUB_STEP_SUMMARY
echo "**Region:** us-central1" >> $GITHUB_STEP_SUMMARY
echo "**URL:** ${{ steps.deploy.outputs.url }}" >> $GITHUB_STEP_SUMMARY