Skip to main content
Glama
angelone-mcp

angelone-mcp

by pyalgobot

angelone-mcp

Listed on mcpservers.org

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

Related MCP server: Zerodha MCP Server

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:

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

  2. If a saved session is found, it loads the cached tokens and verifies them with a real getProfile call.

  3. If that verification succeeds, the restored session is used as-is — no fresh login needed.

  4. 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 RateLimiter tracks 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_profile being 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's placeOrder expects.

  • get_margin and estimate_charges take 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.

Available Tools

32 tools
cancel_orderA

Cancel an open order by its order id. variety: NORMAL | STOPLOSS | AMO | ROBO

Inputs: variety (required), order_id (required). Returns: result

convert_positionB

Convert a position from one product type to another (e.g. INTRADAY -> DELIVERY).

Inputs: exchange (required), quantity (required), symboltoken (required), tradingsymbol (required), +4 more. Returns: result

estimate_chargesA

Estimate brokerage and other charges for a basket of prospective orders.

Each order dict needs: product_type, transaction_type, quantity, price, exchange, symbol_name, token.

Inputs: orders (required). Returns: result

get_all_holdingsA

Get holdings plus a portfolio-level summary (total investment, current value, P&L).

Inputs: no named parameters. Returns: result

get_bse_intraday_dataA

Get BSE intraday most-active-by-volume/value data.

Inputs: no named parameters. Returns: result

get_candle_dataB

Get historical OHLCV candle data.

interval: ONE_MINUTE | THREE_MINUTE | FIVE_MINUTE | TEN_MINUTE | FIFTEEN_MINUTE | THIRTY_MINUTE | ONE_HOUR | ONE_DAY fromdate/todate format: "YYYY-MM-DD HH:MM" (e.g. "2024-01-01 09:15")

Inputs: todate (required), exchange (required), fromdate (required), interval (required), +1 more. Returns: result

get_gainers_losersB

Get top F&O gainers/losers.

datatype: PercPriceGainers | PercPriceLosers | PercOIGainers | PercOILosers expirytype: NEAR | NEXT | FAR

Inputs: datatype (required), expirytype (required). Returns: result

get_holdingsB

Get the equity holdings currently in the demat account.

Inputs: no named parameters. Returns: result

get_individual_order_detailsA

Get full lifecycle detail/history for a single order by its unique order id.

Inputs: unique_order_id (required). Returns: result

get_ltpB

Get the last traded price (LTP) for a single instrument.

Inputs: exchange (required), symboltoken (required), tradingsymbol (required). Returns: result

get_marginA

Calculate span + exposure margin required for a basket of positions before placing them.

Each position dict needs: exchange, qty, price, productType, token, tradeType (BUY/SELL), orderType.

Inputs: positions (required). Returns: result

get_market_quoteA

Get market quotes for up to 50 instruments per exchange in one call.

mode: LTP | OHLC | FULL exchange_tokens: e.g. {"NSE": ["3045", "881"], "NFO": ["58662"]} - a map of exchange -> list of symbol tokens.

Inputs: mode (required), exchange_tokens (required). Returns: result

get_nse_intraday_dataA

Get NSE intraday most-active-by-volume/value data.

Inputs: no named parameters. Returns: result

get_oi_buildupB

Get open-interest buildup data (long/short buildup, unwinding, etc.) for F&O contracts.

datatype: Long Built Up | Short Built Up | Short Covering | Long Unwinding expirytype: NEAR | NEXT | FAR

Inputs: datatype (required), expirytype (required). Returns: result

get_oi_dataB

Get historical open-interest (OI) data for F&O instruments. Same interval/date format as get_candle_data.

Inputs: todate (required), exchange (required), fromdate (required), interval (required), +1 more. Returns: result

get_option_greeksA

Get option greeks (delta, gamma, theta, vega, IV) for all strikes of an underlying. name: e.g. 'NIFTY'. expirydate format: '25MAR2024'.

Inputs: name (required), expirydate (required). Returns: result

get_order_bookA

Get all orders placed today, with their current status.

Inputs: no named parameters. Returns: result

get_positionsA

Get the day's open and net positions (intraday + carryforward).

Inputs: no named parameters. Returns: result

get_profileA

Get the logged-in user's profile: client code, name, email, exchanges enabled, products enabled, and broker.

Inputs: no named parameters. Returns: result

get_put_call_ratioA

Get the current put-call ratio (PCR) across index/stock option contracts.

Inputs: no named parameters. Returns: result

get_rms_limitB

Get available margin / funds (RMS limits): net cash, available margin, utilised margin, etc.

Inputs: no named parameters. Returns: result

get_trade_bookB

Get all executed trades for the day.

Inputs: no named parameters. Returns: result

gtt_cancel_ruleB

Cancel a GTT rule by its id.

Inputs: id (required), exchange (required), symboltoken (required). Returns: result

gtt_create_ruleA

Create a GTT (Good Till Triggered) rule that auto-places an order when the trigger price is hit.

Inputs: qty (required), price (required), exchange (required), producttype (required), +6 more. Returns: result

gtt_detailsB

Get details of a single GTT rule by id.

Inputs: rule_id (required). Returns: result

gtt_listB

List GTT rules. status is a list of any of: NEW, CANCELLED, ACTIVE, SENTTOEXCHANGE, FORALL, REJECTED, EXPIRED, DELETED.

Inputs: status (required), page, count. Returns: result

gtt_modify_ruleC

Modify an existing GTT rule.

Inputs: id (required), qty (required), price (required), exchange (required), +5 more. Returns: result

loginA

Explicitly (re)authenticate with Angel One using the configured client code, PIN, and TOTP secret. Normally not needed - the client logs in automatically on first use - but useful to force a fresh session or verify credentials are configured correctly.

Inputs: no named parameters. Returns: result

logoutA

Terminate the current Angel One trading session.

Inputs: no named parameters. Returns: result

modify_orderA

Modify an existing open order. All identifying fields (tradingsymbol, symboltoken, exchange) must match the original order.

Inputs: orderid (required), variety (required), duration (required), exchange (required), +7 more. Returns: result

place_orderB

Place an order.

variety: NORMAL | STOPLOSS | AMO | ROBO transactiontype: BUY | SELL exchange: NSE | BSE | NFO | MCX | BFO | CDS ordertype: MARKET | LIMIT | STOPLOSS_LIMIT | STOPLOSS_MARKET producttype: DELIVERY | CARRYFORWARD | MARGIN | INTRADAY | BO duration: DAY | IOC price/triggerprice: required for LIMIT/STOPLOSS order types (as strings, e.g. "199.50") squareoff/stoploss/trailingStopLoss: only used when variety=ROBO (bracket order)

Inputs: variety (required), exchange (required), quantity (required), ordertype (required), +12 more. Returns: result

search_scripA

Search for the tradingsymbol and symboltoken of an instrument by name, e.g. exchange='NSE', searchscrip='INFY'. Use this to resolve symboltoken before placing orders or requesting quotes.

Inputs: exchange (required), searchscrip (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 32 tools

    Disambiguation4/5

    Most tools target distinct actions and resources—orders, positions, holdings, GTT rules, market data, and analytics are clearly separated. Minor overlap exists between get_holdings and get_all_holdings, and between get_ltp and get_market_quote, but descriptions clarify their scope.

    Naming Consistency4/5

    The naming mostly follows a clear verb_noun snake_case pattern, such as place_order, cancel_order, get_positions, and search_scrip. The gtt_* tools deviate by leading with the domain prefix instead of the verb, and a few names like get_all_holdings vs get_holdings are slightly inconsistent, but overall the pattern is predictable.

    Tool Count2/5

    With 32 tools, the surface feels heavy and exceeds the 25-tool threshold that typically signals over-expansion. Many market-data and analytics tools could be consolidated or grouped, though the breadth is understandable for a full brokerage API.

    Completeness4/5

    The tool surface covers the core brokerage lifecycle well: authentication, order placement/modification/cancellation, order and trade books, positions, holdings, GTT rules, margin checks, and charge estimation. Some gaps exist such as historical order history or bulk order capabilities, but agents can accomplish most common trading workflows without dead ends.

    Maintenance

    ActivityStale
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      Enables trading and portfolio management on Zerodha Kite Connect through natural language. Supports placing orders, viewing positions/holdings, accessing real-time market data, and managing GTT orders.
      24
      MIT
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables natural language access to Zerodha trading accounts for retrieving portfolio holdings, positions, orders, funds, and real-time market prices. Supports secure authentication for Indian stock market trading operations via Claude, Cursor, and other MCP-compatible AI tools.
      1
      -
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables trading Indian stocks on ICICI Direct through natural conversation with any MCP-compatible AI assistant, featuring automated TOTP login and tools for portfolio monitoring, order placement, and market data.
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables natural language management of Zerodha trading accounts, including placing orders, checking portfolio, and viewing positions through MCP integration.
      32
      Apache 2.0