← Back to PullGuard

PullGuard for coding agents

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.

How it works

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

Register it

Claude Code

claude mcp add pullguard -- pullguard mcp --report pullguard-report.json

Cursor (.cursor/mcp.json)

{
  "mcpServers": {
    "pullguard": {
      "command": "pullguard",
      "args": ["mcp", "--report", "pullguard-report.json"]
    }
  }
}

What the agent can ask

Read — always available

scan_summary

Grade, score, severity counts, scan coverage, and license tier of the loaded report.

list_findings

Findings 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_finding

Full 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_rule

Remediation guidance and taxonomy for a finding type.

Safe actions — on by default

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_scan

A 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_fix

Whether 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.

explain

The 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_report

A rendering of the report — JSON, SARIF or Markdown — written to a path inside the workspace.

Triage — off unless you turn it on

triage_finding and class_triage record a disposition in .pullguardignore. They appear only when your repository’s .driftrc.yml says so. See below.

Three tiers, and the dangerous one is off

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.

TierToolsDefault
Readscan_summary, list_findings, get_finding, explain_rulealways on
Safe actionsrun_scan, verify_fix, explain, export_reporton
Triagetriage_finding, class_triageoff

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.

Connect it to your PullGuard server

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?”

A loop that works

  1. Scan and serve (two commands above).
  2. Ask the agent: “List the taint-proven security findings and fix the criticals first.” Evidence tiers put proven flows ahead of pattern matches.
  3. Before it edits, let it ask 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.
  4. After edits, the agent closes the loop itself: 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.

Tell the agent what “fixed” means

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.


← PullGuard home Configuration reference →