MCP Banking Intelligence Server
by dev1nagaich
README.md
# MCP Banking Intelligence Layer (Lab 1 + Lab 2)
This project turns a pile of financial documents into AI-generated **investment
briefs**. It is built in two stages:
- **Lab 1 — RAG + Multi-Agent (AutoGen):** a `ResearchAgent` writes a brief from
data retrieved out of a ChromaDB vector store, and a `CriticAgent` reviews it
until it meets a quality checklist.
- **Lab 2 — MCP Banking Intelligence Layer:** we take Lab 1's tangled retrieval
logic and expose it as a clean **MCP server** of reusable "banking tools."
A LangChain + Ollama agent then acts as the **MCP client**, discovering and
calling those tools at runtime to build the brief — and reuses the Lab 1
`CriticAgent` for the final review.
Lab 2 **reuses the Lab 1 data** (`chroma_db/`). You do **not** re-run `ingest.py`.
---
## 1. What is MCP, and why does it matter?
**MCP (Model Context Protocol)** is an open standard from Anthropic that defines
*how an AI application talks to external tools and data*. Think of it as a
universal adapter — like USB-C for AI tools.
Without MCP, every agent hardcodes its own data access. In Lab 1, the agent
reaches **directly** into ChromaDB. If a new team wants a loan advisor or a fraud
analyst, they must copy and modify the whole codebase.
With MCP, the data/tool logic lives behind a **standard server interface**:
| Concept | What it is | In this lab |
| --- | --- | --- |
| **MCP Server** | Exposes *tools* (typed functions) over a standard protocol | `server.py` — 4 banking tools + 1 bonus |
| **MCP Client** | Discovers and calls those tools at runtime | `client.py` — LangChain/Ollama agent |
| **Transport** | The channel between client and server | `stdio` (client launches server as a subprocess) |
| **Tool** | A typed, documented function the LLM can call | `search_financials`, `get_company_profile`, ... |
**Why this is powerful:** the agent code never names a database. Tomorrow you can
swap ChromaDB for a live Bloomberg feed by editing only `server.py` — the agent
in `client.py` does not change. Tools become *modular and shareable* across any
MCP-compatible client (Claude Desktop, Cursor, your own agent, etc.).
### Key idea: who thinks vs. who fetches
- The **server tools are "dumb" on purpose.** They only retrieve and process
data and return JSON. They **never call an LLM**.
- The **client agent does the thinking.** The LLM looks at the available tools,
decides which to call (this is *not* hardcoded), reads the JSON results, and
writes the brief.
---
## 2. Architecture
### Lab 1 pipeline (RAG + AutoGen)
```
data/financial/*.txt
│ ingest.py (run ONCE)
▼
┌──────────────┐ retriever.py ┌──────────────┐ APPROVED? ┌──────────┐
│ ChromaDB │ ───────────────────────►│ ResearchAgent│ ◄───────────► │ Critic │
│ financial_docs│ (semantic retrieval) │ (drafts) │ feedback │ Agent │
└──────────────┘ └──────────────┘ └──────────┘
│
▼
output/brief_{company}.txt
```
### Lab 2 pipeline (MCP)
```
┌──────────────────────── client.py (MCP CLIENT) ───────────────────────┐
│ │
--company Apple ─┤ LangChain ReAct agent (Ollama: llama3.1) │
│ │ "which tool should I call?" (LLM decides, not hardcoded) │
│ ▼ │
│ langchain-mcp-adapters ──── stdio ───► server.py (MCP SERVER) │
│ ▲ │ @mcp.tool() functions │
│ │ JSON tool results ▼ │
│ └─────────────────────────────── reads chroma_db/financial_docs │
│ │ (reuses retriever.py) │
│ ▼ │
│ draft brief ──► Lab 1 CriticAgent (one review round) ──► revise │
│ │
└──────────────────────────┬───────────────────────────────────────────┘
▼
output/mcp_brief_{company}.txt
```
---
## 3. The MCP tools (`server.py`)
All tools are defined with `@mcp.tool()`, have typed parameters, return
JSON-serializable dicts, and return `{"error": ...}` instead of crashing on bad
input.
| Tool | Signature | What it does |
| --- | --- | --- |
| `search_financials` | `(query: str, company: str, max_results: int)` | Semantic search over `financial_docs` for a company. |
| `get_company_profile` | `(company: str)` | Aggregated metadata: doc count, available years, doc types. |
| `compare_companies` | `(companies: list[str])` | Side-by-side financial summary of two companies. |
| `generate_risk_flags` | `(company: str)` | Scans risk-related docs and returns flagged risk keywords. |
| `get_market_news` *(bonus)* | `(company: str)` | Up to 3 recent headlines via DuckDuckGo (network, not LLM). |
Missing company → `{"error": "company not found", "company": "..."}`.
> Note: `stdio` uses stdout as the protocol channel, so `server.py` logs only to
> **stderr** — it never prints to stdout.
---
## 4. Project structure
```
MCP/
├── data/financial/*.txt # Lab 1 source documents
├── ingest.py # Lab 1 — builds chroma_db (run ONCE, do not re-run)
├── chroma_db/ # Lab 1 vector store (reused by Lab 2)
├── retriever.py # Lab 1 — semantic retrieval (reused by server.py)
├── agents.py # Lab 1 — ResearchAgent + CriticAgent (CriticAgent reused)
├── main.py # Lab 1 — AutoGen pipeline entry point
├── server.py # Lab 2 — NEW: MCP server (4 tools + bonus)
├── client.py # Lab 2 — NEW: LangChain MCP client + CriticAgent
├── output/ # briefs are written here
├── requirements.txt
└── README.md # this file
```
---
## 5. Setup
**Prerequisites**
1. Conda env `agentic` activated: `conda activate agentic`.
2. [Ollama](https://ollama.com) running locally with the models pulled:
```bash
ollama pull llama3.1:latest
ollama pull nomic-embed-text:latest
```
3. Lab 1 already ingested (`chroma_db/` exists). If not, run `python ingest.py` **once**.
**Install Lab 2 dependencies**
```bash
pip install mcp langchain langchain-community langchain-mcp-adapters langchain-ollama langgraph python-dotenv
# or simply:
pip install -r requirements.txt
```
---
## 6. How to run
**Lab 1 (AutoGen):**
```bash
python main.py --company Apple --year 2024
```
**Lab 2 (MCP):**
```bash
python client.py --company Apple
```
`client.py` automatically launches `server.py` as a subprocess — you do not start
the server yourself.
**Quick server smoke test** (optional — it will wait for a client on stdin):
```bash
python server.py # Ctrl+C to stop
```
Output is written to `output/mcp_brief_{company}.txt`.
Companies available in the dataset: **Apple, Amazon, Microsoft, Nvidia, Tesla**
(years 2023–2024).
---
## 7. How the constraints are satisfied
- **Server and client are separate files** — `server.py` and `client.py`.
- **Tools never call the LLM** — they only query/aggregate ChromaDB and return JSON.
- **Tool routing is not hardcoded** — the LLM in the ReAct agent decides which
MCP tools to call.
- **Reuses the existing `financial_docs` collection** — no new ChromaDB is created;
`compare_companies` even reuses Lab 1's `retriever.py`.
- **CriticAgent runs before saving** — `client.py` calls the Lab 1 `CriticAgent`
for one review round on every brief.
- **Errors return JSON** — invalid input yields `{"error": ...}`, never an
unhandled exception.
---
## 8. Troubleshooting
**`ModuleNotFoundError: No module named 'autogen'`**
The Lab 1 code uses the *classic* AutoGen API. The PyPI `pyautogen` name now
redirects to Microsoft's new `autogen-agentchat` packages, which do **not** ship
the `autogen` module. Install the classic-API fork instead:
```bash
pip install ag2
python -c "import autogen; from autogen import AssistantAgent; print('classic AutoGen OK')"
```
**The brief is full of numbers that aren't in our data (hallucination)**
This means the LLM did *not* actually call the MCP tools — it described the calls
in text and made up the answer. `client.py` now prints
`MCP tools actually called by the LLM: ...` and warns if the list is empty.
Confirm whether Ollama tool-calling works at all:
```bash
python diagnose_tools.py
```
- If `TOOL_CALLS` is empty, your Ollama server and/or `langchain-ollama` are too
old to emit structured tool calls. Upgrade both:
```bash
pip install -U langchain-ollama langchain-core langgraph
# update the Ollama server itself (Linux):
curl -fsSL https://ollama.com/install.sh | sh
ollama --version # need a recent version with tool-call support
```
- Tool calling needs a tool-capable model. `llama3.1` supports it; if it is still
flaky, try `ollama pull qwen2.5:7b-instruct` and set `MODEL` in `client.py`.
**`create_react_agent ... LangGraphDeprecatedSinceV10`**
Harmless warning — it still works. (It will move to
`from langchain.agents import create_agent` in a future major version.)
---
## 9. Bonus / extensions
- ✅ `get_market_news()` using DuckDuckGo (implemented).
- 💡 Expose the server over **SSE** transport instead of stdio.
- 💡 Add a `portfolio_summary` tool that calls `compare_companies` +
`generate_risk_flags` in sequence for a list of companies.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues