Skip to content

Integracion CI/CD

Guia completa para integrar mcp-scan en pipelines de integracion continua.


Tabla de Contenidos

  1. Vision General
  2. GitHub Actions
  3. GitLab CI
  4. Jenkins
  5. Azure DevOps
  6. CircleCI
  7. Bitbucket Pipelines
  8. Drone CI
  9. Estrategias de Integracion
  10. Notificaciones

Vision General

Objetivos de CI/CD

Objetivo Implementacion
Bloquear PRs inseguros --fail-on high
Documentar seguridad Salida SARIF/JSON
Baseline automatico Baseline management
Trending de seguridad Almacenar historico
Cumplimiento Evidence bundles

Flujo Tipico

PR/Commit → Scan Fast → Pass/Fail → Report
        Nightly → Scan Deep → Evidence → Archive

Recomendaciones Generales

Fase Modo Fail On Timeout
PR Check fast high 5m
Merge to main fast critical 5m
Nightly deep medium 30m
Release deep low 1h

GitHub Actions

Configuracion Basica

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

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

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

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install mcp-scan
        run: |
          curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Run security scan
        run: |
          mcp-scan scan . \
            --mode fast \
            --output sarif \
            --fail-on high \
            --baseline baseline.json \
            > results.sarif

      - name: Upload SARIF to GitHub
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: results.sarif

Configuracion Avanzada

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

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'  # Nightly a las 2 AM

env:
  MCP_SCAN_VERSION: "2.0.0"

jobs:
  # Job 1: Scan rapido en PRs
  pr-scan:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - name: Install mcp-scan
        run: |
          curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash -s -- v${{ env.MCP_SCAN_VERSION }}

      - name: Run fast scan
        id: scan
        run: |
          mcp-scan scan . \
            --mode fast \
            --output json \
            --baseline baseline.json \
            > results.json

          # Extraer metricas
          TOTAL=$(jq '.summary.total' results.json)
          CRITICAL=$(jq '.summary.by_severity.critical' results.json)
          HIGH=$(jq '.summary.by_severity.high' results.json)
          SCORE=$(jq '.msss_score.score' results.json)

          echo "total=$TOTAL" >> $GITHUB_OUTPUT
          echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
          echo "high=$HIGH" >> $GITHUB_OUTPUT
          echo "score=$SCORE" >> $GITHUB_OUTPUT
        continue-on-error: true

      - name: Generate SARIF
        run: |
          mcp-scan scan . \
            --mode fast \
            --output sarif \
            --baseline baseline.json \
            > results.sarif
        continue-on-error: true

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: results.sarif

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            const total = '${{ steps.scan.outputs.total }}';
            const critical = '${{ steps.scan.outputs.critical }}';
            const high = '${{ steps.scan.outputs.high }}';
            const score = '${{ steps.scan.outputs.score }}';

            let emoji = score >= 80 ? ':white_check_mark:' : score >= 60 ? ':warning:' : ':x:';

            const body = `## Security Scan Results ${emoji}

            | Metric | Value |
            |--------|-------|
            | Total Findings | ${total} |
            | Critical | ${critical} |
            | High | ${high} |
            | MSSS Score | ${score}/100 |

            [View detailed results in Security tab](${context.payload.repository.html_url}/security/code-scanning)
            `;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body
            });

      - name: Fail if critical findings
        run: |
          CRITICAL=$(jq '.summary.by_severity.critical' results.json)
          HIGH=$(jq '.summary.by_severity.high' results.json)
          if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
            echo "::error::Found $CRITICAL critical and $HIGH high severity findings"
            exit 1
          fi

  # Job 2: Scan nocturno profundo
  nightly-scan:
    if: github.event_name == 'schedule'
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Install mcp-scan
        run: |
          curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash

      - name: Run deep scan
        run: |
          mcp-scan scan . \
            --mode deep \
            --output evidence \
            --timeout 30m \
            > evidence-$(date +%Y%m%d).json

      - name: Upload evidence
        uses: actions/upload-artifact@v4
        with:
          name: security-evidence-${{ github.run_number }}
          path: evidence-*.json
          retention-days: 90

      - name: Generate SARIF
        run: |
          mcp-scan scan . \
            --mode deep \
            --output sarif \
            > results-deep.sarif

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

GitHub Action Reutilizable

# .github/actions/mcp-scan/action.yml
name: 'MCP Security Scan'
description: 'Run mcp-scan security analysis'

inputs:
  mode:
    description: 'Scan mode (fast/deep)'
    default: 'fast'
  fail-on:
    description: 'Severity to fail on'
    default: 'high'
  baseline:
    description: 'Path to baseline file'
    default: ''
  upload-sarif:
    description: 'Upload SARIF to GitHub'
    default: 'true'

outputs:
  score:
    description: 'MSSS security score'
  findings:
    description: 'Total findings count'

runs:
  using: 'composite'
  steps:
    - name: Install mcp-scan
      shell: bash
      run: |
        curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
        echo "$HOME/.local/bin" >> $GITHUB_PATH

    - name: Run scan
      id: scan
      shell: bash
      run: |
        BASELINE_ARG=""
        if [ -n "${{ inputs.baseline }}" ]; then
          BASELINE_ARG="--baseline ${{ inputs.baseline }}"
        fi

        mcp-scan scan . \
          --mode ${{ inputs.mode }} \
          --output json \
          $BASELINE_ARG \
          > results.json || true

        echo "score=$(jq '.msss_score.score' results.json)" >> $GITHUB_OUTPUT
        echo "findings=$(jq '.summary.total' results.json)" >> $GITHUB_OUTPUT

    - name: Generate SARIF
      if: inputs.upload-sarif == 'true'
      shell: bash
      run: |
        mcp-scan scan . --mode ${{ inputs.mode }} --output sarif > results.sarif

    - name: Upload SARIF
      if: inputs.upload-sarif == 'true'
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: results.sarif

    - name: Check threshold
      shell: bash
      run: |
        mcp-scan scan . \
          --mode ${{ inputs.mode }} \
          --fail-on ${{ inputs.fail-on }} \
          $BASELINE_ARG

Uso:

- uses: ./.github/actions/mcp-scan
  with:
    mode: fast
    fail-on: high
    baseline: baseline.json


GitLab CI

Configuracion Basica

# .gitlab-ci.yml
stages:
  - security

mcp-scan:
  stage: security
  image: mcphub/mcp-scan:latest
  script:
    - mcp-scan scan . --mode fast --output json --fail-on high > results.json
  artifacts:
    reports:
      sast: results.json
    paths:
      - results.json
    expire_in: 30 days
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Configuracion Avanzada

# .gitlab-ci.yml
stages:
  - security
  - report

variables:
  MCP_SCAN_VERSION: "2.0.0"

# Template reutilizable
.mcp-scan-template:
  image: mcphub/mcp-scan:${MCP_SCAN_VERSION}
  before_script:
    - mcp-scan version

# Scan en MRs
mr-security-scan:
  extends: .mcp-scan-template
  stage: security
  script:
    - |
      mcp-scan scan . \
        --mode fast \
        --output json \
        --baseline baseline.json \
        > results.json
    - |
      # Convertir a formato GitLab SAST
      jq '{
        version: "15.0.0",
        vulnerabilities: [.findings[] | {
          id: .id,
          category: "sast",
          name: .title,
          message: .description,
          severity: (if .severity == "critical" then "Critical" elif .severity == "high" then "High" elif .severity == "medium" then "Medium" else "Low" end),
          location: {
            file: .location.file,
            start_line: .location.line,
            end_line: .location.end_line
          },
          identifiers: [{
            type: "mcp_scan_rule_id",
            name: .rule_id,
            value: .rule_id
          }]
        }]
      }' results.json > gl-sast-report.json
  artifacts:
    reports:
      sast: gl-sast-report.json
    paths:
      - results.json
      - gl-sast-report.json
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

# Scan nocturno profundo
nightly-deep-scan:
  extends: .mcp-scan-template
  stage: security
  script:
    - |
      mcp-scan scan . \
        --mode deep \
        --output evidence \
        --timeout 30m \
        > evidence-$(date +%Y%m%d).json
  artifacts:
    paths:
      - evidence-*.json
    expire_in: 90 days
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

# Reporte de seguridad
security-report:
  stage: report
  image: alpine:latest
  script:
    - apk add --no-cache jq
    - |
      SCORE=$(jq '.msss_score.score' results.json)
      TOTAL=$(jq '.summary.total' results.json)
      echo "Security Score: $SCORE/100"
      echo "Total Findings: $TOTAL"

      if [ "$SCORE" -lt 60 ]; then
        echo "SECURITY SCORE TOO LOW"
        exit 1
      fi
  needs:
    - mr-security-scan
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Jenkins

Pipeline Basico

// Jenkinsfile
pipeline {
    agent any

    environment {
        MCP_SCAN_VERSION = '2.0.0'
    }

    stages {
        stage('Install') {
            steps {
                sh '''
                    curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
                '''
            }
        }

        stage('Security Scan') {
            steps {
                sh '''
                    mcp-scan scan . \
                        --mode fast \
                        --output json \
                        --fail-on high \
                        > results.json
                '''
            }
        }

        stage('Report') {
            steps {
                script {
                    def results = readJSON file: 'results.json'
                    echo "MSSS Score: ${results.msss_score.score}"
                    echo "Total Findings: ${results.summary.total}"
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'results.json', fingerprint: true
        }
    }
}

Pipeline Avanzado

// Jenkinsfile
pipeline {
    agent any

    parameters {
        choice(name: 'SCAN_MODE', choices: ['fast', 'deep'], description: 'Scan mode')
        choice(name: 'FAIL_ON', choices: ['critical', 'high', 'medium'], description: 'Fail threshold')
    }

    environment {
        MCP_SCAN_HOME = "${WORKSPACE}/.mcp-scan"
    }

    stages {
        stage('Setup') {
            steps {
                sh '''
                    curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
                '''
            }
        }

        stage('Security Scan') {
            parallel {
                stage('MCP Scan') {
                    steps {
                        script {
                            def exitCode = sh(
                                script: """
                                    mcp-scan scan . \
                                        --mode ${params.SCAN_MODE} \
                                        --output json \
                                        --baseline baseline.json \
                                        > results.json
                                """,
                                returnStatus: true
                            )

                            env.SCAN_EXIT_CODE = exitCode
                        }
                    }
                }

                stage('SARIF Generation') {
                    steps {
                        sh '''
                            mcp-scan scan . \
                                --mode fast \
                                --output sarif \
                                > results.sarif
                        '''
                    }
                }
            }
        }

        stage('Analysis') {
            steps {
                script {
                    def results = readJSON file: 'results.json'
                    def score = results.msss_score.score
                    def total = results.summary.total
                    def critical = results.summary.by_severity.critical
                    def high = results.summary.by_severity.high

                    // Crear resumen
                    currentBuild.description = """
                        Score: ${score}/100 |
                        Findings: ${total} |
                        Critical: ${critical} |
                        High: ${high}
                    """

                    // Decidir estado del build
                    if (critical > 0 || high > 0) {
                        currentBuild.result = 'UNSTABLE'
                    }

                    // Fail si excede umbral
                    if (params.FAIL_ON == 'critical' && critical > 0) {
                        error("Critical findings detected")
                    }
                    if (params.FAIL_ON == 'high' && (critical > 0 || high > 0)) {
                        error("High or critical findings detected")
                    }
                }
            }
        }

        stage('Publish') {
            steps {
                // Publicar SARIF (si hay plugin)
                recordIssues(
                    tools: [sarif(pattern: 'results.sarif')],
                    qualityGates: [
                        [threshold: 1, type: 'TOTAL_HIGH', unstable: true],
                        [threshold: 1, type: 'TOTAL_ERROR', unstable: false]
                    ]
                )
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: '*.json,*.sarif', fingerprint: true

            // Cleanup
            cleanWs()
        }

        failure {
            emailext(
                subject: "Security Scan Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: """
                    Security scan detected issues.

                    Build: ${env.BUILD_URL}
                    Results: ${env.BUILD_URL}artifact/results.json
                """,
                to: 'security-team@example.com'
            )
        }
    }
}

Azure DevOps

Pipeline YAML

# azure-pipelines.yml
trigger:
  branches:
    include:
      - main
      - develop

pr:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-latest'

variables:
  MCP_SCAN_VERSION: '2.0.0'

stages:
  - stage: Security
    jobs:
      - job: MCPScan
        steps:
          - task: Bash@3
            displayName: 'Install mcp-scan'
            inputs:
              targetType: 'inline'
              script: |
                curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
                echo "##vso[task.prependpath]$HOME/.local/bin"

          - task: Bash@3
            displayName: 'Run security scan'
            inputs:
              targetType: 'inline'
              script: |
                mcp-scan scan . \
                  --mode fast \
                  --output sarif \
                  --fail-on high \
                  --baseline baseline.json \
                  > $(Build.ArtifactStagingDirectory)/results.sarif

          - task: PublishBuildArtifacts@1
            displayName: 'Publish SARIF'
            inputs:
              pathtoPublish: '$(Build.ArtifactStagingDirectory)/results.sarif'
              artifactName: 'SecurityScan'

          # Si tienes Advanced Security habilitado
          - task: AdvancedSecurity-Publish@1
            displayName: 'Publish to Advanced Security'
            inputs:
              sarif: '$(Build.ArtifactStagingDirectory)/results.sarif'

Pipeline con Templates

# templates/mcp-scan.yml
parameters:
  - name: mode
    type: string
    default: 'fast'
  - name: failOn
    type: string
    default: 'high'

steps:
  - task: Bash@3
    displayName: 'Install mcp-scan'
    inputs:
      targetType: 'inline'
      script: |
        curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash

  - task: Bash@3
    displayName: 'Run mcp-scan'
    inputs:
      targetType: 'inline'
      script: |
        mcp-scan scan . \
          --mode ${{ parameters.mode }} \
          --fail-on ${{ parameters.failOn }} \
          --output json \
          > results.json

  - task: Bash@3
    displayName: 'Check results'
    inputs:
      targetType: 'inline'
      script: |
        SCORE=$(jq '.msss_score.score' results.json)
        echo "##vso[task.setvariable variable=SecurityScore]$SCORE"
        echo "Security Score: $SCORE/100"

Uso:

# azure-pipelines.yml
stages:
  - stage: Security
    jobs:
      - job: Scan
        steps:
          - template: templates/mcp-scan.yml
            parameters:
              mode: 'fast'
              failOn: 'high'


CircleCI

# .circleci/config.yml
version: 2.1

executors:
  mcp-scan:
    docker:
      - image: mcphub/mcp-scan:latest

jobs:
  security-scan:
    executor: mcp-scan
    steps:
      - checkout
      - run:
          name: Run security scan
          command: |
            mcp-scan scan . \
              --mode fast \
              --output json \
              --fail-on high \
              > results.json
      - store_artifacts:
          path: results.json
          destination: security-scan

  nightly-scan:
    executor: mcp-scan
    steps:
      - checkout
      - run:
          name: Run deep scan
          command: |
            mcp-scan scan . \
              --mode deep \
              --output evidence \
              --timeout 30m \
              > evidence.json
      - store_artifacts:
          path: evidence.json
          destination: nightly-evidence

workflows:
  version: 2

  pr-checks:
    jobs:
      - security-scan:
          filters:
            branches:
              ignore: main

  nightly:
    triggers:
      - schedule:
          cron: "0 2 * * *"
          filters:
            branches:
              only: main
    jobs:
      - nightly-scan

Bitbucket Pipelines

# bitbucket-pipelines.yml
image: mcphub/mcp-scan:latest

pipelines:
  pull-requests:
    '**':
      - step:
          name: Security Scan
          script:
            - mcp-scan scan . --mode fast --output json --fail-on high > results.json
          artifacts:
            - results.json

  branches:
    main:
      - step:
          name: Security Scan
          script:
            - mcp-scan scan . --mode fast --output json > results.json
            - |
              SCORE=$(jq '.msss_score.score' results.json)
              echo "Security Score: $SCORE/100"
          artifacts:
            - results.json

  scheduled:
    - step:
        name: Nightly Deep Scan
        script:
          - mcp-scan scan . --mode deep --output evidence > evidence.json
        artifacts:
          - evidence.json

Drone CI

# .drone.yml
kind: pipeline
type: docker
name: security

steps:
  - name: mcp-scan
    image: mcphub/mcp-scan:latest
    commands:
      - mcp-scan scan . --mode fast --output json --fail-on high > results.json
    when:
      event:
        - push
        - pull_request

  - name: deep-scan
    image: mcphub/mcp-scan:latest
    commands:
      - mcp-scan scan . --mode deep --output evidence > evidence.json
    when:
      event:
        - cron
      cron:
        - nightly

trigger:
  branch:
    - main
    - develop
  event:
    - push
    - pull_request
    - cron

Estrategias de Integracion

Estrategia 1: Gate de Seguridad

# Solo bloquear en criticos
scan:
  mode: fast
  fail-on: critical
# Almacenar historico de scores
- name: Track score
  run: |
    SCORE=$(jq '.msss_score.score' results.json)
    DATE=$(date +%Y-%m-%d)
    echo "$DATE,$SCORE" >> security-history.csv

Estrategia 3: Baseline Automatico

# Generar baseline en main
- name: Update baseline
  if: github.ref == 'refs/heads/main'
  run: |
    mcp-scan scan . --output json > scan.json
    mcp-scan baseline generate --from scan.json
    git add baseline.json
    git commit -m "Update security baseline" || true

Estrategia 4: Escaneo Diferencial

# Solo escanear archivos modificados
- name: Get changed files
  id: files
  run: |
    FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -E '\.(py|ts|js)$' | tr '\n' ' ')
    echo "files=$FILES" >> $GITHUB_OUTPUT

- name: Scan changed files
  run: |
    mcp-scan scan ${{ steps.files.outputs.files }} --mode fast

Notificaciones

Slack

- name: Notify Slack
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    fields: repo,message,commit,author
    text: |
      Security scan failed!
      Score: ${{ steps.scan.outputs.score }}/100
      Findings: ${{ steps.scan.outputs.total }}
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Email

- name: Send email
  if: failure()
  uses: dawidd6/action-send-mail@v3
  with:
    server_address: smtp.example.com
    username: ${{ secrets.MAIL_USERNAME }}
    password: ${{ secrets.MAIL_PASSWORD }}
    subject: "Security Scan Failed: ${{ github.repository }}"
    to: security@example.com
    body: |
      Security scan detected issues.

      Repository: ${{ github.repository }}
      Branch: ${{ github.ref }}
      Commit: ${{ github.sha }}

      View results: ${{ github.server_url }}/${{ github.repository }}/security/code-scanning

Teams

- name: Notify Teams
  if: failure()
  uses: jdcargile/ms-teams-notification@v1.3
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    ms-teams-webhook-uri: ${{ secrets.TEAMS_WEBHOOK }}
    notification-summary: "Security scan failed"
    notification-color: "dc3545"

Anterior: Gestion de Baselines | Siguiente: Casos de Uso