Agents in CI

The same URL and header work unattended. What changes is where the key comes from, and that a pipeline needs a decision it can act on rather than prose.

GitHub Actions

name: Security review

on: pull_request

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Configure the Vulnetix MCP server
        run: |
          mkdir -p .mcp
          cat > .mcp/config.json <<'JSON'
          {
            "mcpServers": {
              "vulnetix": {
                "type": "http",
                "url": "https://mcp.vulnetix.com/mcp",
                "headers": { "Authorization": "ApiKey ${VULNETIX_AUTH}" }
              }
            }
          }
          JSON
        env:
          VULNETIX_AUTH: ${{ secrets.VULNETIX_AUTH }}

      - name: Review changed dependencies
        run: your-agent-cli --mcp-config .mcp/config.json --prompt "$(cat .github/review-prompt.md)"
        env:
          VULNETIX_AUTH: ${{ secrets.VULNETIX_AUTH }}

Store VULNETIX_AUTH as <orgId>:<hex> in Settings → Secrets and variables → Actions.

Important Never inline the key in a workflow file. Workflow files are committed, visible to anyone with read access, and appear in fork PRs. GitHub masks values that came from secrets.* in logs; it cannot mask one you typed in yourself.

GitLab CI

security-review:
  image: node:22
  variables:
    MCP_URL: "https://mcp.vulnetix.com/mcp"
  script:
    - |
      cat > mcp.json <<JSON
      {"mcpServers":{"vulnetix":{"type":"http","url":"$MCP_URL",
       "headers":{"Authorization":"ApiKey $VULNETIX_AUTH"}}}}
      JSON
    - your-agent-cli --mcp-config mcp.json --prompt-file .gitlab/review.md

Set VULNETIX_AUTH as a masked, protected CI/CD variable.

Making it a gate

An agent writes prose. A pipeline needs an exit code. Ask for a machine-readable verdict and act on it:

For every dependency added or upgraded in this diff, call vulnetix_package_search and vulnetix_kev_status. Output ONLY a JSON object: {"verdict":"pass"|"fail","reasons":[...]}. Fail if any added package has a KEV-listed vulnerability with no fixed version available.

VERDICT=$(your-agent-cli --mcp-config mcp.json --prompt-file gate.md | tail -1)
echo "$VERDICT" | jq -e '.verdict == "pass"' || {
  echo "$VERDICT" | jq -r '.reasons[]'
  exit 1
}
Warning A model deciding your merge gate is a judgement call. It can be wrong in both directions. For a hard, deterministic gate, use the Vulnetix CLI. vulnetix scan --evaluate-sca --severity high --exploits weaponized exits non-zero on its own, with no model in the path. The MCP server is better at explaining and prioritising than at being the last word.

Quota in CI

Pipelines are the easiest way to burn a daily quota, because they run on every push.

  • Scope to the diff. Look up what changed, not the whole lockfile.
  • Cap list tools. limit exists on every one.
  • Skip drafts. if: github.event.pull_request.draft == false.
  • Watch the headroom. Every result carries _meta["vulnetix/rateLimit"].remaining.
  • Use a service organisation. A key shared with developers means CI exhausting the pool blocks people mid-task.

What CI cannot do

Sixteen of the 33 prompts drive a local repository and expect the CLI. In CI that is not a limitation. Install the CLI in the same job and let the agent call it:

- run: curl -fsSL https://cli.vulnetix.com/install.sh | sh -s -- --install-dir "$HOME/.local/bin"

Then a prompt like vulnetix_sast_scan or vulnetix_verify_fix has something real to run against.