CI/CD Integration

Integrate mcp-scan into CI/CD pipelines with GitHub Actions, GitLab CI, and other platforms – including SARIF upload, baseline management, and severity gates.

MCP Scanner is designed to run as part of automated CI/CD pipelines. This guide covers integration with popular CI/CD platforms, using --fail-on as a quality gate, uploading SARIF results to code scanning dashboards, and managing baselines across builds.

Quick Start

The minimum CI integration is a single command:

mcp-scan scan . --fail-on high

This scans the project, and exits with code 1 if any high or critical findings are detected, failing the pipeline.


GitHub Actions

Basic Workflow

The simplest GitHub Actions integration runs a fast scan on every push and pull request:

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  mcp-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Install MCP-Scan
        run: go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest

      - name: Run Security Scan
        run: mcp-scan scan . --fail-on high

With GitHub Code Scanning (SARIF Upload)

Upload SARIF results to GitHub Code Scanning for findings to appear directly on pull requests and in the Security tab:

name: Security Scan with Code Scanning

on:
  push:
    branches: [main]
  pull_request:

jobs:
  mcp-scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Install MCP-Scan
        run: go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest

      - name: Run Scan
        run: mcp-scan scan . --output sarif > results.sarif
        continue-on-error: true

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif
          category: mcp-scan

With Baseline (Progressive Hardening)

Use a baseline committed to your repository to only fail on new findings:

name: Security Scan with Baseline

on: [push, pull_request]

jobs:
  mcp-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Install MCP-Scan
        run: go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest

      - name: Run Scan with Baseline
        run: |
          mcp-scan scan . \
            --baseline .mcp-scan-baseline.json \
            --fail-on high \
            --output json > results.json

      - name: Upload Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: security-results
          path: results.json

Dual Mode: Fast on PRs, Deep on Main

Use fast mode for quick PR feedback and deep mode for thorough analysis on the main branch:

name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  mcp-scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Install MCP-Scan
        run: go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest

      - name: Quick Scan (PRs)
        if: github.event_name == 'pull_request'
        run: mcp-scan scan . --mode fast --fail-on high

      - name: Deep Scan (Main)
        if: github.ref == 'refs/heads/main'
        run: mcp-scan scan . --mode deep --output sarif > results.sarif
        continue-on-error: true

      - name: Upload SARIF
        if: github.ref == 'refs/heads/main'
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif
          category: mcp-scan

Caching Go Modules

Speed up CI runs by caching Go module dependencies:

      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-

GitLab CI

Basic Integration

# .gitlab-ci.yml
stages:
  - security

mcp-scan:
  stage: security
  image: golang:1.24
  script:
    - go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest
    - mcp-scan scan . --fail-on high
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

With GitLab Security Dashboard

Generate SARIF-compatible output for GitLab’s Security Dashboard:

mcp-scan:
  stage: security
  image: golang:1.24
  script:
    - go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest
    - mcp-scan scan . --output sarif > gl-sast-report.json
  artifacts:
    reports:
      sast: gl-sast-report.json
    paths:
      - gl-sast-report.json
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

With Baseline

mcp-scan:
  stage: security
  image: golang:1.24
  script:
    - go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest
    - mcp-scan scan . --baseline .mcp-scan-baseline.json --fail-on high
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Azure DevOps

# azure-pipelines.yml
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: GoTool@0
    inputs:
      version: '1.24'

  - script: |
      go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest
      mcp-scan scan . --output sarif > $(Build.ArtifactStagingDirectory)/results.sarif
    displayName: 'Run MCP-Scan'

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'security-results'

CircleCI

# .circleci/config.yml
version: 2.1

jobs:
  security-scan:
    docker:
      - image: cimg/go:1.24
    steps:
      - checkout
      - run:
          name: Install MCP-Scan
          command: go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest
      - run:
          name: Run Security Scan
          command: mcp-scan scan . --fail-on high --output json > results.json
      - store_artifacts:
          path: results.json

workflows:
  security:
    jobs:
      - security-scan

Jenkins

// Jenkinsfile
pipeline {
    agent any

    tools {
        go 'go-1.24'
    }

    stages {
        stage('Security Scan') {
            steps {
                sh 'go install github.com/mcphub/mcp-scan/cmd/mcp-scan@latest'
                sh 'mcp-scan scan . --output json > results.json'
                sh 'mcp-scan scan . --fail-on high'
            }
            post {
                always {
                    archiveArtifacts artifacts: 'results.json'
                }
            }
        }
    }
}

Docker

Using the Docker Image

Run mcp-scan directly via Docker without any local installation:

docker run --rm -v $(pwd):/scan mcphub/mcp-scan scan /scan --fail-on high

In Docker Compose

services:
  security-scan:
    image: mcphub/mcp-scan:latest
    volumes:
      - .:/scan:ro
    command: scan /scan --output json

Quality Gates with –fail-on

The --fail-on flag is the primary mechanism for enforcing security standards in CI. It sets a severity threshold: if any finding meets or exceeds that severity, the command exits with code 1.

Environment--fail-onModeRationale
Development / Feature branchescriticalfastDo not block development for non-critical issues
Pull RequestshighfastCatch significant issues before merge
Main Branchhighfast or deepKeep the main branch clean
Staging / Release candidatesmediumdeepStricter gates before production
Security AuditlowdeepSurface everything for human review

Exit Codes

CodeMeaningCI Action
0No findings above thresholdPass
1Findings above thresholdFail
2Configuration errorFail (fix config)
3Scan error (timeout, etc.)Fail (investigate)

Baseline Management Across Builds

Baselines allow progressive hardening: accept existing findings while preventing new ones.

Initial Setup

Generate a baseline from the current state of the codebase:

mcp-scan baseline generate . \
  --reason "Initial baseline - all findings reviewed" \
  --accepted-by "[email protected]"

# Commit the baseline to version control
git add .mcp-scan-baseline.json
git commit -m "chore: add initial mcp-scan baseline"

CI Usage

Reference the baseline in every scan:

mcp-scan scan . --baseline .mcp-scan-baseline.json --fail-on high

This way, only new findings (not in the baseline) will trigger a failure.

Updating the Baseline

When new findings are reviewed and accepted, update the baseline:

# Generate a new baseline
mcp-scan baseline generate . \
  --reason "Sprint 12 review - JIRA-1234" \
  --accepted-by "[email protected]"

# Or merge with the existing baseline
mcp-scan baseline merge \
  .mcp-scan-baseline.json \
  new-findings.json \
  --output .mcp-scan-baseline.json

# Commit the updated baseline
git add .mcp-scan-baseline.json
git commit -m "chore: update mcp-scan baseline after sprint 12 review"

Preventing Baseline Growth

Add a CI check to ensure the baseline does not grow unchecked:

      - name: Check baseline size
        run: |
          ENTRIES=$(jq '.entries | length' .mcp-scan-baseline.json)
          if [ "$ENTRIES" -gt 50 ]; then
            echo "Baseline has $ENTRIES entries (limit: 50). Please review and clean up."
            exit 1
          fi

Notifications

Slack Notification on Failures

      - name: Notify Slack
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "MCP-Scan found security issues in ${{ github.repository }} (${{ github.ref_name }})"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Email on Critical Findings

      - name: Send Email
        if: failure()
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.example.com
          to: [email protected]
          subject: "Security scan failed: ${{ github.repository }}"
          body: |
            MCP-Scan detected findings above threshold.
            See: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

Timeout Configuration

Set appropriate timeouts for both mcp-scan and the CI job:

      - name: Run Scan
        run: mcp-scan scan . --timeout 5m
        timeout-minutes: 10  # Pipeline timeout should exceed scan timeout

For deep mode on large projects, increase the timeout:

      - name: Deep Scan
        run: mcp-scan scan . --mode deep --timeout 30m
        timeout-minutes: 45

Best Practices Summary

  1. Fast mode for PRs, deep mode for main/releases – Optimize for developer experience without sacrificing thoroughness

  2. Always set --fail-on – Without it, the scan always exits 0 regardless of findings, providing no CI gate

  3. Use baselines for progressive hardening – Accept existing findings, prevent new ones, review periodically

  4. Upload SARIF to code scanning – GitHub, GitLab, and Azure DevOps can display findings inline on pull requests

  5. Cache Go modules – Avoid re-downloading dependencies on every build

  6. Set timeouts – Both on mcp-scan (--timeout) and on the CI job (timeout-minutes)

  7. Archive results – Save JSON or SARIF output as build artifacts for audit trails

  8. Notify on failures – Alert security teams via Slack, email, or other channels when scans fail