Building a Reliable Financial Assistant with Model Context Protocol
This guide details building a reliable personal financial assistant using the Model Context Protocol (MCP) and a "Narrator" architectural pattern. By separating deterministic data computation in Python from LLM narration, the system ensures factual accuracy, reduces hallucinations, and provides auditable, data-backed financial insights. It covers MCP client wrappers, budget enforcement, simple request parsing, and precise metric calculation.

LLMs are powerful tools, but in data-sensitive domains like finance, where precision and factual accuracy are non-negotiable, their propensity to "hallucinate" or confidently assert information without supporting data can be a significant drawback. Relying on an LLM for market commentary without real-time data or for financial analysis based on invented metrics is simply not viable for product features or internal tools. This is precisely where the Model Context Protocol (MCP) offers a robust solution, enabling the construction of LLM applications that are grounded in verifiable data and deterministic computations.
This guide will walk you through building a personal financial assistant using MCP. Our primary objective is to generate trustworthy, data-backed insights, ensuring the LLM acts purely as a narrator of established facts, rather than an originator of information.
What is the Model Context Protocol (MCP)?
MCP is a standardized protocol designed to simplify how Language Model (LLM) applications discover and interact with external tools provided by an MCP server. Instead of the cumbersome task of hardcoding function schemas or developing custom connectors for each framework, MCP offers a consistent, streamlined method for tool integration. For development teams, this translates into reduced integration complexity, predictable tool discovery, and a clear architectural separation between the LLM and its data sources.
In our financial assistant, we'll connect to EOD Historical Data (EODHD)'s MCP server. This server exposes crucial market data tools, allowing our application to programmatically fetch prices and fundamental data. A key design choice here is that our Python application, not the LLM, deterministically controls which MCP tools are called and with what parameters. We process the raw data and then provide a curated "facts object" to the LLM for narration, ensuring auditable and factual outputs.
Architecture: The "Narrator" Pattern
The core architectural principle of our financial assistant is the "Narrator" pattern, which rigidly separates the processes of acquiring and computing facts from generating natural language. The LLM's role is strictly limited to transforming pre-computed, verified facts into coherent narrative.
The workflow unfolds as follows:
- Request Parsing: When a user submits a query like "Give me a 30-day brief for AAPL," a lightweight parser extracts essential operational parameters, such as ticker symbols and a lookback window, without attempting complex natural language interpretation.
- MCP Tool Calls: Using the parsed details, our application makes asynchronous calls to the EODHD MCP server. Tools like "get historical prices" and "get fundamentals" are invoked to retrieve the necessary raw market data, ensuring that the assistant operates on current information.
- Deterministic Python Metrics: Upon receiving raw data, Python takes over to compute all financial metrics deterministically. This includes calculations for returns, volatility, maximum drawdown, trend slopes, and volatility regime labels. This stage ensures that all numerical outputs are quantifiable and repeatable.
- LLM Narration: A compact "facts object" containing these computed metrics is then passed to the LLM. The LLM is prompted to use only these provided facts to generate a market brief or watchlist snapshot, explicitly preventing it from performing its own analysis or calculations.
- Structured Response: The final output is a structured object, comprising the narrative (
answer), the precise computed metrics (metrics), a record ofdata_used(tickers, date range, tools called), and atool_trace_idfor debugging. This pattern significantly mitigates hallucinations, guarantees numerical repeatability, and provides full auditability, making it highly suitable for production deployment.
Step 1: MCP Client Wrapper (client.py)
The client.py module serves as the essential interface for interacting with the MCP server. Its primary functions include establishing streamable HTTP MCP sessions, reliably calling tools with built-in timeouts and retries, and augmenting tool outputs with crucial metadata. The module provides a list_tools function to discover and cache available tools and a core call_tool asynchronous function. This call_tool handles the session initiation, tool invocation with specified arguments, and packaging the result with a meta object. This meta object is critical for tracing, containing details like the trace_id, the tool name, its args, and latency_s, offering clear visibility into all data access operations.
Step 2: The Assistant Core (core.py)
This module is the operational heart of the financial assistant, managing everything from initial request processing to the final narrative generation.
Safeguards and Parsing
When interacting with external APIs and LLMs, implementing robust safeguards is crucial. core.py defines critical safety parameters such as MAX_LOOKBACK_DAYS, MAX_TOOL_CALLS, and MAX_TICKERS to prevent overly expensive or slow requests. Each incoming request is assigned a unique trace_id, and a log_event utility emits JSON-formatted log lines, facilitating comprehensive debugging and event flow reconstruction. For stable and predictable behavior, the request parsing is intentionally simple, using basic regular expressions to extract ticker symbols and a numerical lookback window. A blacklist filters common words, and an enforce_budgets function clamps these values to adhere to predefined limits, ensuring resource efficiency.
Data Fetching and Metrics
This section orchestrates the interaction with the EODHD MCP server to retrieve raw financial data and then processes it deterministically. Functions such as fetch_prices and fetch_fundamentals wrap the MCP tool calls. Each tool invocation is tracked by a _bump function, which increments a counter and stores metadata in a tool_trace list, enforcing the MAX_TOOL_CALLS budget. The fetch_prices function specifically normalizes historical data into a concise DataFrame, containing only date and price, optimized for subsequent calculations. Crucially, the LLM is explicitly prevented from performing any numerical analysis. Instead, a compute_metrics function in Python calculates a comprehensive set of financial indicators for each ticker. These include total return, realized volatility (both daily and annualized), maximum drawdown (the worst peak-to-trough decline), a simple trend slope derived from log prices, and a lightweight volatility regime classification. The deterministic nature of these calculations means that identical inputs will always yield identical numerical outputs, making the assistant's financial insights transparent and verifiable.
Conclusion
By embracing the Model Context Protocol and the "Narrator" architectural pattern, developers can construct LLM-powered applications that are not only intelligent but also highly reliable, auditable, and firmly rooted in real-world data. This methodology is especially valuable in sensitive domains like finance, where confidence must derive from verifiable facts, not merely from linguistic fluency. The financial assistant outlined here exemplifies how to leverage LLMs for effective communication while rigorously maintaining numerical integrity.
FAQ
Q: Why not let the LLM decide which MCP tools to call directly?
A: While LLMs can be powerful tool orchestrators, in applications demanding high factual accuracy and auditability, such as finance, directly guiding tool calls through deterministic Python logic significantly mitigates the risk of hallucinations or incorrect data requests. This approach guarantees that data fetching is precise, controlled, and fully traceable.
Q: How does the "Narrator" pattern reduce hallucinations?
A: Hallucinations occur when an LLM invents information. By strictly confining the LLM's role to narrating a pre-computed "facts object," we eliminate its capacity to generate numerical data or perform analysis. It only articulates the precise, verified metrics supplied by the Python logic, thereby effectively preventing numerical hallucinations.
Q: What are the main benefits of using MCP over traditional API integrations?
A: MCP provides a standardized protocol for tool discovery and invocation, simplifying integration across diverse services. It reduces development churn by offering a consistent interface, removing the need for custom wrappers per API or framework, and promotes a clean separation of concerns between the LLM layer and the data access layer. This leads to more maintainable and scalable systems.
Related articles
Building Responsive, Accessible React UIs with Semantic HTML
Build responsive and accessible React UIs. This guide uses semantic HTML, mobile-first design, and ARIA to create inclusive applications, ensuring seamless user experiences across devices.
Beyond Vibe Coding: Engineering Quality in the AI Era
The concept of 'vibe coding,' an extreme form of dogfooding where developers avoid inspecting AI-generated code, often leads to significant quality issues. A more effective approach involves actively guiding AI tools to clean up technical debt and refactor, treating them as powerful assistants under human oversight. Ultimately, maintaining high software quality, even with AI, remains a deliberate choice for developers.
Offline-First Social Systems: The Rise of Phone-Free Venues
Mobile technology, while streamlining communication and access, has also ushered in an era of constant digital distraction. For developers familiar with context switching and notification fatigue, the impact on
Heatbit Maxi Pro Review: An Ingenious Idea, Poor Execution
Heatbit Maxi Pro review: An innovative space heater that mines Bitcoin, but its high price and low profitability make it a poor financial investment despite easy setup and safety features.
Europe's Tech Funding: AI, Quantum & Infrastructure Lead the Week
Europe's tech sector saw substantial funding from March 30-April 5, led by Mistral AI's $830M debt for AI compute. The week highlighted a strategic European focus on building foundational infrastructure across AI, quantum, and deep tech, aiming for increased technological autonomy and global influence.
Lisette: Rust-like Syntax, Go Runtime — Bridging Safety and
Lisette is a new language inspired by Rust's syntax and type system, but designed to compile directly to Go. It aims to combine Rust's compile-time safety features—like exhaustive pattern matching, no nil, and strong error handling—with Go's efficient runtime and extensive ecosystem. This approach allows developers to write safer, more expressive code while seamlessly leveraging existing Go tools and libraries.





