Skip to main content
mcpscore is a command-line tool with documented exit codes, so any CI system can run it as a quality gate. On GitHub, use the GitHub Action. Everywhere else, the job is two lines: install mcpscore, then run it with --fail-under. This is the whole GitLab job:
.gitlab-ci.yml
When the score is below 90%, the job log ends like this and the job fails:
The job fails on the merge request that lowered the score, and the full report stays attached to it.

Step 1: Install and gate

.gitlab-ci.yml
--fail-under 90 makes mcpscore exit with code 3 when the score, as a rounded percentage, is below 90. GitLab fails a job whose script exits with anything but 0, so the threshold is the gate. The other codes a job can see are 2 when mcpscore cannot connect and 1 for a usage error; the troubleshooting page lists them all. Pin the version you tested. A new mcpscore release can add rules, and an unpinned install would move your score on a day your server did not change. Bump the pin in its own merge request, where a score change has one cause. Check it: set the threshold above your current score, for example --fail-under 100, and push. The job fails with Gate failed — --fail-under 100. Put the real threshold back once you have seen it fail.

Step 2: Keep the report

.gitlab-ci.yml
--json writes the full report to stdout; the log lines go to stderr, so redirecting stdout captures clean JSON. --sarif writes the failed rules as SARIF 2.1.0. Both files are written before the gate runs, so a failed job still has them. when: always tells GitLab to keep the artifacts when the job fails, which is when you need them. The JSON carries the score, the maximum and one entry per rule:
The report format is versioned by schema_version and described in the stability contract.

Step 3: Audit a server behind auth

Add a masked CI/CD variable named MCPSCORE_TOKEN in the project settings. mcpscore reads it without a flag and sends it as Authorization: Bearer, so the job file does not change and the token never appears in it.
That line in the job log confirms the token was picked up. Without a working token, the audit is partial, and a partial audit always fails --fail-under, because a score from a handful of checks cannot show the threshold was met. See Audit a server behind auth.

Step 4: Audit the server in your repository

A server in the repository is audited the same way as a URL: put --stdio and the command that starts it where the URL was. --stdio takes the rest of the line, so every mcpscore option goes before it.
.gitlab-ci.yml
This audits every merge request’s code before it is deployed. A server in another language works the same way with its own image and start command, such as --stdio node build/index.js. Smoke mode adds --smoke to also call the server’s read-only tools; a failed smoke check exits 4.

Step 5: Report without blocking

To see the score on every pipeline before you enforce it, let the job fail on the gate without failing the pipeline:
.gitlab-ci.yml
Exit code 3 now shows as a warning. Exit code 2, a server mcpscore could not reach, still fails the pipeline, because that is a broken deployment and not a lower score.

Any other CI

Jenkins, CircleCI, Buildkite, Azure Pipelines and the rest run shell commands, and a shell command’s exit code decides the step. This script works in all of them. It needs Python for mcpscore and jq to print the summary.
mcpscore-gate.sh
The script keeps mcpscore’s exit code and passes it on after printing the summary, so the step fails exactly when the gate does. When mcpscore cannot connect it writes no JSON, and the -s test skips the summary instead of failing on an empty file. Archive mcpscore.json and mcpscore.sarif with your CI’s artifact step. SARIF is the format GitHub code scanning reads; the GitHub Action uploads it for you.

Recap

  • Install a pinned mcpscore and gate with --fail-under.
  • Keep the report with --json and --sarif, as artifacts saved even when the job fails.
  • Pass a token through the masked MCPSCORE_TOKEN variable.
  • Audit the server in your repository with --stdio and its start command.
  • Report without blocking with allow_failure: exit_codes: [3].

What’s next