name: MCP Stack (apply/destroy + build/push)
on:
push:
paths:
- deploy/mcp/**
- Dockerfile.mcp
- src/**
- tests/**
workflow_dispatch:
inputs:
action:
type: choice
required: true
default: apply
options:
- apply
- destroy
concurrency: mcp-stack
jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }}
AWS_OIDC_ROLE_SESSION_NAME: ${{ secrets.AWS_OIDC_ROLE_SESSION_NAME }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
MCP_HTTP_TOKEN: ${{ secrets.MCP_HTTP_TOKEN }}
ALLOWED_CIDRS: ${{ secrets.ALLOWED_CIDRS }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/uv-action@v3
- name: Lint + unit tests
run: |
uv sync --frozen
uv run ruff check
uv run pytest -q
- name: Fail fast if credentials missing
run: |
if [ -z "${AWS_REGION}" ] || [ -z "${AWS_ACCOUNT_ID}" ]; then
echo "::error::Missing AWS_REGION or AWS_ACCOUNT_ID secret"; exit 1; fi
if [ -z "${AWS_ROLE_TO_ASSUME}" ] && { [ -z "${AWS_ACCESS_KEY_ID}" ] || [ -z "${AWS_SECRET_ACCESS_KEY}" ]; }; then
echo "::error::Provide AWS_ROLE_TO_ASSUME (OIDC) OR AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY"; exit 1; fi
- name: Configure AWS (prefer OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}
if: ${{ secrets.AWS_ROLE_TO_ASSUME != '' }}
- name: Configure AWS (static keys)
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
if: ${{ secrets.AWS_ROLE_TO_ASSUME == '' }}
- name: Create ECR repo (idempotent)
run: |
aws ecr describe-repositories --repository-names mcp-vertica >/dev/null 2>&1 || \
aws ecr create-repository --repository-name mcp-vertica >/dev/null
echo "ECR=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/mcp-vertica" >> "$GITHUB_ENV"
- name: Login to ECR
run: aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
- name: Build & push MCP image
run: |
docker build -f Dockerfile.mcp -t mcp-vertica:latest .
docker tag mcp-vertica:latest ${ECR}:latest
docker push ${ECR}:latest
- name: Bootstrap backend (idempotent)
working-directory: deploy/mcp/terraform
run: ./backend-bootstrap.sh
- name: Terraform init
working-directory: deploy/mcp/terraform
run: terraform init -upgrade
- name: Terraform ${{ github.event.inputs.action || 'apply' }}
working-directory: deploy/mcp/terraform
run: |
ACTION="${{ github.event.inputs.action || 'apply' }}"
if [ "$ACTION" = "destroy" ]; then
terraform destroy -auto-approve -var="aws_region=${AWS_REGION}" -var="aws_account_id=${AWS_ACCOUNT_ID}" -var="mcp_image_repo=mcp-vertica"
exit 0
fi
terraform apply -auto-approve \
-var="aws_region=${AWS_REGION}" \
-var="aws_account_id=${AWS_ACCOUNT_ID}" \
-var="mcp_image_repo=mcp-vertica" \
-var="mcp_http_token=${MCP_HTTP_TOKEN}" \
-var='allowed_cidrs=[${ALLOWED_CIDRS:+"'${ALLOWED_CIDRS//,/","}'"}]'
- name: Smoke test via SSM
if: ${{ github.event.inputs.action != 'destroy' }}
run: |
IID=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=mcp-vertica" "Name=instance-state-name,Values=running" \
--query "Reservations[0].Instances[0].InstanceId" --output text)
aws ssm send-command --instance-ids "$IID" \
--document-name "AWS-RunShellScript" \
--parameters commands="/usr/local/bin/mcp-smoke.sh" \
--query "Command.CommandId" --output text > cmd.txt
CID=$(cat cmd.txt)
aws ssm wait command-executed --command-id "$CID" --instance-id "$IID"
- name: Summarize
run: |
echo "### MCP ready" >> "$GITHUB_STEP_SUMMARY"
URL=$(terraform -chdir=deploy/mcp/terraform output -raw mcp_public_url)
echo "MCP URL: ${URL}" >> "$GITHUB_STEP_SUMMARY"