Serve your scan results to Claude Code, Cursor, or any MCP-capable agent — so the agent that writes the code can also read, prioritise, and fix the findings. Deterministic results only: no LLM ever enters the scan path.
pullguard mcp is a local
MCP server over a report
you already generated. It reads the report you name, and — when you ask it to — runs the
same deterministic scan you would run yourself. No model ever enters the scan path, no
socket is opened, and nothing leaves your machine. Recording a triage decision is a
separate, opt-in tier (below); with it off, those tools are not offered at all.
# 1. Generate a report (any tier; your paid tier sees the full analyzer set)
pullguard scan . --format json -o pullguard-report.json
# 2. Serve it to agents (stdio)
pullguard mcp --report pullguard-report.json
claude mcp add pullguard -- pullguard mcp --report pullguard-report.json
.cursor/mcp.json){
"mcpServers": {
"pullguard": {
"command": "pullguard",
"args": ["mcp", "--report", "pullguard-report.json"]
}
}
}
scan_summaryGrade, score, severity counts, scan coverage, and license tier of the loaded report.
list_findingsFindings filtered by severity, category, type, or file substring — paged, each with its fingerprint and evidence tier (taint-proven flows first is the natural triage order).
get_findingFull detail for one finding by fingerprint: description, recommendation, remediation steps with code examples, the structured source→sink taint path where one was proven, and CWE / OWASP Agentic Top-10 mappings with a link to the rule reference.
explain_ruleRemediation guidance and taxonomy for a finding type.
None of these can change what a scan reports or whether a pull request merges. Run them a thousand times and the set of findings that block a merge is identical — which is exactly why they need no switch.
run_scanA full scan of the workspace with its current configuration, refreshing what the read tools serve. The same deterministic analysis you would run yourself — no model in the path, no network, nothing leaves the machine. The licence tier is reported with every result, so a scan that quietly ran at a lower tier cannot be mistaken for a clean one.
verify_fixWhether one finding still fires after a full-project re-scan: fixed, still-firing, or unavailable with the reason. It re-scans the whole project rather than one file, because a cross-file flow is invisible to a single-file scan — and it refuses a verdict it cannot stand over rather than reporting a clean result it did not earn.
explainThe fix contract for one finding: rule intent, source and sink coordinates, the invariant a fix must satisfy, and the reference fixtures the rule is held to. This is what stops an agent editing until the message disappears instead of fixing the property.
export_reportA rendering of the report — JSON, SARIF or Markdown — written to a path inside the workspace.
triage_finding and class_triage record a disposition in
.pullguardignore. They appear only when your repository’s
.driftrc.yml says so. See below.
The line between the tiers is one question: does it move the merge gate? Reading findings does not. Running a scan, verifying a fix, printing a fix contract and exporting a report do not — they are the tools you would run yourself, and they cannot change which findings block a merge. Recording a disposition does, so it is the one thing that needs your permission.
| Tier | Tools | Default |
|---|---|---|
| Read | scan_summary, list_findings, get_finding, explain_rule | always on |
| Safe actions | run_scan, verify_fix, explain, export_report | on |
| Triage | triage_finding, class_triage | off |
To turn the triage tools on, commit one line — a reviewable, diffable decision your code owners see, not a flag somebody sets on a laptop:
# .driftrc.yml
mcp:
dangerousTools: true
While it is off, those tools are not offered at all. They are absent from the agent’s tool list rather than present-and-refusing. That matters more than it sounds: an agent that can see a refusal knows the capability exists, and the next thing it does is ask you to enable it. A tool that is not listed is not part of the agent’s world — nothing to discover, nothing to be talked into.
When it is on, every triage call must carry an explicit confirmation and a written reason,
and the entry it writes names the agent that made it
(agent:<client>) — so a decision an agent recorded never reads as
one a person made.
Security findings are unaffected, in every tier. They can never be blind-suppressed. A genuine false positive still needs a written reason, still stays visible on every surface and in every machine-readable output, and only its merge block is lifted. A blanket rule over a directory can never name a security finding type at all — that would reach findings nobody has seen yet. The worst a thoroughly-misled agent can do is exactly what a maintainer could do, with its name on it. See deployment & data sovereignty for the full no-egress posture.
If you run the customer-hosted server, the agent can read your team’s actual state rather than one report on one laptop: which findings are resolved, which are past their remediation budget, what the gate says, and what has already been triaged — so it stops re-litigating decisions your team already made.
claude mcp add pullguard-server -- \
pullguard mcp --server https://pullguard.internal --token "$PULLGUARD_SERVER_TOKEN"
The token is a scoped, expiring credential you issue in Settings → Tokens. It is never logged or echoed, the URL must be HTTPS, and the agent sees only the repositories the token is scoped for. Reads are filtered by the server, not by the client.
The write tools work the same way as locally, gated on the credential instead of a config file. An administrator ticks agent write on that token to let a connected agent record triage decisions, and admin on top of it to also allow hiding a repository, setting gate and SLA policy, and issuing or revoking tokens. Both are off for every token that already exists. Withdrawing either is one click and takes effect on the next request — no secret rotation, no deploy. Granting agent access is itself the one thing an agent credential can never do.
verify_fix stays local even in this mode, and says so in its own description:
the server holds results and has never seen your working tree, so only a local re-scan can
honestly answer “is it fixed in the code I just edited?”
explain for the finding — the fix contract states the invariant a fix has to satisfy, which is what stops an agent editing until the message disappears.verify_fix re-scans the whole project and answers fixed or still-firing — and refuses a verdict rather than reporting a clean result it did not earn. run_scan refreshes the whole view the same way. No restart, and no stale report.An agent handed a finding gets a description written for a human, so it infers the goal and then edits until the message stops appearing — which optimises for silencing the detector, not for closing the vulnerability. Two commands close that gap.
pullguard explain <fingerprint>Prints a machine-readable fix contract: what the rule is looking for, how strong the evidence is, where the flow starts and ends, the invariant a fix must satisfy, and the names of the reference fixtures the rule is held to — a planted true positive that must keep firing, and a safe sibling that must stay silent. Feed it straight to the agent.
pullguard explain 1w8yn7g_138 --report pullguard-report.json
The contract states the property to establish, never the pattern that detected it — so an agent cannot “fix” a finding by editing around the matcher.
pullguard verify-fix <fingerprint>
Re-scans and answers one question: is that finding fixed
or still firing? Exit code 0 fixed,
1 still firing, 2 no verdict — so an agent
loop can branch on it.
pullguard verify-fix 1w8yn7g_138 . --report pullguard-report.json --json
It always re-scans the whole project, and it says so in the verdict. A finding whose data flow crosses files cannot be checked by re-scanning one file, and an answer that came from looking at less than the flow is not an answer. It also refuses to return a verdict — rather than reporting “fixed” — when the re-scan ran at a lower licence tier than the report it is checking against, or when it produced no findings at all on a project that had them.