idxby Midori Research
Server MCPdocs/mcp.mdVersion 0.8.2

MCP server

idx mcp serves the Model Context Protocol over stdin/stdout. It is the same binary and the same code path as the CLI: every tool calls the provider layer and returns the envelope documented in json-output.md.

Register idx with your agent

Run idx mcp setup <client> and paste what it prints: it fills in the absolute path of the binary (GUI apps rarely inherit your shell PATH), and -o json gives the same steps as data. idx mcp setup --list names every client; --npx swaps the path for npx -y ihsg-mcp (see Without installing idx); --http URL [--token T] produces the remote form of the same entry.

The snippets below use a bare idx for brevity. Formats were checked against each vendor's documentation in October 2026; where a vendor offers both a CLI command and a file, both are shown.

Without installing idx (npx)

With Node 18 or newer, the npm package ihsg-mcp runs idx mcp with no install step. Its first start downloads the idx binary for the platform from the matching GitHub release (about 7 MB) and checks it against the release's SHA256SUMS; later starts reuse it. Download messages go to stderr, so stdout carries only the protocol, and a failed download exits 1 with the reason.

claude mcp add --scope user idx -- npx -y ihsg-mcp
{ "mcpServers": { "idx": { "command": "npx", "args": ["-y", "ihsg-mcp"] } } }

On Windows, clients start commands without a shell and npx is a .cmd script, so the command is cmd /c npx -y ihsg-mcp ("command": "cmd", "args": ["/c", "npx", "-y", "ihsg-mcp"]). idx mcp setup <client> --npx prints any client's entry in this form for the machine it runs on, and so does npx -y ihsg-mcp setup <client> (the package adds --npx to a setup that names no other form). Two things to know about the Windows form:

  • Git Bash turns a lone /c into C:/, so a command line pasted there registers a broken server. Start it with MSYS_NO_PATHCONV=1 or write cmd //c; PowerShell and cmd.exe take it as printed.
  • cmd /c exists only on Windows. A config file also used on macOS or Linux, such as a .mcp.json committed with a project, needs the plain "command": "npx", "args": ["-y", "ihsg-mcp"].

GUI apps that do not inherit your shell PATH (common on macOS with Node from nvm, fnm, Volta or Homebrew) fail to start npx (spawn npx ENOENT). Give such an entry the absolute path that command -v npx prints as command, and an env block whose PATH holds that folder, since npx and the package start node through PATH; or register the idx binary itself with idx mcp setup <client>.

Everything after the package name goes to idx mcp, and IDX_* variables from the client's env block apply as usual: npx -y ihsg-mcp --http 127.0.0.1:8765 --token T serves HTTP, npx -y ihsg-mcp --provider yahoo --offline sets the global flags. Recent npx versions (npm 10.9 was checked) ask the registry at each start and follow new releases, and the package then fetches the matching binary; if npx keeps an outdated copy, run npx -y ihsg-mcp@latest. Straight from this repository, npx -y github:fzn0x/idx mcp runs the ihsg-cli launcher instead.

Claude Code

claude mcp add --scope user idx -- idx mcp        # all projects
claude mcp add --scope project idx -e IDX_PROVIDER=yahoo -- idx mcp

Or commit .mcp.json in the repository:

{ "mcpServers": { "idx": { "command": "idx", "args": ["mcp"] } } }

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows), then restart:

{ "mcpServers": { "idx": { "command": "/abs/path/idx", "args": ["mcp"] } } }

For a remote endpoint use Settings → Connectors → Add custom connector.

OpenAI Codex (CLI, IDE extension, desktop app)

codex mcp add idx -- idx mcp

All three read the same ~/.codex/config.toml (.codex/config.toml in a trusted project also works):

[mcp_servers.idx]
command = "idx"
args = ["mcp"]

Meta Muse Code

~/.config/muse/settings.json. schema_version is mandatory; mode: optional lets a session start even when idx is missing (the default required aborts the run). /mcp inside a session lists servers.

{
  "schema_version": 1,
  "mcp_servers": {
    "idx": { "transport": "stdio", "command": "/abs/path/idx", "args": ["mcp"], "mode": "optional" }
  }
}

Remote form: { "transport": "streamable_http", "url": "https://…/mcp", "headers": { "Authorization": "Bearer T" } }.

Google Antigravity (IDE and CLI)

~/.gemini/config/mcp_config.json, or .agents/mcp_config.json in the workspace. Antigravity does not inherit your shell PATH and rejects a type field, so keep the absolute command path. /mcp opens the manager.

{ "mcpServers": { "idx": { "command": "/abs/path/idx", "args": ["mcp"] } } }

Remote form uses "serverUrl".

Gemini CLI

Free-tier Gemini CLI was replaced by Antigravity CLI in June 2026; paid installs still read ~/.gemini/settings.json.

gemini mcp add -s user idx idx mcp

GitHub Copilot CLI

copilot mcp add idx -- idx mcp

Or ~/.copilot/mcp-config.json (a committed .mcp.json is read too):

{ "mcpServers": { "idx": { "type": "local", "command": "idx", "args": ["mcp"], "tools": ["*"] } } }

VS Code (Copilot agent mode)

code --add-mcp "{\"name\":\"idx\",\"command\":\"idx\",\"args\":[\"mcp\"]}"

Or .vscode/mcp.json (note the servers key, not mcpServers):

{ "servers": { "idx": { "type": "stdio", "command": "idx", "args": ["mcp"] } } }

Cursor

~/.cursor/mcp.json (global) or .cursor/mcp.json (project):

{ "mcpServers": { "idx": { "command": "idx", "args": ["mcp"] } } }

Windsurf

~/.codeium/windsurf/mcp_config.json, same mcpServers shape; remote form uses "serverUrl".

Kiro (IDE and CLI; formerly Amazon Q Developer CLI)

~/.kiro/settings/mcp.json (user) or .kiro/settings/mcp.json (workspace):

{ "mcpServers": { "idx": { "command": "idx", "args": ["mcp"], "disabled": false } } }
kiro-cli mcp add --name idx --command idx --args mcp     # older installs: q mcp add …

JetBrains AI Assistant / Junie / Air

Settings → Tools → AI Assistant → Model Context Protocol (MCP) → + → As JSON, paste the mcpServers snippet above.

Zed

settings.json (Settings → AI → MCP Servers → Add Server also writes it):

{ "context_servers": { "idx": { "command": "idx", "args": ["mcp"], "env": {} } } }

Cline, Roo Code, Kilo Code

MCP Servers → Configure MCP Servers opens the JSON (cline_mcp_settings.json or mcp_settings.json):

{ "mcpServers": { "idx": { "type": "stdio", "command": "idx", "args": ["mcp"], "disabled": false } } }

Continue

~/.continue/mcpServers/idx.yaml:

name: idx
version: 0.0.1
schema: v1
mcpServers:
  - name: idx
    command: idx
    args: ["mcp"]

OpenCode

~/.config/opencode/opencode.json (or opencode.json in the project); opencode mcp add asks the same questions interactively:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": { "idx": { "type": "local", "command": ["idx", "mcp"], "enabled": true } }
}

Goose

goose configure → Add Extension → Command-line Extension → name idx, command idx mcp. Or ~/.config/goose/config.yaml:

extensions:
  idx:
    enabled: true
    type: stdio
    name: idx
    cmd: idx
    args: ["mcp"]
    timeout: 300

Warp

Settings → AI → Manage MCP servers → + Add, paste the mcpServers snippet; or ~/.warp/.mcp.json.

Factory Droid

droid mcp add idx "idx mcp"

Or ~/.factory/mcp.json (.factory/mcp.json in a project):

{ "mcpServers": { "idx": { "type": "stdio", "command": "idx", "args": ["mcp"] } } }

Mistral Vibe

~/.vibe/config.toml (or ./.vibe/config.toml in a trusted project). Tools appear as idx_<tool>:

[[mcp_servers]]
name = "idx"
transport = "stdio"
command = "idx"
args = ["mcp"]

Qwen Code

qwen mcp add idx idx mcp          # user scope by default; ~/.qwen/settings.json

Crush (Charm)

crush.json in the project or ~/.config/crush/crush.json:

{ "$schema": "https://charm.land/crush.json", "mcp": { "idx": { "type": "stdio", "command": "idx", "args": ["mcp"] } } }

Trae, Augment Code

Trae: AI Management → MCP → Add → Configure manually. Augment: panel → Settings → MCP → Import from JSON. Both take the mcpServers snippet.

ChatGPT, Claude.ai, Perplexity, Devin, Manus (remote only)

These products never launch local processes, so serve idx over HTTPS:

export IDX_MCP_TOKEN=$(openssl rand -hex 16)
idx mcp --http 127.0.0.1:8765 --allowed-host '*'
cloudflared tunnel --url http://127.0.0.1:8765     # or ngrok, tailscale funnel, …

Then add https://<tunnel-host>/mcp with header Authorization: Bearer <token>: ChatGPT → Settings → Apps & Connectors → Advanced → Developer mode → Create; Claude.ai → Settings → Connectors → Add custom connector; Perplexity → Settings → Connectors; Devin → Settings → MCP Marketplace → Add custom; Manus → Settings → Connectors. idx mcp setup chatgpt --http URL --token T prints the filled-in version.

All IDX_* environment variables and the config file apply to every client, so --offline behaviour, cache location, TTLs and provider choice can be set per agent (-e IDX_PROVIDER=yahoo in the CLI forms, env in the files). -e IDX_HISTORY_PROVIDER=stockbit takes candles (idx_history, idx_technical, idx_returns, idx_brief) from your Stockbit session while every other tool stays on the free providers; the envelope's provider names the source the candles came from.

HTTP transport

idx mcp --http ADDR serves MCP streamable HTTP at http://ADDR/mcp and a plain GET /healthz. It is the same server and the same 47 tools; several local agents can share one process and one cache this way.

  • --token T (or IDX_MCP_TOKEN) requires Authorization: Bearer T on /mcp. It is mandatory when binding anything other than a loopback address and strongly recommended behind a tunnel.
  • The Host header is checked against loopback names by default (DNS rebinding protection). Pass --allowed-host my.tunnel.example for a tunnel, or --allowed-host '*' to accept any host.
  • Port 0 picks a free port; the chosen address is printed on stderr.
  • Stop with Ctrl-C. Logs go to stderr as with stdio.

Tools

toolargumentsreturns
idx_briefsymbolidx.brief.v1: quote, fundamentals + verdict, technicals + signal, returns vs IHSG, dividend facts, top holders, headlines + sentiment, key_points, sections_failed. Start here.
idx_markettop?, min_market_cap?idx.market.v1: IHSG quote, breadth, turnover, gainers, losers, most traded, heavyweights
idx_market_foreigndate?, top? (default 10), sort? (value | shares), source? (idx | ipot)idx.market.foreign.v1: top foreign net buying and selling across every stock for one session, in shares; with source: "ipot", idx.market.foreign.value.v1: the 100 most traded stocks by their foreign net in rupiah, with the market's totals
idx_market_brokersdate?, top? (default 20, 0 = all), sort? (value | volume | frequency), source? (auto | idx | ipot)idx.market.brokers.v1: every exchange member's day, ranked, with its share of all members' value. auto (default) asks idx.co.id and, when it refuses (Cloudflare), cannot be reached, fails or has not published the session yet, answers from IPOT with a warning if IPOT holds that session (date, or the latest that has ended), without waiting out a Cloudflare challenge where IPOT could answer (idx.co.id is asked again for up to 30 s only if IPOT cannot); idx always retries a challenge, and its blocked error names source: "ipot" where IPOT could answer; ipot holds only its latest session and adds each broker's buy_value, sell_value, net_value and kind (figures of a million and more abbreviated by the source, rounded: true); IPOT's ranking read on a trading day from 08:58 to 19:00 WIB cannot be dated and is refused
idx_indicescodes? ("LQ45,IDX30"), date?idx.indices.v1: every IDX index for one session
idx_calendardate? or from?/to?, days? (default 14), symbol?, types? (all, or dividend,rights,...)idx.calendar.v1: dividend and rights dates, RUPS, public exposes, listings (bonds and buybacks with all)
idx_holidaysyear? (0 = every year), upcoming?idx.holidays.v1: trading holidays, trading days, today and the next trading day (bundled, offline)
idx_astrodate?, days? (default 30), natal? (launch | base | none), backtest?, period? (with backtest only: 1y | 2y | 5y | 10y | max, default max)idx.astro.v1: planets (sign, degree, speed, retrograde) at 16:00 WIB, Moon phase, Mercury retrograde, weton, shio, phases, stations and ingresses ahead, transits to IHSG's chart (computed offline); backtest adds IHSG's daily returns by lunar phase, Mercury, pasaran and Sun sign (Welch t, Bonferroni). Traditional astrology, not a forecast
idx_memberssearch?idx.members.v1: exchange members (broker codes) with licences
idx_quotesymbols ("BBCA,BBRI"; ^JKSE is IHSG)idx.quotes.v1; each row's behind_session flags a last trade dated before the latest IDX session (suspended, not traded, or the source lags)
idx_historysymbol, period?, interval? (1m needs period 1d or 5d, 2m at most 1mo), limit? (default 60 most recent candles, 0 = all)idx.history.v1
idx_intradaysymbol (^JKSE is IHSG), interval? (1m default, 2m, 5m, 15m, 30m, 1h), limit? (default 120 most recent bars, 0 = all)idx.intraday.v1: the latest session by the minute, the quote, and a summary of the bars with VWAP computed by idx
idx_profilesymbolidx.profile.v1
idx_financialssymbol, period? (annual | quarterly | ttm)idx.financials.v1
idx_earningssymbol, period? (annual | quarterly), kind? (reported | estimates)idx.earnings.v1
idx_dividendssymbolidx.dividends.v1: payments, splits, yearly totals, TTM total/yield, streak
idx_returnssymbols, benchmark? (^JKSE default, none)idx.returns.v1: windows, CAGR, volatility, drawdowns, 52-week, excess over the benchmark
idx_fundamentalsymbol, lens? (growth | valuation | risk)idx.fundamentals.v1 (verdict included; when financial_currency differs from currency, P/B, P/S and the EV multiples mix USD and IDR and P/B is not scored); with lens, idx.growth.v1, idx.valuation.v1 or idx.risk.v1: a focused reading with every rule and its points
idx_technicalsymbol, period?idx.technical.v1
idx_comparesymbolsidx.compare.v1
idx_screenpreset?, filter?, sort?, ascending?, limit?, max_fundamentals?, returns?idx.screen.v1: presets top-performers, worst-performers, high-dividend, low-pe, 52w-high, 52w-low, high-volume, large-cap; keys include range (52-week position) and ret1w..ret1y (price return %, as MSN reports it, one market-wide request); a row whose financial_currency differs from currency keeps its P/B and P/S as reported, but no pb or ps clause matches them, and a pb or ps sort puts them last
idx_newssymbol?, sources?, limit?, company_only?idx.news.v1: company items first (scope=company), then market context
idx_sentimentsymbol, sources?idx.sentiment.v1
idx_insightssymbolidx.insights.v1: about 30 MSN Money statements rated good, bad, neutral or not_rated against peers, the industry, the company's own history, the price or a threshold; the peer tickers
idx_corporate_actionssymbol, since? (default a year ago)idx.actions.v1: dividends, splits, bonus, rights (amount, ratio, cum/ex/recording/payment dates, status), RUPS, warrants and rights, upcoming included (IPOT)
idx_foreignsymbol, days? (default 20; at most 250 from idx, 60 from ipot), from?, to? (a date range instead), source? (idx, the default, or ipot)idx.foreign.v1: foreign buy, sell and net per session (shares), net × close, totals and streak (idx.co.id); with source: "ipot" idx.foreign.value.v1: foreign buy, sell and net value in rupiah across all boards, value traded, foreign share, totals and streak (IPOT)
idx_instrumentsboard?, search?idx.instruments.v1: listed stocks with board, listing date and shares
idx_brokersymbols, date? or from?/to?, investor? (all | foreign | domestic), board? (all | regular | cash | negotiated), source? (auto | ipot | stockbit)idx.broker.v1: buyers and sellers ranked by lots with broker names and class, totals, foreign net, buy_value and sell_value (for one investor side the totals are null and these say what the side bought and sold); latest session with trades when no dates are given. auto (default) lists every broker from Stockbit when the user's Stockbit session works (idx_stockbit_status), otherwise IPOT's top 10 per side with a warning that a login lists every broker; stockbit without a working session is a usage error. Each summary carries source, buyer_count, seller_count, buyers_complete and sellers_complete
idx_bandarsymbols, period? (1d | 1w default | 1m | 3m), date? or from?/to?, top? (1, 3, 5 default, 10), investor?, board?, daily?idx.bandar.v1: bandarmology per ticker: top-N net as a share of value, signal and phase, tiers, the top buyers' average against the price, foreign flow, reasons; daily adds the session series
idx_bandar_screenpreset?, filter?, period?, top?, sort?, ascending?, limit?, scan? (default 100), symbols?idx.bandar.screen.v1: the most traded stocks the top brokers accumulate or distribute
idx_bandar_brokercode, period?, limit? (default 20), scan? (default 100), symbols?idx.bandar.broker.v1: where one broker is a top-10 buyer or seller across the scanned stocks, with net where both sides are visible
idx_ownership_tickersymbol, as_of?idx.ownership.holders.v1
idx_ownership_entityname, as_of?, exact? (match one entity_key)idx.ownership.entity.v1
idx_ownership_changesfrom, to, symbol?, min_pct_change?, limit?, offset?idx.ownership.changes.v1: summary over every match, one page of rows
idx_ownership_foreignsymbol?, from?, to?, limit?idx.ownership.foreign.v1 (one issuer over time) or idx.ownership.foreign-flow.v1 (every issuer between two months)
idx_ownership_releasesnoneidx.ownership.releases.v1
idx_ownership_searchquery, limit? (default 20, 0 = all, at most 100), as_of?idx.ownership.search.v1: holders matching a name (every spelling, words in any order) with their entity_key, spellings, issuers held and last_seen
idx_ownership_rankingsview? (holders default | issuers); holders: min_issuers? (default 2), investor_type?, origin? (foreign | local); issuers: symbols?, by? (hhi default | top1 | top3), ascending?; limit? (default 25, 0 = all), as_of?idx.ownership.cross-holders.v1 (holders by the issuers they own above 1 %) or idx.ownership.concentration.v1 (issuers by HHI, top-1 and top-3 stakes, undisclosed percent, controller stake)
idx_ownership_graphroot (ticker or holder), depth? (1-6, default 2), min_pct?, max_nodes? (default 100, at most 1000), holder?, as_of?idx.ownership.graph.v1: issuers and holders with hop counts and the links between them; truncated when max_nodes stopped the walk
idx_ownership_compositionsymbol, as_of?idx.ownership.composition.v1: KSEI's monthly archive, local vs foreign shares in nine investor types as a percent of the scripless shares, scripless_pct, and every imported month. Downloads the newest month from ksei.co.id on first use (not with --offline)
idx_ownership_snapshotaction (status | sync), force? (sync only)status: idx.ownership.status.v1, the local database, the snapshot it came from, the mirror and what sync would do (writes nothing). sync: idx.ownership.snapshot-sync.v1, installs the prebuilt snapshot or imports the missing months; months only the local database has are kept unless force (a .bak copy is kept)
idx_ownership_institutionssymbolidx.ownership.institutions.v1: institutions and funds holding the stock (MSN, live, no import), each with shares, % of shares outstanding, the change since its own previous report and a signal (new, buyer, seller, exited, unchanged), plus institutional, fund and free-float percentages and signal counts per list
idx_orderbooksymbolidx.orderbook.v1: realtime depth ladder (bids/asks, shares), last/OHLC, volume, value and foreign buy/sell/net (null, with a warning, while Stockbit marks them as not current, seen during trading). Needs a Stockbit login (--provider stockbit)
idx_stockbit_statusnonewhether the stored Stockbit session can work now (logged_in; expired, renewable and reason say why not, or why idx cannot renew it) and when its tokens expire (never the token values); sign_in while a page sign-in runs
idx_stockbit_loginmethod? (page | browser | auto), port?, force?, timeout?page returns at once with sign_in.url, a page on 127.0.0.1 (and sign_in.ssh_forward over SSH) where the user sends the session from their own browser; poll idx_stockbit_status until sign_in.state is signed_in or failed. browser opens a browser on the server's machine and waits (local stdio servers). auto picks page over SSH, without a display or a browser. The session is checked with Stockbit before it is stored; one that already works and that idx can renew is reported (already_signed_in) unless force
idx_stockbit_actionssymbol?, kind?, from?, to?, days?idx.stockbit.actions.v1: with symbol, one stock's corporate actions since listing, newest first; without, the market calendar from from to to (default today plus 14 days, at most 366), oldest first, open rights, tender and IPO offers included. kind: comma separated, or all (default: all but ipo for a stock, all but warrant for the calendar). Needs a Stockbit login
idx_stockbit_movementssymbol?, from?, to?, holder?, action? (buy | sell | transfer | cross | corporate_action), source? (idx | ksei), pages? (1-10, default 1)idx.stockbit.movements.v1: insider disclosures and 5 % holder changes, newest first; shares exact and signed, stakes as rounded, filed price/value apart from Stockbit's estimated_price/estimated_value; more when rows remain. Needs a Stockbit login

The last five carry your own Stockbit session (opt-in); see stockbit.md.

Paging: idx_ownership_changes returns total_matches and offset; ask for the next page with offset = offset + limit until offset + rows >= total_matches. The summary block never depends on the page, so one call is enough to describe a month.

Ownership rows are positions: a holder's register lines (scrip and scripless accounts) are summed, with accounts saying how many, and from the 30 Oct 2026 register each row carries controller_or_affiliate (controller_reported on the report says whether the release has it). idx_ownership_entity needs at least 3 letters or digits in name.

To look a holder up, call idx_ownership_search first and pass the entity_key it returns to idx_ownership_entity with exact: true, or to idx_ownership_graph as root. idx_ownership_rankings passes view-specific arguments to one view only. An argument of the other view is a usage error unless it holds its default: symbols, by other than hhi or ascending: true with view: "holders", and min_issuers other than 2, investor_type or origin with view: "issuers". The spelling rules of idx ownership resolve (merging holders KSEI printed two ways) change the user's database, so they stay in the CLI; the MCP tools read the result.

idx_ownership_composition reads a second dataset: KSEI's "Kepemilikan Efek" archive, which counts every holder and names none. It covers the shares held in KSEI accounts only, so its percents are of those (scripless_pct says how much of the listed shares that is). The first call downloads the newest month (about 125 KB) into the same local database; later calls read it and ask ksei.co.id again only once a newer month can exist. provider is ksei when the call imported a month, local-db otherwise.

An empty ownership database is filled by idx_ownership_snapshot with action: "sync": one download of the SQLite file CI builds from the mirrored IDX files (SHA-256 checked, swapped in as one SQLite transaction, so other calls reading the file are not disturbed), then any newer monthly file. It needs the network (an offline_miss error with --offline); action: "status" works offline and reports the local part. See ownership.md.

Harness compatibility

Input schemas are rewritten before they are advertised: no $schema, no format (uint32, double), and optional fields are typed "string" not ["string", "null"] (they are simply absent from required). Claude accepts either form; Gemini function declarations and OpenAI strict tool mode reject the latter, and a unit test plus the stdio test keep it that way.

Tool results are a single text content block holding the pretty-printed envelope. Failures set isError: true and the text is the CLI error object:

{"error":{"code":"not_found","message":"not found: ZZZZ: Quote not found for symbol: ZZZZ.JK"},"exit_code":4}

code values and their meaning are listed in json-output.md. blocked (exit 3) means the source's bot protection (Cloudflare, idx.co.id) refused the request. For idx.co.id's data a challenged request is already retried for 30 s, and a cached copy, when there is one, is served instead of the error. It usually passes within a minute: call the tool again then. If it keeps refusing, the server's network is likely refused (data centres, VPNs and CI runners are refused more often), which only the user can change. Or take the alternative the message names. Over MCP that advice is a tool argument, not a CLI flag (idx_foreign and idx_market_foreign with source: "ipot", idx_market_brokers with source: "ipot" for IPOT's latest session). For the ownership register, let the GitHub mirror serve it.

Behaviour notes

  • Logs go to stderr (-v raises the level, IDX_LOG overrides). stdout carries only protocol frames.
  • Responses are cached exactly like the CLI, so an agent calling idx_quote twice within the quote TTL hits the network once.
  • A cached copy older than its TTL is served when the source fails or refuses the request (or the server runs with --offline): the envelope then has stale: true, stale_reason (fetch_failed or offline) and, after a failure, a warning naming it.
  • The server exits cleanly when the client closes stdin.
  • Verdicts and signals are heuristics; the tool descriptions and the server instructions say so, and so should any agent that repeats them.
  • Tickers are IDX codes. The ten Yahoo-backed tools (idx_quote, idx_history, idx_profile, idx_financials, idx_earnings, idx_dividends, idx_returns, idx_fundamental, idx_technical, idx_compare) also take a ticker from another Yahoo Finance market with its suffix (D05.SI), returned with that suffix; every other tool refuses one with a usage error. The server does not use IDX_EXCHANGE, so a bare code always means IDX, though an invalid value fails startup like any other invalid setting (exit 6).

Verifying

claude mcp get idx            # health check: "Connected"
cargo test --test mcp         # JSON-RPC over stdio without network

From docs/mcp.md on the main branch, version 0.8.2. Back to the top