indmoney-mcp
README.md
# INDmoney MCP Server
> A **Model Context Protocol (MCP)** server that gives Claude read-only access to your [INDmoney](https://www.indmoney.com/) portfolio — stocks, mutual funds, US stocks, gold, credit cards, liquid savings, loans, and debt.
Built with Python + Playwright. Uses browser automation for OTP login and network interception to capture live API data. Sessions are encrypted and persist across restarts.
---
## What you can ask Claude
Once connected, you can ask things like:
- *"Show me my stock holdings and P&L"*
- *"How are my mutual funds performing?"*
- *"What's my US stocks portfolio in INR?"*
- *"What's my outstanding credit card balance?"*
- *"Give me a full portfolio summary with asset allocation"*
- *"How much cash do I have in my savings accounts?"*
---
## Features
| | |
|---|---|
| **14 MCP tools** | Status, connect, disconnect, holdings, MF, US stocks, gold, loans, credit cards, liquid, debt, summary, credit score, discover |
| **Persistent sessions** | AES-256-GCM encrypted session survives server restarts (12-hour TTL) |
| **Two-level cache** | In-memory (5 min) + disk (60 min) — subsequent queries are instant |
| **Reliable fetching** | Direct JWT Bearer calls + page-nav interception fallback |
| **Read-only** | No order placement, no fund transfers — portfolio data only |
| **Type-safe** | Pydantic models throughout; all responses are structured JSON |
---
## Requirements
- Python 3.11+
- macOS / Linux / Windows (WSL2)
- [Claude Desktop](https://claude.ai/download) (or any MCP-compatible client)
- An INDmoney account
---
## Installation
### 1. Clone and install
```bash
git clone https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python.git
cd indmoney-mcp-python
python3.11 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -e .
python -m playwright install chromium
```
### 2. Generate an encryption key
The server encrypts your session on disk with AES-256-GCM. Generate a persistent key once and keep it — losing it means losing your saved session (you'll just need to log in again):
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
# example output: a3f1c8e2b4d7... ← copy this value
```
### 3. Configure Claude Desktop
Open your Claude Desktop config file and add the `indmoney` server block. All configuration is passed directly via the `env` key — **no `.env` file needed**.
| Platform | Config file location |
|----------|---------------------|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
```json
{
"mcpServers": {
"indmoney": {
"command": "/absolute/path/to/indmoney-mcp-python/venv/bin/python",
"args": ["/absolute/path/to/indmoney-mcp-python/main.py"],
"env": {
"PYTHONUNBUFFERED": "1",
"SESSION_ENCRYPTION_KEY": "paste-your-32-byte-hex-key-here"
}
}
}
}
```
> **Important**: Use the full absolute path to `venv/bin/python`, not just `python`.
#### All supported `env` variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `SESSION_ENCRYPTION_KEY` | **Recommended** | *(auto-generated)* | 32-byte hex key for AES-256-GCM session encryption. If omitted, a new key is generated on every restart and your saved session will be lost each time. |
| `SESSION_TTL_HOURS` | No | `12` | How long a logged-in session stays valid before requiring re-login |
| `CACHE_TTL_MINUTES` | No | `60` | Disk cache TTL — how long fetched portfolio data is kept on disk |
| `IN_MEMORY_CACHE_TTL_MINUTES` | No | `5` | In-memory cache TTL — subsequent queries within this window return instantly |
| `API_REQUEST_TIMEOUT` | No | `10` | Per-request timeout in seconds |
| `API_TOTAL_TIMEOUT` | No | `60` | Total timeout for a full data fetch in seconds |
| `LOG_LEVEL` | No | `INFO` | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
| `BROWSER_HEADLESS` | No | `false` | Set `true` to hide the browser window (not recommended — you need to see it for OTP login) |
| `BROWSER_DATA_DIR` | No | `./browser-data` | Path to store the persistent Playwright browser profile (cookies, localStorage) |
| `SESSIONS_DIR` | No | `./sessions` | Path to store encrypted session + cache files |
#### Advanced: full config example with all options
```json
{
"mcpServers": {
"indmoney": {
"command": "/absolute/path/to/indmoney-mcp-python/venv/bin/python",
"args": ["/absolute/path/to/indmoney-mcp-python/main.py"],
"env": {
"PYTHONUNBUFFERED": "1",
"SESSION_ENCRYPTION_KEY": "paste-your-32-byte-hex-key-here",
"SESSION_TTL_HOURS": "12",
"CACHE_TTL_MINUTES": "60",
"IN_MEMORY_CACHE_TTL_MINUTES": "5",
"LOG_LEVEL": "INFO",
"BROWSER_HEADLESS": "false"
}
}
}
}
```
> **Alternative**: Instead of the `env` block, you can copy `.env.example` to `.env` in the project root and set values there. Both approaches work — the Claude config `env` block takes precedence over `.env` if the same key appears in both.
### 3. Restart Claude Desktop
Quit completely and reopen. The `indmoney` server should appear in Settings → MCP Servers.
---
## Connecting for the first time
In Claude, say:
```
Connect me to INDmoney
```
Claude will:
1. Open a Chromium browser window
2. Navigate to INDmoney login
3. Wait while **you** enter your phone number + OTP
4. Detect successful login, save the encrypted session, and close the browser
> The OTP login window may timeout after ~30 seconds on Claude's side — **that's okay**. The browser stays open and the session is saved when you complete the OTP. Call `broker_status` afterward to confirm.
---
## MCP Tools Reference
### Session Management
| Tool | Description |
|------|-------------|
| `broker_status` | Connection status, session age, cache inventory |
| `broker_connect` | Open browser for OTP login (saves encrypted session) |
| `broker_disconnect` | Log out, wipe session + cache |
### Portfolio Data
| Tool | What it returns |
|------|----------------|
| `get_holdings` | Indian stock holdings — symbol, quantity, avg price, current value, P&L |
| `get_mutual_funds` | MF holdings — scheme name, invested, current value, returns, XIRR |
| `get_us_stocks` | US stock holdings — ticker, quantity, value in INR |
| `get_gold` | Gold holdings (digital gold, SGBs, ETFs) |
| `get_liquid` | Cash / savings — bank balances, FDs, EPF |
| `get_credit_cards` | Credit card outstanding balances and due dates |
| `get_loans` | Loan accounts and outstanding amounts |
| `get_debt` | Debt instruments (bonds, NCDs, debt MFs) |
| `get_credit_score` | Experian credit score, rating factors, active loans, and credit card limits |
| `get_portfolio_summary` | Aggregated total invested, current value, P&L, asset allocation |
### Utilities
| Tool | Description |
|------|-------------|
| `discover_endpoints` | Navigates dashboard pages and logs all live API URLs — useful if endpoints change |
---
## How it works
```
Claude ──STDIO JSON-RPC──► MCP Server (Python)
│
┌───────────┴───────────┐
│ │
Session Manager INDmoney Scraper
(disk + AES-256-GCM) (Playwright)
│ │
sessions/indmoney/ browser-data/indmoney/
├── session.json (persistent browser profile
└── cache/ with cookies + localStorage)
├── holdings.json
└── ...
```
**Authentication flow:**
1. Playwright launches a persistent Chromium context (keeps cookies/localStorage between runs)
2. User completes OTP login manually in the browser window
3. Server extracts the JWT from `document.cookie` (`token` cookie on `www.indmoney.com`)
4. JWT is stored encrypted on disk; browser context preserves cookies for the lifetime of the session
**Data fetching:**
- Most endpoints: `page.evaluate(fetch(url, {Authorization: Bearer <jwt>}))` — direct API call from the browser context (avoids CORS)
- Some endpoints (gold, fallbacks): `page.expect_response(predicate)` while navigating to the relevant SPA page — captures the first matching network response
---
## Configuration
Copy `.env.example` to `.env` and edit as needed:
```bash
cp .env.example .env
```
| Variable | Default | Description |
|----------|---------|-------------|
| `SESSION_ENCRYPTION_KEY` | *(auto-generated)* | 32-byte hex key for AES-256-GCM |
| `SESSION_TTL_HOURS` | `12` | How long a session stays valid |
| `CACHE_TTL_MINUTES` | `60` | Disk cache TTL |
| `IN_MEMORY_CACHE_TTL_MINUTES` | `5` | In-memory cache TTL |
| `API_REQUEST_TIMEOUT` | `10` | Per-request timeout (seconds) |
| `LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
| `BROWSER_HEADLESS` | `false` | Set `true` to hide the browser window |
> If `SESSION_ENCRYPTION_KEY` is empty, one is auto-generated and printed on first run. **Copy it to `.env`** to keep sessions valid across restarts.
---
## Project structure
```
indmoney-mcp-python/
├── main.py # Entry point — STDIO MCP transport
├── pyproject.toml
├── .env.example
│
├── mcp_server/
│ ├── server.py # All 14 MCP tool handlers
│ ├── config.py # Config (env vars + paths)
│ ├── logger.py # stderr-only logger
│ │
│ ├── types/ # Pydantic models
│ │ └── portfolio.py # Holding, MutualFundHolding, CreditCardAccount, …
│ │
│ ├── adapters/indmoney/
│ │ ├── scraper.py # Playwright fetch + page-nav interception
│ │ ├── endpoints.py # All API URLs (Octopus, apixt-*, etc.)
│ │ ├── auth.py # OTP login flow
│ │ └── parser.py # Raw API → Pydantic models
│ │
│ ├── session/
│ │ ├── manager.py # SessionManager singleton
│ │ ├── store.py # Disk read/write
│ │ ├── crypto.py # AES-256-GCM encrypt/decrypt
│ │ └── cache.py # Multi-level cache
│ │
│ ├── browser/
│ │ ├── manager.py # Playwright context lifecycle
│ │ └── interceptor.py # Network response interception
│ │
│ └── utils/
│ └── retry.py # Exponential backoff retry decorator
│
├── tests/
│ ├── conftest.py # Autouse fixture — isolates session dir per test
│ ├── test_integration_basic.py
│ ├── test_phase2_session_management.py
│ ├── test_phase3_browser_automation.py
│ └── test_phase4_scraper_and_parser.py
│
├── sessions/ # GITIGNORED — encrypted session data
├── browser-data/ # GITIGNORED — Playwright persistent profile
└── logs/ # GITIGNORED — runtime logs
```
---
## Development
```bash
# Activate venv
source venv/bin/activate
# Run tests
pytest tests/ -v
# Run with coverage
pytest --cov=mcp_server tests/
# Code quality
black mcp_server/
isort mcp_server/
flake8 mcp_server/
# Test server directly (no Claude needed)
python -c "
import asyncio
from mcp_server.server import INDmoneyMCPServer
async def main():
s = INDmoneyMCPServer()
r = await s._handle_broker_status({})
print(r[0].text)
asyncio.run(main())
"
# Interactive tool testing
mcp dev main.py
```
---
## Security
- **Encrypted at rest**: Session tokens are encrypted with AES-256-GCM before being written to disk. The key lives only in `.env` (gitignored).
- **OTP stays in your browser**: The OTP you type never passes through the MCP server — it goes directly from your keyboard to the INDmoney website.
- **Read-only**: The server has no tools for placing orders, transferring funds, or modifying your account.
- **Local only**: All data stays on your machine — nothing is sent to any third-party server.
> **ToS notice**: Browser-based scraping may be against INDmoney's Terms of Service. Use responsibly and at your own risk.
---
## Troubleshooting
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for detailed solutions to common issues.
**Quick fixes:**
| Symptom | Fix |
|---------|-----|
| Server not showing in Claude | Check absolute path in config; restart Claude fully |
| "not connected" after OTP | Call `broker_status` — session may have saved after timeout |
| Tools return stale data | Ask Claude to "clear cache and refresh" |
| Session deleted unexpectedly | Never run `pytest` without the test isolation fixture |
| Gold returns nothing | Your gold holding may already be in `get_holdings` (e.g., GOLDBEES ETF) |
---
## Contributing
1. Fork the repo
2. Create a feature branch: `git checkout -b feature/my-thing`
3. Write tests for your changes
4. Make sure `pytest tests/` passes
5. Open a pull request
All contributions welcome — new broker adapters, parser fixes, documentation improvements.
---
## License
[MIT](LICENSE) — free to use, modify, and distribute.
This server cannot be deployed
Maintenance
ActivityNo data
ResponsivenessSyncing