footnote
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@footnoteVerify this summary against the sources and flag any contradictions"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Footnote
Catches AI hallucinations by checking every claim against your sources, with character-exact citations. 0.93 F1 detection. Zero API keys.
You gave a model some documents and it gave you a paragraph back. Some of that paragraph is in the documents. Some of it is not. Footnote tells you which is which, and points at the exact characters.
$ footnote check answer.md --sources ./reports
claims needing attention
CONTRADICTED 0.45 Free cash flow was 510 million dollars for the quarter.
q3-earnings.txt chars 728-772
claim states 510 million dollars; the cited passage states 410 million dollars
CONTRADICTED 0.28 Headcount at the end of the quarter was 12,400 employees.
q3-earnings.txt chars 903-948
claim states 12,400; the cited passage states 11,400, 10,900
UNSUPPORTED 0.14 The company announced a quarterly dividend of 12 cents per share.
no supporting span found
9 claims 6 supported 0 partial 1 unsupported 2 contradicted
faithfulness 0.73 (lexical judge, 1 source)Add --report out.html and you get a page where clicking any claim highlights the exact sentence it rests on, inside the rendered source.
Open the live example report
Click any claim on the left. The exact characters it rests on light up in the source on the right. Nothing to install.
Why this is not a similarity score
Nearly every attribution tool reduces a claim and a candidate passage to an embedding distance. That approach cannot see the difference between these two sentences:
Free cash flow was 410 million dollars. Free cash flow was 510 million dollars.
They are 98% identical as strings and near-identical as embeddings, so a similarity threshold marks the second one as supported. It is the single most common way a generated document goes wrong, and it is exactly the case similarity is blind to.
Footnote parses figures into comparable values instead. $2.1B, 2.1 billion and 2,100,000,000 resolve to the same number, 34% and 34 percent carry the same unit, and three participants is compared against two participants. It also separates two failures that similarity collapses into one:
Verdict | Meaning | What you do about it |
| every part of the claim is in the source | nothing |
| the source backs some of it and is silent on the rest | soften or cut the unsupported clause |
| no passage addresses the claim at all | find a source or delete it |
| a passage states something incompatible | fix it, the source says otherwise |
The last row is the one worth having. "The corpus does not mention this" and "the corpus says the opposite" need completely different responses, and a single confidence number cannot tell you which one you are looking at.
One more distinction that turns out to matter a lot. If a claim says "six new bus stations" and the source gives no station count anywhere, that is the source being silent, not the source disagreeing. Footnote only calls a figure contradicted when the passage offers a comparable figure, decided by whether the two numbers share an anchor word. Reporting silence as a contradiction is the fastest way for a verification tool to lose a reader's trust.
Related MCP server: Groundcheck
Install
pip install footnote-verify # core: CLI, engine, txt/md/html/json sources
pip install "footnote-verify[pdf]" # + PDF sources
pip install "footnote-verify[mcp]" # + the Model Context Protocol server
pip install "footnote-verify[all]" # everythingPython 3.11 or newer. The CLI is footnote. Quote the brackets, or your shell will treat them as a glob.
The default path needs no API key, no model download and no network. It is deterministic: the same input gives the same verdicts every time.
Quickstart
footnote demo --open # run the bundled example, open the report
footnote check answer.md --sources ./docs
footnote check answer.md -s ./docs -s ./notes.pdf --report out.html --open
echo "Revenue grew 34%." | footnote check - --sources ./docs
footnote check answer.md -s ./docs --json results.json --quietGate a build on it:
footnote check generated-summary.md --sources ./source-of-truth --fail-under 0.9Exits non-zero when the score falls short, so a docs pipeline can refuse to publish a page whose claims have drifted from the material it was written from.
How it works
text ──▶ segment ──▶ retrieve ──▶ align ──▶ judge ──▶ verdicts + citationsSegment. The text is split into claims, abbreviation- and decimal-aware, so Oct. 2025 and 3.5 percent stay in one piece. Headings, questions, code fences, table rules and bare fragments assert nothing and are skipped rather than scored, because scoring them moves the faithfulness number around without telling anyone anything.
Retrieve. Sources are indexed at sentence level, with windows of one to three consecutive sentences so a claim spanning a sentence boundary can still match. Candidates are ranked by IDF-weighted coverage of the claim's content terms, so a passage earns its rank by containing the rare, discriminating words rather than by being long. Recall matters more than precision here: a span that is never retrieved can never be cited.
Align. The winning window is narrowed to the tightest character range that still accounts for the matched terms. This is what turns "somewhere on page 4" into offsets you can highlight.
Judge. Coverage is combined with two checks similarity cannot do: figures are reconciled by value, and polarity is compared so a negation flip is caught. A passage that covers the claim's wording but disagrees on a number is reported as a contradiction, not as weak support.
Measured quality
A test suite proves the code does what it was written to do. It says nothing about whether what it was written to do actually works. So the repository ships a hand-labelled dataset and a harness that scores the real pipeline against it.
footnote eval # reproduces every number below68 labelled claims, 8 source corpora, deterministic judge, no API key:
Verdict | Precision | Recall | F1 | n |
supported | 0.89 | 0.97 | 0.93 | 32 |
contradicted | 0.94 | 0.75 | 0.83 | 20 |
unsupported | 0.73 | 1.00 | 0.84 | 8 |
partial | 0.67 | 0.50 | 0.57 | 8 |
Hallucination detection (positive class = anything not fully supported, which is the decision a user actually makes):
precision | 0.97 |
recall | 0.89 |
F1 | 0.93 |
accuracy (4-way) | 0.85 |
macro F1 | 0.79 |
One false positive in 68. Four missed problems in 36.
These numbers are asserted in the test suite, so a change that degrades verification quality fails CI rather than passing quietly.
Where it fails, and why
Being specific about this is more useful than a bigger headline number. All ten disagreements with the labels fall into four groups:
Entity substitution (3 cases). "data centres in Dublin and Tokyo" against a source saying "Dublin and Singapore" is scored as supported. Every content word appears in the passage, and a bag-of-words judge has no way to know one of them was swapped. This is the clearest case for the LLM judge.
Antonyms and temporal flips (2 cases). "Background tasks run before the response is sent" against "after the response has been sent". No number disagrees and no negation cue fires.
Relations that are not stated (2 cases). If a claim asserts that A was driven by B, and the source mentions A and mentions B but never links them, coverage is satisfied. Bag-of-words cannot represent the relation.
Vocabulary gaps (1 case). "Okafor scored" against "Okafor took the lead with a header". Stemming does not bridge synonyms.
The remaining two are partial boundary calls, which is the hardest and least consequential class.
The LLM judge is optional
Off unless a credential is present on your machine. When it is, the model is kept on a short leash:
It never reads the corpus. It sees the same retrieved passages the deterministic judge saw, so token cost stays bounded and the two are directly comparable in the harness.
A quote it cannot produce verbatim is not cited. Every LLM answer is re-anchored by locating its quote in the passage. If the quote is not there, the citation is dropped and the claim is downgraded. A fabricated citation is impossible by construction, which is not a promise you can make about a model that is asked to emit page numbers.
A consent notice prints before the first byte leaves the machine.
Credentials resolve from the environment or a local
.env, in that order, and are never written anywhere.
cp .env.example .env # add OPENAI_API_KEY or ANTHROPIC_API_KEY
footnote check answer.md -s ./docs --judge llm --model gpt-4o-miniThere is no SDK dependency. The provider adapter is about a hundred lines over the standard library and speaks to OpenAI-compatible endpoints (OpenAI, Groq, Together, OpenRouter, a local Ollama) and to Anthropic.
Model Context Protocol server
Let an agent check its own output mid-task instead of shipping an unverified answer.
pip install "footnote-verify[mcp]"
footnote mcp --sources ./docs// claude_desktop_config.json
{
"mcpServers": {
"footnote": { "command": "footnote", "args": ["mcp", "--sources", "/path/to/docs"] }
}
}Two tools: verify_against_sources for a folder on disk, and verify_against_text for passages already in context, such as results a search tool just returned. Both return the faithfulness score, per-claim verdicts, and a needs_attention list ordered worst first, so the model can rewrite the specific sentence that failed.
Python API
from footnote import check, write_report
result = check("Revenue grew 34% to $2.1B.", ["./sources"])
print(result.faithfulness) # 0.91
print(result.counts()) # {'supported': 4, 'partial': 0, ...}
for claim in result.problems(): # worst first
print(claim.verdict, claim.claim.text)
if claim.best:
print(" ", claim.best.span.locator()) # report.pdf p.4 chars 1180-1223
print(" ", claim.best.span.text) # the exact supporting text
write_report(result, "out.html")Every SourceSpan is a half-open character range into the loaded document, so document.text[span.start:span.end] == span.text always holds. That invariant is what makes the highlighting trustworthy, and it is asserted in the tests.
CLI reference
Command | Purpose |
| verify a file, a literal string, or |
| score the judges against a labelled dataset |
| run the bundled example end to end |
| serve the Model Context Protocol tools on stdio |
Useful flags for check: --report out.html, --json out.json, --open, --judge lexical|llm|auto, --threshold, --fail-under, --top-k, --verbose, --quiet.
Sources it can read
Out of the box, with no extra dependencies: .txt, .md, .html, .json, .jsonl, .csv, .yaml, and common source files. HTML is stripped to readable text with the standard library, and JSON is flattened to path: value lines so prose buried inside it is still findable. PDF needs the pdf extra and carries page numbers through into citations.
Limitations
Bag-of-words judging. The deterministic judge cannot see synonyms, entity swaps or unstated relations. See the failure analysis above. The LLM judge covers these; the deterministic one is the free, offline, reproducible baseline.
The dataset is hand-built, not a public benchmark. It is 68 claims written to span the failure modes deliberately, which makes it useful for regression testing and honest for relative comparison, but it is not a leaderboard result. The loader accepts external JSONL in the same shape if you want to run it against something larger.
English only. The stemmer, stop list and negation cues are English.
No cross-document reasoning. Each claim is judged against the best single window. A claim that is only true when two documents are combined will read as partial.
Development
git clone https://github.com/adwitiyashukla/footnote.git
cd footnote
pip install -e ".[all]"
pip install pytest pytest-cov ruff mypy
ruff check src tests && mypy && pytest
footnote evalCI runs lint, mypy --strict, the test suite and the evaluation harness on Python 3.11 and 3.12.
License
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityBmaintenanceEnables AI agents to fact-check claims, verify citations, and check source freshness using Wikipedia, Wikidata, Crossref, and Wayback Machine.Last updated1
- AlicenseAqualityBmaintenanceVerifies factual claims against live sources and returns a verdict, confidence score, and citations for any agent to use before stating uncertain facts.Last updated6MIT
- Alicense-qualityBmaintenanceEnables per-claim citation verification for AI-generated text by fetching cited sources and judging whether they support the claim, with verdicts and evidence quotes.Last updated45MIT
- Flicense-qualityDmaintenanceA verification component for agents that checks claims on public webpages and returns structured results with evidence text, screenshots, and deterministic JSON.Last updated
Related MCP Connectors
Real-time fact-check, citation verification, and source-freshness for AI agents.
Verify claims with verdict, confidence & cited sources; batch verify, source checks, daily brief.
Verify citations in AI text: fetches each cited source, returns verdicts with evidence quotes.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/adwitiyashukla/footnote'
If you have feedback or need assistance with the MCP directory API, please join our Discord server