Skip to main content
Glama
Zalfrie

idx-stock-mcp

by Zalfrie
README.md
# idx-stock-mcp

[![CI](https://github.com/Zalfrie/idx-stock-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Zalfrie/idx-stock-mcp/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/idx-stock-mcp.svg)](https://www.npmjs.com/package/idx-stock-mcp)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![Node.js β‰₯20](https://img.shields.io/badge/node-%E2%89%A520-brightgreen.svg)

MCP server agar Claude bisa membaca harga saham **Bursa Efek Indonesia (IDX/BEI)**, membandingkan performa saham, dan menghitung nilai portofolio pribadi Anda langsung dari percakapan.

> ⚠️ Server ini **hanya membaca data**. Tidak ada fitur beli/jual dan tidak terhubung ke akun sekuritas mana pun (Bibit, Stockbit, Ajaib, dll). Data untuk informasi, bukan rekomendasi investasi.

> πŸ§ͺ **Status:** parser data diuji dengan 53 test offline (data tiruan berformat respons Yahoo Finance) dan keempat tool sudah diverifikasi dengan data live BEI pada 19 Okt 2026 (harga, perubahan harian, dividen, dan total return cocok dengan respons mentah Yahoo). Jika menemukan angka yang berbeda dengan aplikasi sekuritas Anda, mohon [laporkan](https://github.com/Zalfrie/idx-stock-mcp/issues/new/choose).

## Daftar isi

- [Fitur](#fitur)
- [Cara pakai (langkah demi langkah)](#cara-pakai-langkah-demi-langkah)
  - [1. Persiapan](#1-persiapan)
  - [2. Download & build](#2-download--build)
  - [3. Siapkan file portofolio](#3-siapkan-file-portofolio-opsional)
  - [4. Hubungkan ke Claude](#4-hubungkan-ke-claude)
  - [5. Mulai bertanya](#5-mulai-bertanya)
- [Format file portofolio](#format-file-portofolio)
- [Referensi tools](#referensi-tools)
- [Troubleshooting](#troubleshooting)
- [Sumber data & batasan](#sumber-data--batasan)
- [Pengembangan](#pengembangan)
- [Kontribusi](#kontribusi)

## Fitur

| Tool | Fungsi |
| --- | --- |
| `get_quote` | Harga terakhir, perubahan harian, rentang 52 minggu, dan harga per lot (100 lembar), lengkap dengan **status pasar** (buka/tutup) dan **waktu data dalam WIB**. |
| `get_history` | Riwayat harga (OHLC + volume) dengan return **harga saja** dan **total return termasuk dividen**, daftar dividen & stock split, tertinggi, terendah, dan max drawdown. |
| `compare_stocks` | Bandingkan 2–10 saham/indeks pada rentang waktu yang sama, diurutkan berdasarkan total return (atau return harga jika data dividen tidak lengkap). |
| `get_portfolio` | Hitung nilai portofolio Anda: untung/rugi per saham dan total, estimasi hasil bersih setelah fee jual, serta **peringatan** jika data tidak lengkap, ada stock split, atau nilai reksa dana sudah lama tidak diperbarui. |

Prinsipnya: **lebih baik memberi peringatan daripada angka yang terlihat valid padahal tidak.**

Kode saham bisa ditulis `BBCA`, `bbca`, atau `BBCA.JK`. Untuk indeks gunakan `IHSG`.

## Cara pakai (langkah demi langkah)

### 1. Persiapan

- **Node.js 20 atau lebih baru.** Cek dengan `node -v`. Download di <https://nodejs.org>.
- **Git**, hanya jika memilih instalasi dari source.
- **Claude Desktop** atau **Claude Code** di komputer Anda.

### 2. Download & build

**Opsi cepat (disarankan): tanpa download.** Paket tersedia di [npm](https://www.npmjs.com/package/idx-stock-mcp), jadi Claude
bisa menjalankannya langsung lewat `npx`. Lewati langkah ini, lanjut ke langkah 3, dan di langkah 4 pakai konfigurasi `npx`.
Saat pertama dipanggil, `npx` mengunduh paket (butuh internet, beberapa detik); setelah itu memakai cache.

**Opsi dari source**, jika ingin mengubah kode atau menjalankan test:

```bash
git clone https://github.com/Zalfrie/idx-stock-mcp.git
cd idx-stock-mcp
npm install
npm run build
```

Opsional, pastikan semuanya berjalan:

```bash
npm test          # menjalankan unit test (tanpa internet)
npm run inspect   # membuka MCP Inspector di browser untuk mencoba tools dengan data live
```

Di MCP Inspector, klik **Connect** β†’ tab **Tools** β†’ pilih `get_quote` β†’ isi `ticker` dengan `BBCA` β†’ **Run Tool**. Kalau harga muncul, server siap dipakai.

### 3. Siapkan file portofolio (opsional)

Lewati langkah ini jika Anda hanya ingin melihat harga saham.

Buat file `portfolio.json` di folder mana saja (mis. folder home Anda) dengan isi seperti
[portfolio.example.json](portfolio.example.json), lalu sesuaikan dengan kepemilikan Anda (lihat [format lengkapnya](#format-file-portofolio)).
Jika memakai opsi dari source: `cp portfolio.example.json portfolio.json` di dalam folder repo; file ini sudah masuk `.gitignore`.

Dua field yang sering terlewat:

- **`buyDate`** (`YYYY-MM-DD`) di setiap saham. Dipakai untuk mendeteksi stock split setelah Anda membeli. Tanpa field ini, Anda akan mendapat peringatan setiap kali melihat portofolio.
- **`updatedAt`** (`YYYY-MM-DD`) di setiap aset `manual` (mis. reksa dana). Perbarui setiap kali Anda mengubah `currentValue`. Jika lebih dari 7 hari (atau kosong), nilai tersebut ditandai usang.

Catat **path lengkap** file `portfolio.json`, karena dibutuhkan di langkah berikutnya.

### 4. Hubungkan ke Claude

#### Opsi A β€” Claude Desktop

1. Buka Claude Desktop β†’ **Settings** β†’ **Developer** β†’ **Edit Config**. Ini membuka file `claude_desktop_config.json`:
   - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
   - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
2. Tambahkan konfigurasi berikut (ganti path `portfolio.json` sesuai lokasi file Anda; hapus bagian `env` jika tidak memakai portofolio):

   **Opsi cepat (npx)** β€” macOS / Linux
   ```json
   {
     "mcpServers": {
       "idx-stock": {
         "command": "npx",
         "args": ["-y", "idx-stock-mcp"],
         "env": {
           "IDX_PORTFOLIO_PATH": "/Users/nama/portfolio.json"
         }
       }
     }
   }
   ```

   **Opsi cepat (npx)** β€” Windows (Claude Desktop di Windows lebih andal memanggil `npx` lewat `cmd`)
   ```json
   {
     "mcpServers": {
       "idx-stock": {
         "command": "cmd",
         "args": ["/c", "npx", "-y", "idx-stock-mcp"],
         "env": {
           "IDX_PORTFOLIO_PATH": "C:\Users\nama\portfolio.json"
         }
       }
     }
   }
   ```

   **Opsi dari source** β€” macOS / Linux
   ```json
   {
     "mcpServers": {
       "idx-stock": {
         "command": "node",
         "args": ["/Users/nama/idx-stock-mcp/dist/index.js"],
         "env": {
           "IDX_PORTFOLIO_PATH": "/Users/nama/idx-stock-mcp/portfolio.json"
         }
       }
     }
   }
   ```

   **Opsi dari source** β€” Windows (gunakan `\\` pada path)
   ```json
   {
     "mcpServers": {
       "idx-stock": {
         "command": "node",
         "args": ["C:\\Users\\nama\\idx-stock-mcp\\dist\\index.js"],
         "env": {
           "IDX_PORTFOLIO_PATH": "C:\\Users\\nama\\idx-stock-mcp\\portfolio.json"
         }
       }
     }
   }
   ```

   Jika file config sudah berisi server lain, tambahkan `"idx-stock": {...}` di dalam `mcpServers` yang sudah ada.
3. **Tutup Claude Desktop sepenuhnya** (quit, bukan hanya menutup jendela), lalu buka lagi.
4. Di kotak chat, klik ikon tools/connector. `idx-stock` dengan 4 tools harus muncul di sana.

#### Opsi B β€” Claude Code

Opsi cepat (npx):

```bash
claude mcp add idx-stock -e IDX_PORTFOLIO_PATH=/path/lengkap/portfolio.json -- npx -y idx-stock-mcp
```

Opsi dari source:

```bash
claude mcp add idx-stock \
  -e IDX_PORTFOLIO_PATH=/path/lengkap/idx-stock-mcp/portfolio.json \
  -- node /path/lengkap/idx-stock-mcp/dist/index.js
```

Cek dengan `claude mcp list`, atau ketik `/mcp` di dalam sesi Claude Code.

#### Pastikan sudah terhubung dengan benar

Buka chat baru dan tanyakan:

> Berapa harga BBCA sekarang?

Jawaban yang benar menyebut **harga**, **waktu data dalam WIB**, dan **status pasar** (buka/tutup). Contohnya: "...per Sen, 19 Okt 2026 16:00 WIB, pasar sedang tutup". Jika Claude menjawab tanpa memanggil tool `get_quote`, atau tidak menyebut waktu data, kemungkinan server belum terhubung. Lihat [Troubleshooting](#troubleshooting).

### 5. Mulai bertanya

Cukup bertanya dengan bahasa biasa, dan Claude akan memanggil tool yang sesuai.

| Anda bertanya | Tool yang dipakai |
| --- | --- |
| "Berapa harga BBCA sekarang? 1 lotnya berapa?" | `get_quote` |
| "Bagaimana kondisi IHSG hari ini?" | `get_quote` |
| "Pasar sudah buka belum? Harga BBRI ini data jam berapa?" | `get_quote` |
| "Tunjukkan pergerakan TLKM 1 tahun terakhir, turunnya paling dalam berapa persen?" | `get_history` |
| "Berapa total return BBRI setahun terakhir termasuk dividen? Dividennya berapa per lembar?" | `get_history` |
| "Bandingkan BBCA, BBRI, BMRI, dan IHSG selama 6 bulan." | `compare_stocks` |
| "Bank mana yang paling tahan banting tahun ini?" | `compare_stocks` |
| "Berapa nilai portofolio saya hari ini? Untung atau rugi?" | `get_portfolio` |
| "Kalau semua saham saya jual sekarang, dapat bersih berapa?" | `get_portfolio` |
| "Ada peringatan apa di portofolio saya?" | `get_portfolio` |
| "Berapa dividen yang sudah saya terima dari BBCA? Untung yang sudah terealisasi berapa?" | `get_portfolio` |

Tips:

- Sebutkan rentang waktu kalau perlu: `1mo`, `3mo`, `6mo`, `1y`, `2y`, `5y`, `ytd`, `max`.
- Setelah membeli atau menjual saham, **tambahkan satu baris di `transactions`** (atau update `holdings`). Server membaca file ini setiap kali dipanggil, jadi tidak perlu restart.
- Dengan `transactions`, Anda bisa bertanya "sudah dapat dividen berapa dari BBCA?" atau "total untung saya termasuk yang sudah dijual dan dividen berapa?" (`totals.totalReturn`).
- Untuk reksa dana Bibit, update nilai `currentValue` dan `updatedAt` di bagian `manual` sesekali, misalnya seminggu sekali, sesuai angka di aplikasi.
- Untuk membandingkan saham yang rutin membagi dividen (mis. saham bank), minta Claude memakai **total return**, bukan hanya perubahan harga.
- Jika Claude menyebut "Total TIDAK LENGKAP", ada saham yang harganya gagal diambil dan tidak ikut dihitung. Coba lagi beberapa saat kemudian.

## Format file portofolio

Ada dua cara mencatat saham, dan keduanya bisa dipakai bersamaan untuk ticker yang berbeda:

- **`transactions`** (disarankan): catat setiap beli dan jual. Server menghitung sendiri posisi, harga rata-rata, untung/rugi yang sudah terealisasi, dan dividen yang Anda terima.
- **`holdings`**: cukup tulis posisi saat ini (lot + harga rata-rata). Cocok kalau tidak ingin mencatat riwayat, tetapi tanpa realized profit dan dividen.

Satu ticker tidak boleh ada di keduanya.

```json
{
  "buyFeePct": 0.15,
  "sellFeePct": 0.25,
  "transactions": [
    { "ticker": "BBCA", "type": "buy",  "date": "2026-07-20", "lots": 1, "price": 6500, "note": "beli pertama" },
    { "ticker": "BBCA", "type": "buy",  "date": "2026-08-20", "lots": 1, "price": 6100 },
    { "ticker": "BBCA", "type": "sell", "date": "2026-10-01", "lots": 1, "price": 6300, "fee": 1575 }
  ],
  "holdings": [
    { "ticker": "BBRI", "lots": 2, "avgPrice": 3500, "buyDate": "2026-10-20" }
  ],
  "manual": [
    { "name": "Reksa dana indeks IDX30 (Bibit)", "invested": 500000, "currentValue": 512000, "updatedAt": "2026-10-19" }
  ]
}
```

| Field | Keterangan |
| --- | --- |
| `buyFeePct` | Fee beli dalam persen (default `0.15`). Dipakai untuk transaksi beli yang tidak mengisi `fee`; masuk ke modal. |
| `sellFeePct` | Fee jual dalam persen, termasuk pajak penjualan (default `0.25`). Dipakai untuk transaksi jual yang tidak mengisi `fee`, dan untuk **estimasi** hasil bersih jika dijual. Sesuaikan dengan tarif sekuritas Anda. |
| `manualStaleDays` | Setelah berapa hari nilai aset `manual` dianggap usang dan diberi peringatan (default `7`). |
| `transactions[].ticker` | Kode saham BEI, mis. `BBCA`. |
| `transactions[].type` | `buy` atau `sell`. |
| `transactions[].date` | Tanggal transaksi `YYYY-MM-DD`. Pada hari yang sama, beli diproses sebelum jual. |
| `transactions[].lots` | Jumlah lot (1 lot = 100 lembar). |
| `transactions[].price` | Harga eksekusi **per lembar**, sebelum fee. |
| `transactions[].fee` | Fee absolut dalam rupiah (opsional). Jika diisi, menggantikan `buyFeePct`/`sellFeePct` untuk transaksi itu. |
| `transactions[].note` | Catatan bebas (opsional). |
| `holdings[].ticker` | Kode saham BEI, mis. `BBCA`. |
| `holdings[].lots` | Jumlah lot (1 lot = 100 lembar). |
| `holdings[].avgPrice` | Harga rata-rata beli **per lembar**. Fee beli tidak dihitung terpisah; masukkan ke `avgPrice` jika ingin modal akurat. Kalau membeli beberapa kali, hitung rata-ratanya (lihat di bawah). |
| `holdings[].buyDate` | Tanggal beli `YYYY-MM-DD` (disarankan). Dipakai untuk mendeteksi stock split setelah Anda membeli. |
| `holdings[].note` | Catatan bebas (opsional). |
| `manual[]` | Aset yang tidak diperdagangkan di bursa, mis. reksa dana. `invested` = total modal, `currentValue` = nilai terkini dari aplikasi, `updatedAt` = tanggal Anda terakhir memperbarui `currentValue`. |

**Cara server menghitung `transactions`** (metode harga rata-rata, sama seperti tampilan aplikasi sekuritas di Indonesia):

- Beli: modal bertambah `lot Γ— 100 Γ— price + fee`. Harga rata-rata = modal Γ· lembar.
- Jual: `realizedPnl` bertambah `lot Γ— 100 Γ— price βˆ’ fee βˆ’ (lot Γ— 100 Γ— harga rata-rata saat itu)`. Modal berkurang sebesar harga rata-rata Γ— lembar yang dijual.
- Menjual lebih banyak dari yang dimiliki pada tanggal itu adalah error, bukan angka tebakan.
- Posisi yang sudah habis dijual tetap dilaporkan di `closed[]` dengan realized profit dan dividennya.
- **Dividen diterima** dihitung dari ex-date yang dilaporkan Yahoo: lembar yang Anda pegang berdasarkan transaksi **sebelum** ex-date Γ— dividen per lembar. Beli tepat di ex-date tidak dapat dividen; jual tepat di ex-date masih dapat. Nilainya **bruto**, belum dipotong pajak dividen (umumnya 10% final untuk individu). Dividen yang jadwalnya belum masuk data Yahoo tidak terhitung.

**Kalau saham Anda melakukan stock split**, misalnya 1 lembar menjadi 5 (5:1), jumlah lot dan harga di file portofolio **tidak berubah otomatis**. Untuk `holdings`: sesuaikan `lots Γ— 5` dan `avgPrice Γ· 5`. Untuk `transactions`: sesuaikan `lots Γ— 5` dan `price Γ· 5` pada transaksi sebelum tanggal split. Server akan memberi peringatan jika mendeteksi split setelah tanggal beli.

**Menghitung `avgPrice` jika membeli beberapa kali** (hanya perlu untuk `holdings`; dengan `transactions` server menghitungnya sendiri):

```
avgPrice = total uang yang dikeluarkan / total lembar
```

Contoh: beli 1 lot BBCA di Rp4.820 lalu 1 lot lagi di Rp5.200 β†’ (482.000 + 520.000) / 200 = **Rp5.010** dengan `lots: 2`.

## Referensi tools

### `get_quote`
| Parameter | Tipe | Contoh |
| --- | --- | --- |
| `ticker` | string | `"BBCA"`, `"IHSG"` |

Mengembalikan: `price`, `previousClose` (+ `previousCloseSource`), `change`, `changePct`, `dayHigh`, `dayLow`, `fiftyTwoWeekHigh`, `fiftyTwoWeekLow`, `pricePerLot` (`null` untuk indeks seperti IHSG, karena indeks tidak diperdagangkan per lot), serta info kesegaran data:

| Field | Arti |
| --- | --- |
| `marketState` | `open`, `closed`, atau `unknown` (jam bursa tapi status tidak bisa dipastikan, mis. kemungkinan hari libur). |
| `dataAsOfLocal` | Waktu harga terakhir dalam WIB, mis. `Jum, 16 Okt 2026 16:00 WIB`. |
| `dataAgeMinutes` | Umur data dalam menit. |
| `freshnessNote` | Kalimat penjelasan yang disampaikan Claude ke pengguna. Saat **jeda siang** BEI (Sen–Kam 12:00–13:30, Jum 11:30–14:00 WIB, sesuai [jam perdagangan resmi BEI](https://www.idx.co.id/id/produk/mekanisme-dan-jam-perdagangan)) `marketState` tetap `open`, tetapi catatan ini menyebut harga adalah penutupan sesi 1 dan jam pembukaan sesi 2. Di luar jeda, ada peringatan jika pasar seharusnya buka tapi harga tidak bergerak lebih dari 30 menit. |

### `get_history`
| Parameter | Tipe | Default | Pilihan |
| --- | --- | --- | --- |
| `ticker` | string | β€” | `"TLKM"` |
| `range` | string | `"6mo"` | `1d` `5d` `1mo` `3mo` `6mo` `1y` `2y` `5y` `10y` `ytd` `max` |
| `interval` | string | `"1d"` | `1d` `1wk` `1mo` |

Mengembalikan `summary` dan `candles` (maks. 300 terakhir; untuk rentang panjang gunakan `1wk` atau `1mo`). Isi `summary`:

| Field | Arti |
| --- | --- |
| `priceReturnPct` | Perubahan **harga saja**, tanpa dividen. |
| `totalReturnPct` | Perubahan harga **+ dividen**, dari harga yang disesuaikan. `null` jika data penyesuaian tidak tersedia. |
| `dividendsInRange`, `dividendsPerShareInRange` | Dividen per lembar yang dibagikan dalam rentang. |
| `splitsInRange` | Stock split dalam rentang. |
| `high`, `low`, `maxDrawdownPct` | Tertinggi, terendah, penurunan terdalam dari puncak (berbasis harga disesuaikan, sehingga split tidak terbaca sebagai anjlok). |
| `tradingDays` | Jumlah candle yang punya harga penutupan. Untuk `interval` `1wk`/`1mo` ini adalah jumlah minggu/bulan, bukan hari. |
| `notes` | Catatan interpretasi. |

Untuk saham yang rutin membagi dividen (mis. saham bank), `totalReturnPct` lebih mencerminkan hasil riil pemegang saham.

### `compare_stocks`
| Parameter | Tipe | Default |
| --- | --- | --- |
| `tickers` | string[] (2–10) | β€” |
| `range` | string | `"6mo"` |

Diurutkan berdasarkan `totalReturnPct` jika tersedia untuk semua saham; jika tidak, berdasarkan `priceReturnPct`. Dasar pengurutan tercantum di field `rankedBy`. Ticker yang gagal tetap muncul dengan field `error`.

### `get_portfolio`
Tanpa parameter. Membaca file dari environment variable `IDX_PORTFOLIO_PATH`.

| Bagian | Isi |
| --- | --- |
| `holdings[]` | Per saham: `source` (`manual`/`transactions`), `status` (`priced`/`unpriced`), `lastPrice`, `priceAsOf`, `marketState`, `marketValue`, `unrealizedPnl`, `unrealizedPnlPct`, `estimatedNetIfSold`, `dayChangePct`, `realizedPnl`, `dividendsReceived`, `dividends[]` (per ex-date: `amountPerShare`, `shares`, `total`), `warnings`. `realizedPnl` dan `dividendsReceived` bernilai `null` untuk `source: "manual"`. |
| `closed[]` | Posisi dari `transactions` yang sudah habis dijual: `realizedPnl`, `dividendsReceived`, `dividends[]`, `buyCount`, `sellCount`, `firstBuyDate`, `lastTransactionDate`. |
| `manual[]` | Aset manual + `pnl` dan `stale` (usang atau tidak). |
| `totals` | `cost`, `marketValue`, `unrealizedPnl`, `unrealizedPnlPct`, `estimatedNetIfSold`, `realizedPnl`, `dividendsReceived` (bruto), `totalReturn` (= unrealized + realized + dividen), `incomplete`, `excludedTickers`, `excludedCost`. |
| `assumptions` | Asumsi perhitungan: `buyFeePct`, `sellFeePct`, metode harga rata-rata (`costMethod`), perlakuan pajak dividen (`dividendTax`), ukuran lot. |
| `warnings` | Peringatan yang harus disampaikan ke pengguna. |

Aturan integritas:

- **Jual melebihi kepemilikan** pada tanggal itu dianggap error file portofolio, bukan dihitung minus.
- **Dividen** hanya dihitung untuk ticker yang punya `transactions` dan datanya berhasil diambil; jika gagal, `dividendsReceived` bernilai `null` dan ada peringatan, sehingga `totals.dividendsReceived` bisa kurang dari kenyataan.

- **Saham yang harganya gagal diambil tidak ikut dihitung** dalam `totals` (bukan dianggap impas). Saham tersebut tercantum di `excludedTickers`, dan peringatan "Total TIDAK LENGKAP" muncul di urutan pertama `warnings`.
- **Stock split** setelah `buyDate` dideteksi dan diberi peringatan per saham.
- **Aset manual** yang `updatedAt`-nya lebih lama dari `manualStaleDays`, atau tidak punya `updatedAt`, diberi peringatan.
- `estimatedNetIfSold` = nilai pasar βˆ’ `sellFeePct`. Ini **perkiraan**; hasil jual sebenarnya tergantung harga eksekusi dan tarif sekuritas.

## Troubleshooting

| Masalah | Solusi |
| --- | --- |
| `idx-stock` tidak muncul di Claude Desktop | Pastikan JSON config valid (tanpa koma berlebih), path ke `dist/index.js` benar dan sudah `npm run build`, lalu quit Claude Desktop sepenuhnya dan buka lagi. Log ada di menu Developer β†’ Open Logs. |
| `idx-stock` tidak muncul saat memakai `npx` | Pastikan Node.js β‰₯20 ada di PATH (`npx -v` di terminal). Di Windows pakai `"command": "cmd"` dengan `"args": ["/c", "npx", "-y", "idx-stock-mcp"]`. Pemanggilan pertama butuh internet untuk mengunduh paket. |
| `node: command not found` | Node.js belum terpasang atau tidak ada di PATH. Ganti `"command": "node"` dengan path lengkap, mis. `/usr/local/bin/node` (cek dengan `which node`). |
| `IDX_PORTFOLIO_PATH belum di-set` | Tambahkan bagian `env` di config (langkah 4), lalu restart Claude Desktop. |
| `File portofolio tidak ditemukan` | Path di `IDX_PORTFOLIO_PATH` salah. Gunakan path lengkap (absolut), bukan `./portfolio.json`. |
| `Format portofolio tidak valid` | Periksa nama field dan tipe datanya (angka tanpa tanda kutip, tanpa titik ribuan: `4820`, bukan `"4.820"`). |
| `Saham "XXXX" tidak ditemukan` | Kode saham salah atau sudah delisting. Cek kode di <https://www.idx.co.id>. |
| `Terlalu banyak permintaan ke Yahoo Finance (HTTP 429) ... (sudah dicoba 3 kali)` | Yahoo membatasi akses dan 2 percobaan ulang otomatis masih gagal. Tunggu beberapa menit. |
| `Tidak dapat terhubung ke Yahoo Finance (ENOTFOUND / ECONNRESET / ...)` | Komputer tidak bisa mencapai Yahoo. Periksa koneksi internet, VPN, atau firewall. |
| `Yahoo Finance tidak merespons dalam 10 detik` | Jaringan lambat atau Yahoo sedang bermasalah. Coba lagi beberapa saat. |
| `Respons Yahoo Finance bukan JSON yang valid` | Yahoo membalas halaman HTML (mis. halaman persetujuan atau pembatasan akses). Coba lagi nanti; jika terus terjadi, [laporkan](https://github.com/Zalfrie/idx-stock-mcp/issues/new/choose) karena endpoint mungkin berubah. |
| Harga berbeda dengan aplikasi sekuritas | Data Yahoo bisa tertunda beberapa menit selama jam bursa. Cek `dataAsOfLocal` dan `freshnessNote`. Untuk transaksi, selalu acu harga di aplikasi sekuritas. |
| Untung/rugi sebuah saham terlihat sangat besar/aneh | Kemungkinan saham tersebut melakukan stock split. Cek `warnings`, lalu sesuaikan `lots` dan `avgPrice`. |
| Peringatan `buyDate ... tidak diisi` | Tambahkan `buyDate` di holding tersebut agar pengecekan split akurat. |

## Sumber data & batasan

- Harga diambil dari endpoint chart **Yahoo Finance** (simbol berakhiran `.JK`, IHSG = `^JKSE`). Endpoint ini **tidak resmi**: bisa berubah atau dibatasi tanpa pemberitahuan, dan harga bisa tertunda dari bursa. Pastikan penggunaan sesuai ketentuan layanan Yahoo.
- Respons di-cache 60 detik untuk mengurangi jumlah request.
- Jika Yahoo membalas HTTP 429/5xx atau koneksi gagal sementara, server **mencoba ulang otomatis** sampai 2 kali
  (jeda 0,5 detik lalu 1 detik, atau sesuai header `Retry-After`, maksimum 15 detik). Error 404 dan respons bukan JSON tidak diulang.
- Reksa dana tidak tersedia di sumber data ini, jadi nilainya diisi manual di file portofolio.
- Kode sumber data terisolasi di `src/yahoo.ts`, sehingga mudah diganti dengan penyedia data berlisensi bila diperlukan.

## Pengembangan

```
src/
  index.ts      # entry point (stdio transport)
  server.ts     # definisi tools MCP
  yahoo.ts      # klien & parser data harga, dividen, split, status pasar
  summary.ts    # ringkasan performa (price/total return, drawdown)
  portfolio.ts  # skema & perhitungan portofolio
test/           # unit + integration test (node:test, fetch tiruan)
```

| Perintah | Fungsi |
| --- | --- |
| `npm run build` | Compile TypeScript ke `dist/` |
| `npm test` | Jalankan test (offline, memakai data tiruan) |
| `npm run inspect` | Coba tools secara interaktif di MCP Inspector |
| `npm start` | Jalankan server via stdio |
| `npm publish` | Publish ke npm (otomatis build + test dulu lewat `prepublishOnly`) |

## Kontribusi

Laporan bug, usulan fitur, dan PR sangat diterima. Baca [CONTRIBUTING.md](CONTRIBUTING.md) terlebih dahulu.
Saat melapor bug, gunakan template issue dan sertakan output JSON dari MCP Inspector.

## Lisensi

[MIT](LICENSE) Β© 2026 Zalfrie

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation5/5

Each tool targets a clearly distinct operation: current quote (get_quote), historical candles (get_history), multi-stock comparison (compare_stocks), and local portfolio evaluation (get_portfolio). There is no meaningful overlap in purpose or output, so an agent can select correctly without guessing.

Naming Consistency4/5

Three tools follow a clean verb_noun pattern (get_quote, get_history, get_portfolio) while compare_stocks deviates only in verb (compare vs get). The deviation is minor and still perfectly readable, so the set is nearly uniform.

Tool Count4/5

Four tools is on the lean side but each earns its place by covering a distinct analytical need (quote, history, comparison, portfolio). A symbol-search or market-overview tool could round it out, but nothing is redundant.

Completeness4/5

The surface covers the core investor workflows: live pricing, historical returns, comparative performance, and portfolio P&L including dividends and splits. Gaps like symbol search or corporate-action lookup exist but are minor and don't block the primary use cases.

Maintenance

ActivityNo data
ResponsivenessUnresponsive