Metadata-Version: 2.4
Name: indmoney-mcp
Version: 1.0.0
Summary: MCP server for INDmoney — read-only portfolio access via Claude (stocks, MF, US stocks, gold, credit cards, liquid, loans, debt)
Author-email: Vigneshwaran M <vickymsmuthu@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python
Project-URL: Repository, https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python
Project-URL: Documentation, https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python#readme
Project-URL: Bug Tracker, https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python/issues
Keywords: mcp,indmoney,portfolio,finance,claude,broker,playwright
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp>=0.1.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: playwright>=1.48.0
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: cryptography>=41.0.0
Requires-Dist: orjson>=3.9.0
Provides-Extra: dev
Requires-Dist: pytest>=7.4.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: isort>=5.12.0; extra == "dev"
Requires-Dist: mypy>=1.5.0; extra == "dev"
Requires-Dist: flake8>=6.0.0; extra == "dev"
Dynamic: license-file

# 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.
