ilostat-mcp-server
Click on "Deploy 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., "@ilostat-mcp-servercompare unemployment rates in France and Germany"
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.
Overview
Labour statistics from ILOSTAT, the International Labour Organization's statistical database, read from two keyless ILO APIs: the ILOSTAT data API (rplumber.ilo.org) and the ILO SDMX API (sdmx.ilo.org). Find an indicator, read its unit and breakdown codes, pull observations for countries, regions, and income groups, compare areas, and build a headline labour-market profile. Every value is marked as reported, modelled estimate, or projection. Large results stage as dataframes you query with SQL. Runs as a stdio process or a local Streamable HTTP server.
Tools
Tool | Description |
| Search the indicator catalog by plain-language terms and filters; each hit lists a dataset ID per available frequency |
| Explain one dataset: definition, unit and multiplier, breakdown codes in use, covered areas, and how its values are classed |
| Fetch observations for up to 3 datasets, filtered by area or area group, sex, breakdown codes, source, and period |
| Headline labour-market figures for one area: latest reported value and latest ILO modelled estimate, side by side |
| Rank areas on one slice of one dataset at a common or latest period, with optional change over N years |
| Decode ILOSTAT's code vocabulary: areas, area groups, databases, subjects, sexes, breakdowns, sources, status flags, notes |
| Describe a staged |
| Run one read-only SQL |
| Drop a staged dataframe before its TTL |
ilostat_dataframe_drop is off unless ILOSTAT_DATAFRAME_DROP_ENABLED=true, and CANVAS_PROVIDER_TYPE=none turns off all three dataframe tools.
Related MCP server: Sozio Thin
Capability reference
ilostat_search_indicators tool
Plain-language
query(every term must match a word or word prefix; case, accents, and labour/labor folded) plusfrequency,database,subject,breakdown, andaggregates_onlyfilters; omitqueryto browse by filtersUp to 50 hits per page (default 10), continued with
next_cursor; each hit is one indicator with adataset_idper frequency, coverage years,n_ref_area,last_update, andhas_aggregates, andfacetscount the whole match setAn unrecognized
database,subject, orbreakdowncode fails asunknown_filter_code
ilostat_describe_indicator tool
One
dataset_id(UNE_DEAP_SEX_AGE_RT_A) or a bare indicator code, which describes every frequency; an unknown code returnsfound: falsewithguidanceDefinition,
unitand itsmultiplier, frequency variants,breakdowns(sex codes, plusclassif1/classif2codes with totals markedis_total),default_slice, coveredref_areas,basis_rule, and up to 20related_datasetsBreakdowns, unit, and areas come from the SDMX API; when it can't answer,
structure_statusisunavailableand those fields are absent rather than the call failing
ilostat_query_indicator tool
1–3
dataset_ids, filtered byref_areas(up to 300) and/or anarea_group,sex,classif1,classif2,sources, and period: one exacttime, or atime_from/time_toyear window,latest_only, or bothsource_selectionisbestby default (the preferred source per area and period),all, orsecondary; rows carrysource,obs_status,notes, andbasis, decoded inlegend, andapplied_filtersechoes every parameter sentA result past the inline preview stages in full as a
df_<id>dataframe; an unfiltered request larger thanILOSTAT_MAX_ROWSfails asrequest_too_broad, and a filtered one that streams past it asresult_too_large
ilostat_get_country_profile tool
One
ref_areawith annual data, an ISO3 country (KEN) or an X-coded aggregate (X01World, regions, income groups), andsex(SEX_Tby default,SEX_M, orSEX_F)Nine headline indicators (labour force participation, employment-to-population ratio, unemployment, youth unemployment, youth NEET, informal employment, employment, labour income share, working poverty), each with its latest
reportedvalue and, separately, its latest non-projectedmodelledestimateA missing reported value stays missing and is listed in
reported_missing; aggregates carry modelled values only
ilostat_compare_geographies tool
One
dataset_idand one slice (sex,classif1,classif2, defaulting to the dataset's totals); areas fromref_areas(up to 300), anarea_group, or both, and at least one is requiredperiodputs every area at one period; without it, each area gets its latest value withinlookback_years(default 10), projections excluded unlessinclude_projections;change_years(1–30) adds each value's change over that spanRows carry
rank,value,period,basis, andsource; areas without a value land inmissingwith areason, andcomparabilityreportsmixed_periods,basis_counts, anddistinct_sources
ilostat_list_reference tool
topic:ref_areas,area_groups,databases,subjects,sexes,classifications,classification_types,sources,obs_status,notes, orfrequenciesA text
filteror up to 100 exactcodes(misses innot_found; exactarea_groupslookups list member countries);ref_areanarrowssources, andclassification_typenarrowsclassificationsUp to 500 entries per page (default 50), continued with
next_cursor
ilostat_dataframe_describe tool
Pass one
df_XXXXX_XXXXX, or omitnameto list every staged dataframe, newest first. Listing is off under HTTP with authnone, where every caller shares one canvas: there a dataframe is reached by its exact name onlyEach entry carries the producing
source_tooland itsquery_params,datasets,coverage,basis_counts,attribution,created_at/expires_at,row_count, and thecolumn_schemato write SQL against
ilostat_dataframe_query tool
One DuckDB
SELECT(joins, aggregates, window functions, CTEs) overdf_<id>tables, up to 20,000 characters; writes, DDL, multi-statement SQL, file-reading functions, and system catalogs are rejectedrow_limitcaps the rows materialized (default 1,000, max 10,000;row_count_cappedflags a hit) andpreviewthe rows returned inline; BIGINT values arrive as stringsregister_asstores the full result as a new dataframe with a fresh TTL, so analyses chain; a result over 1,000,000 rows is refused asregister_as_too_large, andevictednames any older dataframes dropped to make room
ilostat_dataframe_drop tool
Drops one staged dataframe by
namebefore its TTL; idempotent, withdropped: falsewhen nothing matchedListed only when
ILOSTAT_DATAFRAME_DROP_ENABLED=trueand dataframes are on; it is the only destructive tool
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
ILOSTAT-specific:
Two keyless ILO APIs: the ILOSTAT data API serves the catalog, the code dictionaries, and observations; the ILO SDMX API serves each dataset's breakdown codes, default slice, and unit
The catalog (both tables of contents and 13 code dictionaries) is held in memory and rechecked every
ILOSTAT_CATALOG_REFRESH_HOURS; every code a tool takes is validated against it before a request goes upstreamOne request pacer per upstream host; a 429 or challenge page starts a cooldown and reaches the caller as a retryable
upstream_busycarryingretryAfterData responses of up to 5,000 rows are cached for
ILOSTAT_CACHE_TTL_SECONDSDataframes on by default: a query or comparison larger than
ILOSTAT_PREVIEW_CHARSstages in full as a DuckDBdf_<id>table that lives forILOSTAT_DATASET_TTL_SECONDS. A tenant holds at most 1,000,000 staged rows in 100 dataframes, so a new table can evict the oldest ones before their TTL, and the response names them inevicted. WithCANVAS_PROVIDER_TYPE=none, or in the.mcpbbundle (which ships without DuckDB's native binding), results stop at the inline preview and say soILOSTAT data and metadata are published under the ILO Open Access policy as CC BY 4.0; every data response carries an
attributionto keep with the numbers
Agent-friendly output:
Basis on every value: each row is
reported,modelled_estimate, orprojection, and results carrybasis_counts. The country profile returns reported and modelled values in separate fields and never fills one from the otherProvenance and decoding: rows keep
source,obs_status, and note codes with alegendthat decodes them, and query and comparison responses echoapplied_filters, defaults includedMisses as data: an unknown dataset returns
found: falsewithguidance, and compared areas without a value land inmissingwith a typedreasonTyped errors: failures carry a reason (
unknown_code,request_too_broad,upstream_busy, …) and a recovery hint
Getting started
Add the following to your MCP client configuration file.
{
"mcpServers": {
"ilostat-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/ilostat-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"ilostat-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/ilostat-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with Docker:
{
"mcpServers": {
"ilostat-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/ilostat-mcp-server:latest"]
}
}
}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/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
No API key or account: both ILO APIs are open.
Installation
Clone the repository:
git clone https://github.com/cyanheads/ilostat-mcp-server.gitNavigate into the directory:
cd ilostat-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# every variable is optional; see Configuration belowConfiguration
Variable | Description | Default |
| Hours between checks of the ILOSTAT tables of contents (1–168). The dictionaries are re-fetched only when a dataset was added, removed, or updated. |
|
| Ceiling on the rows one query may return or stage (1,000–1,000,000). |
|
| Inline preview budget in serialized characters (at least 1,000); a larger result stages as a dataframe. |
|
| Seconds an upstream data response of up to 5,000 rows stays cached; |
|
| Per-table TTL for staged dataframes, in seconds (at least 60). |
|
| Set |
|
| DataCanvas engine for staged dataframes: |
|
| Transport: |
|
| HTTP server port. |
|
| HTTP session mode: |
|
| Authentication: |
|
| Log level ( |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry. |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdioRun checks and tests:
bun run devcheck # Lints, formats, type-checks, and more bun run test # Runs the test suite
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Clients for the ILOSTAT data API and the ILO SDMX API, on the paced, retried HTTP layer in |
| In-memory catalog snapshot, indicator search, reference listings, paging. |
| Per-indicator SDMX structure and unit, cached. |
| Request validation, size preflight, row decoding, comparisons, response cache. |
| Headline country profile. |
| Rules classing each value as reported, modelled estimate, or projection. |
| DataCanvas adapter: |
| Vitest suite mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor logging,ctx.statefor storageRegister new tools in
buildToolDefinitions()insrc/mcp-server/tools/definitions/index.tsWrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
This project is licensed under the Apache 2.0 License. See the LICENSE file for details. The ILOSTAT data the tools return belongs to the International Labour Organization and is published under CC BY 4.0; cite ILOSTAT and the dataset ID.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query, join, profile, clean and convert CSV/JSON/Parquet with server-side DuckDB over MCP.
Search and query 1,500+ OECD statistical datasets via SDMX. Keyless.
UN FAOSTAT global food & agriculture statistics over a local SQLite mirror, via MCP.
Query official statistics of Catalonia (Idescat): tables, metadata and JSON-stat data via MCP.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables querying Eurostat statistical data through natural language or direct MCP tools, wrapping the Eurostat API without authentication.267 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables reproducible analysis of 100 curated Swiss open-data resources by searching profiles, materializing PXWeb data, validating and executing SQL, and formatting reproduction details, with reasoning delegated to the MCP client.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables access to global labour statistics from ILOSTAT via the Pipeworx gateway.647 npmMIT
- AlicenseAqualityBmaintenanceMCP server for Eurostat statistics, enabling seamless search, query, and analysis of over 8,900 EU datasets with support for SDMX, DuckDB SQL, NUTS regional filtering, and CSV export.121MIT