financial-analyst-mcp
Click on "Deploy 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., "@financial-analyst-mcpcompare AAPL and MSFT over the last 6 months with normalized returns"
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.
financial-analyst-mcp
Natural language in, a real stock comparison chart out — via a Planner Agent (LLM), Pydantic-validated structured output, an optional Critic agent, and a real MCP client/server pair driving deterministic yfinance/pandas/Matplotlib tools. No Cursor, no arbitrary LLM-generated code execution.
This project is a from-scratch redesign of a reference "Multi-Agent Financial Analyst (MCP)" project that used CrewAI + 3 agents + Cursor-IDE-as-MCP-host. This version removes the Cursor dependency entirely (its MCP client couldn't be driven programmatically), removes arbitrary code generation/execution, and reduces the agent count from three to two (Planner + optional Critic), with the reasoning for each change documented in
ARCHITECTURE.md.
What it does
"Compare Apple's stock performance with Microsoft's over the last 6 months
and plot their normalized returns."
│
▼
Planner Agent (LLM) → tickers=[AAPL, MSFT], timeframe=last_6_months, metric=normalized_returns
│
▼
Pydantic validation + deterministic date resolution
│
▼
Optional Critic pass (re-checks the extraction against the raw prompt)
│
▼
MCP Client → MCP Server → fetch (yfinance) → compute (pandas) → plot (Matplotlib)
│
▼
A real PNG chart + return/volatility/drawdown metrics, back in the UIRelated MCP server: Finance MCP Server
Why MCP, and why not Cursor
MCP's actual value here is host interoperability: backend/mcp_server.py
is a standalone process exposing tools over a standard protocol, callable by
this app's own client, by Claude Desktop, or by anything else that speaks
MCP — without touching the tool implementations. For a single app in
isolation, plain function calls would work identically; MCP is what makes
the tool surface reusable outside this one app.
Cursor was dropped entirely. In the reference project, Cursor served as the
MCP host — but Cursor's MCP client is built for a human typing into an
IDE chat panel, not for a Python agent pipeline to invoke programmatically.
There is no clean way for orchestrator.py to "call Cursor." This project
ships its own ~150-line MCP client (backend/orchestrator.py) using the
official mcp SDK instead, so the whole system runs standalone, with zero
IDE dependency, for anyone who clones it. Full reasoning in
ARCHITECTURE.md.
How the Planner works
The Planner (backend/planner.py) sends the user's prompt plus today's date
to an LLM and asks it to emit a JSON object matching the AnalysisRequest
Pydantic schema (backend/schemas.py): tickers, a normalized timeframe
pattern (e.g. "last_6_months", "year:2025", "since:2024-01-01" — never
raw date arithmetic), a metric, and an analysis type. Pydantic then
deterministically resolves that pattern into concrete start_date/
end_date via backend/date_utils.py — date math is never left to the
LLM. If the LLM can't confidently identify a ticker or timeframe, the
request comes back with needs_clarification=true instead of a guess.
How MCP client/server communication works
backend/mcp_server.py registers four tools (fetch_stock_data,
calculate_returns, compute_comparison, plot_comparison) using the
official mcp SDK's FastMCP API, each wrapping a deterministic function
in backend/tools/. backend/orchestrator.py's MCPClient launches that
server as a subprocess over stdio, calls list_tools() to discover what's
available, and call_tool(name, arguments) to invoke one, with a timeout
and structured error translation (OrchestratorError) at every step.
Available tools
Tool | Purpose |
| Raw OHLCV + headline metrics for one ticker |
| Daily or cumulative return series for one ticker |
| Multi-ticker aligned comparison data, no chart |
| The primary end-to-end tool: fetch + compute + render a PNG in one call |
Installation
git clone <this repo>
cd financial-analyst-mcp
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtEnvironment setup
cp .env.example .envBy default LLM_PROVIDER=mock, which needs no API key and no network —
see "Offline mock mode" below. To use a real LLM, set LLM_PROVIDER=openai
or LLM_PROVIDER=anthropic and fill in LLM_API_KEY in .env.
How to start the MCP server
You normally don't need to start this yourself — backend/orchestrator.py
launches it automatically as a subprocess per request. To run/inspect it
standalone:
macOS/Linux/WSL/Git Bash:
./scripts/run_mcp_server.sh
# or, with the official inspector UI:
mcp dev backend/mcp_server.pyWindows PowerShell (.sh scripts won't run natively in PowerShell —
use the .ps1 equivalents, or run the commands directly):
.\scripts\run_mcp_server.ps1
# or directly:
python -m backend.mcp_serverIf PowerShell blocks script execution, either run once per session:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned, or just
run the underlying command shown above directly instead of the .ps1 file.
How to start the FastAPI backend
macOS/Linux/WSL/Git Bash:
./scripts/run_backend.shWindows PowerShell:
.\scripts\run_backend.ps1
# or directly:
if (!(Test-Path ".env")) { Copy-Item ".env.example" ".env" }
uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reloadNote: there is no route at / — visiting http://127.0.0.1:8000/ will
correctly show {"detail":"Not Found"}. Use these instead:
http://127.0.0.1:8000/health— status checkhttp://127.0.0.1:8000/docs— interactive API docs, lets you try/analyzedirectly
How to start Streamlit
In a second terminal, with the backend already running:
macOS/Linux/WSL/Git Bash:
./scripts/run_frontend.shWindows PowerShell:
.\scripts\run_frontend.ps1
# or directly:
streamlit run frontend/app.pyVisit the URL Streamlit prints (typically http://localhost:8501).
How to run tests
macOS/Linux/WSL/Git Bash:
pip install -r requirements.txt # needed for the full suite, see below
./scripts/run_tests.shWindows PowerShell:
pip install -r requirements.txt
.\scripts\run_tests.ps1
# or directly:
python -m pytest tests\ -vEvery test file also runs with plain stdlib unittest, no pytest required:
python -m unittest discover -s tests -vHow to run evaluations
python eval/run_eval.py # offline, uses LLM_PROVIDER=mock by default
python eval/run_eval.py --provider openai # grade a real LLM provider instead
python eval/run_eval.py --verboseOffline mock mode
LLM_PROVIDER=mock (the default) uses a deterministic, regex/keyword-based
stand-in LLM (backend.llm_client.MockLLMClient) that requires no API key
and no network access. This exists for three real reasons, not just as a
test double:
Zero-friction local development and CI — no API credits burned, no network flakiness.
A genuinely usable offline demo — clone,
pip install, run, and the whole pipeline (Planner → Critic → MCP → tools → chart) works end to end with zero external accounts, aside from yfinance itself needing network access to fetch real market data.It's what let this repository's own automated tests and evaluation dataset (
eval/eval_prompts.json) be exercised without any external dependency.
It is intentionally limited: a fixed dictionary of ~13 well-known company names, and regex-based timeframe/metric detection. See "Known Limitations."
Example prompts
Compare Apple's stock performance with Microsoft's over the last 6 months and plot their normalized returns.
Show me Tesla's performance during 2025.
Compare AAPL, MSFT and NVDA over the past 6 months.
Plot normalized returns for Tesla and Apple since January 2024.
Compare Google and Amazon cumulative returns since 2023-01-01.Expected output
A JSON response from POST /analyze shaped like:
{
"success": true,
"summary": "Analyzed AAPL, MSFT from 2026-02-21 to 2026-08-21 (normalized_returns). AAPL: total return +18.4%, ...",
"chart_path": "output/comparison_ab12cd34ef.png",
"request_id": "a1b2c3d4e5f6",
"extracted_request": { "tickers": ["AAPL", "MSFT"], "start_date": "2026-02-21", "...": "..." },
"metrics": { "AAPL": { "total_return": 0.184, "cagr": 0.41, "annualized_volatility": 0.28, "max_drawdown": -0.11 }, "...": "..." },
"errors": [],
"warnings": []
}...and, in Streamlit, that same information rendered as a summary, a line chart, and a metrics table.
What Was and Wasn't Executed (read this before judging "does it work")
This codebase was written and tested in a sandboxed environment with no
network access whatsoever (verified: pip install and raw HTTP requests
both fail there) and only pandas/numpy/matplotlib pre-installed.
Being upfront about exactly what that means:
Fully executed and verified in that sandbox (82 automated tests, all passing, plus real generated PNG files inspected):
backend/date_utils.py— 22 tests, including month-end edge casesbackend/tools/data.py— 18 tests using a fake yfinance module (dependency-injected), covering retries, empty results, malformed ticker rejection, and a real bug this caught and fixed (fetch_multiplewas keying results by unnormalized ticker strings)backend/tools/analysis.py— 19 tests of the financial math against hand-computed expected valuesbackend/tools/plotting.py— 6 tests that generate real PNG files and verify thembackend/llm_client.py'sMockLLMClient— 9 tests, including two real bugs this caught and fixed (the bare-ticker regex was picking up "YTD" and "S"/"P" from "S&P" as fake stock tickers)backend/config.pyandbackend/logging_config.py— 8 tests
Written completely (no TODOs, no stubs, no placeholders) against the
documented, real APIs, but not executable in that no-network sandbox
because they require pydantic, fastapi, mcp, or streamlit, none of
which could be installed:
backend/schemas.py,backend/planner.py,backend/critic.pybackend/mcp_server.py,backend/orchestrator.pybackend/main.py(FastAPI),frontend/app.py(Streamlit)tests/test_planner.py,tests/test_mcp.py,tests/test_api.py— these are written and ready to run; they were not executed here, but the logic they exercise (mock extraction, date resolution) was independently verified via the modules aboveeval/run_eval.py— written and ready to run; the ground-truthexpectedvalues ineval_prompts.jsonwere derived by actually runningMockLLMClientagainst each prompt in the sandbox and then applying the separately-tested schema-resolution rules by hand, not guessed
To close this gap yourself: pip install -r requirements.txt (needs
network) and run ./scripts/run_tests.sh. Everything above should pass; if
anything doesn't, it's a real bug for you to report/fix, not an
intentional gap — the design has been reasoned through carefully, but I
have not had the chance to execute the pydantic/FastAPI/MCP layers myself.
Known limitations
MockLLMClient's company dictionary is small (~13 names). Uncommon company names will fail to resolve and correctly triggerneeds_clarificationrather than silently guessing — but this means the offline mode has real, documented gaps (seeeval/eval_prompts.jsoncasep23, included deliberately to demonstrate this rather than hide it). A real LLM provider does not have this limitation.No support for index tickers (S&P 500, Dow, etc.) — yfinance can fetch these via tickers like
^GSPC, but nothing in this system maps natural-language index names to those symbols yet.No quarter-based timeframes ("last quarter", "Q3 2024") are in the supported timeframe vocabulary yet — these correctly trigger clarification rather than a wrong guess, but adding quarter support to
date_utils.pywould be a small, contained change.compute_comparison's inner-join alignment trims to the shortest overlapping trading history across tickers — a very recently-IPO'd company compared against a decades-old one will only compare over the newer company's whole lifetime, which is correct but not necessarily obvious from the output alone.The MCP server and client run as a fresh subprocess per
/analyzerequest (seeorchestrator.py'sasync with MCPClient(...)) rather than a persistent long-lived connection, which is simpler and more robust for a single-instance demo but adds subprocess-startup latency to every request. A production version would likely pool a persistent MCP session instead.
Future improvements
Persistent MCP session pooling instead of per-request subprocess launch.
Quarter/half-year timeframe support in
date_utils.py.A larger, LLM-maintained company→ticker lookup (or a real symbol-search API) instead of the mock's fixed dictionary.
Multi-step analysis chains (e.g. "compare X and Y, then tell me which had lower volatility") requiring the Planner to sequence multiple tool calls rather than always calling
plot_comparisononce.A measured comparison of Planner accuracy with vs. without the Critic step on a shared eval set, to quantify whether the Critic is actually earning its cost.
Response caching (
CacheConfigis already wired intoconfig.pybut not yet consumed bytools/data.py— a natural next step, keyed on ticker+date range).Observability/tracing across the full pipeline (currently: structured logs with request-ID correlation, but no span-level tracing).
Troubleshooting
Symptom | Likely cause | Fix |
| FastAPI isn't running, or | Start it with |
| The MCP server subprocess failed to start | Run |
|
| Add a real key, or switch to |
| Ticker doesn't exist, is delisted, or has no data in the requested range | Check spelling; try a shorter/more recent range |
Tests in | Missing |
|
Chart doesn't render in Streamlit | Backend's | Confirm |
| Check |
|
| PowerShell doesn't execute bash scripts natively | Use the |
PowerShell refuses to run | Default PowerShell execution policy | Run once per session: |
| Expected — there is no route at | Use |
License
Provided as-is for portfolio/educational use.
This server cannot be deployed
Maintenance
Related MCP Connectors
SEC & financial-data MCP: filings, financials, ownership, factors, fund letters, prompts.
Screen 11,000+ stocks using natural language and detect chart patterns via MCP.
Real SEC, 13F, insider, congress & macro data your AI agent can cite. Hosted MCP, 24 tools.
Portfolio analytics + US-equity market research for AI clients. ChatGPT deep-research compat.
Related MCP Servers
- FlicenseNot gradedqualityFmaintenanceEnables comprehensive stock market analysis with portfolio management, technical indicators, dividend tracking, sector analysis, risk metrics, and price alerts. Provides real-time stock data, trend analysis, and investment insights through natural language interactions.-
- AlicenseNot gradedqualityNot gradedmaintenanceProvides real-time financial data from Yahoo Finance, enabling stock price lookups, historical data analysis, company information retrieval, and multi-stock comparisons through natural language queries.-
- FlicenseNot gradedqualityDmaintenanceProvides tools for stock data retrieval, historical analysis, and market comparison using yfinance. It features robust guardrails to ensure secure interactions and blocks restricted content like investment advice.-
- FlicenseNot gradedqualityCmaintenanceProvides yfinance-based quant tools for stock price lookups, fundamentals, metric comparisons, price history statistics, and earnings dates, enabling natural-language stock questions.-