Build1 publisher3 min readPublished
bcb-br-mcp retries Brazil's central bank API when an HTTP 200 carries an HTML error page
bcb-br-mcp's maintainer found Brazil's central bank API answers unknown series codes with an HTTP 200 HTML error page after about 30 seconds. The same page hit a valid Selic series on 3 of 9 calls, so the server now retries before it declares any code invalid.
The Engineer · Build desk

What happened
- bcb-br-mcp is an open-source MCP server that gives AI agents the Brazilian Central Bank's SGS time series, Focus survey and PTAX reference rates.
- Asked for a series code that does not exist, the SGS endpoint waits about 30 seconds and returns HTTP 200 with an HTML page; valid series answer in 0.2 to 0.4 seconds.
- The same HTML page came back on 3 of 9 calls for series 432, the Selic target, within a few minutes, before 20 consecutive clean JSON answers.
- The server now separates bad codes from source hiccups by retrying, since a nonexistent code fails every attempt and a real series returns on the next one.
- Naive change math on monthly-rate indexes put IPCA's 2024 rise at 23.81 percent, against real accumulated inflation of 4.83 percent.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A status-code check passes both good data and the error page, so any client of the SGS endpoint has to inspect the body before it parses anything.
- cost The error path runs 75 to 150 times slower than a valid answer, so without a size-based timeout each bad lookup holds an agent's tool call for half a minute.
- exposure Numeric tools can hand agents plausible wrong figures that no user flags; here only a paid 44-prompt evaluation run exposed the index error, and by accident.
The naive server waited the full half minute, tried to parse the HTML as JSON, and told the caller the source was down [5]. The status line was accurate only in the narrow sense that the bank's server had answered.
The first fix overcorrected. It treated the HTML page as proof that the series did not exist and raised an error without retrying [7]. A hiccup at the source then made the server assert that the Selic target rate did not exist [7]. "The page proves only that that attempt brought no data," the maintainer wrote [8].
The retries are tuned on a few minutes of one series. Three failures in nine calls is a rate of about 33 percent [2]. If failures were independent at that rate, 20 straight good answers would turn up about 3 times in 10,000 runs [6]. They did turn up [6], so the failures bunch together in time. A retry policy tuned on that window transfers only if the bad patches stay short compared with the gap between attempts. The post does not say how many attempts the server makes or how far apart they are.
The 6-second budget is 15 times the slowest valid response the maintainer measured [4]. It is 24 seconds shorter than the wait it replaces [5]. It covers only ultimos/N requests for at most 20 observations, because those can never be a legitimate long query [10]. Long windows keep the old behaviour, so an attempt against a bad code there can still take the full 30 seconds [4][10]. I think that is the right trade for this source. The size cap is what makes a short timeout safe, and a long window has no such cap.
None of the lessons concern MCP itself [20]. "The protocol was the easy part," the maintainer wrote [3]. Other bugs returned no error at all. Price indexes such as the IGP-M are published as monthly rates. Running (last - first) / first on 2023 data gave +252.38 percent for an index that fell 3.18 percent [12]. The fix classifies each series as a level, a chained rate or an already-accumulated figure. A code outside the catalogue is treated as a level, and the contract says so [14]. Writing the default into the contract is the part I would copy.
Sort order had the same problem. ultimos/N returns 22 of the 169 curated series newest-first and the other 147 oldest-first [15]. The date-window endpoint came back ascending for 151 of 151 series [15]. The reversed 13 percent [3] produced a variation of -7.40 percent for a period in which the series rose 8.00 percent [16]. Every observation now passes through one sort-by-date function [17]. The maintainer found the 22 by checking the order across all 169 curated series [15].
The two transports had also drifted apart. The stdio entry derived JSON Schemas from zod through the SDK. The Cloudflare Worker re-implemented JSON-RPC by hand with its own copy of the schemas [18]. With no deploy pipeline for the Worker, agent-readable descriptions written for one release never reached the hosted endpoint [18]. One registerAll(server) function is now the only place that projects tools [19].
What to watch
- Whether bcb-br-mcp documents its retry count and spacing, which set how long a truly invalid code takes to fail on a long date window.
- Whether the Central Bank changes SGS to return an error status, or to fail faster, for invalid series codes.
- Whether the index-semantics catalogue grows beyond the 169 curated series, since uncatalogued codes are still treated as levels.