股票研究助手

FinSight MCP是一款基于模型上下文协议(MCP)的金融研究助手,专为印度国家证券交易所(NSE)股票设计,提供实时股价查询、股票比较、基本面分析和新闻搜索等功能。

  • by Akash-47-tank
  • Python
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 Akash-47-tank/finsight-mcp

使用说明

📈 FinSight MCP — Agentic Indian Stock Market Research Assistant


A production-style implementation of Anthropic's Model Context Protocol (MCP) that transforms a local LLM into an agentic financial research assistant for Indian NSE stocks — built 100% with free and open-source tools.

Built after completing the Anthropic Academy — Introduction to Model Context Protocol course


🚀 Quick Start · 📸 Screenshots · 🏗️ Architecture · 📚 What I Learned


📌 Table of Contents


🎯 What is This Project?

FinSight MCP is a complete, end-to-end implementation of the Model Context Protocol. It consists of two parts working together:

Part File Role
MCP Server finsight_server.py Exposes stock market capabilities through all 3 MCP primitives: Tools, Resources, and Prompts
MCP Client finsight_client.py Connects to the server, discovers capabilities, runs a conversational chat loop via Ollama LLM

Core insight: MCP separates "what the AI can do" (server) from "how the AI is used" (client) — making AI capabilities modular, reusable, and composable across any client.

Why Indian Stock Market?

I work as a Junior Data Scientist at Pure Broking Pvt. Ltd. (a brokerage firm) and have built production RAG systems for regulatory compliance using Anthropic and OpenAI APIs. Combining MCP with Indian financial data made this project authentic to my real-world domain — and immediately legible to fintech/AI recruiters.


📸 Live Demo Screenshots

1. MCP Inspector — Server Connection

The MCP Inspector is a developer tool that connects directly to your MCP server to browse and test all tools, resources, and prompts — no client needed. Think Postman, but for MCP servers.

Inspector launched with Transport: STDIO. Clicking Connect spawns the server subprocess and discovers all capabilities.


2. All Three MCP Primitives — One Screen

Typing tools calls list_tools(), list_resources(), and list_prompts() on the server simultaneously — proving complete discovery of all three MCP primitives.

Column What it shows
Tools 4 callable functions the LLM can invoke
Resources 3 context files served at file:// URIs
Prompts 2 parameterised server-side templates

3. Live Tool Execution — Real-Time Stock Price

"What is the current price of TCS?" → keyword router fires → get_stock_price(ticker="TCS.NS") → live JSON → LLM summarises.

Live result: TCS at ₹2,410.50 (−1.31%) · Market Cap ₹8.72 Lakh Cr · Timestamp 2026-03-15 14:49:46


4. Multi-Stock Comparison

"Compare TCS, Infosys, and Wipro" → compare_stocks(["TCS.NS","INFY.NS","WIPRO.NS"]) → side-by-side metrics + auto-rankings.

Result: TCS ₹2,410 · Infosys ₹1,248 · Wipro ₹197 — with winner detection across P/E, ROE, 52-week performance.


5. MCP Prompt Template — Deep Analysis Report

analyse RELIANCE.NS → fetches stock_analysis_prompt from server → fills {{ticker}} + {{timeframe}} → auto-calls 2 tools → LLM generates 8-section report.

Result: Full structured report — P/E 22.47, ROE 0.6%, D/E 35.65, 52W range ₹1,142–₹1,611, news catalysts, investment verdict.


🧠 What is MCP? My Understanding

The Problem MCP Solves

Before MCP, every AI application built its own custom glue layer between the LLM and external tools. Want Claude to query your database? Custom code. Want GPT-4 to query the same database? Rewrite it entirely. No standard existed.

MCP is the USB-C of AI tool integrations — a universal protocol so any MCP client (Claude Desktop, custom Python, VS Code) can plug into any MCP server (databases, APIs, file systems) without custom integration code.

The Three MCP Primitives

mindmap
  root((MCP))
    Tools
      get_stock_price
      search_news
      analyse_fundamentals
      compare_stocks
    Resources
      nse_symbols.json
      market_glossary.md
      portfolio_template.txt
    Prompts
      stock_analysis_prompt
      portfolio_review_prompt
Primitive Analogy When to use Example here
Tools API endpoint Real-time data, actions with side effects Live stock price from yfinance
Resources Reference manual Static context injected into LLM memory Finance glossary, NSE symbol list
Prompts Workflow template Reusable, parameterised LLM workflows 8-section analysis report

MCP Communication Protocol

sequenceDiagram
    participant U as 👤 User
    participant C as MCP Client
    participant O as Ollama LLM
    participant S as MCP Server
    participant Y as yfinance API

    U->>C: "What is the price of TCS?"
    C->>C: Keyword router detects price + TCS
    C->>S: tools/call → get_stock_price(ticker="TCS.NS")
    S->>Y: yf.Ticker("TCS.NS").info
    Y-->>S: Live JSON data
    S-->>C: TextContent(JSON)
    C->>O: Summarise this JSON for user
    O-->>C: Natural language response
    C-->>U: "TCS is trading at ₹2,410.50 (−1.31%)"

🏗️ Architecture and Flow Diagram

Full System Architecture

graph TB
    subgraph USER["👤 User Interface"]
        U[Terminal Chat Loop]
    end

    subgraph CLIENT["🖥️ MCP Client — finsight_client.py"]
        KR[Keyword Router\nFAST PATH]
        LLM[Ollama llama3.2\nSLOW PATH]
        AF[Argument Auto-Fixer]
        CW[Context Window Manager]
    end

    subgraph SERVER["⚙️ MCP Server — finsight_server.py"]
        direction TB
        subgraph TOOLS["🔧 Tools"]
            T1[get_stock_price]
            T2[search_news]
            T3[analyse_fundamentals]
            T4[compare_stocks]
        end
        subgraph RESOURCES["📁 Resources"]
            R1[nse_symbols.json]
            R2[market_glossary.md]
            R3[portfolio_template.txt]
        end
        subgraph PROMPTS["💬 Prompts"]
            P1[stock_analysis_prompt]
            P2[portfolio_review_prompt]
        end
    end

    subgraph DATA["🌐 Data Sources"]
        YF[yfinance\nYahoo Finance]
        NA[NewsAPI\nFree Tier]
        FS[Local Filesystem]
    end

    U --> KR
    U --> LLM
    KR -->|direct tool call| SERVER
    LLM --> AF
    AF -->|fixed tool call| SERVER
    T1 & T2 & T3 & T4 --> YF
    T2 --> NA
    R1 & R2 & R3 --> FS
    SERVER -->|JSON-RPC stdio| CLIENT
    CW --> LLM

    style USER fill:#1a1a2e,color:#e0e0e0
    style CLIENT fill:#16213e,color:#e0e0e0
    style SERVER fill:#0f3460,color:#e0e0e0
    style DATA fill:#533483,color:#e0e0e0
    style TOOLS fill:#1a472a,color:#e0e0e0
    style RESOURCES fill:#4a1942,color:#e0e0e0
    style PROMPTS fill:#7b2d00,color:#e0e0e0

Request Flow — Simple Price Query

flowchart TD
    A([👤 User types:\n'What is the price of TCS?']) --> B

    B{Keyword\nRouter}
    B -->|Detects 'price' + 'tcs'| C
    B -->|Complex query| G

    C[Maps 'tcs' → 'TCS.NS'\nBuilds tool call directly]
    C --> D

    D[/"MCP tools/call\n{name: get_stock_price,\n arguments: {ticker: TCS.NS}}"/]
    D --> E

    E["⚙️ Server executes\nyf.Ticker('TCS.NS').info\n→ Yahoo Finance API"]
    E --> F

    F[/"Returns JSON:\n{current_price: 2410.5,\n price_change_pct: -1.31,\n market_cap: ₹8.72 Lakh Cr}"/]
    F --> H

    G["🧠 Ollama LLM\nInterprets query\nGenerates tool call JSON"]
    G --> D

    H["🧠 Ollama summarises JSON\ninto natural language"]
    H --> I

    I([✅ Output:\n'TCS is trading at ₹2,410.50\ndown 1.31% today on NSE'])

    style A fill:#1a472a,color:#fff
    style I fill:#1a472a,color:#fff
    style B fill:#7b2d00,color:#fff
    style D fill:#0f3460,color:#fff
    style F fill:#0f3460,color:#fff
    style E fill:#533483,color:#fff

Prompt Template Flow — analyse RELIANCE.NS

flowchart LR
    A([analyse RELIANCE.NS]) --> B

    B["Client calls\nprompts/get on server\n\nname: stock_analysis_prompt\nargs: {ticker: RELIANCE.NS,\n       timeframe: medium-term}"]

    B --> C["Server reads\nstock_analysis.txt\n\nFills {{ticker}} → RELIANCE.NS\nFills {{timeframe}} → medium-term\n\nReturns PromptMessage"]

    C --> D

    D["Client auto-calls\n2 tools in parallel"]

    D --> E["analyse_fundamentals\n→ P/E, ROE, EPS\n   margins, debt"]
    D --> F["search_news\n→ Latest 5 headlines\n   about Reliance"]

    E --> G["Both results\ninjected into\nLLM context"]
    F --> G

    G --> H["🧠 Ollama generates\n8-section report\nusing template +\nreal tool data"]

    H --> I(["✅ Full structured\nanalysis report\nwith live data\nand verdict"])

    style A fill:#1a472a,color:#fff
    style I fill:#1a472a,color:#fff
    style D fill:#7b2d00,color:#fff
    style H fill:#0f3460,color:#fff

MCP Session Lifecycle

stateDiagram-v2
    [*] --> Initialising : python client/finsight_client.py

    Initialising --> Discovering : session.initialize()
    note right of Discovering
        list_tools() → 4 tools
        list_resources() → 3 resources
        list_prompts() → 2 prompts
    end note

    Discovering --> LoadingContext : Resources read
    note right of LoadingContext
        read_resource(portfolio_template)
        Injected as system message
        LLM now knows user holdings
    end note

    LoadingContext --> ChatLoop : Ready

    ChatLoop --> KeywordRoute : Simple query\n(price, compare, news)
    ChatLoop --> LLMRoute : Complex query
    ChatLoop --> BuiltinCmd : tools / analyse / portfolio

    KeywordRoute --> ToolCall : Direct, no LLM
    LLMRoute --> ArgFix : LLM generates JSON
    ArgFix --> ToolCall : Auto-fix wrong arg names
    BuiltinCmd --> PromptCall : get_prompt()

    ToolCall --> Summarise : JSON result back
    PromptCall --> Summarise : Template + tool data

    Summarise --> ChatLoop : Response shown
    ChatLoop --> [*] : quit

🧩 MCP Primitives Implemented

Tools (4 total)

Tools are executable functions the LLM can call with structured, schema-validated arguments.

graph LR
    subgraph Schema["JSON Schema Validation"]
        direction TB
        S1["get_stock_price\nrequired: ticker string"]
        S2["search_news\nrequired: query string\noptional: max_results int"]
        S3["analyse_fundamentals\nrequired: ticker string"]
        S4["compare_stocks\nrequired: tickers string array"]
    end

    subgraph Output["Returns"]
        direction TB
        O1["price, change%, volume\nmarket cap, timestamp"]
        O2["headlines, source\ndescription, date"]
        O3["P/E, P/B, ROE, EPS\nmargins, debt, 52W range"]
        O4["side-by-side table\nauto-rankings"]
    end

    S1 --> O1
    S2 --> O2
    S3 --> O3
    S4 --> O4
# How a tool is registered on the server
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="get_stock_price",
            description="Fetch the current live price for an Indian NSE stock. "
                        "Use NSE ticker format: TCS.NS, RELIANCE.NS, INFY.NS",
            inputSchema={
                "type": "object",
                "properties": {
                    "ticker": {
                        "type": "string",
                        "description": "NSE ticker symbol e.g. TCS.NS"
                    }
                },
                "required": ["ticker"]
            }
        )
    ]

Resources (3 total)

Resources are read-only context files — loaded once, injected into LLM memory.

graph TD
    subgraph RES["MCP Resources"]
        R1["📄 file://nse_symbols\nnse_symbols.json\n50 NSE tickers + sectors"]
        R2["📖 file://market_glossary\nmarket_glossary.md\n30+ finance term definitions"]
        R3["💼 file://portfolio_template\nportfolio_template.txt\n7-stock sample portfolio"]
    end

    subgraph USAGE["Injected into LLM as..."]
        U1["LLM knows all valid tickers\navoids hallucinating symbols"]
        U2["LLM explains P/E, ROE etc.\naccurately from glossary"]
        U3["System message at startup\nLLM knows user holdings\nfor entire session"]
    end

    R1 --> U1
    R2 --> U2
    R3 --> U3

    style RES fill:#4a1942,color:#fff
    style USAGE fill:#1a472a,color:#fff

Key insight: Resources avoid re-fetching static data on every request. Load once → inject → LLM remembers throughout the session.


Prompts (2 total)

Prompts are parameterised workflow templates stored on the server — the most powerful and most underused MCP primitive.

graph LR
    subgraph Client
        C1["client calls\nget_prompt()"]
    end

    subgraph Server
        T["stock_analysis.txt\n\nPerform a {{timeframe}}\nanalysis of {{ticker}}...\n[8 structured sections]"]
    end

    subgraph Filled
        F["Perform a medium-term\nanalysis of RELIANCE.NS...\n[sections filled in]"]
    end

    subgraph Result
        R["Returned as\nPromptMessage\nrole: user"]
    end

    C1 -->|"ticker=RELIANCE.NS\ntimeframe=medium-term"| T
    T --> F
    F --> R

    style Server fill:#7b2d00,color:#fff
    style Filled fill:#0f3460,color:#fff
    style Result fill:#1a472a,color:#fff

Why this matters: Any MCP client — Claude Desktop, VS Code, a mobile app — can trigger the same structured analysis workflow with a single get_prompt() call. The workflow logic lives on the server, not in the client.


📁 Project Structure

finsight-mcp/
│
├── 📂 server/                        ← MCP Server  (the "what")
│   ├── finsight_server.py            ← Tools + Resources + Prompts registered here
│   ├── 📂 tools/
│   │   ├── stock_price.py            ← get_stock_price  (yfinance)
│   │   ├── news_search.py            ← search_news      (NewsAPI + yfinance fallback)
│   │   ├── fundamentals.py           ← analyse_fundamentals  (50+ metrics)
│   │   └── compare_stocks.py         ← compare_stocks   (multi-ticker, auto-ranked)
│   └── 📂 resources/
│       ├── nse_symbols.json          ← 50 NSE tickers + company names + sectors
│       ├── market_glossary.md        ← 30+ financial term definitions
│       └── portfolio_template.txt    ← 7-stock sample user portfolio
│
├── 📂 client/
│   └── finsight_client.py            ← MCP Client  (the "how")
│                                        • Keyword router (fast path)
│                                        • Ollama integration (slow path)
│                                        • Argument name auto-fixer
│                                        • Context window management
│
├── 📂 prompts/
│   ├── stock_analysis.txt            ← 8-section deep analysis template
│   └── portfolio_review.txt          ← Portfolio score + buy/hold/sell template
│
├── 📂 tests/
│   └── test_tools.py                 ← Unit tests for all 4 tools
│
├── 📂 screenshots/                   ← Demo screenshots for this README
├── .env.example                      ← Config template (Ollama model, NewsAPI key)
├── requirements.txt
└── README.md

🛠️ Tech Stack (100% Free)

graph LR
    subgraph Free["💰 Total Cost: ₹0"]
        direction TB
        A["🐍 Python 3.11\nLanguage"]
        B["🔌 MCP SDK\nAnthropic official"]
        C["🧠 Ollama + llama3.2\nLocal LLM on M1 Mac"]
        D["📈 yfinance\nYahoo Finance API"]
        E["📰 NewsAPI\nFree tier 100 req/day"]
        F["🎨 rich\nTerminal UI"]
        G["🌐 httpx\nAsync HTTP client"]
    end

    style Free fill:#1a1a2e,color:#e0e0e0
Component Technology Why chosen
MCP Framework mcp Python SDK (Anthropic official) Official SDK, best practices built-in
Local LLM Ollama + llama3.2 Runs on M1 Mac, no API cost, fast
Stock Data yfinance Free, NSE supported, no rate limits
News NewsAPI free + yfinance fallback Graceful degradation if no key
Terminal UI rich Tables, markdown, colour-coded panels
HTTP httpx async Non-blocking Ollama communication

🚀 Setup and Installation (macOS M1)

Prerequisites

# 1. Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 2. Python 3.11
brew install python@3.11 && python3 --version

# 3. Ollama (local LLM runtime)
brew install ollama

# 4. Node.js (for MCP Inspector)
brew install node

Project Setup

git clone https://github.com/Akash-47-tank/finsight-mcp.git
cd finsight-mcp

python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env

Download LLM Model (one-time, ~2GB)

# Terminal 1 — keep running
ollama serve

# Terminal 2
ollama pull llama3.2
ollama list    # confirm it appears

▶️ How to Run

Option 1 — Full Chat Client

# Terminal 1: keep Ollama running
ollama serve

# Terminal 2: launch FinSight
source venv/bin/activate
python client/finsight_client.py

Available commands:

Command MCP Primitive Used What happens
tools list_tools + list_resources + list_prompts Shows all server capabilities
What is the price of TCS? Tool → get_stock_price Live NSE price
Compare TCS and Infosys Tool → compare_stocks Side-by-side metrics
News about Reliance Tool → search_news Recent headlines
analyse RELIANCE.NS Prompt → stock_analysis_prompt + 2 tools 8-section report
portfolio Prompt → portfolio_review_prompt + Resource Portfolio score
What does P/E mean? Resource (glossary in context) LLM answers accurately

Option 2 — MCP Inspector (Developer View)

npx @modelcontextprotocol/inspector python server/finsight_server.py
# Open http://localhost:5173

Option 3 — Run Tests

python tests/test_tools.py

🔑 Key Implementation Details

Dual-Path Tool Routing

The most important engineering decision in this project. Local LLMs (llama3.2) sometimes generate wrong argument names — e.g., {"param": "TCS.NS"} instead of {"ticker": "TCS.NS"}. I solved it with two layers of defence:

flowchart TD
    A["User Input"] --> B{Keyword\nRouter}

    B -->|price / compare / news\nkeywords detected| C["FAST PATH\nDirect tool call\nNo LLM needed\nCorrect args guaranteed"]

    B -->|Complex / ambiguous\nquery| D["SLOW PATH\nOllama generates\ntool call JSON"]

    D --> E{Arg names\ncorrect?}

    E -->|Yes| F["Call MCP tool\ndirectly"]
    E -->|No — e.g. param\ninstead of ticker| G["Auto-fixer remaps:\nparam → ticker\nsymbol → ticker\nstock → ticker"]

    G --> F
    C --> F

    F --> H["Tool executes\non server"]

    style C fill:#1a472a,color:#fff
    style G fill:#7b2d00,color:#fff
    style F fill:#0f3460,color:#fff
# Auto-fixer in extract_tool_call()
wrong_keys = {"param", "symbol", "stock", "ticker_symbol", "stock_ticker"}
for wrong in wrong_keys:
    if wrong in args and "ticker" not in args:
        args["ticker"] = args.pop(wrong)   # remap silently

Context Window Management

# Always keep system prompts + last 20 messages
if len(messages) > 22:
    messages = messages[:2] + messages[-20:]

Portfolio Resource Injection at Startup

# Loaded ONCE — stays in LLM context for entire session
portfolio = await session.read_resource("file://portfolio_template")
messages.append({
    "role": "system",
    "content": f"USER PORTFOLIO:\n{portfolio_text}"
})
# LLM now knows all 7 holdings throughout the conversation

📊 Outcomes and Results

All Features Verified Working

Feature MCP Primitive Status Actual Output
Live stock price Tool ✅ TCS ₹2,410.50 (−1.31%)
Stock comparison Tool ✅ TCS vs Infosys vs Wipro ranked table
Fundamentals analysis Tool ✅ P/E 22.47, ROE 0.6%, D/E 35.65
Financial news Tool ✅ Recent headlines with source + date
NSE symbol context Resource ✅ 50 companies loaded at startup
Finance glossary Resource ✅ LLM explains terms from loaded context
Portfolio context Resource ✅ 7 holdings injected as system memory
Deep analysis report Prompt + 2 Tools ✅ 8-section Reliance report
Portfolio review Prompt + Resource ✅ Scored review with recommendations
MCP Inspector All 3 primitives ✅ All browsable and testable

Reliance Industries — Actual Analysis Output

Price    : ₹1,380.70 (+0.25%)
P/E      : 22.47  vs sector avg 20.12  → slightly overvalued
ROE      : 0.6%   (below 15% ideal)    → concern flagged
D/E      : 35.65                        → high leverage
52W Range: ₹1,142 – ₹1,611
Trend    : Bullish (above 50 DMA ₹1,420 and 200 DMA ₹1,448)
News     : $300B US refinery · Venezuela oil strategy
Verdict  : Overvalued on P/E and P/B vs sector peers

📚 What I Learned

1. stdio Transport Is Simpler Than It Sounds

The stdio transport spawns the server as a child process and uses stdin/stdout pipes for JSON-RPC 2.0 messages. Server print() goes to stderr — MCP messages go to stdout. Keeping these separate was the key to debugging without breaking the protocol.

2. Tool Schema Quality Directly Controls LLM Behaviour

The JSON Schema is exactly what the LLM reads to understand how to call a tool. Vague schemas → wrong argument names. I learned to always put a concrete example in the description field ("e.g. TCS.NS, RELIANCE.NS") — not just abstract descriptions.

3. Resources vs Tools — Choosing Correctly Matters

  • Resources = context that doesn't change per request (glossary, symbol lists, user profile)
  • Tools = actions that need real-time data or have side effects (live prices, news search)
  • Using a Tool to serve a static glossary wastes a round-trip. Using a Resource for live prices returns stale data.

4. MCP Prompts Enable Composable AI Workflows

Server-side Prompts encode standardised workflows that any MCP client can trigger. The stock analysis workflow (template → 2 tool calls → LLM report) can be triggered from Claude Desktop, a VS Code extension, or a mobile app — without rewriting the workflow logic in each client.

5. Local LLMs Require Defensive Engineering

llama3.2 on M1 is fast and capable, but occasionally outputs wrong argument names. I built both a keyword router (avoid the LLM entirely for simple queries) and an argument auto-fixer (correct wrong names silently). Production agentic systems need validation layers, not just prompting.

6. The Server-Client Separation Is the Core Value

The MCP server has zero knowledge of Ollama. The client has zero knowledge of yfinance. They communicate only through the MCP protocol. Practical consequence:

  • Swap Ollama → Claude API? Zero server changes.
  • Swap yfinance → Bloomberg? Zero client changes.
  • Add a VS Code extension as a second client? Zero server changes.

This modularity is what makes MCP a genuine architectural standard, not just a library.


🔮 Possible Extensions

Extension Effort What it adds
Streamlit Web UI Medium Replace terminal with browser interface
Historical price charts Low get_price_history tool + matplotlib
BSE support Low Add BSE-listed tickers alongside NSE
Portfolio P&L tracker Medium Track buy prices, show real gain/loss
SSE transport Medium Deploy server remotely, not just local
Claude API Low Swap Ollama for Anthropic Claude in production
Multi-server client High Connect to stocks + news + macro MCP servers simultaneously

⚠️ Disclaimer

For educational purposes only. Nothing produced by this application constitutes financial advice. Always consult a SEBI-registered investment advisor before making investment decisions.


📄 License

MIT License — free to use, modify, and distribute with attribution.


Built to demonstrate complete Model Context Protocol (MCP) implementation skills.

Anthropic Academy — Introduction to MCP course project

⭐ Star this repo if it helped you understand MCP

github.com/Akash-47-tank/finsight-mcp