angelone-mcp
Ein MCP-Server (Model Context Protocol), der Angel Ones SmartAPI kapselt – Handel, Portfolio, Marktdaten, GTT-Regeln und Margin/Brokerage.
Dokumentation
angelone-mcp
An MCP (Model Context Protocol) server that wraps Angel One's SmartAPI — trading, portfolio, market data, GTT rules, and margin/brokerage — so any MCP client (Claude, Claude Code, etc.) can query your account and place orders through natural conversation.
⚠️
This places real orders on a real trading account. Test with small quantities first, and keep in mind Angel One (like most brokers) does not let you "undo" a filled order.
What's included
angelone_mcp/client.py– REST client for every documented SmartAPI route: auth, orders, positions/holdings, GTT rules, historical candles/OI, quotes, option greeks, gainers/losers, margin calculator, brokerage estimator. Handles TOTP login, auto re-login on token expiry, and paces itself against SmartAPI's documented rate limits (see "Rate limiting" below).angelone_mcp/server.py– MCP server exposing 32 tools built on top of the client (see full list below).
1. Prerequisites
- Python 3.10+
- An Angel One trading account with SmartAPI access
- A SmartAPI app created at https://smartapi.angelone.in/ (gives you an API key)
- TOTP set up on your Angel One account, and the base32 secret used to set up that authenticator (not the 6-digit code — the secret behind it). You get this once, when you first scan the QR code to enable TOTP; if you don't have it saved, you'll need to reset/reconfigure TOTP on your account to get a fresh secret.
2. Install
cd angelone-mcp
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
3. Configure credentials
Set these environment variables (e.g. in a .env file you source, or directly in your MCP client config):
| Variable | Description |
|---|---|
ANGELONE_API_KEY | API key from your SmartAPI app |
ANGELONE_CLIENT_CODE | Your Angel One client/trading account code |
ANGELONE_PIN | Your login PIN |
ANGELONE_TOTP_SECRET | Base32 TOTP secret for your account |
Never commit these to source control. Treat ANGELONE_TOTP_SECRET and ANGELONE_PIN like passwords — anyone with them plus your API key can trade on your account.
Optional: running behind an HTTP proxy
If your machine/network requires an outbound HTTP proxy to reach the internet, set:
| Variable | Description |
|---|---|
ANGELONE_HTTP_PROXY | Proxy URL used for http:// requests, e.g. http://user:pass@proxyhost:8080 |
ANGELONE_HTTPS_PROXY | Proxy URL used for https:// requests (this is the one that matters — SmartAPI is https-only). Falls back to ANGELONE_HTTP_PROXY if unset. |
ANGELONE_NO_PROXY | Optional comma-separated list of hosts to bypass the proxy for |
These are only needed if the standard HTTP_PROXY / HTTPS_PROXY environment variables aren't already visible to the server process. That's commonly the case for MCP servers, since MCP clients usually launch the server with an explicit env block (like the JSON below) instead of inheriting your shell's environment — so a proxy configured in your shell won't reach the server unless you either add it to that env block yourself under HTTPS_PROXY, or use the ANGELONE_* variables above. If neither ANGELONE_HTTP_PROXY nor ANGELONE_HTTPS_PROXY is set, the server falls back to the standard HTTP_PROXY / HTTPS_PROXY / NO_PROXY variables automatically.
4. Run it
Standalone (for testing):
python -m angelone_mcp.server
It speaks MCP over stdio, so it's meant to be launched by an MCP client, not run interactively.
Claude Desktop / Claude Code config
Add to your MCP client's config (e.g. claude_desktop_config.json):
{
"mcpServers": {
"angelone": {
"command": "/absolute/path/to/angelone-mcp/.venv/bin/python",
"args": ["-m", "angelone_mcp.server"],
"cwd": "/absolute/path/to/angelone-mcp",
"env": {
"ANGELONE_API_KEY": "your_api_key",
"ANGELONE_CLIENT_CODE": "your_client_code",
"ANGELONE_PIN": "your_pin",
"ANGELONE_TOTP_SECRET": "your_base32_totp_secret",
"ANGELONE_HTTPS_PROXY": "http://user:pass@proxyhost:8080"
}
}
}
}
Tools exposed
Session login, logout, get_profile
Orders place_order, modify_order, cancel_order, get_order_book, get_trade_book, get_individual_order_details
Portfolio / funds get_positions, get_holdings, get_all_holdings, get_rms_limit, convert_position
GTT (Good Till Triggered) rules gtt_create_rule, gtt_modify_rule, gtt_cancel_rule, gtt_details, gtt_list
Market data get_ltp, get_market_quote, search_scrip, get_candle_data, get_oi_data, get_option_greeks, get_gainers_losers, get_put_call_ratio, get_oi_buildup, get_nse_intraday_data, get_bse_intraday_data
Margin & brokerage get_margin, estimate_charges
How auth works
AngelOneClient logs in lazily on the first tool call using clientcode + pin + a TOTP generated on the fly from ANGELONE_TOTP_SECRET (via pyotp). It caches the resulting jwtToken, refreshToken, and feedToken in memory for the life of the process. If any call comes back with a 401/403 or a TokenException, it transparently re-logs-in once and retries — you don't need to call login yourself unless you want to force a fresh session.
Sessions issued by SmartAPI are valid until midnight IST regardless of activity, so a long-running server may still need a fresh login the next day — the auto-retry logic handles that automatically on the next call.
Session persistence across restarts
A successful login is also cached to a file on disk, so a fresh server process doesn't need a fresh TOTP-based login every time it starts (handy since TOTP requires the code to be freshly generated — restarting the server several times in a row otherwise means several real logins in a row).
On startup, before serving any tool calls, the server calls AngelOneClient.restore_session(), which:
- Looks for a previously saved session file. If there isn't one, it does nothing further — the client stays in its normal lazy mode and logs in on the first tool call, same as before this feature existed.
- If a saved session is found, it loads the cached tokens and verifies them with a real
getProfilecall. - If that verification succeeds, the restored session is used as-is — no fresh login needed.
- If it fails for any reason (expired token, revoked session, corrupt file, etc.), the cached tokens are discarded and a normal fresh login runs instead.
Every successful login (fresh or via the automatic 401/403 retry described above) re-saves the session file, so it stays current across the whole time the server runs, not just at startup. logout deletes the file.
| Variable | Description |
|---|---|
ANGELONE_SESSION_PERSIST | Set to false / 0 / no / off to disable session persistence entirely (default: enabled) |
ANGELONE_SESSION_FILE | Override the file path used to persist the session. Default: a file under the OS temp directory, named from a hash of your client code (so multiple accounts on the same machine don't collide) |
The session file holds a live access token — not your PIN or TOTP secret, but enough to call the API as you until it expires. It's written with owner-only file permissions where the OS supports it; treat it as sensitive the same way you'd treat any cached login session.
Rate limiting
AngelOneClient paces every outgoing call against SmartAPI's documented per-endpoint rate limits — login and most portfolio reads at 1 request/sec, getProfile at 3/sec, quotes/GTT/order-detail lookups at 10/sec, order placement at 20/sec, and so on. Limits are per SmartAPI endpoint, not global, so calling different tools back-to-back is never slowed down by this — only a repeat call to the same endpoint made faster than SmartAPI's own limit allows gets held back, which you'd want anyway.
If SmartAPI reports its own limit was hit regardless (HTTP 403/429, "Access denied because of exceeding access rate"), the call backs off and retries a few times with increasing delay before giving up — and that response no longer gets misread as an expired session and doesn't trigger a spurious extra login the way it used to.
This applies to every tool automatically; there's nothing to configure to get it. To turn client-side pacing off entirely (SmartAPI still enforces its own limits server-side either way — this only controls whether the client tries to stay under them proactively):
| Variable | Description |
|---|---|
ANGELONE_RATE_LIMIT_DISABLED | Set to true / 1 / yes / on to disable proactive pacing (default: enabled) |
Testing
pip install -e ".[test]"
# Offline: verifies the server registers the expected tools. No credentials
# or network access needed.
python -m pytest tests/test_tool_registration.py -v
# Offline: unit tests for session persistence (login state cached to disk,
# restored + verified via get_profile on restart, falls back to a fresh
# login when the cache is missing/invalid). Uses a fake HTTP layer - no
# credentials or network access needed.
python -m pytest tests/test_session_persistence.py -v
# Offline: unit tests for AngelOneClient's own rate limiting (pacing per
# ROUTE_MIN_INTERVAL, backoff/retry on a 403/429 rate-limit response, and
# that such a response is never misread as an expired session). Uses a fake
# HTTP layer - no credentials or network access needed.
python -m pytest tests/test_client_rate_limiting.py -v
# Live, read-only smoke test against your real account. Calls get_profile,
# get_order_book, get_holdings, search_scrip, get_ltp, etc. through the
# actual MCP server subprocess, plus a check that a session survives a
# restart of the server without calling the "login" tool again. Never calls
# place_order/modify_order/cancel_order/gtt_create_rule/gtt_modify_rule/
# gtt_cancel_rule/convert_position/logout - a SafeSession wrapper
# hard-asserts those are never invoked. On top of the server's own rate
# limiting (see "Rate limiting" above), the test itself also paces its tool
# calls and backs off/retries if the API reports one was hit anyway (see
# "Rate limiting in the live test" below) - belt and suspenders. Requires
# ANGELONE_API_KEY/ANGELONE_CLIENT_CODE/ANGELONE_PIN/ANGELONE_TOTP_SECRET
# to be set; skips automatically if they aren't.
python -m pytest tests/test_readonly_live.py -v -s
# or, for a plain-text report without pytest:
python tests/test_readonly_live.py
Rate limiting in the live test
The live test (tests/test_readonly_live.py) calls a real account against the real SmartAPI. The server it drives already paces itself (see "Rate limiting" above), but the test adds its own independent pacing on top - useful because it also exercises things the server-side limiter doesn't see by itself, like two separate server subprocesses (the session-persistence check) hitting the same account back to back:
- A
RateLimitertracks the last time each MCP tool was called and, before calling it again, waits out the rest of that endpoint's minimum interval (1/req-per-second-limit, plus a ~20% safety margin). Distinct tools hit distinct SmartAPI endpoints with independent limits, so this only ever delays a repeat call to the same tool (e.g.get_profilebeing called again by the second server spawn in the session-persistence check) - a normal single pass through the suite, where every tool is called once or twice, isn't slowed down by it in practice. - If SmartAPI reports a rate limit was hit anyway (HTTP 403, "Access denied because of exceeding access rate"), the test backs off and retries a couple of times with increasing delay instead of failing outright.
- This governs the test suite's own request pace only - it has no effect on how the MCP server behaves for a real MCP client (Claude, etc.); SmartAPI still enforces its limits server-side either way.
Notes / limitations
- Order params (
price,quantity, etc.) are passed as strings, matching what SmartAPI'splaceOrderexpects. get_marginandestimate_chargestake a list of position/order dicts — see the SmartAPI docs for exact field names per instrument type (https://smartapi.angelone.in/docs/Margin,.../Brokerage).- Rate limits are enforced by Angel One per endpoint; see https://smartapi.angelone.in/docs/RateLimit. This server does not do its own client-side rate limiting.
- Not affiliated with or endorsed by Angel One / Angel Broking.