sdmx-data-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sdmx-data-mcphow much do Swiss banks have in foreign claims since 2020?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
sdmx-data-mcp
An MCP server that lets an AI assistant discover and retrieve official statistics from SDMX services.
Built on the BIS's own pysdmx library.
Why this exists
Other SDMX MCP servers navigate metadata well and then hand back a query URL. The assistant ends up with a link, not numbers — and cannot answer the question it was asked.
sdmx-data-mcp finishes the job. get_data returns the observations.
Related MCP server: Countries MCP Server
Quick start
pip install sdmx-data-mcpThen register it with your client:
claude mcp add sdmx -- sdmx-data-mcpThat is the whole setup. Ask your assistant something like "what is the Swiss policy rate since 2020?" and it will find the dataflow, check what it can filter on, and come back with the actual series.
Claude Desktop — in claude_desktop_config.json:
{
"mcpServers": {
"sdmx": {
"command": "sdmx-data-mcp"
}
}
}Cursor — in .cursor/mcp.json:
{
"mcpServers": {
"sdmx": {
"command": "sdmx-data-mcp"
}
}
}If sdmx-data-mcp is not on your PATH, use the absolute path to the
executable, or "command": "python", "args": ["-m", "sdmx_data_mcp"].
Over HTTP, to share one instance:
sdmx-data-mcp --transport http --host 127.0.0.1 --port 8000The four tools
Meant to be called in order. Only the last one moves data.
Tool | Purpose |
| Known endpoints, plus any SDMX-REST v2 base URL you supply |
| One |
| Components, available codes, size signals |
| Retrieves the observations |
A real session
Transcripts below are actual tool output, not illustrations. More in docs/EXAMPLES.md.
1. Find the dataflow
search_dataflows(query="policy rate central bank")
{
"search_terms": ["policy", "rate", "central", "bank"],
"total_dataflows_on_service": 32,
"match_count": 12,
"dataflows": [
{ "ref": "BIS:WS_CBPOL(1.0)", "name": "Central bank policy rates",
"matched_on": "name" },
{ "ref": "BIS:WS_CBS_PUB(1.0)", "name": "Consolidated banking",
"matched_on": "name" }
// ...
]
}One request to the service; the terms are matched locally. Searching for synonyms costs nothing extra, so put them all in one query.
2. See what you can filter on
inspect_dataflow(ref="BIS:WS_CBPOL(1.0)", find_code="CH")
{
"series_count": 98,
"obs_count": null, // BIS does not report this
"size_warning": null, // small enough to retrieve
"dimensions": [
{ "id": "FREQ", "name": "Frequency", "code_count": 2,
"codes": [{"id": "D", "name": "Daily"}, {"id": "M", "name": "Monthly"}] },
{ "id": "REF_AREA", "name": "Reference area", "code_count": 49,
"codes": [ /* ... */ {"id": "CH", "name": "Switzerland"} /* ... */ ] }
]
}3. Get the numbers
get_data(
ref="BIS:WS_CBPOL(1.0)",
filters="FREQ = 'M' AND REF_AREA = 'CH' AND TIME_PERIOD >= '2020-01'",
columns=["OBS_VALUE"],
)
{
"filter_applied": "FREQ = 'M' AND REF_AREA = 'CH' AND TIME_PERIOD >= '2020-01'",
"filter_fallback": null,
"row_count": 79,
"total_rows_available": 79,
"truncated": false,
"records": [
{"SERIES_KEY": "M.CH", "OBS_VALUE": "-0.75", "TIME_PERIOD": "2020-01"},
{"SERIES_KEY": "M.CH", "OBS_VALUE": "-0.75", "TIME_PERIOD": "2020-02"}
// ...
],
"next_step": "Complete: all 79 matching rows were returned. Safe to aggregate."
}truncated: false and row_count == total_rows_available, so this really is
the whole series and it is safe to average or chart.
Several values of one component
The query parser accepts AND but not OR. Use IN (...):
get_data(
ref="BIS:WS_XRU(1.0)",
filters="FREQ = 'M' AND CURRENCY IN ('CHF', 'KES') AND TIME_PERIOD >= '2026-01'",
columns=["OBS_VALUE"],
)
{ "row_count": 14, "truncated": false, "records": [
{"SERIES_KEY": "M.CH.CHF.E", "OBS_VALUE": "0.768269", "TIME_PERIOD": "2026-01"},
{"SERIES_KEY": "M.KE.KES.A", "OBS_VALUE": "129.126615", "TIME_PERIOD": "2025-12"}
// ...
] }With both legs retrieved, an assistant can compute what neither service publishes — a CHF/KES cross rate — and correctly note that the newest period the two currencies share is December 2025, because KES lags CHF.
What the server enforces
These rules are handled by the server rather than left for the assistant to remember. Each one prevents a specific class of confidently wrong answer.
Conjunctions only. AND between clauses, never OR. Several values of one
component use IN ('A', 'B').
Availability is not validity. inspect_dataflow reports codes for which
data currently exist. A code missing from that list may still be valid in the
full codelist, so its absence is never reported as proof that something does
not exist.
Ambiguous codes are surfaced, not guessed. On BIS consolidated banking,
CH is available in three components at once — reporting country, counterparty
country, and a bank type that happens to share the country codelist:
inspect_dataflow(ref="BIS:WS_CBS_PUB(1.0)", find_code="CH")
{ "code_locations": [
{"component_id": "L_REP_CTY", "role_hint": "reporting country"},
{"component_id": "CBS_BANK_TYPE", "role_hint": "bank type (shares a country codelist)"},
{"component_id": "L_CP_COUNTRY", "role_hint": "counterparty country"}
],
"next_step": "That code is ambiguous - it appears in 3 components ..." }Claims by Swiss banks and claims on Switzerland are different questions. The server makes the assistant choose rather than silently pick one.
Size before retrieval. obs_count is frequently unreported — the BIS
returns null for it on both full and filtered scopes — so series_count is
the signal relied on. A size_warning appears when retrieval would truncate.
Truncation is not sampling. When truncated is true, the rows are the
first N in service order, and next_step says so explicitly:
Truncated: 112,648 rows matched but only 500 were returned. These are the first rows in service order, not a sample — do not compute totals or averages from them.
Time filters degrade gracefully. TIME_PERIOD is pushed down to the
service first. On a client-side rejection the clause is stripped, the narrower
query is retried, and the cutoff is applied with pandas — reported in
filter_fallback. It deliberately does not fall back on NotFound,
Unavailable or InternalError, where dropping a clause cannot help and would
only obscure the real error.
Errors keep their meaning. The pysdmx error hierarchy is preserved rather than flattened, so an assistant can tell a transient outage from a bad reference instead of retrying blindly or giving up too early:
[internal_error] Unexpected message format - The payload could not be
deserialized. | retriable=false | next_step: The service failed, or returned a
response that could not be parsed. Do not repeat the identical call - narrow
the filter or try a different dataflow, since the fault is server-side.Kind | Retriable | Means |
| yes | Service unreachable; retry after a delay |
| yes | Transient failure |
| no | Resource does not exist; do not repeat |
| no | Malformed filter; check syntax and code IDs |
| no | Credentials rejected |
| no | Service lacks the required API |
| no | Server-side fault; narrow or change the query |
| no | Bug in this server; please report it |
Services
pysdmx.api.dc.Endpoints currently ships exactly one endpoint, the BIS. Every
tool therefore takes a service argument accepting any SDMX-REST v2 base
URL as a first-class input, not a fallback:
search_dataflows(query="prices", service="https://your-service.org/api/v2")The service must return structural metadata as SDMX-JSON 2.0.0 and data as SDMX-CSV. Other providers (ECB, OECD, IMF, Eurostat, ILO) are deliberately not hardcoded: each needs verifying against those requirements first, and listing them unverified would invite confident failures.
Development
pip install -e ".[dev]"
ruff format && ruff check && mypy
pytest --cov=sdmx_data_mcp --cov-branch --cov-report=term-missing162 tests at 100% statement and branch coverage, mypy in strict mode, CI across Linux, Windows and macOS on Python 3.10–3.13.
Most server tests inject a fake connector so every branch is reachable
deterministically. A separate end-to-end module drives the real
PandasConnector against respx-mocked responses, so drift in URL
construction or SDMX-CSV parsing surfaces there.
Relationship to pysdmx
This package depends on pysdmx[data] from PyPI. It does not fork or vendor
it, and uses only the public API — PandasConnector, Endpoints and the
errors hierarchy.
The same server has also been proposed upstream as bis-med-it/pysdmx#669. This package exists so it is installable today regardless of what happens there.
Licence
Apache-2.0. See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityDmaintenanceA model Context Protocol (MCP) server that provides comprehensive OECD statistics through the SDMX API, supporting AI assistants and chatbots to query OECD datasets in areas such as economy, health, education, and environment.92Apache 2.0
- AlicenseNot gradedqualityDmaintenanceMCP Server that gives AI assistants access to comprehensive country data from 250+ countries.1MIT
- AlicenseBqualityFmaintenanceMCP server for accessing Japanese government statistics portal 'e-Stat' API, enabling language models to search and retrieve statistical data.520MIT
- FlicenseNot gradedqualityDmaintenanceMCP server that lets AI agents discover and call R and Python statistical functions without writing code.
Related MCP Connectors
MCP server connecting AI agents to non-custodial staking data across 130+ networks.
MCP server for AI access to Swagger by SmartBear.
MCP server providing access to the Scorecard API to evaluate and optimize LLM systems.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/markchweya/sdmx-data-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server