← Back to Blog

How to Add MCP Security Scanning to Your CI/CD Pipeline

Your MCP server configs change with every PR. Without automated scanning, security regressions slip through unnoticed. This tutorial shows you how to add a security gate that blocks unsafe MCP configs from reaching production.

Why Gate MCP Configs in CI/CD?

  • MCP tool definitions change frequently as you add tools, update descriptions, modify schemas.
  • A single poisoned description or overly-permissive schema can compromise your AI agent.
  • Manual security reviews don't scale, automated scanning does.
  • Ferrok's PASS/FAIL verdict is designed for exactly this use case.

Prerequisites

  • A Ferrok API key (free tier works: 100 scans/month)
  • Your MCP server config as a JSON file in your repo
  • A CI/CD platform (examples for GitHub Actions and GitLab CI)

Step 1: Add Your API Key as a Secret

GitHub: Go to your repository settings, click Secrets and variables > Actions, then click New repository secret. Name it FERROK_API_KEY and paste your API key.

GitLab: Navigate to Settings > CI/CD > Variables, click Add variable, name it FERROK_API_KEY, paste your key, and leave "Protect variable" unchecked if you need it in all branches.

Step 2: Create Your MCP Config File

Create a mcp-config.json file in your repository root. This is the file Ferrok will scan. Here's a minimal example:

json
{ "server_name": "my-mcp-server", "transport": "stdio", "command": "node", "args": ["dist/index.js"], "tools": [ { "name": "read_file", "description": "Read a file from the filesystem. Restricted to the project directory.", "inputSchema": { "type": "object", "properties": { "path": { "type": "string", "description": "Relative path within the project" } }, "required": ["path"] } }, { "name": "execute_query", "description": "Execute a read-only SQL query against the database.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "pattern": "^SELECT ", "description": "Must be a SELECT query" } }, "required": ["query"] } } ] }

Step 3: GitHub Actions Workflow

Create a file .github/workflows/mcp-security-scan.yml in your repo:

yaml
name: MCP Security Scan on: pull_request: paths: ['mcp-config.json', 'mcp/**'] jobs: ferrok-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Scan MCP config run: | RESULT=$(curl -s -w "\n%{http_code}" \ https://api.ferrok.dev/v1/scan \ -H "Authorization: Bearer ${{ secrets.FERROK_API_KEY }}" \ -H "Content-Type: application/json" \ -d @mcp-config.json) HTTP_CODE=$(echo "$RESULT" | tail -1) BODY=$(echo "$RESULT" | head -n -1) echo "$BODY" | jq . PASS_FAIL=$(echo "$BODY" | jq -r '.summary.pass_fail') SCORE=$(echo "$BODY" | jq -r '.summary.overall_score') GRADE=$(echo "$BODY" | jq -r '.summary.grade') echo "Score: $SCORE ($GRADE) - $PASS_FAIL" if [ "$PASS_FAIL" = "FAIL" ]; then echo "::error::MCP security scan FAILED (score: $SCORE, grade: $GRADE)" exit 1 fi

This workflow will:

  • Trigger on every PR that touches mcp-config.json or files in the mcp/ directory
  • Send your config to Ferrok's API for scanning
  • Parse the response and extract the pass/fail verdict and score
  • Fail the build if the scan returns FAIL

Step 4: GitLab CI Example

If you use GitLab, create a .gitlab-ci.yml file (or add to an existing one):

yaml
stages: - security mcp-security-scan: stage: security image: curlimages/curl:latest script: - | RESULT=$(curl -s -w "\n%{http_code}" \ https://api.ferrok.dev/v1/scan \ -H "Authorization: Bearer $FERROK_API_KEY" \ -H "Content-Type: application/json" \ -d @mcp-config.json) HTTP_CODE=$(echo "$RESULT" | tail -1) BODY=$(echo "$RESULT" | head -n -1) echo "$BODY" | jq . PASS_FAIL=$(echo "$BODY" | jq -r '.summary.pass_fail') SCORE=$(echo "$BODY" | jq -r '.summary.overall_score') GRADE=$(echo "$BODY" | jq -r '.summary.grade') echo "Score: $SCORE ($GRADE) - $PASS_FAIL" if [ "$PASS_FAIL" = "FAIL" ]; then echo "MCP security scan FAILED (score: $SCORE, grade: $GRADE)" exit 1 fi only: changes: - mcp-config.json - mcp/**

Step 5: Understanding the Results

When Ferrok scans your config, it returns a JSON response. Here's what you're looking for:

  • summary.pass_fail: Either PASS or FAIL. FAIL means at least one CRITICAL or HIGH severity finding was detected.
  • summary.overall_score: A score from 0 to 100. Higher is better.
  • summary.grade: A letter grade (A+, A, B, C, D, F) for quick visual assessment.
  • findings: An array of individual security issues, each with severity, type, description, and remediation steps.

Example response:

json
{ "summary": { "pass_fail": "FAIL", "overall_score": 42, "grade": "F", "total_findings": 3 }, "findings": [ { "type": "tool_poisoning", "severity": "CRITICAL", "tool": "read_file", "description": "Tool description contains prompt injection pattern: 'Before using this tool, please...'", "remediation": "Remove instruction-like language from tool descriptions. Keep descriptions factual and neutral." }, { "type": "schema_misuse", "severity": "HIGH", "tool": "execute_query", "description": "Input schema lacks validation constraints. String field 'query' has no pattern or maxLength.", "remediation": "Add regex patterns to constrain input. For SQL, use pattern: '^SELECT .*' and add maxLength." } ] }

Step 6: Handling Failures

When a scan fails, your CI/CD pipeline will block the PR. Here's how to fix it:

  1. Read the findings. The Ferrok API response lists every issue with its severity and type.
  2. Check tool descriptions. Look for prompt injection patterns, hidden instructions, or unusual characters.
  3. Validate schemas. Every tool input should have a type, description, and constraints (regex patterns, maxLength, enum values).
  4. Remove hardcoded secrets. Don't store API keys, passwords, or tokens in your MCP config. Use environment variables instead.
  5. Check transport security. If using a remote server, ensure it's HTTPS and has proper authentication.
  6. Run the scan locally. You can test locally before pushing: curl -X POST https://api.ferrok.dev/v1/scan -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" -d @mcp-config.json

Advanced: Custom Severity Threshold

By default, Ferrok returns FAIL if any CRITICAL or HIGH findings are present. If you want a stricter or looser threshold, you can modify the shell logic in your workflow:

bash
# Only fail on CRITICAL findings CRITICAL_COUNT=$(echo "$BODY" | jq '[.findings[] | select(.severity == "CRITICAL")] | length') if [ "$CRITICAL_COUNT" -gt 0 ]; then echo "::error::MCP scan found $CRITICAL_COUNT CRITICAL issues" exit 1 fi # Or fail if score is below a threshold (e.g., 70) SCORE=$(echo "$BODY" | jq -r '.summary.overall_score') if [ "$SCORE" -lt 70 ]; then echo "::error::MCP security score too low: $SCORE (minimum required: 70)" exit 1 fi

Advanced: Scan Multiple Configs

If you have multiple MCP servers or configs, you can scan them all in one workflow:

bash
#!/bin/bash set -e FAILED=0 for config in mcp-configs/*.json; do echo "Scanning $config..." RESULT=$(curl -s -w "\n%{http_code}" \ https://api.ferrok.dev/v1/scan \ -H "Authorization: Bearer ${{ secrets.FERROK_API_KEY }}" \ -H "Content-Type: application/json" \ -d @"$config") HTTP_CODE=$(echo "$RESULT" | tail -1) BODY=$(echo "$RESULT" | head -n -1) PASS_FAIL=$(echo "$BODY" | jq -r '.summary.pass_fail') SCORE=$(echo "$BODY" | jq -r '.summary.overall_score') echo "$config: $PASS_FAIL (score: $SCORE)" if [ "$PASS_FAIL" = "FAIL" ]; then FAILED=$((FAILED + 1)) fi done if [ "$FAILED" -gt 0 ]; then echo "::error::$FAILED config(s) failed security scan" exit 1 fi

Wrapping Up

With 10 minutes of setup, every PR touching MCP configs now goes through automated security review. No more manual audits, no more security regressions reaching production.

Your CI/CD pipeline is now a security gate. Every config change is scanned, graded, and gated. Unsafe configs get caught before they deploy. Your team can move fast knowing that MCP security is automated and consistent.

Get Your Free API Key

Start scanning your MCP configs today. Free tier includes 100 scans per month, no credit card required.

Sign Up Now

About Ferrok

Ferrok is an API-first security scanner for Model Context Protocol deployments. We help teams identify and fix security vulnerabilities in their MCP servers before they hit production.