bcb-mcp
MCP server for Brazilian Central Bank (BCB-SGS) time series — the Python counterpart of the R package GetBCBData.
It exposes the Central Bank of Brazil's SGS database (Sistema Gerenciador de Séries Temporais) to any MCP client (Claude Desktop, Cursor, Zed, VS Code, OpenCode, …) with three things the raw API does not give you:
- A curated dictionary of ~150 key Brazilian macro series (inflation, rates, activity, labor, external sector, fiscal, credit, monetary, energy, commodities) with Portuguese and English aliases, so an LLM can go from "unemployment" or "desemprego" to SGS
24369without guessing. - A parquet cache — every query is stored as a parquet file and served from disk on the next call, with only the missing date ranges downloaded.
- DuckDB SQL over everything cached, so the model can join, aggregate and compare series locally.
Like GetBCBData::gbcbd_get_series(), it accepts vectors of ids, returns long or wide data, splits long histories into API-friendly chunks (the BCB API rejects requests spanning more than ~10 years), retries failed calls, and reports per-series failures instead of aborting.
Installation
pip install bcb-mcp
# or
uvx bcb-mcp --stdio
Note: if PyPI returns "not found", the release is not published yet — use the source install below (or
uvx --from git+https://github.com/msperlin/bcb-mcp bcb-mcp).
From source:
git clone https://github.com/msperlin/bcb-mcp
cd bcb-mcp
uv venv && uv pip install -e .
Quick start
As an MCP server (STDIO)
{
"mcpServers": {
"bcb": {
"command": "uvx",
"args": ["bcb-mcp", "--stdio"]
}
}
}
As an HTTP server
bcb-mcp --http --host 127.0.0.1 --port 8000 --path /mcp
Directly in Python
import asyncio
from bcb_mcp import SeriesService
async def main():
svc = SeriesService()
df = await svc.get_series(["selic", 433], first_date="2020-01-01", format_data="wide")
print(df["columns"], df["row_count"])
await svc.close()
asyncio.run(main())
Tools
| Tool | What it does |
|---|---|
search_series |
Natural language → SGS ids. Searches the bundled dictionary and (optionally) the live BCB catalog. |
list_categories |
Dictionary categories with descriptions and example ids. |
list_series |
Browse the dictionary, optionally by category. |
describe_series |
Name, unit, frequency, source, description, aliases and cache state for given ids. |
get_series |
Download observations. Long/wide, chunked, cached, per-series failure reporting. |
get_series_stats |
Compact summary (last value, mean, min, max, change) without the raw rows. |
query_cache |
Read-only DuckDB SQL over all cached parquet files. |
cache_status |
What is cached: series, coverage ranges, rows, disk usage. |
clear_cache |
Evict selected series or the whole cache. |
today |
Server date plus handy "one/five years ago" dates. |
Output shape
get_series returns one compact row per observation (ref_date, value,
series_id). The name, unit, frequency and source of each series are returned
once in the series array, and failures lists the ids that could not be
fetched. Payload size matters for LLMs: the default max_rows is 1,000 per
series (about 50k tokens of JSON), so use get_series_stats when you only need
the numbers, format_data="wide" to align series on dates, or raise max_rows
for the full history.
Cache freshness: a range is downloaded once, but requests that reach the present
revalidate the last BCB_MCP_TAIL_REFRESH_DAYS days at most every
BCB_MCP_TAIL_REFRESH_MINUTES minutes, so new observations and BCB revisions
show up without refetching on every call.
Resources
bcb://dictionary/series— the full curated dictionary (JSON).bcb://dictionary/categories— categories and counts.bcb://cache/status— local cache summary.
Prompts
macro_briefing— guided workflow for a short Brazilian macro briefing.compare_series— guided workflow to compare series over time.
Example prompts
"What is the latest Selic rate and the 12-month IPCA? Compare them since 2015."
"Puxe a taxa de desemprego (PNAD Contínua) e o IBC-Br dos últimos 3 anos e diga se a atividade está acelerando."
"Compare credit outstanding as a share of GDP with the average lending rate since 2015. Use SQL if needed."
The model will call search_series("selic") → get_series_stats([432, 13522]) →
get_series([432, 13522], format_data="wide"), and can fall back to query_cache
for cross-series SQL. Note the units: 432 is the Selic target in % p.y.
(comparable to annual inflation), while 11 is the daily Selic in % p.d..
The dictionary
src/bcb_mcp/data/series_dictionary.json ships 151 hand-picked series, each with:
{
"id": 24369,
"name": "Unemployment rate - PNADC",
"name_pt": "Taxa de desocupacao - PNAD Continua",
"category": "labor",
"unit": "%",
"frequency": "Monthly",
"source": "IBGE",
"description": "Unemployment rate from the Continuous PNAD survey.",
"aliases": ["desemprego", "taxa de desemprego", "unemployment", "unemployment rate", "pnadc"]
}
Search is accent- and case-insensitive (desemprego, Desemprego, DESEMPREGO), matches English and Portuguese, ignores short function words (da, de, of, ...) in multi-word queries, and ranks exact alias hits above substring hits. The catalog includes the headline aggregates an LLM usually asks for first — IPCA in 12 months (13522), core IPCA (11427/4466), gross and net public debt (13762/4513) — plus Portuguese aliases such as dívida pública and taxa de câmbio. Anything not in the dictionary can still be found through the live BCB catalog search (English names), and any numeric id can be passed straight to get_series.
Regenerate the dictionary metadata from the official catalog:
python scripts/build_dictionary.py --check
Cache layout
~/.cache/bcb-mcp/ # or $BCB_MCP_CACHE_DIR
├── parquet/
│ ├── series/sgs_11.parquet # consolidated, deduplicated series store
│ ├── queries/sgs_11__2024-01-01__2024-12-31.parquet
│ └── catalog/search_<hash>.parquet # cached catalog searches
├── meta/sgs_11.json # coverage ranges, rows, official metadata
└── .write.lock # cross-process lock for cache mutations
- Every executed query is written as its own parquet file (auditable, reusable).
- The consolidated store tracks coverage intervals: asking for 2010–2024 after having 2015–2024 downloads only 2010–2014.
- Writes use a cross-process file lock and unique temporary files, so several MCP clients (Claude Desktop, Cursor, OpenCode, …) can share the same cache directory safely.
- Requests that reach the present revalidate the last few days periodically
(see the freshness settings below); future dates are clamped to today so a
last_datein the future cannot mark data as permanently cached. - All I/O goes through DuckDB; no pandas/pyarrow needed.
- Point the cache elsewhere with
--cache-dirorBCB_MCP_CACHE_DIR.
Querying the cache
SELECT series_name,
date_trunc('year', ref_date) AS year,
avg(value) AS avg_value
FROM cached_series
WHERE series_id IN (11, 433)
GROUP BY 1, 2
ORDER BY 1, 2;
Views available in query_cache: cached_series, cached_queries, series_metadata.
Mapping from the R package
| GetBCBData | bcb-mcp |
|---|---|
gbcbd_get_series(id, first_date, last_date, format_data) |
get_series(series_ids, first_date, last_date, format_data) |
names(id) <- c("Selic" = 11) |
series_names=["Selic"] (or just use aliases) |
gbcbd_search_series("selic") |
search_series("selic") |
use_memoise = TRUE |
use_cache=True (default) — parquet instead of memoise |
cache_path |
--cache-dir / BCB_MCP_CACHE_DIR |
do_parallel = TRUE |
always concurrent, bounded by BCB_MCP_MAX_CONCURRENCY (default 4) |
| 3-year chunking | 10-year chunking (the API limit), same retry/backoff behaviour |
| long / wide data frames | long / wide JSON rows |
Configuration
| Variable | Default | Meaning |
|---|---|---|
BCB_MCP_CACHE_DIR |
~/.cache/bcb-mcp |
Cache directory. |
BCB_MCP_TIMEOUT |
60 |
HTTP timeout (seconds). |
BCB_MCP_MAX_RETRIES |
3 |
Retries per request (exponential backoff). |
BCB_MCP_MIN_INTERVAL |
0.2 |
Minimum seconds between requests. |
BCB_MCP_MAX_CONCURRENCY |
4 |
Concurrent series downloads. |
BCB_MCP_MAX_ROWS |
1000 |
Default observations kept per series. |
BCB_MCP_TAIL_REFRESH_DAYS |
3 |
Days revalidated when a request reaches the present (0 disables). |
BCB_MCP_TAIL_REFRESH_MINUTES |
60 |
How old the cached copy must be before the tail is revalidated. |
Development
uv venv && uv pip install -e ".[dev]"
pytest --cov=bcb_mcp --cov-report=term-missing
ruff check . && ruff format --check .
Tests mock HTTP with respx; no network access is required.
Releasing to PyPI
The package is ready to publish. Two options:
- Trusted publishing (recommended, no token). Create the project on PyPI as a
pending publisher for
bcb-mcpwith workflowpublish.ymland environmentpypi, then publish a GitHub release. The workflow builds the sdist/wheel and uploads them with OIDC. - Manual.
uv build && uv publish(needsUV_PUBLISH_TOKENor a~/.pypirc).
Before tagging, bump the version in pyproject.toml and src/bcb_mcp/config.py
and add a CHANGELOG.md entry.
Data source and disclaimer
Data comes from the public BCB SGS endpoints (api.bcb.gov.br and www3.bcb.gov.br). Series may be revised or discontinued by the Central Bank. This project is not affiliated with the Banco Central do Brasil. Use at your own risk.
License
MIT — see LICENSE.
Metadata
Release files for bcb-mcp 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| bcb_mcp-0.1.1.tar.gz | 50.0 kB | With provenance | Details for bcb_mcp-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bcb_mcp-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for bcb_mcp-0.1.1-py3-none-any.whl |
Total release size: 96.1 kB
Release files / bcb_mcp-0.1.1.tar.gz
| Download URL | bcb_mcp-0.1.1.tar.gz |
|---|---|
| Size | 50.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd72ebdfdb00d221254043b0e50e079014210d873fff2fd77f69f75cc55b8ac2
|
|
BLAKE2b-256 checksum How to use checksums |
a9cb4dd8e70c28041fde877b23e59394c8b714f5180f67af14acbf8b478532a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.
Transparency logRelease files / bcb_mcp-0.1.1-py3-none-any.whl
| Download URL | bcb_mcp-0.1.1-py3-none-any.whl |
|---|---|
| Size | 46.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e05f61b75fc7286fab124ad410950ec31a4560fed04a0c5bf51f91876551c6a7
|
|
BLAKE2b-256 checksum How to use checksums |
0d8cf0b66d5dfbdf42c6587f0bf3cf8bfbd7917b130951196b1213b19c6425dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.
Transparency logSecurity for bcb-mcp 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for bcb_mcp-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.