Skip to main content
Glama

Hallucination Firewall

An MCP server that checks an AI agent's final answer against the tool outputs it actually received, and blocks or flags anything unsupported.

Agents call tools, get correct data back, and still misstate it in the answer: a wrong figure, an invented detail, a claim no source supports. The Hallucination Firewall records everything the tools returned, splits the draft answer into single-fact claims, checks each one against that evidence with a tiered verifier, and returns a verdict per claim with the tool output that proves or disproves it.

https://github.com/Parvathy-Raj/hallucination-firewall/raw/main/docs/demo.mp4

Demo: Claude Code answers a UAE VAT question using a document-search tool. The draft gets the registration threshold wrong; the firewall flags it ❌, cites the FTA guide passage with the correct figure, and the corrected answer goes out with a citation.

┌──────────┐     ┌────────────────────────────┐     ┌──────────────────┐
│  Agent   │────▶│   Hallucination Firewall   │────▶│ Downstream MCP   │
│ (Claude) │◀────│   (MCP server + client)    │◀────│ servers (DB, RAG,│
└──────────┘     │                            │     │ web, files...)   │
                 │  1. Evidence ledger        │     └──────────────────┘
                 │  2. Claim extractor        │
                 │  3. Evidence retriever     │
                 │  4. Verifier (tiered)      │
                 │  5. Verdict + rewrite      │
                 └────────────────────────────┘

How it works

  1. Evidence ledger. Every tool output is recorded, chunked and indexed with hybrid retrieval (BM25 + bge-small embeddings, merged with reciprocal rank fusion). Outputs get there three ways:

    • Proxy mode: the firewall wraps your other MCP servers and re-exposes their tools (uae_docs__search_guides), recording every result.

    • Claude Code hook: a PostToolUse hook records the output of any tool (Read, WebFetch, Bash, other MCP servers).

    • By hand / SDK: record_evidence(source, content), or fw.record() / @fw.watch in Python.

  2. Claim extraction. An LLM splits the draft into atomic claims and labels each one factual, opinion or hedge. Only factual claims are checked. (A rule-based extractor is used when no LLM is configured.)

  3. Tiered verification, cheapest first. Each tier either decides or passes the claim on:

    Tier

    Method

    Cost

    Catches

    0

    Exact checks: numbers, currency, percentages, dates, names, with rounding-aware matching

    ~0

    $4.2M vs $4.7M, AED 500,000 vs AED 375,000

    1

    NLI fact-checking model on CPU (DeBERTa-v3-base-mnli-fever-anli)

    ~0.15 s per evidence passage

    Most clearly supported or contradicted claims

    2

    LLM judge, only when tier 1 is below the confidence threshold

    $

    Paraphrases, multi-hop, "the evidence doesn't say that"

  4. Verdict + policy. Each claim is ✅ supported, ❌ contradicted, ⚠️ unsupported or ➖ not_checkable. The policy decides whether to block, annotate or rewrite. The rewrite is deterministic on purpose (an LLM rewriter could add hallucinations of its own): wrong figures are replaced with the evidence value, unsupported claims are hedged or removed, and citations to the tool outputs are added.

Related MCP server: footnote

The trust model (read this first)

An MCP server cannot force an agent to call verify_answer. This project closes that gap in three layers, from weakest to strongest:

Layer

How

Guarantee

Server instructions

The server tells the agent to verify every answer built on tool results

Best effort: the model usually complies

Claude Code Stop hook

hfw-hook stop blocks the reply if the agent used tools this turn but did not call verify_answer afterwards

Enforced by the harness, inside Claude Code

Python SDK

fw.guard(draft) raises HallucinationBlocked; your code decides what ships

Enforced in your own code

What it does not do: check claims against world knowledge. A claim that no tool output supports is unsupported, even if it happens to be true. That is the point: in an agent, an answer should be traceable to what the tools returned.

MCP interface

Tools

Tool

What it does

verify_answer(draft, strictness?, action?)

Verdict, confidence, deciding tier, evidence and correction for every claim, plus an overall grounding score and a block decision

verify_claim(claim)

Checks one claim

explain_verdict(claim_id)

Full evidence passages and what each tier said

rewrite_grounded(draft, unsupported="hedge")

Corrected answer with citations

set_policy(strictness, tiers, block_on, action, threshold)

lenient / balanced / strict, which tiers run, what blocks, the tier-1 confidence threshold

record_evidence(source, content)

Add evidence that was not proxied or hooked

reset_session()

Clear the ledger for a new task

Resources: ledger://session/current (everything the tools returned) and report://session/current (hallucination report for the session).

Quickstart

git clone https://github.com/Parvathy-Raj/hallucination-firewall
cd hallucination-firewall
uv sync --extra ml          # tiers 1 and dense retrieval (CPU PyTorch); plain `uv sync` = tier 0 + BM25 only
uv run pytest -q

Check a draft from the command line:

uv run hfw verify --evidence report.txt --draft draft.txt --rewrite

Use it in Claude Code

  1. Copy examples/claude_code/.mcp.json into your project and fix the paths. It runs the firewall in proxy mode in front of the example UAE tax-docs server (downstream.json); put your own MCP servers there.

  2. Copy the hooks from examples/claude_code/settings.json into .claude/settings.json. capture records every tool output; stop makes Claude verify before it answers.

  3. Set an LLM for claim extraction and the tier-2 judge (below). Without one, tiers 0 and 1 still run.

Claude Desktop: see examples/claude_desktop/claude_desktop_config.json.

Use it from Python (enforced in code)

from hfw import Firewall, HallucinationBlocked

fw = Firewall()

@fw.watch                      # every return value becomes evidence
def get_invoice(invoice_id: str) -> dict: ...

get_invoice(invoice_id="INV-1042")
try:
    answer = fw.guard(draft)   # raises if the policy blocks
except HallucinationBlocked as e:
    answer, _ = fw.rewrite_grounded(draft)

Full example: examples/sdk_example.py.

Configuration

Variable

Default

Meaning

HFW_LLM_PROVIDER

auto

anthropic, groq, openai (any OpenAI-compatible endpoint), or none. auto picks Anthropic if ANTHROPIC_API_KEY is set, then Groq

HFW_LLM_MODEL

claude-haiku-4-5 / openai/gpt-oss-20b

Model for claim extraction and the judge

HFW_LLM_BASE_URL

provider default

e.g. http://localhost:11434/v1 for Ollama

HFW_NLI_MODEL

MoritzLaurer/DeBERTa-v3-base-mnli-fever-anli

Any HF NLI classifier; cross-encoder/nli-deberta-v3-xsmall is ~3x faster

HFW_EMBED_MODEL

BAAI/bge-small-en-v1.5

Empty string = BM25 only

HFW_LEDGER

.hfw/ledger.jsonl

Shared by the server and the Claude Code hook

HFW_PROXY_CONFIG

Downstream servers JSON (same as --proxy)

HFW_DOTENV

Extra .env file to load

Evaluation

Three benchmarks, all scored at the claim level (one claim per answer sentence, so human span labels map onto claims directly and every configuration sees identical claims):

  • RAGTruth (main benchmark): RAG answers (QA, summarization, data-to-text) with human-labelled hallucinated spans. Test split, sampled.

  • HaluEval QA: each question once with the right answer and once with a hallucinated one.

  • UAE agent traces (custom): 20 simulated agent turns built from the FTA guide chunks of my UAE regulatory RAG. The tool returns the gold passage plus two distractors; the draft has one correct sentence and three planted errors (changed number, negated obligation, claim from a guide that was never retrieved). Rebuild with make traces UAE_CHUNKS=...; the corpus itself is not redistributed.

Metrics: precision / recall / F1 for catching hallucinated claims; false-flag rate (correct claims wrongly flagged); false-block rate (fully correct answers that the balanced policy would block, which is what users notice); latency p50/p95 per answer; LLM cost per answer.

make eval regenerates everything below. Every NLI score and LLM call is cached, so the configurations share identical judge calls and re-runs are free; cost and latency still charge each configuration for every call it makes.

Run make eval to fill this in.

Reading the results

The numbers here come from a small sample (rate limits on the free Groq tier I used for the judge), so treat differences of a few points as noise. The judge in these runs is gpt-oss-20b via Groq, not Claude; set HFW_EVAL_MODEL / HFW_EVAL_BASE_URL to compare others.

Repo layout

src/hfw/
├── server.py        MCP server + tools + resources
├── proxy.py         downstream MCP clients, ledger capture
├── ledger.py        evidence store, chunking, hybrid index
├── claims.py        claim extraction (LLM or rules)
├── verify/
│   ├── exact.py     tier 0
│   ├── nli.py       tier 1
│   ├── judge.py     tier 2
│   └── pipeline.py  confidence routing
├── policy.py        strictness presets, block / annotate / rewrite
├── rewrite.py       deterministic grounded rewrite with citations
├── firewall.py      facade used by the server, CLI and SDK
├── hooks.py         Claude Code PostToolUse + Stop hooks
└── cli.py           `hfw serve`, `hfw verify`
evals/               datasets, runner, custom traces, results/
examples/            Claude Code / Desktop configs, demo server, SDK example
tests/               pytest (offline, no models needed)

Likely questions

Objection

Answer

"The agent can just skip verification."

The Stop hook or the SDK enforces it; the trust model above is explicit about what each layer guarantees.

"The checker makes mistakes too."

The false-flag and false-block rates are measured, and the tier-1 threshold and block_on let you trade recall for fewer false blocks. Unsupported claims are annotated, not blocked, in the default policy.

"Too slow."

Tier 0 is microseconds and tier 1 runs on CPU; the LLM judge only sees the claims the cheap tiers could not settle.

"What about facts not from tools?"

Out of scope by design: it checks claims against tool evidence, not world knowledge.

Roadmap

  • MiniCheck / Vectara HHEM as alternative tier-1 models, and a FEVER run

  • Claim-to-span alignment so rewrites can edit inside long sentences

  • OpenTelemetry spans per tier

  • PyPI release

License

MIT

Available Tools

7 tools
explain_verdictA

Show the full evidence passages and what each tier decided for a claim id (e.g. "r1.c2").

Inputs: claim_id (required). Returns: object

record_evidenceA

Add evidence by hand (e.g. text from a tool that is not proxied or hooked). Use the exact text the source returned, never your own summary.

Inputs: source (required), content (required). Returns: object

reset_sessionA

Clear the evidence ledger and verdict history (start of a new task).

Inputs: no named parameters. Returns: object

rewrite_groundedA

Return a corrected version of the draft: wrong figures replaced with the evidence values, unsupported claims hedged (or removed with unsupported="remove"), and citations to the tool outputs added.

Inputs: draft (required), unsupported. Returns: object

set_policyB

Configure the firewall.

Args: strictness: lenient | balanced | strict (presets for block_on and thresholds). tiers: which verifier tiers may run: 0 exact, 1 NLI model, 2 LLM judge. block_on: verdicts that block the answer, e.g. ["contradicted", "unsupported"]. action: what to do with a flagged answer: annotate | block | rewrite. threshold: tier-1 confidence (0.5-1.0) needed to decide without the LLM judge.

Inputs: tiers, action, block_on, threshold, +1 more. Returns: object

verify_answerA

Check every factual claim in a draft answer against recorded tool outputs.

Call this before giving the user any answer based on tool results. Returns a verdict per claim (supported / contradicted / unsupported / not_checkable), the evidence behind it, a correction for wrong figures, and whether the draft is blocked.

Args: draft: the full answer you intend to send. strictness: optional one-off override: lenient | balanced | strict. action: optional one-off override: annotate | block | rewrite.

Inputs: draft (required), action, strictness. Returns: object

verify_claimB

Check one standalone factual claim against recorded tool outputs.

Inputs: claim (required). Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A3.8/5.0

    Scored across 7 tools

    Disambiguation4/5

    Each tool targets a distinct step in the verify workflow: policy setup, evidence capture, single-claim check, whole-answer check, verdict inspection, and rewriting. The only mild overlap is verify_claim vs verify_answer, though the descriptions clearly distinguish one claim from every claim in a draft.

    Naming Consistency5/5

    All seven tools follow a clean snake_case verb_noun pattern (verify_claim, set_policy, record_evidence, reset_session, verify_answer, explain_verdict, rewrite_grounded). No mixing of conventions.

    Tool Count5/5

    Seven tools is well-scoped for a claim-verification firewall, with each tool covering a necessary stage of the workflow. Nothing feels padded or redundant.

    Completeness4/5

    The surface covers the full lifecycle: configure policy, record evidence, verify (single and bulk), explain verdicts, rewrite, and reset. Minor gaps exist in that there is no read-back of the current policy or a way to list/inspect recorded evidence, but core workflows are fully served.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables agents to verify their own output mid-task by checking every claim against provided sources, returning supported, partial, unsupported, or contradicted verdicts with exact citations.
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Enables production-grade answer verification for LLM agents by independently re-checking answers with a configurable verifier model, enforcing confidence policies, and producing Ed25519-signed, auditable verification results with optional web search and knowledge-base evidence.
      3
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables an agent to verify every number in its answer against the tool outputs of the same session, storing recorded outputs and flagging fabricated values that no tool produced. It exposes check, record, and reset tools so agents can recompute, correct, or drop unvouched numbers before finalizing.
      MIT