> πŸ‡¨πŸ‡­ **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)** # 🏦 swiss-snb-mcp ![Version](https://img.shields.io/badge/version-0.4.5-blue) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-purple)](https://modelcontextprotocol.io/) [![Data Source](https://img.shields.io/badge/Data-data.snb.ch-red)](https://data.snb.ch) ![CI](https://github.com/malkreide/swiss-snb-mcp/actions/workflows/ci.yml/badge.svg) > MCP server for the Swiss National Bank (SNB) data portal β€” exchange rates, balance sheet, interest rates, SARON, monetary aggregates, banking statistics, and balance of payments. [πŸ‡©πŸ‡ͺ Deutsche Version](README.de.md)

Demo: Claude queries SNB banking statistics via MCP tool call

--- ## Overview `swiss-snb-mcp` connects AI models to the official Swiss National Bank data portal at [data.snb.ch](https://data.snb.ch) via the Model Context Protocol (MCP). It provides structured access to SNB's public REST API β€” no authentication required. The server covers three tiers of datasets, all confirmed against the live API: **Phase 1 β€” Dedicated tools:** - **Exchange rates** (monthly averages, month-end rates, annual averages) for the 28 currency series `devkum` publishes against CHF β€” including two USD forward rates, which are labelled as such - **SNB balance sheet** (Bilanz): gold reserves, foreign exchange investments, banknotes in circulation, sight deposits, and totals **Phase 2 β€” Via generic cube tools (`snb_get_cube_data` + `snb_get_cube_metadata`):** - **SNB policy rate (Leitzins) and SARON** daily fixing, emergency facility rate, sight deposit rates - **SARON compound rates**: Overnight, 1M, 3M, 6M - **International money market rates**: SARON (CH), SOFR (USA), TONA (JP), SONIA (UK), €STR/EURIBOR (EZ) - **Official central bank rates**: SNB, Fed, ECB, Bank of England, Bank of Japan - **Monetary aggregates M1, M2, M3**: stock levels and year-on-year changes **Phase 3 β€” Warehouse API (banking statistics) and balance of payments:** - **Banking balance sheets** (BSTA BIL): total assets and liabilities by bank group β€” annual and monthly - **Banking income statements** (BSTA EFR): operating income, expenses, taxes by bank group β€” annual - **Balance of payments**: current account, capital account, financial account (quarterly) - **International investment position**: components by investment type (quarterly) - **Generic warehouse access**: raw access to any SNB Warehouse cube by ID **Anchor demo query:** *"What was the EUR/CHF exchange rate during the 2015 Franc shock, and where does the SNB policy rate stand today compared to the Fed and ECB?"* --- ## Features - πŸ’± **Exchange rates** β€” monthly CHF rates for EUR, USD, JPY, GBP, CNY and 23 more series - πŸ“… **Annual averages** β€” year-by-year rates from 1980 onwards - πŸ›οΈ **SNB balance sheet** β€” gold, foreign exchange investments, banknotes, sight deposits (monthly) - πŸ”„ **Currency conversion** β€” convert any amount to CHF using official SNB rates - πŸ“ˆ **Policy rate & SARON** β€” daily fixing, Leitzins, compound rates (1M/3M/6M) - 🌍 **International rate comparison** β€” SNB, Fed, ECB, Bank of England, Bank of Japan side by side - πŸ’° **Monetary aggregates** β€” M1, M2, M3 stock levels and year-on-year growth - 🏦 **Banking statistics** β€” balance sheets and income statements by bank group (12 groups) - πŸ“Š **Balance of payments** β€” current account, IIP, and international investment position - πŸ” **Generic cube access** β€” query any SNB data cube or Warehouse cube by ID - πŸ”“ **No authentication required** β€” fully public SNB data portal --- ## Prerequisites - Python 3.11+ - `uv` or `pip` - MCP-compatible client (Claude Desktop, Claude Code, or any MCP host) --- ## Installation **Via uvx (recommended β€” no permanent installation needed):** ```bash uvx swiss-snb-mcp ``` **Via pip:** ```bash pip install swiss-snb-mcp ``` **From source:** ```bash git clone https://github.com/malkreide/swiss-snb-mcp.git cd swiss-snb-mcp pip install -e . ``` --- ## Usage / Quickstart **Claude Desktop β€” add to `claude_desktop_config.json`:** ```json { "mcpServers": { "swiss-snb-mcp": { "command": "uvx", "args": ["swiss-snb-mcp"] } } } ``` **Config file locations:** - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` Try it immediately in Claude Desktop: > *"What is the current EUR/CHF exchange rate according to the SNB?"* > *"Show me the SNB balance sheet for the last 12 months β€” gold and foreign reserves."* --- ## Configuration No API key or authentication required. The SNB data portal is fully public. **Optional environment variable:** | Variable | Default | Description | |---|---|---| | `SNB_TIMEOUT` | `15` | HTTP request timeout in seconds | --- ## Available Tools ### Phase 1 β€” Dedicated Tools | Tool | Description | |---|---| | `snb_get_exchange_rates` | Monthly CHF rates for EUR, USD, JPY, GBP, CNY and 22 more currencies | | `snb_get_annual_exchange_rates` | Annual average rates, data from 1980 | | `snb_get_balance_sheet` | SNB Bilanz positions in millions CHF (monthly) | | `snb_convert_currency` | Convert any amount to CHF using official SNB rates | ### Phase 2 β€” Generic Cube Tools | Tool | Description | |---|---| | `snb_get_cube_data` | Generic access to any SNB cube by ID | | `snb_get_cube_metadata` | Inspect dimensions and filter values of any cube | ### Phase 3 β€” Warehouse API (Banking Statistics) and Balance of Payments | Tool | Description | |---|---| | `snb_get_banking_balance_sheet` | Banking balance sheets by bank group (monthly/annual, assets/liabilities) | | `snb_get_banking_income` | Banking income statements by bank group (annual) | | `snb_get_balance_of_payments` | Balance of payments and international investment position (quarterly) | | `snb_get_warehouse_data` | Generic access to any SNB Warehouse cube by ID | | `snb_get_warehouse_metadata` | Inspect dimensions and last update of a Warehouse cube | ### Resources (static catalogs) Discovery aids served as MCP resources rather than tools so they don't crowd the tool manifest: | URI | Description | |---|---| | `data://snb/currencies` | All 28 currency IDs with labels and units | | `data://snb/balance-sheet-positions` | Asset and liability position IDs | | `data://snb/cubes` | All verified Cube-API IDs (Phase 1–2) + discovery guide | | `data://snb/warehouse-cubes` | Available Warehouse cube IDs (BSTA) | | `data://snb/bank-groups` | All 12 bank group IDs with labels | ### Example Use Cases | Query | Tool | |---|---| | *"What is the current EUR/CHF rate?"* | `snb_get_exchange_rates` | | *"Convert CHF 10,000 to USD"* | `snb_convert_currency` | | *"Show SNB gold reserves over the last year"* | `snb_get_balance_sheet` | | *"What is the current SNB policy rate?"* | `snb_get_cube_data` (cube: `snbgwdzid`) | | *"How do SNB, Fed and ECB rates compare?"* | `snb_get_cube_data` (cube: `snboffzisa`) | | *"What is the SARON 3M compound rate?"* | `snb_get_cube_data` (cube: `zirepo`) | | *"How fast is M3 money supply growing?"* | `snb_get_cube_data` (cube: `snbmonagg`) | | *"Total assets of all Swiss banks?"* | `snb_get_banking_balance_sheet` | | *"Income statement of cantonal banks?"* | `snb_get_banking_income` (bank_group: `G10`) | | *"Switzerland's balance of payments?"* | `snb_get_balance_of_payments` | | *"Which cubes are available?"* | resource `data://snb/cubes` | β†’ [More use cases by audience](EXAMPLES.md) β†’ --- ## Safety & Limits | Aspect | Details | |--------|---------| | **Access** | Read-only (`readOnlyHint: true`) β€” the server cannot modify or delete any data | | **Personal data** | No personal data β€” all sources are aggregated, public macroeconomic statistics | | **Rate limits** | SNB Warehouse API has WAF protection (HTTP 503 after ~100 rapid requests); the server retries automatically with exponential backoff (max 3 retries, delays 2/4/8s) | | **Timeout** | 15 seconds per API call | | **Authentication** | No API keys required β€” both APIs (`/api/cube/` and `/api/warehouse/cube/`) are publicly accessible | | **Data source** | [Swiss National Bank β€” data.snb.ch](https://data.snb.ch) | | **Terms of Service** | Subject to SNB's [Terms of Use](https://www.snb.ch/en/the-snb/mandates-goals/legal-framework/terms-of-use) and [Copyright](https://www.snb.ch/en/the-snb/mandates-goals/legal-framework/copyright); data is free for non-commercial use with source attribution | --- ## Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Claude / AI │────▢│ Swiss SNB MCP │────▢│ data.snb.ch β”‚ β”‚ (MCP Host) │◀────│ (MCP Server) │◀────│ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ /api/cube/ (JSON) β”‚ β”‚ 11 Tools Β· 5 Resources β”‚ β”‚ /api/warehouse/ β”‚ β”‚ Stdio | SSE β”‚ β”‚ Public Β· No Auth β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Phase 1: dedicated tools β”‚ β”‚ Exchange rates β”‚ β”‚ Phase 2: generic cubes β”‚ β”‚ Balance sheet β”‚ β”‚ Phase 3: warehouse + β”‚ β”‚ Interest rates β”‚ β”‚ banking stats β”‚ β”‚ Banking statistics β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Balance of payments β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Cube Discovery Pattern The SNB API follows a consistent cube-based structure. Read the `data://snb/cubes` resource to explore verified cube IDs, then `snb_get_cube_metadata` to inspect dimensions before querying with `snb_get_cube_data`. Phase 3 adds the Warehouse API (`/api/warehouse/cube/`) for granular banking statistics β€” start from the `data://snb/warehouse-cubes` and `data://snb/bank-groups` resources. --- ## Project Structure ``` swiss-snb-mcp/ β”œβ”€β”€ src/ β”‚ └── swiss_snb_mcp/ β”‚ β”œβ”€β”€ __init__.py β”‚ β”œβ”€β”€ server.py # Core tools and FastMCP server (Phase 1–2 + BoP) β”‚ └── warehouse.py # Warehouse API tools (Phase 3: banking statistics) β”œβ”€β”€ scripts/ β”‚ └── record_fixtures.py # records tests/fixtures/* from data.snb.ch β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ fixtures/ # recorded responses + PROVENANCE.md (date, rule, SHA-256) β”‚ β”œβ”€β”€ fixture_data.py # loader β€” a missing name is an error, not an empty dict β”‚ β”œβ”€β”€ test_unit.py # respx-mocked unit tests (run in CI) β”‚ β”œβ”€β”€ test_live_scenarios.py # 20 live scenarios for Phase 1–2 (nightly) β”‚ └── test_live_warehouse.py # 20 live scenarios for Phase 3 (nightly) β”œβ”€β”€ pyproject.toml # Build configuration (hatchling) β”œβ”€β”€ CHANGELOG.md β”œβ”€β”€ CONTRIBUTING.md # Contribution guidelines (English) β”œβ”€β”€ CONTRIBUTING.de.md # German version β”œβ”€β”€ SECURITY.md # Security policy & posture (English) β”œβ”€β”€ SECURITY.de.md # German version β”œβ”€β”€ LICENSE β”œβ”€β”€ README.md # This file (English) └── README.de.md # German version ``` --- ## Known Limitations - **Exchange rates:** Monthly averages only β€” no intraday or daily rates available via this API - **Balance sheet:** Monthly data; some positions may have a publication lag of 1–2 months - **Cube access:** Cube IDs are not officially documented by the SNB β€” read the `data://snb/cubes` resource for verified IDs - **Historical depth:** Coverage varies by series; exchange rates go back to 1980, some interest rate series start later - **No forecasts:** All data is historical/realised β€” SNB does not publish forecasts via this API --- ## MCP Protocol Version This server speaks **two protocol eras** over the same endpoint. The client's first request on a connection decides which one applies; a later claim from the other era is refused. | Era | Revision | Who reaches it | |---|---|---| | `initialize` handshake | `2024-11-05` … **`2025-11-25`** | What today's clients speak. The server answers with the revision asked for, or with the `2025-11-25` ceiling when the request asks for something newer. | | Per-request envelope | **`2026-07-28`** | A request carrying the `2026-07-28` `_meta` envelope opens a modern connection. | Both revisions are pinned in [`tests/test_protocol_version.py`](tests/test_protocol_version.py) and asserted against the installed SDK, so a Dependabot bump of `mcp` cannot move either one silently. The table above is **measured, not inferred**. The same gate drives the server's real serving loop over an in-memory stream pair β€” the loop stdio runs in production β€” and sends actual JSON-RPC frames through it: an enveloped request is served at `2026-07-28`, an `initialize` asking for `2026-07-28` is answered `2025-11-25`, and a claim from the other era is refused on an already-decided connection (`-32022` one way, `-32600` the other). An earlier revision of this section claimed the measurement needed an ASGI app; it does not β€” the modern era has no `initialize`, and the handshake never rode on HTTP to begin with. What the server reports about itself is measured the same way, in [`tests/test_server_identity.py`](tests/test_server_identity.py): the package version and the project URL are read back off the wire, not off the constructor. Note that the SDK's `LATEST_PROTOCOL_VERSION` is an alias for the **modern** era, not for the handshake era β€” pinning against it alone would leave the era that current clients actually negotiate free to drift. **Update policy.** When the gate fails, do not edit the constant blindly: read the spec changelog between the two revisions, verify the server still behaves, then move the constant, this section, `README.de.md` and [`CHANGELOG.md`](CHANGELOG.md) together. --- ## Testing ```bash # Unit tests (no API key required) PYTHONPATH=src pytest tests/ -m "not live" # Integration tests (live SNB API) PYTHONPATH=src pytest tests/ -m "live" # Re-record the fixtures from data.snb.ch (writes tests/fixtures/PROVENANCE.md) python scripts/record_fixtures.py ``` The unit-test payloads are **recorded, not invented**. Source, retrieval date, selection rule and SHA-256 per file are in [`tests/fixtures/PROVENANCE.md`](tests/fixtures/PROVENANCE.md). A hand-written mock encodes its author's assumption and can therefore never refute it β€” production code and fixture come from the same head, so where both are wrong, both are wrong together and the suite stays green. Each file keeps **every series** and only shortens the value lists: the code reasons about the dimensions and merely displays the values, so cutting "the first N series" would have hidden exactly what three of the findings depended on. --- ## Changelog See [CHANGELOG.md](CHANGELOG.md) --- ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on reporting issues, suggesting new SNB cube IDs, and contributing code. --- ## Security This server is read-only, processes no personal data, and talks only to `data.snb.ch`. See [SECURITY.md](SECURITY.md) for the full security posture, audit results, and how to report a vulnerability. --- ## License MIT License β€” see [LICENSE](LICENSE) --- ## Author Hayal Oezkan Β· [github.com/malkreide](https://github.com/malkreide) --- ## Credits & Related Projects - **Data:** [Swiss National Bank](https://data.snb.ch) β€” SNB data portal (public REST API) - **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) β€” Anthropic / Linux Foundation - **Related:** [zurich-opendata-mcp](https://github.com/malkreide/zurich-opendata-mcp) β€” MCP server for Zurich city open data - **Related:** [swiss-transport-mcp](https://github.com/malkreide/swiss-transport-mcp) β€” Swiss public transport MCP server - **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide) ## Installation Run via [`uv`](https://docs.astral.sh/uv/)'s `uvx` β€” no clone or manual install needed. Add to your MCP client config (`mcpServers` for Claude Desktop, Cursor and Windsurf; use a top-level `servers` key for VS Code in `.vscode/mcp.json`): ```json { "mcpServers": { "swiss-snb-mcp": { "command": "uvx", "args": [ "swiss-snb-mcp" ] } } } ```