Skip to main content
Glama
README.md
<div align="center">

<img src=".github/banner.svg" alt="boi-mcp" width="100%"/>

[![CI](https://github.com/Shtibel1/boi-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Shtibel1/boi-mcp/actions/workflows/ci.yml)
[![version](https://img.shields.io/github/package-json/v/Shtibel1/boi-mcp?color=60a5fa)](package.json)
[![license](https://img.shields.io/github/license/Shtibel1/boi-mcp?color=34d399)](LICENSE)
![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white)
[![MCP](https://img.shields.io/badge/Model_Context_Protocol-server-1d4ed8)](https://modelcontextprotocol.io)
![Data](https://img.shields.io/badge/data-Bank_of_Israel_SDMX-0b3d91)

**Official Bank of Israel exchange rates (שערים יציגים) and interest rate for any MCP client.**

</div>

<p align="center"><a href="#quick-start">Quick start</a> · <a href="#tools">Tools</a> · <a href="#live-example">Live example</a> · <a href="#architecture">Architecture</a> · <a href="#roadmap">Roadmap</a></p>

A [Model Context Protocol](https://modelcontextprotocol.io) server that gives AI agents the **official representative exchange rates** published daily by the Bank of Israel, the rate on any past date (with automatic previous-business-day fallback), history and statistics over a range, ILS conversions and cross rates, and the **Bank of Israel policy interest rate** with its change history.

It talks directly to the Bank of Israel's free, public [SDMX REST API](https://www.boi.org.il/en/economic-roles/statistics/) at `edge.boi.gov.il`: no API key, no scraping, no third-party FX feed.

Part of a small family of Israeli MCP servers: [hebrew-il-mcp](https://github.com/Shtibel1/hebrew-il-mcp) (local validators: ID, phone, VAT, IBAN, plates, holidays) and [il-opendata-mcp](https://github.com/Shtibel1/il-opendata-mcp) (data.gov.il datasets).

## Tools

| Tool | What it does |
| --- | --- |
| `list_currencies` | No network. The 14 currencies the BOI publishes a representative rate for (USD, EUR, GBP, JPY, CHF, CAD, AUD, DKK, NOK, SEK, ZAR, JOD, EGP, LBP), with English and Hebrew names and the quoting unit. |
| `get_latest_rates` | Latest published rate for all currencies (or a `currencies` subset) in **one** SDMX request (`lastNObservations=1`). Each entry has `rate` as published, `unit`, `ils_per_unit` and its own `date`. |
| `get_rate` | Rate for one `currency` on a `date` (default: today in Israel). Weekends and holidays fall back to the previous business day, flagged with `is_fallback` and `days_back`. Future dates are rejected. |
| `get_rate_history` | Daily rates between `start_date` and `end_date` (≤ ~10 years). Returns `count`, `first`/`last`, `min`/`max` with dates, `change` and `change_pct`, plus the observations (the most recent 400 if longer; stats always cover the whole range). |
| `convert_currency` | Convert `amount` between ILS (also `NIS`, `₪`, `שקל`) and any supported currency, or between two foreign currencies via ILS, on the latest or a historical `date`. Returns the result, the cross rate and the rate date used for each leg. |
| `get_interest_rate` | The BOI policy rate (ריבית בנק ישראל) on a `date`, `effective_since` and every change in the look-back window (default 2 years, or `since`) with the previous rate and the change in basis points. |

Every tool returns both human-readable JSON text and `structuredContent`. Bad input (unknown currency, malformed or future date, empty range) produces an MCP `isError` result with a clear message instead of crashing the session.

Currency inputs are forgiving: `usd`, `$`, `€`, `£`, `¥`, `דולר`, `יורו` all work.

### Details that are easy to get wrong

- **Quoting units.** The BOI quotes JPY per **100** yen and LBP per **10** pounds (`UNIT_MULT` in SDMX). `rate` is kept exactly as published and `ils_per_unit` is normalized, so conversions are always right.
- **"Today" is Israeli time.** Dates default to `Asia/Jerusalem`, not UTC, so a request at 01:00 in Tel Aviv asks for the right day.
- **No data ≠ error.** The API answers HTML `404` when a query matches nothing (e.g. a Saturday). The client maps that to "no observations" and the tools fall back or explain.

## Quick start

### Claude Desktop / Cursor / any MCP client

```json
{
  "mcpServers": {
    "boi": {
      "command": "npx",
      "args": ["-y", "github:Shtibel1/boi-mcp"]
    }
  }
}
```

Claude Desktop: put this in `claude_desktop_config.json`. Cursor: `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project).

Or from a local clone:

```bash
npm install && npm run build
node dist/cli.js   # speaks MCP over stdio
```

Then ask your agent things like:

- "What is the dollar's representative rate today, and how did it move this month?"
- "Convert 2,500 EUR to shekels at the rate of 2026-10-04."
- "מה ריבית בנק ישראל ומתי היא השתנתה לאחרונה?"

### Hosted: Streamable HTTP

```bash
npx -y github:Shtibel1/boi-mcp --http --port 3000
# → boi-mcp listening on http://127.0.0.1:3000/mcp
curl http://127.0.0.1:3000/healthz   # {"status":"ok","name":"boi-mcp","version":"0.1.0"}
```

```json
{ "mcpServers": { "boi": { "url": "http://127.0.0.1:3000/mcp" } } }
```

| Flag | Env | Default | Notes |
| --- | --- | --- | --- |
| `--http` | | off | Serve [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) instead of stdio |
| `--port` | `PORT` | `3000` | |
| `--host` | `HOST` | `127.0.0.1` | Binds to loopback by default; use `0.0.0.0` inside a container |
| `--allowed-origin` | `ALLOWED_ORIGINS` | none | Repeatable flag, or a comma-separated env var. Browser requests from any other `Origin` get `403` (DNS-rebinding protection); non-browser clients without `Origin` are always allowed |
| `--version` | | | Print the version and exit |

The HTTP mode is **stateless**: every `POST /mcp` gets a fresh server and transport, there are no session ids, and `GET`/`DELETE /mcp` return `405`, so instances can sit behind any load balancer. Bodies over 1 MB get `413`, malformed JSON gets `400`.

### Docker

The image runs the HTTP mode as the unprivileged `node` user on `0.0.0.0:3000` with a `HEALTHCHECK` against `/healthz`:

```bash
docker build -t boi-mcp .
docker run --rm -p 3000:3000 -e ALLOWED_ORIGINS=https://app.example boi-mcp
```

CI builds the image on every push and smoke-tests it: health check, a real `tools/list` call, the origin guard and the non-root user.

### As a library

```ts
import { BoiClient } from "boi-mcp";

const boi = new BoiClient();
const { rates } = await boi.getLatestRates(["USD", "EUR"]);
const onSunday = await boi.getRate("USD", "2026-10-04"); // → Friday 2026-10-02, is_fallback: true
const history = await boi.getRateHistory("EUR", "2026-10-01", "2026-11-14");
const { result } = await boi.convert({ amount: 100, from: "EUR", to: "JPY" });
const policy = await boi.getInterestRate();
```

## Live example

Real output from the built server against the live Bank of Israel API, run on **2026-11-15** (Sunday, Asia/Jerusalem — the latest rates are Friday's):

| Call | Result |
| --- | --- |
| `get_latest_rates` | as of **2026-11-13**: USD **3.199**, EUR **3.5394**, GBP **4.2365**, JPY **2.0009** per 100 (0.020009 per yen) |
| `get_rate { currency: "usd", date: "2026-11-14" }` | 3.199 from **2026-11-13**, `is_fallback: true`, `days_back: 1` (Saturday) |
| `get_rate_history { EUR, 2026-10-01 → 2026-11-14 }` | 31 business days, 3.4826 → 3.5394 (**+1.63%**), min 3.4291 on 2026-10-09, max 3.5724 on 2026-11-06 |
| `convert_currency { 1000 USD → ILS }` | **3,199 ILS** |
| `convert_currency { 100 EUR → JPY }` | **17,689.0399 JPY** (cross rate 176.8904 via ILS) |
| `get_interest_rate` | **3.25%** since 2026-09-03; five −25 bp cuts since 4.5% (2025-11-27, 2026-01-08, 2026-05-28, 2026-07-09, 2026-09-03) |

<details>
<summary>Raw <code>get_rate</code> response</summary>

```json
{
  "currency": "USD",
  "name": "US Dollar",
  "name_he": "דולר ארה\"ב",
  "rate": 3.199,
  "unit": 1,
  "ils_per_unit": 3.199,
  "date": "2026-11-13",
  "requested_date": "2026-11-14",
  "is_fallback": true,
  "days_back": 1,
  "source": "Bank of Israel SDMX API (edge.boi.gov.il)"
}
```

</details>

## Architecture

```mermaid
flowchart LR
  C["MCP client<br/>(Claude Desktop, Cursor, agents)"] -- stdio --> CLI[cli.ts]
  R["Remote agents"] -- "POST /mcp" --> H["http.ts<br/>stateless Streamable HTTP<br/>origin check · /healthz"]
  CLI --> S["server.ts<br/>zod schemas · isError results"]
  CLI -- "--http" --> H
  H -- "new server per request" --> S
  S --> BOI
  subgraph L["src/lib — no MCP imports"]
    BOI["boi.ts<br/>injectable fetch · SDMX client<br/>fallback · stats · conversion"]
    CSV["csv.ts<br/>RFC 4180 parser"]
    CUR["currencies.ts<br/>14 currencies · units · aliases"]
    DT["dates.ts<br/>Asia/Jerusalem today"]
    BOI --> CSV
    BOI --> CUR
    BOI --> DT
  end
  BOI -- "HTTPS · format=csv" --> API["edge.boi.gov.il<br/>SDMX v2: EXR · BR"]
  APP["Your TS app"] -- import --> L
```

### How the data is fetched

| Need | SDMX query (dataflow `BOI.STATISTICS`) |
| --- | --- |
| Latest rates | `EXR/1.0/RER_USD_ILS+RER_EUR_ILS+…?lastNObservations=1` |
| Rate on a date (with fallback) | `EXR/1.0/RER_USD_ILS?endperiod=2026-10-04&lastNObservations=1` |
| History | `EXR/1.0/RER_EUR_ILS?startperiod=…&endperiod=…` |
| Policy interest rate | `BR/1.0/MNT_RIB_BOI_D?startperiod=…&endperiod=…` |

All with `format=csv`. The fallback is done by the API itself (`endperiod` + `lastNObservations=1`), so a holiday week never needs a retry loop.

### Layout

```
src/
  lib/boi.ts         injectable-fetch SDMX client: rates, fallback, history stats, conversion, policy rate
  lib/csv.ts         small RFC 4180 CSV parser (quoted Hebrew comments, doubled quotes, CRLF, BOM)
  lib/currencies.ts  supported currencies, quoting units, Hebrew names and aliases
  lib/dates.ts       Asia/Jerusalem "today", strict YYYY-MM-DD validation
  server.ts          MCP tool wiring (zod schemas)
  http.ts            stateless Streamable HTTP transport (node:http, no framework)
  cli.ts             entry: stdio by default, --http for hosted mode
test/
  fixtures/          real BOI API responses captured on 2026-11-15 (see fixtures/README.md)
  *.test.ts          55 vitest tests; fetch is mocked with the fixtures, so CI never hits the network
```

## Development

```bash
npm test          # vitest, replaying recorded BOI responses (no network)
npm run typecheck
npm run build
```

## Attribution

This is an unofficial client. Exchange rates and interest rates come from the [Bank of Israel](https://www.boi.org.il) and are subject to its terms of use. Representative rates are reference rates, not dealing quotes. This project has no affiliation with the Bank of Israel.

## Roadmap

- [ ] Publish to npm so `npx boi-mcp` works without the GitHub shorthand
- [ ] Effective exchange-rate indices (nominal/real basket) and USD/ILS implied volatility from the same EXR dataflow
- [ ] CPI and inflation expectations series
- [ ] Small in-memory cache for the HTTP mode (rates change once per business day)
- [ ] Render / Fly one-click deploy

## Contributing

Issues and PRs are welcome. Keep API logic in `src/lib` with unit tests and add an end-to-end case in `test/server.test.ts` for new tools. New fixtures must be real captured responses — never hand-written API data.

## License

[MIT](LICENSE) © Nadav Shtibel

TDQS

A4.1/5.0

Scored across 6 tools

Disambiguation4/5

Each tool has a fairly distinct purpose, but get_rate, get_latest_rates, and get_rate_history all deal with representative rates and could be confused at a glance (single-date vs latest snapshot vs range). The descriptions do a good job of disambiguating them, so misselection risk is low but not zero.

Naming Consistency5/5

All names follow a clean verb_noun pattern (list_currencies, convert_currency, get_rate, get_rate_history, get_latest_rates, get_interest_rate) with consistent snake_case and the get_ prefix for retrieval. The list_ vs get_ distinction is used meaningfully.

Tool Count5/5

Six tools is well-scoped for a central-bank exchange-rate/interest-rate server, with no redundant or filler tools. Every tool maps to a clear data need (enumerate, convert, current, single-date, history, policy rate).

Completeness4/5

Coverage is strong: currency enumeration, conversion (including cross rates and historical dates), latest/single/historical rates, and policy interest rate with change history. Minor gaps exist, such as no multi-currency snapshot for an arbitrary historical date or batch conversion, but core workflows are fully served.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues