CI/CD Integration
7 min read
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
The continue-on-error: true on the scan step ensures the SARIF upload always runs, even when findings are detected. Without it, a non-zero exit code would skip the upload step.
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.
Recommended Thresholds
| Environment | --fail-on | Mode | Rationale |
|---|---|---|---|
| Development / Feature branches | critical | fast | Do not block development for non-critical issues |
| Pull Requests | high | fast | Catch significant issues before merge |
| Main Branch | high | fast or deep | Keep the main branch clean |
| Staging / Release candidates | medium | deep | Stricter gates before production |
| Security Audit | low | deep | Surface everything for human review |
Exit Codes
| Code | Meaning | CI Action |
|---|---|---|
| 0 | No findings above threshold | Pass |
| 1 | Findings above threshold | Fail |
| 2 | Configuration error | Fail (fix config) |
| 3 | Scan 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
Fast mode for PRs, deep mode for main/releases – Optimize for developer experience without sacrificing thoroughness
Always set
--fail-on– Without it, the scan always exits 0 regardless of findings, providing no CI gateUse baselines for progressive hardening – Accept existing findings, prevent new ones, review periodically
Upload SARIF to code scanning – GitHub, GitLab, and Azure DevOps can display findings inline on pull requests
Cache Go modules – Avoid re-downloading dependencies on every build
Set timeouts – Both on mcp-scan (
--timeout) and on the CI job (timeout-minutes)Archive results – Save JSON or SARIF output as build artifacts for audit trails
Notify on failures – Alert security teams via Slack, email, or other channels when scans fail