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/mcpThe Model Context Protocol is an open standard for connecting an AI assistant to a source of data. The assistant asks the server for what it needs through a fixed set of named tools, and the server answers with structured data rather than prose. Artha runs one of these servers, so the assistant you already use can read your own account directly, with your permission, instead of working from whatever you paste into it.
If you would rather see it than read about it, the wiki article What is an MCP server, and why does it matter for investors? explains it at length and shows real screenshots of this server answering in Claude and in ChatGPT.
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, and the breakdown by sector, market-cap band, or instrument type. 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
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, and reference lookup across the NSE-listed universe.
list_watchlists, list_alerts, search_symbols
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.
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 no holdings for an account that has never synced one, so the tools return an empty result with a null date and a sentence explaining why, which your assistant can relay to you word for word. Connect a broker in Artha and run a sync first, or the answers you get back will be correct and empty.
Connect a broker in settingsEvery 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.
Connecting is a settings step in your assistant, not something you can ask it to do. It takes about a minute and you do it once. After that you never touch the settings again, and you ask in plain English.
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.
Developer documentation
Twelve 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 |
| 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 |
| get_allocation | portfolio:read | by (sector, marketCap or instrument), required | by, slices with name, value and weight_pct, coverage_pct, holdings_count |
| 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. |
| 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_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, portfolio value, and risk metrics | get_portfolio_overview, get_holdings, get_allocation, get_portfolio_history, get_risk_metrics, list_broker_connections |
| watchlist:read | Read your watchlists and price alerts | list_watchlists, list_alerts, search_symbols |
| family:read | Read your family's combined exposure, for members who allowed it | 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 |
| search_symbols | 10 | 50 |
| 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"
}
}
}'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.