Wraps the ECB's exchange rate data via the Frankfurter API with seven tools covering currency lookup, spot rates, historical conversions, and time series queries. You get point-in-time FX rates back to 1999, automatic cross-rate triangulation through EUR, and weekend/holiday date snapping with clear flags when the API returns a prior business day. Long time series (over 90 days) spill into a DuckDB-backed DataCanvas workspace where you can run SQL aggregations and window functions. Built on mcp-ts-core, runs over stdio or streamable HTTP, and needs no API keys. Useful when you need reliable historical currency data or want to analyze multi-year exchange rate trends without managing your own dataset.
FRANKFURTER_BASE_URLdefault: https://api.frankfurter.dev/v1Frankfurter API base URL. Override for local testing or self-hosted instances.
FX_TIMESERIES_CANVAS_THRESHOLD_DAYSdefault: 90Day range above which fx_get_timeseries spills results to DataCanvas for SQL analytics.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.
Convert currencies, get FX rates, and query historical ECB exchange rate data via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://exchange-rates.caseyjhand.com/mcp
Seven tools for working with ECB FX rate data — currency lookup and disambiguation, point-in-time rates and conversions, historical time-series retrieval, and SQL analytics over the DataCanvas workspace that long time-series calls produce:
| Tool | Description |
|---|---|
fx_list_currencies | List all ~30 ECB-supported ISO 4217 currencies with full names. Use before converting to disambiguate "dollars" (USD vs AUD vs CAD vs HKD vs SGD). |
fx_get_rates | Snapshot of all available rates for a base currency at latest or a historical date. Optional symbols filter for smaller responses. |
fx_get_rate | Exchange rate for a single currency pair at latest or a historical date. Surfaces date_snapped when a weekend/holiday request returns the prior business-day rate. |
fx_convert_currency | Convert an amount between any two currencies at latest or a historical rate. Cross-rates are triangulated through EUR. Returns converted amount, rate used, rate date, and whether the date was snapped. |
fx_get_timeseries | Historical daily rates for a currency pair over a date range. Short ranges (≤90 days) are returned inline; long ranges spill to DataCanvas with a canvas_id for SQL follow-up. |
fx_dataframe_describe | List DataCanvas tables and their columns from a prior fx_get_timeseries call. Required first step before fx_dataframe_query. |
fx_dataframe_query | Run a read-only SQL SELECT against a DataCanvas table produced by fx_get_timeseries. Supports aggregations, GROUP BY, window functions, and JOINs across multiple registered tables. |
fx_list_currenciesEnumerate all supported currencies before converting or querying.
[{ code, name }] for all ~30 ECB-scoped currenciesfx_get_ratesFull rates snapshot for a base currency in one call.
symbols parameter narrows the response to specific quote currenciesfx_get_ratePoint-in-time exchange rate for a single pair.
date_snapped: true when the API silently moved a weekend/holiday request to the prior business dayfx_convert_currency when you need the converted amount; use this tool when you only need the rate numberfx_convert_currencyConvert an amount between any two currencies.
converted_amount, rate, rate_date, date_snapped, plus rate_type and source provenance on every responsefx_get_timeseries + fx_dataframe_describe / fx_dataframe_queryHistorical rate series and DataCanvas SQL analytics.
fx_get_timeseries returns a date-keyed series (business days only — ECB publishes once per business day):
FX_TIMESERIES_CANVAS_THRESHOLD_DAYS, default 90 days) → inline rates map + metadatacanvas_id, table_name, and truncated: true — the full series is registered as a DuckDB-backed tableOnce a canvas_id is in hand:
fx_dataframe_describe — list the tables and columns on the canvas (required before fx_dataframe_query)fx_dataframe_query — run arbitrary SQL SELECT against the registered table; supports aggregations, GROUP BY, window functions, JOINs across tables from multiple fx_get_timeseries callsThe canvas uses a session-scoped TTL. To continue working with a prior series, call fx_get_timeseries again with the same parameters to obtain a fresh canvas_id.
| Type | Name | Description |
|---|---|---|
| Resource | fx://currencies | All supported currencies as a stable reference document. Injectable context for clients that support resources. |
| Resource | fx://rates/latest/{base} | Latest rates snapshot for a base currency as a stable URI. |
All resource data is also reachable via tools. Use fx_list_currencies or fx_get_rates for programmatic access.
Built on @cyanheads/mcp-ts-core:
unsupported_currency, date_out_of_range, canvas_not_found, invalid_querynone, jwt, oauthECB FX–specific:
date_snapped flag surfaces when the API returns a different date than requestedfx_list_currencies always reflects the live setfx_get_timeseries spills long ranges to DuckDB for aggregations and trend analysisrate_type: "ECB reference (mid-market)" and source: "ECB via Frankfurter" — explicitly mid-market, not tradeable bid/askAgent-friendly output:
rate_type, source, rate_date, and date_snapped so agents can reason about trust and freshnessreason fields (unsupported_currency, date_out_of_range, invalid_query, …) let callers branch on failure type, not string parsingcanvas_id and truncated: true signal when a time-series exceeds the inline limit and SQL follow-up is neededA public instance is available at https://exchange-rates.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"exchange-rates-mcp-server": {
"type": "streamable-http",
"url": "https://exchange-rates.caseyjhand.com/mcp"
}
}
}
No API key required — Frankfurter is keyless. Add the following to your MCP client configuration file:
{
"mcpServers": {
"exchange-rates-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/exchange-rates-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"exchange-rates-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/exchange-rates-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"exchange-rates-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/exchange-rates-mcp-server:latest"
]
}
}
}
To enable DataCanvas for long time-series SQL analytics, add CANVAS_PROVIDER_TYPE=duckdb:
{
"mcpServers": {
"exchange-rates-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/exchange-rates-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"CANVAS_PROVIDER_TYPE": "duckdb"
}
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/exchange-rates-mcp-server.git
cd exchange-rates-mcp-server
bun install
cp .env.example .env
# edit .env as needed (all vars are optional — no keys required)
All configuration is validated at startup via Zod schemas. Environment variables:
| Variable | Description | Default |
|---|---|---|
FRANKFURTER_BASE_URL | Frankfurter API base URL. Override for local testing or a self-hosted instance. | https://api.frankfurter.dev/v1 |
FX_TIMESERIES_CANVAS_THRESHOLD_DAYS | Day range above which fx_get_timeseries spills to DataCanvas instead of returning inline. | 90 |
CANVAS_PROVIDER_TYPE | Canvas engine. Set to duckdb to enable DataCanvas for fx_get_timeseries long-range spillover. | none |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424: debug, info, notice, warning, error). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides including storage, session, and telemetry vars.
Build and run:
bun run rebuild
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t exchange-rates-mcp-server .
docker run --rm -p 3010:3010 exchange-rates-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/exchange-rates-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them. DuckDB native binaries are pre-built in the build stage and copied to production, keeping the production image free of build tools.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, resources, and canvas accessor. |
src/config/ | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools/ | Tool definitions (*.tool.ts) — fx_* tools. |
src/mcp-server/resources/ | Resource definitions — fx://currencies and fx://rates/latest/{base}. |
src/services/frankfurter/ | Frankfurter HTTP client, retry logic, and domain types. |
src/services/canvas/ | Module-level DataCanvas accessor for fx_get_timeseries spillover. |
tests/ | Unit and integration tests mirroring src/. |
docs/ | Design document and idea notes. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsrate_type provenance in every responseIssues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
com.mcparmory/google-sheets
domdomegg/google-sheets-mcp
henilcalagiya/google-sheets-mcp
cct15/war-dashboard-data
moooonad/mcp-google-sheets-full
io.github.br0ski777/csv-to-json