Model Context Protocol
A read-only MCP server over your own Artha account, so your assistant answers from your holdings instead of guessing at a screenshot.
Connector URL
https://arthaterminal.com/api/mcpFour steps
Connecting is a settings step in your assistant, not something you can ask it to do. It takes about a minute, once. After that you ask in plain English.
Copy the connector URL
It is the field at the top of this page.
Add it as a custom connector
In Claude that is Settings → Connectors → Add custom connector. In ChatGPT it is Settings → Connectors. In Cursor it is Settings → MCP → Add server.
Sign in and choose what it may read
A window opens on arthaterminal.com. Sign in, then tick the permissions you want to grant.
Ask it something
For example: what is my largest holding, and when was it last synced?
WhereSettings, then Connectors. Same steps on claude.ai in a browser.
https://arthaterminal.com/api/mcpFree accounts can hold one custom connector. Paid plans can hold several.
Seventeen tools
Five groups of read-only tools over your own account. A question that works in one connected client works in all of them, because every client calls the same tools.
Claude Desktop and Claude Code both add a remote MCP server by URL. Once you approve the connector, ask in plain English.
Totals, individual holdings, mutual funds, SIPs and fund orders, the breakdown by sector, market-cap band, or instrument type, and the stored fundamentals of the stocks you hold or watch. Every figure states when it was last synced, and the allocation states what share of the portfolio it could classify.
get_portfolio_overview, get_holdings, get_allocation, get_portfolio_history, list_broker_connections, get_mutual_fund_sips, get_mutual_fund_orders, get_fundamentals
Sharpe, Sortino, beta, R-squared, maximum drawdown, volatility, and value at risk, computed on Indian conventions: 252 trading days and a 6.5 percent risk-free rate. Every result reports the sample it rests on and flags a sample too small to quote.
get_risk_metrics
Your watchlists and the symbols on them, your price and portfolio alerts with the condition in a plain sentence, your saved screens with their conditions and match counts, and reference lookup across the NSE-listed universe.
list_watchlists, list_alerts, search_symbols, list_saved_screens
Household totals and combined exposure by symbol and by sector, across the members who chose to allow it. Aggregates only. No tool can name a member or enumerate what they hold.
list_families, get_family_overview, get_family_exposure
Who the connected account belongs to. The one tool that needs no scope at all, so a connection you approve without ticking anything still resolves your display name and when you joined.
get_profile
That is the designed behaviour, not a failure. Artha holds nothing for an account that has never synced, so each tool returns an empty result, a null date, and a sentence explaining why, which your assistant can relay word for word. Connect a broker and run a sync, or the answers stay correct and empty.
Connect a broker in settingsPermissions
You tick each permission separately when you connect. There are three, and granting one does not grant the others. A tool whose scope you did not grant is never offered to the assistant, so it cannot be called.
Every tool reads. None writes. There is no path from the MCP server to your data, your settings, or your broker.
You approve each scope separately on the consent screen. A tool whose scope you did not grant is not offered and cannot be called.
Access is granted per client through OAuth and stays tied to your Artha account. Disconnect it and Artha stops answering that assistant, and the approval is withdrawn at the login provider too.
Artha is not a broker and no write path to one exists. An assistant connected here cannot place, change, or cancel a trade.
Broker tokens and client ids never leave Artha. The connections tool returns the broker name, the connection status, and the last sync time, and nothing else.
Access is revoked from Settings → API Keys → Connected assistants, and stops on the next request. Every assistant you have connected is listed there, one row each.
Developer documentation
Seventeen tools, all read-only. Every result carries as_of, the date or timestamp of the underlying data rather than of the request, and is_demo, which is true for the seeded demo accounts. Any result holding a monetary value also carries currency: "INR". List results add truncated and total_available.
| Tool | Scope | Input | Returns |
|---|---|---|---|
| get_profile | Any valid token | No input | user_id, display_name, username, member_since |
| get_portfolio_overview | portfolio:read | No input | total_value, total_invested, total_pnl, total_pnl_percent, day_pnl, day_pnl_percent, holdings_count. Where funds are held, also breakdown (equity, and mutual_funds with nav_as_of) and day_pnl_covers |
| get_holdings | portfolio:read | limit (1 to 200, defaults to 50), sortBy (value, pnl, pnlPercent, symbol) | symbol, name, exchange, quantity, average_cost, last_close, value, unrealised_pnl, unrealised_pnl_percent, weight_pct. Where funds are held, also mutual_funds with isin, fund_name, folio, units, average_cost, nav, nav_date, value, unrealised_pnl and unrealised_pnl_percent. A fund has no exchange and no last_close |
| get_allocation | portfolio:read | by (sector, marketCap or instrument), required | by, slices with name, value and weight_pct, coverage_pct, holdings_count. Where funds are held, also mutual_funds with included, positions, value, nav_as_of and excluded_reason |
| get_portfolio_history | portfolio:read | range (1M, 3M, 6M, 1Y or MAX), required | range, points of recorded_at, value, invested and pnl |
| get_risk_metrics | portfolio:read | benchmark (NIFTY50), optional and the only accepted value | sharpe, sortino, beta, r_squared, max_drawdown_pct, volatility_pct, value_at_risk_95, value_at_risk_99, conditional_var_95, beta_coverage_pct, observations, insufficient_sample, risk_free_rate, trading_days_per_year, holdings_measured, holdings_excluded, holdings_as_of |
| list_broker_connections | portfolio:read | No input | broker, status, is_active, connected_at, last_synced. No credentials of any kind. |
| get_mutual_fund_sips | portfolio:read | limit (1 to 200, defaults to 50), includeEnded (defaults to false), both optional | summary with active_count, paused_count, ended_count, monthly_equivalent, next_instalment, due_next_30_days and settings_as_of; sips with sip_id, isin, fund_name, frequency, instalment_amount, next_instalment, status and ended_at. No NAV and no units |
| get_mutual_fund_orders | portfolio:read | limit (1 to 200, defaults to 50), cursor, period (7d, 30d, 90d, 1y or all), side, kind, state, broker, isin or schemeCode, sort (date or amount), dir (desc or asc), all optional | orders with order_id, isin, fund_name, side, kind, state, amount, units, allotment_nav, exchange_date and order_timestamp; summary with completed_purchase_total, completed_redemption_total, order_count and recorded_since; history_incomplete, next_cursor |
| list_watchlists | watchlist:read | No input | id, name, symbols, is_default, symbol_count, symbols_truncated. Tickers only, no prices. |
| list_alerts | watchlist:read | status (active or triggered), limit (1 to 200, defaults to 50), both optional | id, source, category, title, description, status, is_default, symbol, last_triggered_at, created_at |
| search_symbols | watchlist:read | query (1 to 64 characters, required), limit (1 to 50, defaults to 10) | symbol, name, exchange, type, asset_class. Reference data only, with no price, volume or valuation field. |
| get_fundamentals | portfolio:read | symbols (1 to 25 NSE tickers from the holdings or the watchlists, optional; omit to read all of them) | stocks with symbol, sector, industry, is_financial and metrics (field, label, value, unit, text, basis, as_of, source, missing_reason), symbols_without_data, symbols_not_in_account. Watchlist symbols need watchlist:read too. |
| list_saved_screens | watchlist:read | No input | id, name, universe, conditions, count and count_date of the latest nightly snapshot, changes_since_last_visit (entered, left), last_viewed_at, updated_at |
| list_families | family:read | No input | household_id, name, caller_consented. The household id the other two family tools require; no member is named and no value is reported. |
| get_family_overview | family:read | householdId (required) | household_name, member_count, consented_member_count, total_value, holdings_count |
| get_family_exposure | family:read | householdId (required), limit (1 to 25, defaults to 25) | consented_member_count, symbols with value, weight_pct and holder_count, sectors with value and weight_pct |
Scroll sideways to see every column.
There is no tool for placing, changing, or cancelling an order, and no tool that returns a market price for a symbol you do not hold. There is no positions tool either: positions live only in the broker's book, and serving them would mean calling the broker on every invocation, which the read-only design does not permit.
Three scopes, granted separately on the consent screen. Scope is enforced before a tool runs, and a token sees only the tools its scopes reach: a client without portfolio:read does not find the portfolio tools in tools/list at all.
| Scope | What you are approving | Tools it unlocks |
|---|---|---|
| portfolio:read | Read your holdings, mutual funds and SIPs, fund orders, portfolio value, risk metrics, and the stored fundamentals of those stocks | get_portfolio_overview, get_holdings, get_allocation, get_portfolio_history, get_risk_metrics, list_broker_connections, get_mutual_fund_sips, get_mutual_fund_orders, get_fundamentals |
| watchlist:read | Read your watchlists, price alerts, and saved screens | list_watchlists, list_alerts, search_symbols, list_saved_screens |
| family:read | Read your family's combined exposure, for members who allowed it | list_families, get_family_overview, get_family_exposure |
Scroll sideways to see every column.
Requests are counted against your token rather than your address, so one busy agent cannot exhaust another user's budget.
| Bucket | Limit | Applies to |
|---|---|---|
| General | 60 requests per 60 seconds | Every tool except the three analytics tools |
| Analytics | 10 requests per 60 seconds | get_risk_metrics, get_portfolio_history, get_family_exposure |
Scroll sideways to see every column.
Every list-returning tool has a hard row cap. When a cap bites, the result says so rather than trimming quietly.
| Tool | Default | Maximum |
|---|---|---|
| get_holdings | 50 | 200 |
| get_mutual_fund_sips | 50 | 200 |
| get_mutual_fund_orders | 50 | 200 |
| search_symbols | 10 | 50 |
| get_fundamentals | All account symbols, up to 25 | 25 symbols |
| list_saved_screens | All | 25 screens, 50 symbols per change list |
| list_alerts | 50 | 200 |
| list_watchlists | All | 50 lists, 200 symbols each |
| get_family_exposure | 25 | 25 |
| get_portfolio_history | Set by range | 1,825 points |
Scroll sideways to see every column.
Every failure carries a stable code and one human sentence. Only two are HTTP statuses: the transport refuses a request before it reaches a tool. The rest are tool errors inside a successful JSON-RPC response, so the status is 200 and the failure travels in the body as isError. Branching on res.ok alone will swallow four of the six. An account with nothing synced is not an error at all: the tool returns an empty result, an as_of of null, and a reason your assistant can read out.
| Code | HTTP | Meaning |
|---|---|---|
| unauthorized | HTTP 401 | The token is missing, expired, or was issued for another resource. Returned by the transport, with a WWW-Authenticate header |
| rate_limited | HTTP 429 | The bucket is exhausted. Returned by the transport, with Retry-After and X-RateLimit headers |
| insufficient_scope | Tool error (HTTP 200) | The token is valid but does not carry the scope the tool needs |
| not_found | Tool error (HTTP 200) | The household or watchlist id does not belong to the calling account |
| invalid_input | Tool error (HTTP 200) | The arguments failed schema validation. The SDK rejects these before the tool runs, so the message is its wording rather than ours |
| upstream_unavailable | Tool error (HTTP 200) | A database or service read failed |
Scroll sideways to see every column.
Use an MCP client if you have one. If you are testing by hand, protocol revision 2026-07-28 rejects a request that omits the Mcp-Method header, omits Mcp-Name on a tool call, or omits the params._meta envelope carrying the protocol version. The errors it returns read like a malformed request rather than a missing header, so copy the example whole.
curl https://arthaterminal.com/api/mcp \
-H "Authorization: Bearer $ARTHA_TOKEN" \
-H "Content-Type: application/json" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: get_portfolio_overview" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_portfolio_overview",
"arguments": {},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
}
}
}'Artha is operated by Quantrast Technologies Private Limited, CIN U66190MR2026PTC475667. Artha is not a SEBI-registered Investment Adviser or Research Analyst: no advice, no target prices, no recommendations. The server returns your own stored figures, each dated. Read the risk disclosure.
Something not working? A tool that answered wrongly, a client that will not connect, or a step above that does not match what you see.
We use analytics cookies to understand how you use Artha and improve your experience. No data is sold to third parties.