Skip to content

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.

  • A committed .mcp.json / .vscode/mcp.json / opencode.json change 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/workflows/mcp-scan.yml
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.json

Scanning on every push instead of config-path filters also works — it’s cheap enough.

GoalCommand
Block criticals and highs (default)ringzero-mcp scan --fail-on high
Strictest — any finding failsringzero-mcp scan --fail-on low
Allowlist violations onlyringzero-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.

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.

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 --upload

Without the secret, --upload logs the skip reason and the scan still gates normally — forks and key-less environments never break.