CI integration
MCP configs are code: they change in PRs, and the moment to catch a typosquat, an inline secret, or an unapproved server is review time — before the config ever launches an agent session.
Why scan in CI
Section titled “Why scan in CI”- A committed
.mcp.json/.vscode/mcp.json/opencode.jsonchange is the only signal you get before every dev’s next session loads the new server. - The scan is offline and fast (no network, no account needed) — it costs seconds per build.
- Exit codes are the contract: 0 clean, 1 gating findings, 2 usage error.
GitHub Actions
Section titled “GitHub Actions”name: MCP security scan
on: pull_request: paths: - '**/.mcp.json' - '**/.vscode/mcp.json' - '**/.cursor/mcp.json' - '**/opencode.json' - '**/opencode.jsonc' - '.ringzero/mcp-policy.json'
jobs: mcp-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - name: Validate the policy file run: npx @ringzero/mcp-scan@latest validate - name: Scan MCP server configs run: npx @ringzero/mcp-scan@latest scan --fail-on high --json > mcp-scan.json - uses: actions/upload-artifact@v4 if: always() with: name: mcp-scan path: mcp-scan.jsonScanning on every push instead of config-path filters also works — it’s cheap enough.
Exit-code gating recipes
Section titled “Exit-code gating recipes”| Goal | Command |
|---|---|
| Block criticals and highs (default) | ringzero-mcp scan --fail-on high |
| Strictest — any finding fails | ringzero-mcp scan --fail-on low |
| Allowlist violations only | ringzero-mcp scan --fail-on never with an enforce-mode policy |
| Report-only (never fails) | ringzero-mcp scan --fail-on never with a warn-mode policy |
Two gates compose: --fail-on covers severities, and an enforce-mode
policy makes Unapproved server findings (and requirePinned violations)
gate regardless of --fail-on.
A third gate covers the MCP Registry:
ringzero-mcp registry sync --check exits 1 when committed harness configs
drift from the org catalog — see the
registry deployment guide
for the workflow recipe.
Enforcing the allowlist on PRs
Section titled “Enforcing the allowlist on PRs”With .ringzero/mcp-policy.json committed and "mode": "enforce", any PR
that adds a server outside the allowlist fails CI with:
[unverified] crash-reports — https://mcp.sentry-tools.io/v1 (opencode.jsonc) medium Unapproved server — Server is not on the org allowlist [gating]The dev’s path forward is the policy PR itself: add the server to allow,
get it reviewed, merge — the propose → review → allow loop with your normal
code-review tooling as the review step.
Uploading results
Section titled “Uploading results”Store an API key with the mcp:upload scope (Settings → API keys) as a
repo secret and add --upload:
- name: Scan and upload env: RINGZERO_API_KEY: ${{ secrets.RINGZERO_API_KEY }} run: npx @ringzero/mcp-scan@latest scan --fail-on high --uploadWithout the secret, --upload logs the skip reason and the scan still gates
normally — forks and key-less environments never break.