Why LLMs hallucinate financial data — and how MCP fixes it
Ask any frontier model for Toyota's FY2025 revenue. You will get a fluent, confident answer in under two seconds. You will also, with very high probability, get the wrong number.
Toyota's consolidated Yūho for the fiscal year ended March 31, 2025 (EDINET doc ID S100VWVY) reports ¥48,036,704 million in revenue — ¥48.0 trillion — with an operating margin of 10.0% and net income of ¥4,765,086 million. These figures were filed June 18, 2025.
A model without live filing access cannot know this. What it does instead is generate the most plausible number its training weights have seen — a blend of press clips, stale annual reports, analyst estimates, and Wikipedia edits, all flattened into one confident sentence with no filing ID and no period anchor.
The gap between "the LLM answered" and "the LLM retrieved the correct figure from a primary source" is not a quirk of current models. It is a structural feature of how language models work. And it is fixable — not by making the model smarter, but by changing how it retrieves.
The number is wrong — and why that's architectural
Language models are trained on text. For any listed company, the training corpus for that company's financials contains:
- The primary regulatory filing (10-K, Yūho, annual business report)
- Press releases summarizing that filing, with rounded numbers
- News articles citing the press releases, sometimes erroneously
- Analyst reports with forward-looking adjustments and different FX rates
- Earnings call transcripts with spoken approximations
- Wikipedia articles citing some or all of the above
- Blog posts, forums, Reddit threads with no provenance
All of these are semantically similar — they all describe "Toyota's revenue." The model sees them as a distribution of plausible figures. During training, it learns to predict which figures are more likely given the surrounding context, not which figures are correct. There is no "truth" signal in the training objective that distinguishes the EDINET-sourced ¥48.0 trillion from an analyst estimate from three months earlier.
The result: when you ask the model at inference time, it samples from that learned distribution. It does not retrieve the filing. It recalls the centroid of what the training corpus said, which is structurally guaranteed to be imprecise.
This is not a failure of the model. It is a correct execution of the training objective on the available data. The fix is not better training. The fix is tool access to primary sources.
Three mechanisms that produce wrong answers
1. Training cutoff
Toyota filed its FY2025 Yūho in June 2025. A model with a training cutoff of early 2025 cannot have seen this document. The most recent data in its weights, for Toyota's revenue, would be the FY2024 figure (¥45.1 trillion, filed June 2024). The model may or may not disclose its uncertainty — but even when it says "as of my knowledge cutoff," it is citing an old figure with implicit confidence.
The gap matters: ¥45.1T vs ¥48.0T is a 6.5% error on one of the world's largest companies. In a financial model, that error propagates.
2. Label translation failure
Revenue is not a universal line item. Different accounting standards label it differently, and those labels are not interchangeable:
| Market | Filing label | Standard |
|---|---|---|
| US SEC | Revenues / RevenueFromContractWithCustomerExcludingAssessedTax | US GAAP |
| Japan EDINET | 売上高 (uriage-daka) | J-GAAP or IFRS |
| Korea DART | 매출액 (maechul-aek) | K-GAAP / K-IFRS |
| EU ESEF | ifrs-full:Revenue | IFRS |
A model that retrieved the wrong line item — say, Toyota's parent-only 売上高 instead of the consolidated figure — would give a number that looks plausible but is wrong by an order of magnitude. The consolidated FY2025 figure is ¥48.0 trillion. The parent-only figure is a fraction of that.
The model has no way to know which line it cited without inspecting the original XBRL context tags. Without tool access, it cannot.
3. Confidence is the product
LLMs are optimized to generate fluent, plausible text. Plausible financial figures sound like: "Toyota's revenue for fiscal year 2025 was approximately ¥45-46 trillion." The model has no way to express the epistemic status "I am interpolating from training data that is at least 12 months old and may not reflect the most recent filing." Doing so would produce worse RLHF scores — users do not reward hedged, uncertain answers in evaluations.
The incentive structure of model training actively selects against the honesty required for financial data. [1]
Why RAG alone does not solve this
The standard grounding solution is Retrieval-Augmented Generation: retrieve relevant documents at query time and quote from them. RAG works. But it has a failure mode that is particularly acute for financial data.
The source discrimination problem. If your retrieval corpus contains both the primary SEC filing and analyst commentary about that filing, they live in the same vector space. A question about "Apple's Q3 revenue" will retrieve chunks from both — and there is no guarantee the primary filing chunk ranks highest. The analyst note is often more semantically similar to the query (it uses natural language about revenue) than the XBRL-tagged HTML of the 10-Q.
The version problem. Companies file amendments (10-K/A, 20-F/A). Your retrieval index may contain both the original and the amended filing. Without explicit provenance metadata, you cannot tell which number came from which version.
The period problem. "Apple's latest revenue" is ambiguous without a period anchor. The chunks in your index may come from different fiscal periods. Revenue for Q2 2024 and Q4 2024 are both "Apple's revenue" — but they are different numbers, and combining them is a category error.
The fix is not better embedding models. It is structured data with typed metadata: every financial figure must carry filing_id, period_end, currency, accounting_standard, and source_registry — and those fields must be first-class, not inferred.
That is exactly what a typed MCP tool layer provides.
The hidden cost of reading the full filing
The natural next step after RAG disappoints is to skip retrieval entirely: parse the filing to HTML, dump the whole document into the context window, and let the model read it. This works in demos. It fails in production — on three independent axes.
Parsing noise. A Toyota Yūho or Apple 10-K run through MinerU, pdfminer, or a custom HTML extractor does not arrive as clean prose. Nested tables break apart. Footnotes inject themselves between line items. Page headers repeat mid-paragraph. Currency disclosure notes sit three pages away from the revenue table they annotate. The model receives a structurally correct but semantically scrambled document — and reads it with the same confidence it would bring to a clean filing.
Token volume. A single annual filing in parsed HTML — tables, inline XBRL, footnotes, repeated headers — runs 150,000 to 400,000 tokens. That is not an edge case. It is the normal size of a 10-K or Yūho.
| Scenario | Tokens (input) | Claude Sonnet 4.6 | GPT-5.5 |
|---|---|---|---|
| One filing, ~200k tokens | 200k | ~$0.60 | ~$1.00 |
| One filing, ~350k tokens | 350k | ~$1.05 [3] | ~$2.14 [4] |
| FY2024 + FY2025 + one peer (3 filings) | ~600k | ~$1.80 | ~$4.60 [4] |
Claude Sonnet 4.6 prices input at $3/M and output at $15/M at a flat rate across its full 1M context window — no long-context premium. [3] GPT-5.5 prices input at $5/M and output at $30/M; beyond 272k input tokens, OpenAI applies a 2× input / 1.5× output multiplier on the excess. That surcharge lands exactly in the range where large regulatory filings live.
A single user question — "compare Toyota FY2025 to FY2024 and to a peer" — can cost $1.80 to $4.60 in input tokens alone on a frontier model, before a single token of model reasoning. And that is the optimistic scenario, assuming the parsed HTML is clean.
Year confusion — the failure mode that looks like success. This is the most insidious problem. The model is not hallucinating. It is reading a real number from a real document — but from the wrong column.
Toyota's FY2025 Yūho (period ended 2025-03-31) reports consolidated revenue of ¥48,036,704 million. Toyota's FY2024 Yūho (period ended 2024-03-31) reports ¥45,095,325 million. In a 300-page parsed HTML dump, both figures appear in tables with similar headers — 売上高, consolidated, millions of yen. A model asked for "Toyota's latest revenue" may return ¥45.1 trillion with full confidence: that number is in the document, correctly parsed, correctly cited. It is simply from the prior fiscal year.
You paid frontier-model pricing for a wrong-period answer that passes every surface-level sanity check. No hedging, no "as of my knowledge cutoff" — just the wrong year, indistinguishable from a correct retrieval without manual validation against the filing ID.
Structured retrieval via MCP eliminates this structurally: period_end: "2025-03-31" is set by the tool, not inferred by the model from table proximity. The cost of two get_filing_kpis calls is a few hundred tokens — not three hundred thousand. Grounded financial data is not a luxury on top of context-window retrieval. It is what makes the economics work.
What MCP actually changes
The Model Context Protocol is an open standard for connecting LLMs to external tools. Instead of putting financial data into the context window as text, you expose it as callable tools that return typed objects.
{
"tool": "get_filing_kpis",
"result": {
"ticker": "7203",
"company": "TOYOTA MOTOR CORPORATION",
"period": "FY2025",
"period_end": "2025-03-31",
"currency": "JPY",
"accounting_standard": "IFRS",
"source": {
"registry": "jp_edinet",
"doc_id": "S100VWVY",
"filed_at": "2025-06-18",
"url": "https://disclosure2.edinet-fsa.go.jp/WZEK0040.aspx?S100VWVY"
},
"canonical": {
"revenue": 48036704000000,
"operating_income": 4795586000000,
"operating_margin": 0.0998,
"net_income": 4765086000000
}
}
}
Three properties of this response matter:
Provenance. The source.doc_id and source.url are the EDINET document identifiers. Any downstream system can verify the number by loading the original document. The model is no longer the authority — the filing is.
Period anchoring. period_end: "2025-03-31" is unambiguous. There is no "approximately" or "as of my knowledge cutoff." The number is tied to a specific balance-sheet date.
Currency denomination. currency: "JPY" means every consumer of this data knows they are dealing with yen — not yen rounded to billions, not USD at some implicit exchange rate. The scale question is explicitly answered.
When the model cites ¥48.0 trillion, it is citing the canonical field from a DART/EDINET-sourced object with a specific document ID. That is categorically different from sampling a training-data distribution.
A real session — Toyota FY2025
Here is what an agent session looks like with OpenFilings MCP connected to Claude:
User: What was Toyota's operating margin for FY2025, and how does it compare
to FY2024? Use filed data, not estimates.
Claude: [calls list_filings(ticker="7203", market_id="jp", form_types=["10-K"])]
→ FY2025: filing_id e330a4ec (S100VWVY, filed 2025-06-18)
→ FY2024: filing_id 5a62cfa8 (S100TR7I, filed 2024-06-25)
[calls get_filing_kpis(filing_id="e330a4ec")]
→ revenue: ¥48,036,704M operating_income: ¥4,795,586M
operating_margin: 10.0% (IFRS, period_end 2025-03-31)
[calls get_filing_kpis(filing_id="5a62cfa8")]
→ revenue: ¥45,095,325M operating_income: ¥5,352,934M
operating_margin: 11.9% (IFRS, period_end 2024-03-31)
Toyota's operating margin compressed from 11.9% in FY2024 to 10.0% in
FY2025. Revenue grew 6.5% (¥45.1T → ¥48.0T), but operating income
declined 10.4% (¥5.35T → ¥4.80T) — driven primarily by higher labour
and material costs in the consolidated automotive manufacturing segment.
Source: EDINET Yūho, doc IDs S100TR7I (FY2024) and S100VWVY (FY2025).
The model made two tool calls, retrieved structured objects with explicit provenance, and reasoned from the delta. No guessing. No "approximately." The source documents are cited by their EDINET document identifiers — traceable to the original filings in seconds.
The multi-market problem
The hallucination problem compounds when you compare companies across markets. A developer building a global equity research tool has to handle different registries (SEC, EDINET, DART, ESMA), different accounting standards (US GAAP, IFRS, J-GAAP, K-GAAP) [2], different fiscal calendars (Apple ends September, Toyota ends March), and different currencies with no stable implicit mapping.
Here is what that looks like without tool access:
User: Compare Apple and Toyota operating margins from their latest annual filings.
Which company is more profitable on an operating basis?
Claude (no MCP): Apple reported an operating margin of approximately 30% in its
most recent fiscal year, while Toyota's operating margin was around 11-12%.
Apple is significantly more profitable on an operating basis.
Both figures are directionally plausible. Neither is usable.
Problems:
→ Apple FY2024 (period ended 2024-09-28): operating margin 32.0%, revenue $416.2B
→ Toyota FY2025 (period ended 2025-03-31): operating margin 10.0%, revenue ¥48.0T
→ "Latest annual" maps to different calendar years — Apple's FY2024 ended six months
before Toyota's FY2025 began
→ USD and JPY are compared with no FX rate, no currency field, no filing ID
→ Toyota's 11-12% is likely FY2024 training data (actual: 11.9%), not the filed
FY2025 figure (10.0%) — margin compressed, not stable
→ No way to verify which EDINET doc ID or SEC accession number produced either number
The answer sounds authoritative. It is a blend of stale training weights, mismatched fiscal periods, and silent currency conflation. A developer cannot build a comp sheet from this — and a quant cannot audit it.
With MCP, the same prompt produces typed objects: period_end, currency, accounting_standard, and source.doc_id on every response. The model surfaces the comparability caveats explicitly — different fiscal year-ends, different currencies, different GAAP presentations — rather than presenting incomparable numbers as if they were equivalent.
Implementation checklist
If you are building any system that touches financial data from LLMs, four rules cover most of what matters:
Do not rely on parametric memory for specific financial figures. Numbers change every quarter. The model's weights are always behind.
Use a typed tool interface. get_filing_kpis returning { revenue: number, currency: string, period_end: string, source: {...} } is grounding. A text chunk in a vector index is not.
Include provenance in every response. The filing ID, the document URL, and the period end date should be visible to the user — not buried in tool internals.
Handle multi-currency explicitly. Never let the model silently aggregate or compare USD and JPY figures. Surface the currency field and require explicit FX handling.
The infrastructure is the hard part. The model reasoning, once it has reliable inputs, is the easy part.
Those principles are model-agnostic. To test them against real filings in under five minutes, wire up OpenFilings MCP — the setup guide covers Claude Code, Claude.ai, Cursor, and ChatGPT. The one-liner for Claude Code:
claude mcp add --transport http --scope user openfilings https://api.openfilings.org/mcp
Then /mcp → openfilings → Authenticate. A first sanity-check prompt:
Call list_filings for Toyota (7203, jp) and Apple (AAPL, us).
Return revenue and operating margin from get_filing_kpis for each latest annual filing.
Include period_end, currency, and source doc ID in your answer.
If the response cites EDINET doc ID S100VWVY for Toyota and a specific SEC accession number for Apple — with matching period_end fields — the architecture is working.