SDMX Gateway
FreeNot checkedAn MCP server providing progressive discovery tools for SDMX statistical data, enabling AI agents to explore and access SDMX-compliant repositories through inte
About
An MCP server providing progressive discovery tools for SDMX statistical data, enabling AI agents to explore and access SDMX-compliant repositories through interactive tools, resources, and prompts.
README
A Model Context Protocol (MCP) server that provides progressive discovery tools for SDMX statistical artefacts and data. This implementation enables AI agents to explore and access SDMX-compliant statistical data repositories through interactive tools, resources, and prompts.
Version 0.2.0 - Now with structured outputs, Streamable HTTP transport, and elicitation support.
🚀 Key Features
- Progressive Discovery: Reduces metadata transfer from 100KB+ to ~2.5KB
- Structured Outputs: All tools return validated Pydantic models
- Multiple Transports: STDIO (development) and Streamable HTTP (production)
- Interactive Elicitation: User confirmation dialogs for endpoint switching
- Multi-Provider Support: SPC, FBOS, SBS, ECB, UNICEF, IMF, OECD, ESTAT, ILO, ABS, BIS
Quick Start
A public instance of this server is hosted on Railway. Point any MCP client at the URL below and you can skip cloning, installing dependencies, and managing a Python environment.
https://sdmx-mcp-gateway-production.up.railway.app/mcp
Transport is Streamable HTTP. The endpoint is shared and stateless from the client's perspective; each MCP session gets its own server-side state (endpoint selection, client pool, mismatch-hint cache).
Quick check that it responds:
curl -X POST https://sdmx-mcp-gateway-production.up.railway.app/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
See MCP Client Configuration for ready-to-paste configs for Claude Code, Claude Desktop, Codex, Cursor, Zed, and OpenCode.
Health Status
A standalone monitor checks the hosted gateway and every provider endpoint
(through the gateway and directly) every two hours. See monitor/README.md
for running or deploying it. Once deployed, its status page shows current
health, per-endpoint history, and whether a failure sits in the gateway or
the upstream provider.
Self-Hosting
If you prefer to run the server yourself (offline use, private deployments, development on the tools themselves), see Installation and Running the Server.
cd sdmx-mcp-gateway
uv sync
uv run python main_server.py # STDIO, for local clients
uv run python main_server.py --transport http --port 8000 # HTTP, for remote clients
uv run mcp dev ./main_server.py # MCP Inspector (browser UI)
The Problem We Solve
Traditional SDMX queries with references=all return 100KB+ of XML metadata, overwhelming LLM context windows. Our progressive discovery approach provides a layered exploration:
| Step | Operation | Data Size |
|---|---|---|
| 1 | Find dataflows by keyword | ~300 bytes |
| 2 | Get dimension structure | ~1KB |
| 3 | Explore specific dimension codes | ~500 bytes |
| 4 | Check data availability | ~700 bytes |
| 5 | Build final query URL | ~200 bytes |
| Total | ~2.5KB |
Architecture
sdmx-mcp-gateway/
├── main_server.py # FastMCP server with CLI
├── app_context.py # Lifespan management & shared resources
├── config.py # Endpoint configuration
├── sdmx_progressive_client.py # SDMX 2.1 REST client
├── utils.py # Validation & utilities
├── models/
│ ├── __init__.py
│ └── schemas.py # Pydantic output schemas
├── tools/
│ ├── sdmx_tools.py # Discovery tools implementation
│ └── endpoint_tools.py # Endpoint management
├── resources/
│ └── sdmx_resources.py # MCP resources
├── prompts/
│ └── sdmx_prompts.py # Guided prompts
└── tests/ # Test suite
Available Tools
Discovery Tools
| Tool | Description | Output Schema |
|---|---|---|
list_dataflows |
Find dataflows by keyword | DataflowListResult |
get_dataflow_structure |
Get dimensions and structure | DataflowStructureResult |
get_dimension_codes |
Explore codes for a dimension | DimensionCodesResult |
get_data_availability |
Check what data exists | DataAvailabilityResult |
get_structure_diagram |
Generate Mermaid diagram of relationships | StructureDiagramResult |
compare_structures |
Compare two structures for differences | StructureComparisonResult |
validate_query |
Validate query parameters | ValidationResult |
build_key |
Construct SDMX key | KeyBuildResult |
build_data_url |
Generate data retrieval URL | DataUrlResult |
get_codelist |
Browse specific codelist | dict |
SDMX 2.1 has no server-side pagination, so list_dataflows fetches a provider's entire dataflow listing under the hood even when limit is small; for ESTAT that listing alone is 37 MB. The parsed result is cached process-wide (shared across every session, not per client) for DATAFLOW_CACHE_TTL_S seconds (default 900, 15 minutes), keyed on the base URL, agency, and the other parameters that change the answer. The result's next_step field always states whether that call was served from cache and, if so, how old the entry is, so a caller never has to guess. Pass fresh=True to bypass the cache and force a live re-fetch; the fresh result still refreshes the cache for everyone else. Use fresh=True for liveness checks, where a cached answer would say nothing about whether the provider is reachable right now.
Reference Metadata
| Tool | Description | Output Schema |
|---|---|---|
get_reference_metadata |
Summarise source, methodology, licence and caveats for a dataflow | ReferenceMetadataResult |
get_metadata_attribute |
Get every value of one metadata attribute, with the slice each applies to | MetadataAttributeValuesResult |
Reference metadata is the descriptive material about a dataflow rather than its structure: who compiled it, from what source, under what licence, with what caveats. Coverage varies by provider, so the result's channels field reports which channel was available: .Stat Suite deployments (SPC, FBOS, SBS, OECD) publish it through a v2 MSD query, some other providers carry equivalent detail only in ordinary DSD attributes on the data message, and some publish neither. A channel status of inconclusive means the query did not produce a usable answer; that is different from a confirmed absence, which is what empty reports.
Pass key to narrow the query to one series. This matters for large dataflows: SPC's DF_SDG metadata query is 5.37 MB unfiltered against 5.6 KB with a partial key, and the tool refuses an unfiltered query over 2 MB (too_broad) by aborting the read partway through rather than downloading it in full; the same cap applies to both the MSD query and the DSD-attribute fallback used by providers without a /v2/ endpoint.
get_reference_metadata: the summary
get_reference_metadata returns one entry per attribute the provider declares, in metadata_attributes, plus a coverage count and a per-channel channels status. Each attribute carries:
status:populatedwhen the provider published at least one value,declared_emptywhen every occurrence in the response read was blank -- for the whole dataflow when no key was supplied, or for the slice queried when one was, since a keyed request only reads that slice. A declared-but-empty attribute is a real, observed answer, not a missing one, and it is listed rather than omitted, so a blank licence field reads differently from a provider that has no licence concept at all.valueanddrill_down:valuecarries the headline text when exactly one distinct value exists and either it describes the whole dataflow (dataflow/datasetscope, provider-marked unqualified), or it was identical on every data row this query actually returned (all_observed_rowsscope). The second case is common: SPC'sDF_SDGpublishes the same value on every per-country row and has no dataflow-wide row at all, so before this rule every one of its populated attributes came backvalue: null.drill_downisfalseonly fordataflow/datasetscope; forall_observed_rowsit staystrue, since that scope says nothing about rows the query did not return (a different key, or rows beyond a truncated response's row cap) and the per-row detail behind it still matters. When the attribute's values differ across the rows read,valueisnullanddrill_downistrue, meaning no single value can stand in as the answer.distinct_valuesandscope: how many distinct values were found, and what the headline (when present) attaches to: the whole dataflow (dataflow/dataset), every row this query returned (all_observed_rows, weaker thandataflow: no provider ever marked it unqualified), or one slice (partial_key).
coverage (declared / populated / empty) is reported only when the MSD channel itself answered found on an untruncated read: that is the only channel that can see a declared-but-empty attribute (parse_msd_csv's declared_empty status). Neither an MSD empty answer nor the DSD-attribute fallback can establish it, even when both agree: the DSD fallback only ever sees what a message actually populates, so it can never confirm that a provider declares nothing further, and a truncated MSD read cannot rule out a value past the row cap. coverage is None in every one of those cases -- when the only channel that answered was the DSD-attribute fallback, which shows populated attributes only; when the MSD channel answered empty (with or without the DSD fallback also resolving); or when the MSD channel found something but the read was cut off before finishing.
get_metadata_attribute: the drill-down
Call get_metadata_attribute(dataflow_id, attribute_id, key=None, agency_id=None) after get_reference_metadata reports drill_down: true for an attribute, to read every distinct value with the dimension key (key_context) it applies to. attribute_id is the short id from the summary, not the full dotted path. The result (MetadataAttributeValuesResult) has no separate error field; every case below is a normally-shaped result distinguished by total and by the text of notes. Four answers matter and are kept distinct:
- Populated: one entry per distinct (value,
key_context) pair; the same value may appear multiple times with differentkey_contextvalues.totalcounts these pairs, while the summary'sdistinct_valuescounts distinct values only (the two need not match), andtruncatedistruewhen more than 200 pairs exist, withvalueslimited to the first 200. - Declared but empty:
total: 0,values: [], and a note stating the attribute is declared and left blank -- for the whole dataflow when no key was supplied, for the slice queried when one was. The first note does not start with"Error:". - Unknown attribute id:
total: 0,values: [], and a first note starting with"Error: "reading"Error: Unknown attribute '<id>' for <dataflow>: declared attributes are <ids>", naming the dataflow's declared attribute ids so a typo reads as "here is what exists" rather than as an empty result. This wording is only used when the MSD channel itself answeredfound: that is the one channel outcome that can vouch for a provider's full declared set, so the listed ids are genuinely everything declared. A caller distinguishes this from the declared-but-empty case above by checking whether the first note starts with"Error:", not by looking for a field that does not exist on this result. - No channel confirmed a declared set:
total: 0,values: [], and notes explaining which channel could not answer and why that is not evidence the attribute is missing. This covers an MSD channel that answeredtoo_broad,inconclusiveorunsupported, and also the MSD channel's ownemptyanswer -- even when the DSD-attribute fallback separately found something or itself resolved toempty, since that fallback only ever sees what a message actually populates and can never see an attribute the DSD declares but a given response leaves blank, so it cannot vouch for the full declared set on its own. Like the declared-but-empty case, the notes here do not start with"Error:".
Each value's key_context is null in two different situations that a caller must not conflate: from the MSD channel, null means the value is genuinely dataflow-wide; from the DSD-attribute fallback channel (used by providers without a /v2/ endpoint), null means that channel has no per-value key to report at all, even when the value actually attaches to one series or observation rather than to the whole dataflow. A note on the result says which channel supplied the attribute when this applies.
Endpoint Management
| Tool | Description | Output Schema |
|---|---|---|
get_current_endpoint |
Show the session's default provider | EndpointInfo |
list_available_endpoints |
List all configured providers | EndpointListResult |
The session default is set once at startup from the SDMX_ENDPOINT env var and is not mutable at runtime. To target a specific provider for an individual call, pass endpoint=<KEY> to any endpoint-scoped tool.
Resources
sdmx://agencies- List of known SDMX data providerssdmx://agency/{id}/info- Specific agency detailssdmx://formats/guide- Data format comparisonsdmx://syntax/guide- Query syntax reference
Prompts
discovery_guide- Step-by-step data discovery workflowtroubleshooting_guide- Common issue resolutionbest_practices- Use-case specific guidancequery_builder- Interactive query construction
Supported Data Sources
| Key | Provider | Description | Constraints |
|---|---|---|---|
SPC |
Pacific Data Hub | Pacific regional statistics (default) | Actual (single + bulk) |
FBOS |
Fiji Bureau of Statistics | Fiji official national statistics | Actual (single + bulk) |
SBS |
Samoa Bureau of Statistics | Samoa official national statistics | Actual (single + bulk) |
ECB |
European Central Bank | European financial statistics | Allowed (single + bulk) |
UNICEF |
UNICEF | Children and youth statistics | Actual (single + bulk) |
IMF |
International Monetary Fund | Global financial statistics | Actual (single) |
OECD |
OECD | Economic and social statistics | Actual (single) |
BIS |
Bank for International Settlements | International financial statistics | Actual (single) |
ABS |
Australian Bureau of Statistics | Australian official statistics | Actual (single) |
ILO |
International Labour Organization | Labour and employment statistics | Actual (single) |
ESTAT |
Eurostat | European Union official statistics | None |
Target a provider per call:
# Pass endpoint= on any endpoint-scoped tool
list_dataflows(endpoint="ECB", limit=10)
get_dataflow_structure(dataflow_id="DF_CPI", endpoint="FBOS")
# Or rely on the session default (set at startup from SDMX_ENDPOINT env var)
list_dataflows(limit=10)
See docs/ENDPOINT_CONFIGURATION.md for provider-specific behaviours and constraint strategies.
Installation
Prerequisites
- Python 3.12 or higher
- uv (recommended) or pip
Using UV (Recommended)
cd sdmx-mcp-gateway
uv sync
Using pip
cd sdmx-mcp-gateway
pip install -r requirements.txt
Dependencies
mcp[cli]>=1.26.0- Model Context Protocol SDKpydantic>=2.0.0- Structured output validationhttpx>=0.27.0- Async HTTP clientcertifi>=2024.0.0- SSL certificates
Running the Server
CLI Options
uv run python main_server.py [OPTIONS]
Options:
--transport, -t Transport type: stdio, http, streamable-http (default: stdio)
--host Host for HTTP transport (default: HOST env or 0.0.0.0)
--port, -p Port for HTTP transport (default: PORT env or 8000)
--stateless Run in stateless mode (HTTP only)
--json-response Use JSON responses instead of SSE (HTTP only)
--debug Enable debug logging
Development Mode (STDIO)
# Direct execution
uv run python main_server.py
# With MCP Inspector (opens browser UI)
uv run mcp dev ./main_server.py
Production Mode (Streamable HTTP)
uv run python main_server.py --transport streamable-http --host 0.0.0.0 --port "${PORT:-8000}"
For container platforms such as Vercel, Railway, or Fly, prefer binding to 0.0.0.0
and reading the port from the platform-provided PORT environment variable. The
server now fails fast if the installed MCP SDK ignores the requested HTTP bind
settings, rather than silently falling back to localhost.
MCP Client Configuration
The recommended path is to point your client at the hosted Railway URL. Each section below shows:
- Hosted (HTTP): uses
https://sdmx-mcp-gateway-production.up.railway.app/mcp. Nothing to install beyond the client itself. - Self-hosted (STDIO): runs the server from a clone of this repo. Requires
uvandgit(see Installation).
Clients that only speak STDIO can still reach the hosted instance via the mcp-remote bridge, which proxies HTTP MCP servers through stdio via npx.
Claude Code
Hosted, one-liner:
claude mcp add --transport http sdmx-gateway https://sdmx-mcp-gateway-production.up.railway.app/mcp
Or in .claude/settings.json / ~/.claude/settings.json:
{
"mcpServers": {
"sdmx-gateway": {
"type": "http",
"url": "https://sdmx-mcp-gateway-production.up.railway.app/mcp"
}
}
}
Self-hosted (STDIO):
{
"mcpServers": {
"sdmx-gateway": {
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/sdmx-mcp-gateway",
"python",
"main_server.py"
]
}
}
}
If
uvis not on your PATH, use the full path (e.g."/home/user/.local/bin/uv").
OpenAI Codex CLI
Hosted, via ~/.codex/config.toml (uses mcp-remote to bridge HTTP into stdio):
[mcp_servers.sdmx]
command = "npx"
args = ["-y", "mcp-remote", "https://sdmx-mcp-gateway-production.up.railway.app/mcp"]
enabled = true
tool_timeout_sec = 120
Or via the CLI:
codex mcp add sdmx -- npx -y mcp-remote https://sdmx-mcp-gateway-production.up.railway.app/mcp
Self-hosted:
[mcp_servers.sdmx]
command = "uv"
args = ["run", "--directory", "/path/to/sdmx-mcp-gateway", "python", "main_server.py"]
enabled = true
tool_timeout_sec = 120
The
commandfield must be the executable only. Ifuvornpxis not on your PATH, use the full path. Arguments go inargsas a separate array.
Claude Desktop
Claude Desktop does not yet speak HTTP MCP natively, so use mcp-remote to reach the hosted server.
Config file locations:
- Linux:
~/.config/Claude/claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Hosted:
{
"mcpServers": {
"sdmx-gateway": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://sdmx-mcp-gateway-production.up.railway.app/mcp"
]
}
}
}
Self-hosted (STDIO):
{
"mcpServers": {
"sdmx-gateway": {
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/sdmx-mcp-gateway",
"python",
"main_server.py"
]
}
}
}
On Windows, escape the path:
"C:\\path\\to\\sdmx-mcp-gateway".
Cursor
- Open Cursor Settings > MCP.
- Add a new global MCP server.
- Set the URL to
https://sdmx-mcp-gateway-production.up.railway.app/mcp(Cursor supports Streamable HTTP servers directly).
For a self-hosted instance, use the STDIO command shown for Claude Code.
Zed
Zed uses "Context Servers" for MCP integration. Settings file:
- Linux:
~/.config/zed/settings.json - macOS:
~/Library/Application Support/Zed/settings.json - Project-specific:
.zed/settings.jsonin your project root
Add the context_servers key at the top level of your settings.json, alongside other settings like theme and ui_font_size.
Hosted (via mcp-remote):
{
"context_servers": {
"sdmx-gateway": {
"command": {
"path": "npx",
"args": [
"-y",
"mcp-remote",
"https://sdmx-mcp-gateway-production.up.railway.app/mcp"
]
}
}
}
}
Self-hosted:
{
"context_servers": {
"sdmx-gateway": {
"command": {
"path": "uv",
"args": [
"run",
"--directory",
"/path/to/sdmx-mcp-gateway",
"python",
"main_server.py"
]
}
}
}
}
OpenCode
~/.config/opencode/config.json:
{
"mcpServers": {
"sdmx-gateway": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://sdmx-mcp-gateway-production.up.railway.app/mcp"
]
}
}
}
Or, for a self-hosted instance, swap to the uv run ... command shown in the Claude Code section.
Generic MCP Client (Streamable HTTP)
Any client with Streamable HTTP support connects directly to the hosted URL:
https://sdmx-mcp-gateway-production.up.railway.app/mcp
To run your own HTTP instance locally:
uv run python main_server.py --transport http --port 8000
Point the client at http://localhost:8000/mcp. Add --stateless --json-response if your client cannot consume Server-Sent Events.
Usage Examples
Progressive Discovery Workflow
# Step 1: Find relevant dataflows
list_dataflows(keywords=["digital", "development"])
# → Returns: DataflowListResult with matching dataflows
# Step 2: Get structure
get_dataflow_structure("DF_DIGITAL_DEVELOPMENT")
# → Returns: DataflowStructureResult with dimensions
# Step 3: Find country code
get_dimension_codes("DF_DIGITAL_DEVELOPMENT", "GEO_PICT", search_term="tonga")
# → Returns: DimensionCodesResult with TO = Tonga
# Step 4: Check availability
get_data_availability("DF_DIGITAL_DEVELOPMENT", dimension_values={"GEO_PICT": "TO"})
# → Returns: DataAvailabilityResult with time ranges
# Step 5: Build query
build_data_url("DF_DIGITAL_DEVELOPMENT", key="A..TO.", format_type="csv")
# → Returns: DataUrlResult with ready-to-use URL
Structure Relationship Visualization
Understand how SDMX structures relate to each other with Mermaid diagrams:
# See what a DSD references (codelists, concept schemes)
get_structure_diagram("datastructure", "DSD_DF_POP", direction="children")
# → Returns: StructureDiagramResult with mermaid_diagram field
# See what uses a codelist (impact analysis)
get_structure_diagram("codelist", "CL_FREQ", direction="parents")
# → Shows which DSDs and concept schemes use this codelist
# Get full relationship graph
get_structure_diagram("dataflow", "DF_POP", direction="both")
# → Shows both parent and child relationships
# Show version numbers on all nodes (important for impact analysis!)
get_structure_diagram("datastructure", "DSD_SDG", direction="children", show_versions=True)
# → Displays version numbers like "CL_FREQ v1.0", "CL_GEO v2.0"
# This is critical because different versions are independent -
# a dataflow using CL_FREQ v1.0 won't be affected by changes to v2.0
The mermaid_diagram field contains ready-to-render Mermaid code.
Without versions (default):
graph TD
subgraph dataflow["Dataflows ⭐"]
dataflow_DF_POP["📊 <b>DF_POP</b><br/>Population Statistics"]
end
subgraph datastructure["Data Structures"]
datastructure_DSD_POP["🏗️ DSD_POP<br/>Population DSD"]
end
subgraph codelist["Codelists"]
codelist_CL_FREQ["📋 CL_FREQ<br/>Frequency"]
codelist_CL_GEO["📋 CL_GEO<br/>Geography"]
end
dataflow_DF_POP -->|"defines structure"| datastructure_DSD_POP
datastructure_DSD_POP -->|"uses codelist"| codelist_CL_FREQ
datastructure_DSD_POP -->|"uses codelist"| codelist_CL_GEO
With show_versions=True (shows exact version dependencies):
graph TD
subgraph datastructure["Data Structures ⭐"]
datastructure_DSD_SDG["🏗️ <b>DSD_SDG</b> v3.0<br/>DSD for SDG"]
end
subgraph codelist["Codelists"]
codelist_CL_FREQ["📋 CL_FREQ v1.0<br/>Frequency"]
codelist_CL_GEO["📋 CL_GEO v2.0<br/>Geography"]
end
datastructure_DSD_SDG -->|"uses codelist"| codelist_CL_FREQ
datastructure_DSD_SDG -->|"uses codelist"| codelist_CL_GEO
Comparing Structures
Identify differences between two structures (useful for version upgrades and cross-structure analysis).
Comparing Codelists (compares actual codes):
# Compare two versions of a codelist - what codes changed?
compare_structures(
structure_type="codelist",
structure_id_a="CL_GEO",
version_a="1.0",
version_b="2.0"
)
# → Shows added/removed/renamed codes between versions
# Compare two different codelists - find intersection and differences
compare_structures(
structure_type="codelist",
structure_id_a="CL_FREQ",
structure_id_b="CL_TIME_FREQ"
)
# → Shows which codes are unique to each, and which are shared
Comparing DSDs (compares codelist/conceptscheme references):
# Compare two versions of a DSD - what codelist references changed?
compare_structures(
structure_type="datastructure",
structure_id_a="DSD_SDG",
version_a="2.0",
version_b="3.0"
)
# → Shows added/removed/version-changed codelist references
# Compare two different DSDs
compare_structures(
structure_type="datastructure",
structure_id_a="DSD_SDG",
structure_id_b="DSD_EDUCATION"
)
# → Shows which codelists are unique to each, and which are shared
The comparison identifies:
- ➕ Added: Items that exist in B but not A
- ➖ Removed: Items that exist in A but not B
- 🔄 Modified: Same ID but changed (version change for DSD refs, name change for codes)
- ✓ Unchanged: Identical items in both
Example codelist comparison output:
Comparing codelist CL_GEO: v1.0 → v2.0
Total codes: A has 25, B has 28
Summary: 5 change(s) detected
- ➕ Added codes: 3
- ➖ Removed codes: 0
- 🔄 Name changed: 2
- ✓ Unchanged: 23
➕ Added codes:
- `PW`: Palau
- `MH`: Marshall Islands
- `FM`: Federated States of Micronesia
Example DSD comparison with diff diagram:
graph LR
subgraph comparison["Structure Comparison"]
A["🏗️ DSD_SDG<br/>v3.0"]
B["🏗️ DSD_EDUCATION<br/>v1.0"]
end
subgraph added_group["➕ Added"]
add_CL_EDUCATION["📋 CL_EDUCATION_INDICATORS<br/>v1.0"]
end
subgraph removed_group["➖ Removed"]
rem_CL_SDG["📋 CL_SDG_INDICATORS<br/>v3.0"]
end
subgraph changed_group["🔄 Version Changed"]
chg_CL_GEO["📋 CL_GEO<br/>v1.0 → v2.0"]
end
A -.->|removed| rem_CL_SDG
B -->|added| add_CL_EDUCATION
A -.->|was| chg_CL_GEO
B -->|now| chg_CL_GEO
style add_CL_EDUCATION fill:#c8e6c9,stroke:#388e3c
style rem_CL_SDG fill:#ffcdd2,stroke:#d32f2f
style chg_CL_GEO fill:#fff9c4,stroke:#fbc02d
Targeting a Provider Per Call
Every endpoint-scoped tool accepts an optional endpoint=<KEY> argument:
list_dataflows(endpoint="ECB", limit=10)
get_dataflow_structure(dataflow_id="EXR", endpoint="ECB")
build_data_url(dataflow_id="DF_CPI", filters={"GEO_AREA": "FJI"}, endpoint="FBOS")
Calls without endpoint= use the session's default (set at server startup from the SDMX_ENDPOINT env var). Parallel calls to different providers are safe — each resolves independently.
Structured Outputs
All tools return Pydantic models with validated, typed data:
# Example: DataflowListResult
{
"discovery_level": "overview",
"agency_id": "SPC",
"total_found": 45,
"showing": 10,
"offset": 0,
"limit": 10,
"dataflows": [
{"id": "DF_GDP", "name": "GDP Statistics", "description": "..."},
...
],
"pagination": {
"has_more": true,
"next_offset": 10,
"total_pages": 5,
"current_page": 1
},
"next_step": "Use get_dataflow_structure() to explore a dataflow"
}
Testing
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=. --cov-report=html
# Run specific test categories
uv run pytest tests/unit/
uv run pytest tests/integration/
uv run pytest tests/e2e/
Known Limitations
Multi-User Endpoint Isolation
Each MCP session has its own client pool (one SDMXProgressiveClient per endpoint it has touched) and its own mismatch-hint registry. STDIO mode uses a single session; HTTP transport uses Mcp-Session-Id headers for per-user isolation. Sessions timeout after 30 minutes of inactivity. The session default endpoint is immutable at runtime — set it via the SDMX_ENDPOINT env var at server startup.
See docs/MULTI_USER_CONSIDERATIONS.md for production deployment details.
Project Status
| Feature | Status |
|---|---|
| SDK upgrade (v1.26.0) | ✅ Complete |
| Structured outputs | ✅ Complete |
| Streamable HTTP transport | ✅ Complete |
| Lifespan context | ✅ Complete |
| Elicitation support | ✅ Complete |
| Icons & metadata | 🔄 Pending |
| Documentation | ✅ Complete |
See TODO.md for detailed modernization progress.
Contributing
Key areas for contribution:
- Additional SDMX provider support
- Enhanced semantic search
- Performance optimization
- Test coverage expansion
References
License
MIT License - See LICENSE file for details.
Installing SDMX Gateway
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/nescoffee-create/sdmx-mcp-gatewayFAQ
Is SDMX Gateway MCP free?
Yes, SDMX Gateway MCP is free — one-click install via Unyly at no cost.
Does SDMX Gateway need an API key?
No, SDMX Gateway runs without API keys or environment variables.
Is SDMX Gateway hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install SDMX Gateway in Claude Desktop, Claude Code or Cursor?
Open SDMX Gateway on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Fetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
by modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
by xuzexin-hzCompare SDMX Gateway with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All ai MCPs
