ilostat-mcp-server
Server Details
Search ILOSTAT labour indicators, query and compare series, build country profiles, run SQL.
- Status
- Healthy
- Uptime
- 99.7% over 22 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- Repository
- cyanheads/ilostat-mcp-server
- GitHub Stars
- 1
- Server Listing
- ilostat-mcp-server
TDQS
Scored across 8 tools
The tools map cleanly onto workflow stages: search (search_indicators), explain (describe_indicator), decode vocabularies (list_reference), fetch values (query_indicator), cross-area comparison (compare_geographies), curated profile (get_country_profile), and post-processing (dataframe_describe/query). The main overlaps are search_indicators vs describe_indicator and query_indicator vs compare_geographies, but the descriptions draw clear boundaries between discovering, explaining, and fetching values.
All names share the ilostat_ prefix and snake_case style, and most follow verb_noun (describe_indicator, query_indicator, get_country_profile, list_reference, search_indicators, compare_geographies). The dataframe pair (dataframe_describe, dataframe_query) inverts to noun_verb, a minor deviation from the otherwise consistent pattern.
Eight tools is well-scoped for a statistical data server, with each tool earning its place across discovery, metadata, retrieval, comparison, profiling, and SQL post-processing. No redundant or filler tools.
For a read-only labour-statistics domain the surface is complete: catalog search, dataset explanation, code-vocabulary lookup, value retrieval, cross-geography comparison, curated country profiles, and staged-dataframe inspection/querying cover the full workflow. No obvious lifecycle gaps or dead ends.
Available Tools
8 toolsilostat_compare_geographiesCompare areas on an ILOSTAT datasetARead-onlyIdempotentInspect
Compare reference areas on one ILOSTAT dataset and one slice — a sex code plus breakdown codes, defaulting to the dataset's totals — giving each area's value at a common period or at its latest non-projected period, optional change over N years, and a rank, with each value's period, source, status, and basis (reported, modelled_estimate, or projection). A change carries the earlier value's source, basis, status, and notes, and flags whether the source or basis differs or the series breaks between the two values. Areas without a value are listed separately with the reason, and the response flags mixed periods and mixed bases rather than hiding them. Select areas by code list, by group (X01 for every country, an ILO region or subregion, or a World Bank income group), or both; X-coded aggregates require a dataset with aggregates.
| Name | Required | Description | Default |
|---|---|---|---|
| sex | No | Sex code SEX_T, SEX_M, SEX_F, or SEX_O; T/M/F/O and total/both/male/female/other are accepted. Defaults to SEX_T on a dataset with a sex breakdown; refused on one without. | |
| sort | No | Row order: value_desc (default), value_asc, or ref_area. rank is always by value, highest first. | value_desc |
| period | No | A common period, YYYY, YYYYQn, or YYYYMmm matching the dataset frequency (2024-Q2 and 2025-03 are normalized). Omit to compare each area at its latest period. | |
| classif1 | No | First breakdown code, case-insensitive. Defaults to the dataset's total code; required when the breakdown has no total (deciles); refused on a dataset without the breakdown. ilostat_describe_indicator lists the dataset's codes and marks its totals. | |
| classif2 | No | Second breakdown code; same defaults and rules as classif1. | |
| ref_areas | No | Reference areas (up to 300): ISO3 codes (USA) or X-coded aggregates (X01 World); case-insensitive, ILO_GEO_ forms accepted. At least one of ref_areas or area_group is required. | |
| area_group | No | X01 for every country, an ILO region or subregion, or a World Bank income group (X06, X56, X02, …); expands to its member countries. The group's own aggregate is compared only when listed in ref_areas. ilostat_list_reference topic area_groups lists the codes. | |
| dataset_id | Yes | One dataset ID (UNE_DEAP_SEX_AGE_RT_A), as ilostat_search_indicators returns it; case-insensitive, a DF_ prefix (the SDMX dataflow form) stripped, a bare indicator code resolved when it has one frequency. | |
| change_years | No | Adds each value's change from the same sub-period this many years earlier, in the dataset unit. | |
| lookback_years | No | Latest mode: an area's latest value must fall within this many years of the current year. | |
| include_projections | No | Latest mode: let projections (ILO modelled values after the cutoff) be an area's latest value. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | Inline preview budget, in serialized characters. |
| mode | No | latest: each area's latest value; period: every area at one period. |
| rows | No | Areas with a value, in the requested order; the staged dataframe holds all when larger. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Rows returned inline. |
| slice | No | The one series compared per area. |
| legend | No | Labels for the status flags and note codes in rows. |
| notice | No | Mixed periods, mixed bases, changes that are not like-for-like, missing areas, a unit or area list the structure service could not supply, where the full comparison is staged, or why the inline rows stop early. |
| period | No | Period mode: the period compared. |
| dataset | No | One requested dataset and how its values are classed. |
| missing | No | Requested areas with no value, and why. |
| dataframe | No | The staged dataframe holding the full result; present only when staged. |
| truncated | No | True when the inline rows stop before the last area — also when a dataframe holds the full comparison. |
| attribution | No | Citation to keep with any use of the data. |
| window_from | No | Latest mode: the first year requested — lookback_years before the current year, and change_years further back to reach the change base. A latest value still falls within lookback_years. |
| change_years | No | Years the change is measured over, when requested. |
| comparability | No | What makes the current values more or less comparable, over every area; each change's baseline is compared by its own source_changed, basis_changed, and break_in_series. |
| applied_filters | No | Every parameter sent upstream. |
| include_projections | No | Whether a projection could be an area's latest value. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/openWorld/idempotent, but the description adds real behavioral context: refusal conditions (sex on a dataset without a breakdown, classif1 where the breakdown lacks a total), how areas without a value are reported, and that mixed periods and mixed bases are flagged rather than hidden. It stops short of covering auth, rate limits, or pagination behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded and nearly every clause carries information (modes, change semantics, missing-area handling, selection methods). However, the first sentence runs to roughly seventy words as a single breath, making it dense to parse; splitting the response-behavior detail into its own sentence would improve structure.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter tool with one required field, an output schema, and safety annotations, the description covers the mode selection, area-selection paths, refusal cases, and edge-case reporting. Nothing an agent needs to call it correctly is missing, and return-value detail is already handled by the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is already 100%, so the schema carries most parameter meaning (defaults, patterns, enums). The description nonetheless adds a governing constraint not present in the schema — that X-coded aggregates require a dataset with aggregates — plus the totals-defaulting rule, which is genuine added semantics beyond the field docs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb+resource+scope: 'Compare reference areas on one ILOSTAT dataset and one slice.' It specifies the outputs (per-area value at a common or latest period, optional change over N years, and a rank), which makes it unmistakably distinct from single-series siblings like ilostat_query_indicator without needing to name them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly explains the two operating modes (a common period vs. each area's latest non-projected period) and the three ways to select areas (code list, group, or both), and it routes the agent to ilostat_describe_indicator and ilostat_list_reference for code discovery. It never states when not to use this tool or how it compares to ilostat_query_indicator, so it stops short of full alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_dataframe_describeDescribe staged ILOSTAT dataframesARead-onlyIdempotentInspect
Describe the df_ dataframes staged by ilostat_query_indicator and ilostat_compare_geographies or stored by ilostat_dataframe_query register_as: the tool and parameters that produced each, the datasets it holds (label, last update, and the unit with its measure, type, and multiplier — values stay in the published scale the multiplier names), coverage, basis counts, attribution, creation and expiry times, row count, and column schema. Pass name for one dataframe. Without it, every staged dataframe is listed, except on a deployment whose callers share one canvas, where listing is off and only the exact name works. Read the schema here before writing SQL for ilostat_dataframe_query.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | One dataframe name as the producing tool returned it (df_XXXXX_XXXXX: letters and digits, five in each part; case-insensitive, as in SQL). Omit to list every staged dataframe; a deployment whose callers share one canvas refuses the listing and needs the name. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| notice | No | Guidance when the named dataframe does not exist. |
| dataframes | No | Staged dataframes, newest first; empty when none. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent, so the safety profile is covered; the description goes further by disclosing that values stay in the published scale the multiplier names, that entries carry creation and expiry times, and that shared-canvas deployments refuse listing. That is genuine behavioral context beyond the structured hints, though it stops short of saying what happens to expired dataframes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core verb and resource, and every clause after it carries information. It is dense, and the long enumeration of returned fields partly duplicates the output schema, which is the only mild redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, yet the description still covers storage provenance, deployment caveat, and the SQL prerequisite. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema description already explains the df_XXXXX_XXXXX pattern, case-insensitivity, and the omit-to-list behavior. The description restates the same semantics rather than adding new meaning, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb and resource (describe the df_<id> dataframes) and pins down exactly which objects it covers by naming the two producing tools plus the register_as storage path. An agent can distinguish it from ilostat_dataframe_query and the query siblings without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
States the two invocation modes (pass name for one, omit to list all), names the deployment condition where listing is refused and only the exact name works, and gives an explicit prerequisite: read the schema here before writing SQL for ilostat_dataframe_query. Alternatives and exclusions are both present.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_dataframe_queryQuery staged ILOSTAT dataframesARead-onlyIdempotentInspect
Run a single-statement SELECT against the df_ dataframes staged by ilostat_query_indicator and ilostat_compare_geographies or stored by an earlier register_as. Inspect a dataframe with ilostat_dataframe_describe first; its column schema is what the SQL has to match. Read-only: writes, DDL, DROP, COPY, PRAGMA, ATTACH, file references, file-reading table functions, and functions that read engine settings or name a table from a string are rejected, and system catalogs (information_schema, pg_catalog, sqlite_master, duckdb_*) are denied. Breakdown versions overlap (AGE_YTHADULT_*, AGE_AGGREGATE_*, AGE_10YRBANDS_*), so filter to one version before summing. Optional register_as stores the result as a new dataframe with a fresh TTL.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | One SELECT over df_<id> tables (DuckDB SQL: joins, aggregates, window functions, CTEs), at most 20,000 characters. BIGINT results such as COUNT or SUM of integers serialize as strings; CAST to DOUBLE for inline arithmetic. | |
| preview | No | Rows returned inline; defaults to row_limit, and a larger value is applied as row_limit. Inline rows also stop at a size budget (applied_limits.preview_chars). Set lower when register_as keeps the full result. | |
| row_limit | No | Cap on rows materialized; defaults to 1,000, or the per-call cap when that is lower. The per-call cap is 10,000, or the lower cap the deployment sets; a larger value, up to 1,000,000, is applied as that cap. A query matching more stops at the cap and row_count_capped is true; register_as keeps the full result. | |
| register_as | No | Store the result as a new dataframe under this name (df_XXXXX_XXXXX: letters and digits, five in each part; stored uppercased after df_) with a fresh TTL, to chain analyses. A result over 1,000,000 rows is refused, and storing one can evict the oldest dataframes, which evicted names. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The row cap that bound: preview when lower than row_limit, else row_limit. |
| rows | No | Result rows keyed by the names in columns, bounded by preview, row_limit, and an inline size budget; rows stop before the first that would pass the budget, and a value is never clipped. BIGINT values arrive as strings. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Rows returned inline. |
| notice | No | Guidance when the query returned no rows, a cap withheld some, or a limit was applied below the value sent. |
| columns | No | Column names in projection order. |
| evicted | No | Dataframes dropped, oldest first, to keep this tenant within 1,000,000 staged rows and 100 dataframes; present only when storing the result evicted any. |
| row_count | No | Rows the query produced. With register_as this is the exact count of the stored dataframe, which row_limit does not bound; otherwise at most row_limit, and when row_count_capped is true it is the cap, not a total. |
| truncated | No | True when a cap withheld rows from this response. |
| expires_at | No | ISO 8601 expiry of the new dataframe. |
| registered_as | No | The new dataframe name, when register_as stored the result. |
| applied_limits | No | The limits this call ran under, after any clamp. |
| row_count_capped | No | True when the query matched more rows than row_limit; never true with register_as, which stores every row. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavioral context beyond the annotations: it lists rejected SQL operations, denied system catalogs, the read-only enforcement scope, and the overlap warning for breakdown versions. It also notes that register_as creates a new dataframe with a fresh TTL and that registration can evict older dataframes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core operation, then layers in the inspection prerequisite, read-only constraints, overlap warning, and register_as behavior without redundant restatement. Every sentence carries useful guidance for invoking the tool correctly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the query complexity, full schema coverage, output schema, and read-only annotations, the description supplies the remaining operational context: what is rejected, which catalogs are denied, when to inspect schema first, and how register_as enables chaining. Nothing an agent needs in order to call the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all four parameters thoroughly. The description adds little parameter-specific meaning beyond what the schema provides, aside from briefly noting that register_as stores a new dataframe with a fresh TTL.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: running a single-statement SELECT against df_<id> dataframes. It distinguishes the tool from its siblings by identifying those dataframes as staged by ilostat_query_indicator and ilostat_compare_geographies or created by an earlier register_as.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly explains the context of use: query staged dataframes, inspect them first with ilostat_dataframe_describe, and optionally chain results with register_as. It does not explicitly state when to prefer this over querying raw indicators directly, so it falls just short of a full when/when-not/alternatives statement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_describe_indicatorDescribe an ILOSTAT datasetARead-onlyIdempotentInspect
Explain one ILOSTAT dataset before comparing numbers: its definition, unit and multiplier, frequency variants and coverage, the sex and breakdown codes in use across its frequencies (the total code of each breakdown marked), the reference areas it covers, whether it carries World/regional/income-group aggregates, and how its observations are classed as reported, modelled, or projected. Accepts a dataset ID (UNE_DEAP_SEX_AGE_RT_A) or a bare indicator code (UNE_DEAP_SEX_AGE_RT), which describes every frequency. An unknown code returns found: false with guidance.
| Name | Required | Description | Default |
|---|---|---|---|
| dataset_id | Yes | One dataset ID (indicator code plus _A, _Q, or _M, e.g. UNE_DEAP_SEX_AGE_RT_A) or a bare indicator code (UNE_DEAP_SEX_AGE_RT), as ilostat_search_indicators returns them. Case-insensitive; a DF_ prefix (the SDMX dataflow form) is stripped. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The most related indicators listed. |
| unit | No | Unit of the values, already in the multiplier scale; absent when unresolved. |
| error | No | Present when the call failed. Absent on success. |
| found | No | False when no dataset or indicator has the code. |
| label | No | Indicator label as ILOSTAT publishes it. |
| shown | No | Related indicators listed. |
| notice | No | Where to find breakdown codes and units when the structure service did not supply them, and how to list every related indicator when the list is cut — whichever apply, joined. |
| measure | No | Measure (the quantity measured, shared by breakdown variants) code and label. |
| subject | No | Subject code and label. |
| database | No | Source database code and label. |
| datasets | No | Every frequency variant of the indicator, annual first. |
| guidance | No | On a miss: how to find a valid dataset ID. |
| indicator | No | Indicator code. |
| ref_areas | No | Reference areas the indicator covers across its frequencies, from ILOSTAT's structure service; absent when structure_status is unavailable. |
| truncated | No | True when related_datasets lists fewer than related_total. |
| basis_rule | No | How observations are classed reported, modelled_estimate, or projection. |
| breakdowns | No | Breakdown codes in use, from ILOSTAT's structure service; absent when structure_status is unavailable. |
| dataset_id | No | The dataset requested, when a dataset ID (not a bare indicator code) was given. |
| definition | No | ILOSTAT definition of the indicator, HTML stripped; links kept as "text (url)". |
| attribution | No | Citation to keep with any use of the data. |
| catalog_as_of | No | ISO timestamp the catalog was last confirmed current. |
| default_slice | No | The dataset's total codes from its default view — the slice ilostat_compare_geographies uses when sex, classif1, or classif2 is omitted; a breakdown with no total (deciles) is left out. |
| related_total | No | How many other indicators share this measure. ilostat_search_indicators with measure set to measure.code lists them all. |
| has_aggregates | No | Whether the requested dataset (or, for an indicator code, any of its datasets) carries World, regional, or income-group rows. |
| related_datasets | No | The first 20 other indicators sharing this measure under other breakdowns, by code; related_total counts all of them. |
| structure_status | No | unavailable when ILOSTAT's structure service has no entry for the indicator or cannot be reached; breakdowns, default_slice, unit, and ref_areas are then absent. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent/openWorld annotations: it discloses the metadata categories returned, the total-code marking of breakdowns, and the error contract ('an unknown code returns found: false with guidance'). It also explains code-form handling (ID vs bare code, DF_ prefix stripping), which the annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and error behavior in two sentences with no filler. The first sentence is a dense enumeration of return categories, which is long but each clause carries information an agent needs to decide whether to call it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and safety annotations covering the read-only/idempotent profile, the description only needs to add purpose, input-form semantics, and error handling — all of which it does. Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: a bare indicator code 'describes every frequency' versus a suffixed dataset ID describing one, and the failure behavior for unknown codes. That meaning is not present in the schema description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (explain/describe) and resource (one ILOSTAT dataset) and enumerates exactly what the response covers: definition, unit/multiplier, frequency variants, breakdown codes, reference areas, aggregates, and observation status. This is clearly distinguishable from data-fetching siblings like ilostat_query_indicator and lookup siblings like ilostat_search_indicators.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The opening clause 'before comparing numbers' implies a workflow position but never names alternatives (e.g. use ilostat_query_indicator for values, ilostat_search_indicators to find codes) or states exclusions. Usage is implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_get_country_profileGet an ILOSTAT labour-market profileARead-onlyIdempotentInspect
Build a headline labour-market profile for one reference area: labour force participation, employment-to-population ratio, unemployment and youth unemployment rates, youth NEET rate, informal employment rate, employment level, labour income share, and working poverty rate. Each indicator shows its latest reported value (from a national or institutional source, with its source and notes) and, separately, its latest ILO modelled estimate that is not a projection, each with its dataset, period, and status — a missing reported value stays missing and is never filled from the model. Accepts a country (ISO3, e.g. KEN) or an X-coded aggregate (X01 World, regions, income groups), for which only modelled values exist.
| Name | Required | Description | Default |
|---|---|---|---|
| sex | No | SEX_T (default), SEX_M, or SEX_F; T/M/F and total/both/male/female are accepted. Indicators without a sex breakdown (labour income share) are unaffected. | SEX_T |
| ref_area | Yes | One reference area with annual data: an ISO3 country code (KEN) or an X-coded aggregate (X01 World, X06, X02); ILO_GEO_ forms accepted, case-insensitive. ilostat_list_reference topic ref_areas lists them. |
Output Schema
| Name | Required | Description |
|---|---|---|
| sex | No | Sex code profiled. |
| error | No | Present when the call failed. Absent on success. |
| notice | No | Which indicators have no reported value, and which of those have an ILO modelled estimate instead. |
| ref_area | No | The area profiled. |
| indicators | No | The headline indicators, in a fixed order. |
| attribution | No | Citation to keep with any use of the data. |
| catalog_as_of | No | ISO timestamp the catalog was last confirmed current. |
| reported_missing | No | Keys with no reported value, including those that have no reported dataset. |
| modelled_cutoff_year | No | Latest year the modelled values could come from; later modelled years are projections. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent and open-world behaviour, so the bar is lower. The description nonetheless adds real behavioural context beyond them: each indicator shows latest reported value plus separately the latest non-projection ILO modelled estimate, without imputing missing reported values, and aggregates carry only modelled values. That is substantive disclosure of data-provenance semantics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three long sentences carry dense but relevant content, and the indicator inventory plus scope are front-loaded. The first sentence's indicator list is heavy but every element is load-bearing for a profile tool, so there is little waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full schema coverage, the description needn't explain return values, and it covers the non-obvious behavioural points an agent needs: reported vs modelled values, missing-value handling, and aggregate-only modelling. It stops short of routing guidance among the seven sibling tools, which is the only material gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented, and the baseline of 3 applies. The description reinforces ref_area semantics by naming ISO3 and X-coded aggregate forms with examples and noting that aggregates only have modelled values, but says nothing about the sex parameter beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb ('Build a headline labour-market profile') and resource (one reference area), and enumerates the nine indicators returned, so the agent knows exactly what the tool produces. It is clearly a bundled multi-indicator snapshot, which implicitly separates it from single-indicator query tools, but it never names a sibling to make the contrast explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by scope: 'one reference area' with country or X-coded aggregate accepted, which tells the agent the input shape but not when to prefer this over ilostat_query_indicator or ilostat_compare_geographies. There are no explicit when-to-use or when-not conditions and no named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_list_referenceList ILOSTAT reference codesARead-onlyIdempotentInspect
Decode ILOSTAT's code vocabulary: reference areas (ISO3 countries and X-coded aggregates, with World Bank income group and ILO region), the groups that area_group accepts (X01 for every country, ILO regions and subregions, World Bank income groups; exact-code lookups list their member countries), databases, subjects, sex codes, breakdown codes for classif1/classif2 with their classification types, per-country data sources, observation status flags, note codes, and frequencies. Filter by text or look up exact codes; long topics page with a cursor. These are ILOSTAT-wide vocabularies; ilostat_describe_indicator lists the sex and breakdown codes one dataset actually uses.
| Name | Required | Description | Default |
|---|---|---|---|
| codes | No | Exact codes to look up (up to 100; case-insensitive, and ILO_GEO_ forms accepted for ref_areas and area_groups). Codes the topic lacks are listed in not_found. With topic area_groups, each group found also lists its member countries. | |
| limit | No | Entries per page, at most 500; a larger value, up to 10,000, is applied as 500 and the notice says so. | |
| topic | Yes | Vocabulary to list: ref_areas, area_groups (the X codes area_group accepts), databases, subjects, sexes, classifications (classif1/classif2 codes), classification_types, sources, obs_status, notes, or frequencies. | |
| cursor | No | Opaque continuation token: the previous page's next_cursor, passed unchanged. | |
| filter | No | Text filter: every word must match a word or word prefix of the code or label (case, accents, and punctuation ignored; labor matches labour). Omit to list the whole topic. | |
| ref_area | No | Topic sources only: list the sources of this reference area (ISO3 or X code; case-insensitive, ILO_GEO_ forms accepted). | |
| classification_type | No | Topic classifications only: keep codes of this classification type, the code prefix (AGE, ECO, EDU, …). |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Entries returned on this page. |
| topic | No | The topic listed. |
| total | No | Entries matching the filters, across all pages. |
| notice | No | That a limit past the page cap was applied as the cap, why a filter with no searchable word was not applied, why nothing matched, that the cursor starts past the last entry, and how to reach the remaining pages — whichever apply, joined. |
| entries | No | This page of decoded codes, ordered by code — except sexes, which keep the ILOSTAT dictionary's order (SEX_T first), and frequencies, listed annual, quarterly, monthly. |
| not_found | No | Requested codes the topic does not contain; present only when codes missed. |
| truncated | No | True when more entries remain beyond this page. |
| next_cursor | No | Pass as cursor to get the next page; absent on the last page. |
| catalog_as_of | No | ISO timestamp the in-memory catalog was last confirmed current. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only/idempotent/closed-world, so the bar is lower; the description still adds real behavior: cursor pagination for long topics, the fact that exact-code lookups enumerate member countries for area_groups, and the clamp-and-notice behavior on oversized limits. It does not describe rate limits or failure modes, but that is a minor gap against the annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then a long but load-bearing enumeration of the 11 topics, then the routing sentence. The topic list is dense and could be trimmed since the enum is in the schema, but every remaining sentence carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need no explanation, and the description still covers pagination, filtering semantics, cross-tool routing, and the special area_groups expansion. What is missing is only edge behavior (invalid topic, empty result shape), which the schema largely absorbs.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the description goes past that by spelling out the topic-to-parameter coupling (area_group membership, classif1/classif2 with classification types, sources per country) and by restating the filter's prefix/'labor' vs 'labour' matching semantics that make the filter actually usable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ('Decode ILOSTAT's code vocabulary') and then enumerates exactly which vocabularies are covered, including the trickier area_groups/classif1/classif2 cases. It also explicitly distinguishes itself from the sibling ilostat_describe_indicator, so an agent can route without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
States the two selection modes ('Filter by text or look up exact codes'), notes the cursor case for long topics, and names the alternative tool with the condition that selects it ('ilostat_describe_indicator lists the sex and breakdown codes one dataset actually uses'). Nothing about when to reach for this tool is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_query_indicatorQuery ILOSTAT observationsARead-onlyIdempotentInspect
Fetch observations for up to 3 ILOSTAT datasets per call, filtered by reference area or area group, sex, breakdown codes (classif1, classif2), source, and period; datasets past the first 3 come back as remaining_dataset_ids for another call. Rows keep their source, observation status, decoded notes, and a basis — reported, modelled_estimate, or projection — and the response echoes every filter applied, including the best-source default. Codes are checked against ILOSTAT's dictionaries before the request is sent: ilostat_list_reference lists valid codes and ilostat_describe_indicator lists the codes a dataset actually uses. A result larger than the inline preview is staged in full as a df_ dataframe for SQL through ilostat_dataframe_describe and ilostat_dataframe_query when this deployment enables dataframes. A request with no filters at all is refused when the dataset exceeds the row ceiling, and a filtered request that still exceeds it is refused with guidance to narrow it.
| Name | Required | Description | Default |
|---|---|---|---|
| sex | No | Sex codes SEX_T, SEX_M, SEX_F, SEX_O; T/M/F/O and total/both/male/female/other are accepted. | |
| time | No | One exact period: YYYY (on a quarterly or monthly dataset, every period of that year), YYYYQn, or YYYYMmm; 2024-Q2, 2024 Q2, and 2025-03 are normalized. Not combinable with time_from, time_to, or latest_only. | |
| sources | No | Source codes (e.g. BA:453); ilostat_list_reference topic sources with ref_area lists an area's sources. Without source_selection, setting sources switches it to all, since a secondary source matches nothing under best. | |
| time_to | No | Last year (YYYY), not before time_from. | |
| classif1 | No | Codes of the first breakdown (e.g. AGE_YTHADULT_YGE15); case-insensitive. ilostat_describe_indicator lists the codes a dataset uses. | |
| classif2 | No | Codes of the second breakdown, for datasets that have one; case-insensitive. ilostat_describe_indicator lists them. | |
| ref_areas | No | Reference areas (up to 300): ISO3 country codes (USA) or X-coded aggregates (X01 World); case-insensitive, ILO_GEO_ forms accepted. Aggregates need a dataset with has_aggregates true. Omit for every area. | |
| time_from | No | First year (YYYY); upstream filters by year only. | |
| area_group | No | X01 for every country, an ILO region or subregion, or a World Bank income group (X06, X56, X02, …); expands to its member countries, unioned with ref_areas. ilostat_list_reference topic area_groups lists the codes. | |
| dataset_ids | Yes | Dataset IDs, up to 100 — an indicator code plus _A, _Q, or _M (UNE_DEAP_SEX_AGE_RT_A), as ilostat_search_indicators returns them. Case-insensitive; a DF_ prefix (the SDMX dataflow form) is stripped, a bare indicator code resolves when it has one frequency, an element holding + or , joined IDs is split, and duplicates are dropped. Every ID is checked; the first 3 distinct datasets are queried and the rest come back as remaining_dataset_ids for another call. | |
| latest_only | No | Only each series' latest period — a series is one dataset, reference area, sex, classif1, and classif2 code — with every row at that period (one per source under source_selection all or secondary; the latest quarter or month on sub-annual datasets); combines with time_from/time_to. When the request's full history is too large or too slow to read in one call, it falls back to the latest period per area and dataset, which can miss a series that ends earlier; applied_filters.latest_mode says which ran, and the notice says so. time_from or classif1 narrow it back under; sex or fewer ref_areas alone may still read too much and fall back. | |
| source_selection | No | best (default): the preferred source per area and period; all: secondary sources too, each row flagged best_source; secondary: secondary sources only. Defaults to all when sources is set. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | Inline preview budget, in serialized characters. |
| rows | No | Inline preview rows; the staged dataframe holds every row when the result is larger. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Rows returned inline. |
| legend | No | Labels for every code in rows. |
| notice | No | Datasets left for another call, a latest_only fallback to the latest period per area and dataset, filters that could not narrow a dataset, a unit the structure service could not supply, why nothing matched, where the full result is staged, or why reading stopped early. |
| summary | No | Summary over every row read, not just the preview. |
| datasets | No | The queried datasets, in request order. |
| dataframe | No | The staged dataframe holding the full result; present only when staged. |
| row_count | No | Rows the request returned — exact unless reading stopped early at the inline preview (summary.complete false), when it counts the rows read. |
| truncated | No | True when reading stopped at the inline preview and more rows exist. |
| attribution | No | Citation to keep with any use of the data. |
| applied_filters | No | Every parameter sent upstream, defaults included. |
| remaining_dataset_ids | No | Requested datasets past the first 3, as canonical IDs in request order; not fetched in this call. Pass them as dataset_ids with the same filters to get them. Absent when every requested dataset was queried. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the readOnly/openWorld/idempotent annotations: discloses the 3-dataset ceiling, the df_<id> staging path, the row-ceiling refusal behavior for unfiltered vs filtered requests, and the latest_only fallback that can miss earlier-ending series. These are exactly the operational traits an agent cannot infer from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core operation before limits, validation, and staging behavior, and every clause carries operational information. It is a dense single block, but the density tracks the tool's real complexity rather than padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return values need no explanation, and the description still covers the parts that matter: dataset caps, filter echoing, code validation, refusal thresholds, and dataframe staging. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all 12 parameters in depth. The description mostly restates the filter axes and the best-source default rather than adding syntax or semantics beyond the schema; baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope ('Fetch observations for up to 3 ILOSTAT datasets per call') and enumerates the filter axes. It is clearly distinguishable from siblings, naming list_reference, describe_indicator, and the dataframe tools in their supporting roles.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete routing: use ilostat_list_reference for valid codes, ilostat_describe_indicator for codes a dataset actually uses, and dataframe tools when a result is staged as df_<id>. It also explains the 3-dataset cap and the remaining_dataset_ids continuation. It stops short of stating when to prefer ilostat_compare_geographies or ilostat_get_country_profile instead, so no explicit exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ilostat_search_indicatorsSearch ILOSTAT indicatorsARead-onlyIdempotentInspect
Search ILOSTAT's catalog of labour-statistics indicators by plain-language terms and filters. Each hit is one indicator with its datasets — one per available frequency (annual, quarterly, monthly) — plus breakdowns, coverage years, number of reference areas, source database, and last update; pass a dataset ID to ilostat_describe_indicator for its units and breakdown codes, then to ilostat_query_indicator or ilostat_compare_geographies for values. Every search term must match a word or word prefix in the indicator's label, subject, database, breakdown names, code, or definition; a term that matches no word or word prefix anywhere in the catalog matches the words sharing its stem instead (informality finds informal, unionization finds union), and the notice names it. British and American spellings (labour/labor) match alike. The measure filter lists every indicator of one measured quantity, as ilostat_describe_indicator's related indicators count them. Facet counts reflect all applied filters.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Hits per page, at most 50; a larger value, up to 1,000, is applied as 50 and the notice says so. | |
| query | No | Plain-language terms, e.g. "youth unemployment" or "informal employment rate". Case, accents, and punctuation are ignored, labor matches labour, and a trailing plural s is tolerated; a term no catalog word starts with is matched by its stem (-ity, -ization, -ive, -ment, -ing, -ed, -ly, and a plural s dropped, -ies read as y). Omit to browse by filters (results then order by indicator code). | |
| cursor | No | Opaque continuation token: the previous page's next_cursor, passed unchanged. | |
| measure | No | Measure code, exactly as ilostat_describe_indicator's measure.code gives it (e.g. EMP_TEMP_NB); case-insensitive. Keeps every indicator measuring that quantity, under any breakdown. | |
| subject | No | Subject code, e.g. LUU (unemployment and labour underutilization); case-insensitive. ilostat_list_reference topic subjects lists them. | |
| database | No | Source database code, e.g. LFS or ILOEST (the ILO modelled estimates); case-insensitive. ilostat_list_reference topic databases lists them. | |
| breakdown | No | Classification type the indicator is broken down by, e.g. AGE, ECO, GEO, SEX; case-insensitive. ilostat_list_reference topic classification_types lists them. | |
| frequency | No | Keep only datasets of this frequency: A annual, Q quarterly, M monthly. Narrows each hit's datasets; an indicator with none left drops out. | |
| aggregates_only | No | Keep only datasets carrying World, regional, or income-group rows, dropping indicators with none. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| hits | No | This page of hits. With a query: tier, then more reference areas first, then code. Without: by code. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Hits returned on this page. |
| total | No | Indicators matched after all filters. |
| facets | No | Counts over the fully filtered match set, not just this page. |
| notice | No | That a limit past the page cap was applied as the cap, why the query was browsed by filters alone, which terms were matched by stem and as which catalog words (check those hits are on topic), how to widen a search that matched nothing, that the cursor starts past the last match, and how to reach the remaining pages — whichever apply, joined. |
| truncated | No | True when more hits remain beyond this page. |
| next_cursor | No | Pass as cursor to get the next page; absent on the last page. |
| catalog_as_of | No | ISO timestamp the searched catalog was last confirmed current. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and closed-world behaviour, so the safety profile is covered. The description adds real value beyond that with matching semantics (word/prefix matching, stem fallback with a naming notice, labour/labor equivalence) and the note that facet counts respect all applied filters; however, the limit is documented as being capped at 50 in the schema itself.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and workflow are front-loaded in the first sentence, and each subsequent clause carries distinct information about matching or filtering. The middle sentence is a dense run-on packing prefix matching, stem fallback and spelling equivalence, which could be split for readability, but nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter search tool with full schema coverage, an output schema and existing annotations, the description covers everything an agent needs: result shape, filter semantics, match fallbacks, the limit cap, and the hand-off chain to sibling tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds semantics the schema does not carry: that the measure filter returns every indicator of that quantity under any breakdown and counts them as ilostat_describe_indicator does, and that facet counts reflect all applied filters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb and resource (search ILOSTAT's catalog of labour-statistics indicators) and immediately defines the unit of a hit, so the agent knows exactly what comes back. It is clearly separable from siblings like ilostat_describe_indicator and ilostat_query_indicator, which it explicitly positions downstream.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit workflow: search here, pass a dataset ID to ilostat_describe_indicator for units and breakdowns, then to ilostat_query_indicator or ilostat_compare_geographies for values. It also tells the agent to omit the query to browse by filters and points to ilostat_list_reference for valid subject/database/classification codes.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
7 tool updates
- Changed
ilostat_compare_geographies15 fields changed- changed
Output schema / properties / applied_filters / properties / time / descriptionPrevious value: -"Exact periods sent (period mode)."New value: +"Exact periods sent: period mode without change_years, or the period and its change base when the years between could not be read within the call's size and time limits." - changed
Output schema / properties / applied_filters / properties / time_from / descriptionPrevious value: -"First year sent (latest mode); no end year is sent."New value: +"First year sent: latest mode's window start (no end year is sent), or period mode's change base year." - added
Output schema / properties / applied_filters / properties / time_toAdded value: +{ + "description": "Last year sent: the period's year, in period mode with change_years.", + "type": "number" +} - changed
Output schema / properties / comparability / descriptionPrevious value: -"What makes the values more or less comparable, over every area."New value: +"What makes the current values more or less comparable, over every area; each change's baseline is compared by its own source_changed, basis_changed, and break_in_series." - changed
Output schema / properties / notice / descriptionPrevious value: -"Mixed periods, mixed bases, missing areas, a unit or area list the structure service could not supply, where the full comparison is staged, or why the inline rows stop early."New value: +"Mixed periods, mixed bases, changes that are not like-for-like, missing areas, a unit or area list the structure service could not supply, where the full comparison is staged, or why the inline rows stop early." - changed
Output schema / properties / rows / items / properties / change / descriptionPrevious value: -"Change over change_years; absent when the earlier value is missing."New value: +"Change over change_years with the earlier value's provenance; absent when the earlier value is missing. delta is like-for-like only when source_changed, basis_changed, and break_in_series are all false." - added
Output schema / properties / rows / items / properties / change / properties / basis_changedAdded value: +{ + "description": "True when the earlier value has another basis.", + "type": "boolean" +} - added
Output schema / properties / rows / items / properties / change / properties / break_in_seriesAdded value: +{ + "description": "True when a break in series (status B or an I11 note) is marked on the value or on an observation of its source after from_period: the series' method changed within the span. A break marked on the earlier value alone came before the span and does not count. null when only the two periods were read (notice says so) and the value marks no break: a break between them is unknown.", + "type": [ + "boolean", + "null" + ] +} - added
Output schema / properties / rows / items / properties / change / properties / from_basisAdded value: +{ + "description": "Basis of the earlier value: reported, modelled_estimate, or projection.", + "enum": [ + "reported", + "modelled_estimate", + "projection" + ], + "type": "string" +} - added
Output schema / properties / rows / items / properties / change / properties / from_notesAdded value: +{ + "description": "Note codes of the earlier value (legend.notes decodes them).", + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / rows / items / properties / change / properties / from_obs_statusAdded value: +{ + "description": "Status flag of the earlier value (legend.obs_status decodes it).", + "type": "string" +} - added
Output schema / properties / rows / items / properties / change / properties / from_sourceAdded value: +{ + "description": "Source code of the earlier value.", + "type": "string" +} - added
Output schema / properties / rows / items / properties / change / properties / from_source_labelAdded value: +{ + "description": "Source label of the earlier value.", + "type": "string" +} - added
Output schema / properties / rows / items / properties / change / properties / source_changedAdded value: +{ + "description": "True when the earlier value comes from another source.", + "type": "boolean" +} - changed
Output schema / properties / rows / items / properties / change / requiredPrevious value: -[ - "from_period", - "from_value", - "delta" -]New value: +[ + "from_period", + "from_value", + "delta", + "from_source", + "from_basis", + "from_notes", + "source_changed", + "basis_changed", + "break_in_series" +]
- Changed
ilostat_dataframe_describe6 fields changed- changed
Output schema / properties / dataframes / items / properties / datasets / items / properties / unit / descriptionPrevious value: -"Unit label, when resolved."New value: +"Unit label (its code when unlabelled), when resolved." - added
Output schema / properties / dataframes / items / properties / datasets / items / properties / unit_measureAdded value: +{ + "description": "Unit code (PT percent, PS persons, …), when resolved.", + "type": "string" +} - added
Output schema / properties / dataframes / items / properties / datasets / items / properties / unit_multiplierAdded value: +{ + "description": "Power of ten the values are published in (0 units, 3 thousands, 6 millions), as in the staged unit_multiplier column; values are never rescaled. Absent when the unit did not resolve.", + "type": "number" +} - added
Output schema / properties / dataframes / items / properties / datasets / items / properties / unit_multiplier_labelAdded value: +{ + "description": "Multiplier label.", + "type": "string" +} - added
Output schema / properties / dataframes / items / properties / datasets / items / properties / unit_typeAdded value: +{ + "description": "Unit type code (RT rate, NB number, …), when resolved.", + "type": "string" +} - added
Output schema / properties / dataframes / items / properties / datasets / items / properties / unit_type_labelAdded value: +{ + "description": "Unit type label.", + "type": "string" +}
- Changed
ilostat_dataframe_query10 fields changed- changed
Input schema / properties / preview / descriptionPrevious value: -"Rows returned inline; defaults to row_limit. Set lower when register_as keeps the full result."New value: +"Rows returned inline; defaults to row_limit, and a larger value is applied as row_limit. Inline rows also stop at a size budget (applied_limits.preview_chars). Set lower when register_as keeps the full result." - changed
Input schema / properties / preview / maximumPrevious value: -10000New value: +9007199254740991 - removed
Input schema / properties / row_limit / defaultRemoved value: -1000 - changed
Input schema / properties / row_limit / descriptionPrevious value: -"Hard cap on rows materialized (1–10,000). A query matching more stops at the cap and row_count_capped is true; register_as keeps the full result."New value: +"Cap on rows materialized; defaults to 1,000, or the per-call cap when that is lower. The per-call cap is 10,000, or the lower cap the deployment sets; a larger value, up to 1,000,000, is applied as that cap. A query matching more stops at the cap and row_count_capped is true; register_as keeps the full result." - changed
Input schema / properties / row_limit / maximumPrevious value: -10000New value: +1000000 - added
Output schema / properties / applied_limitsAdded value: +{ + "additionalProperties": false, + "description": "The limits this call ran under, after any clamp.", + "properties": { + "preview": { + "description": "Inline rows applied: preview, at most the applied row_limit.", + "type": "number" + }, + "preview_chars": { + "description": "Inline size budget applied, in characters; rows stop before the first that would pass it.", + "type": "number" + }, + "row_limit": { + "description": "Row cap applied: row_limit (default 1,000), or the per-call cap when that is lower. With register_as it bounds the inline rows, never the stored result.", + "type": "number" + } + }, + "required": [ + "row_limit", + "preview", + "preview_chars" + ], + "type": "object" +} - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `canvas_unavailable`: The DataCanvas DuckDB engine cannot load in this deployment. `system_catalog_access`: The SQL references a system catalog. `missing_table`: A referenced df_<id> does not exist or has expired. `invalid_sql`: The SELECT fails to prepare: an unknown column or an invalid expression. `sql_execution_error`: The SELECT prepared but failed on the data. `register_as_clash`: register_as names an existing dataframe. `register_as_too_large`: The register_as result holds more than 1,000,000 rows. `non_select_statement`: The SQL is not a SELECT. `multi_statement`: The SQL holds more than one statement, or a PIVOT with no IN list, which DuckDB expands into several. `denied_function`: The SQL calls a file-reading or external table function. `plan_operator_not_allowed`: The plan uses an operator outside the read-only allowlist, such as range(). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `canvas_unavailable`: The DataCanvas DuckDB engine cannot load in this deployment. `system_catalog_access`: The SQL references a system catalog. `missing_table`: A referenced df_<id> does not exist or has expired. `invalid_sql`: The SELECT fails to prepare: an unknown column or an invalid expression. `sql_execution_error`: The SELECT prepared but failed on the data. `register_as_clash`: register_as names an existing dataframe. `register_as_too_large`: The register_as result holds more than 1,000,000 rows. `non_select_statement`: The SQL is not a SELECT. `multi_statement`: The SQL holds more than one statement, or a PIVOT with no IN list, which DuckDB expands into several. `denied_function`: The SQL calls a file-reading or external table function, a function that reads engine settings (current_setting, current_schema), or one that runs SQL or names a table from a string (query_table, query() over anything but one string literal). `sql_read_only`: The SQL names a file in place of a table, or attempts a write the engine refuses. `plan_operator_not_allowed`: The plan uses an operator outside the read-only allowlist, such as range(). Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "canvas_unavailable", - "system_catalog_access", - "missing_table", - "invalid_sql", - "sql_execution_error", - "register_as_clash", - "register_as_too_large", - "non_select_statement", - "multi_statement", - "denied_function", - "plan_operator_not_allowed" -]New value: +[ + "canvas_unavailable", + "system_catalog_access", + "missing_table", + "invalid_sql", + "sql_execution_error", + "register_as_clash", + "register_as_too_large", + "non_select_statement", + "multi_statement", + "denied_function", + "sql_read_only", + "plan_operator_not_allowed" +] - changed
Output schema / properties / notice / descriptionPrevious value: -"Guidance when the query returned no rows or a cap withheld some."New value: +"Guidance when the query returned no rows, a cap withheld some, or a limit was applied below the value sent." - changed
Output schema / properties / rows / descriptionPrevious value: -"Result rows keyed by the names in columns, bounded by preview and row_limit; BIGINT values arrive as strings."New value: +"Result rows keyed by the names in columns, bounded by preview, row_limit, and an inline size budget; rows stop before the first that would pass the budget, and a value is never clipped. BIGINT values arrive as strings."
- Changed
ilostat_describe_indicator6 fields changed- added
Output schema / properties / capAdded value: +{ + "description": "The most related indicators listed.", + "type": "number" +} - changed
Output schema / properties / notice / descriptionPrevious value: -"Where to find breakdown codes and units when the structure service did not supply them."New value: +"Where to find breakdown codes and units when the structure service did not supply them, and how to list every related indicator when the list is cut — whichever apply, joined." - changed
Output schema / properties / related_datasets / descriptionPrevious value: -"Up to 20 other indicators sharing this measure under other breakdowns."New value: +"The first 20 other indicators sharing this measure under other breakdowns, by code; related_total counts all of them." - added
Output schema / properties / related_totalAdded value: +{ + "description": "How many other indicators share this measure. ilostat_search_indicators with measure set to measure.code lists them all.", + "type": "number" +} - added
Output schema / properties / shownAdded value: +{ + "description": "Related indicators listed.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when related_datasets lists fewer than related_total.", + "type": "boolean" +}
- Changed
ilostat_list_reference3 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Entries per page (1–500)."New value: +"Entries per page, at most 500; a larger value, up to 10,000, is applied as 500 and the notice says so." - changed
Input schema / properties / limit / maximumPrevious value: -500New value: +10000 - changed
Output schema / properties / notice / descriptionPrevious value: -"Why a filter with no searchable word was not applied, why nothing matched, that the cursor starts past the last entry, and how to reach the remaining pages — whichever apply, joined."New value: +"That a limit past the page cap was applied as the cap, why a filter with no searchable word was not applied, why nothing matched, that the cursor starts past the last entry, and how to reach the remaining pages — whichever apply, joined."
- Changed
ilostat_query_indicator12 fields changed- changed
Input schema / properties / dataset_ids / descriptionPrevious value: -"One to three dataset IDs — an indicator code plus _A, _Q, or _M (UNE_DEAP_SEX_AGE_RT_A), as ilostat_search_indicators returns them. Case-insensitive; a DF_ prefix (the SDMX dataflow form) is stripped, a bare indicator code resolves when it has one frequency, and an element holding + or , joined IDs is split."New value: +"Dataset IDs, up to 100 — an indicator code plus _A, _Q, or _M (UNE_DEAP_SEX_AGE_RT_A), as ilostat_search_indicators returns them. Case-insensitive; a DF_ prefix (the SDMX dataflow form) is stripped, a bare indicator code resolves when it has one frequency, an element holding + or , joined IDs is split, and duplicates are dropped. Every ID is checked; the first 3 distinct datasets are queried and the rest come back as remaining_dataset_ids for another call." - changed
Input schema / properties / dataset_ids / maxItemsPrevious value: -3New value: +100 - changed
Input schema / properties / latest_only / descriptionPrevious value: -"Only the latest period per reference area and dataset (the latest quarter or month on sub-annual datasets); combines with time_from/time_to."New value: +"Only each series' latest period — a series is one dataset, reference area, sex, classif1, and classif2 code — with every row at that period (one per source under source_selection all or secondary; the latest quarter or month on sub-annual datasets); combines with time_from/time_to. When the request's full history is too large or too slow to read in one call, it falls back to the latest period per area and dataset, which can miss a series that ends earlier; applied_filters.latest_mode says which ran, and the notice says so. time_from or classif1 narrow it back under; sex or fewer ref_areas alone may still read too much and fall back." - changed
Output schema / properties / applied_filters / properties / dataset_ids / descriptionPrevious value: -"Datasets requested."New value: +"Datasets queried — at most 3." - added
Output schema / properties / applied_filters / properties / latest_modeAdded value: +{ + "description": "How latest_only was answered: per_series, each series' own latest period; latest_year, the fallback to the latest period per area and dataset, which can miss a series that ends earlier. Absent without latest_only.", + "enum": [ + "per_series", + "latest_year" + ], + "type": "string" +} - changed
Output schema / properties / applied_filters / properties / latest_only / descriptionPrevious value: -"Whether only the latest period per area was requested."New value: +"Whether only each series' latest period was requested." - changed
Output schema / properties / datasets / descriptionPrevious value: -"The requested datasets, in request order."New value: +"The queried datasets, in request order." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `unknown_dataset`: A dataset ID is not in the catalog, or a bare indicator code has several frequencies. `unknown_code`: A ref_areas, sex, classif1, classif2, or sources value is not in the ILOSTAT dictionaries. `unknown_area_group`: area_group is not X01, an ILO region or subregion, or a World Bank income group. `aggregates_unavailable`: An X-coded aggregate was requested from a dataset that has no aggregate rows. `invalid_period`: A sub-annual time on a dataset of another frequency, time combined with a range or latest_only, or time_from after time_to. `request_too_broad`: No filters at all and the unfiltered size exceeds the row ceiling. `result_too_large`: The filtered result passed the row ceiling while streaming. `dataset_retired`: Upstream reports a catalog dataset ID as deprecated or invalid. `upstream_busy`: The server-side pacer shed the call, or the ILOSTAT API answered 429 or a challenge page. `catalog_unavailable`: No catalog snapshot has loaded and the load failed, or the first load has not finished within 30 s. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `unknown_dataset`: Any dataset ID in dataset_ids, queried or not, is not in the catalog, or a bare indicator code has several frequencies. `unknown_code`: A ref_areas, sex, classif1, classif2, or sources value is not in the ILOSTAT dictionaries. `unknown_area_group`: area_group is not X01, an ILO region or subregion, or a World Bank income group. `aggregates_unavailable`: An X-coded aggregate was requested from a dataset that has no aggregate rows. `invalid_period`: A sub-annual time on a dataset of another frequency, time combined with a range or latest_only, or time_from after time_to. `request_too_broad`: No filters at all and the unfiltered size exceeds the row ceiling. `result_too_large`: The filtered result passed the row ceiling while streaming. `dataset_retired`: Upstream reports a catalog dataset ID as deprecated or invalid. `upstream_busy`: The server-side pacer shed the call, or the ILOSTAT API answered 429 or a challenge page. `catalog_unavailable`: No catalog snapshot has loaded and the load failed, or the first load has not finished within 30 s. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / notice / descriptionPrevious value: -"Filters that could not narrow a dataset, a unit the structure service could not supply, why nothing matched, where the full result is staged, or why reading stopped early."New value: +"Datasets left for another call, a latest_only fallback to the latest period per area and dataset, filters that could not narrow a dataset, a unit the structure service could not supply, why nothing matched, where the full result is staged, or why reading stopped early." - added
Output schema / properties / remaining_dataset_idsAdded value: +{ + "description": "Requested datasets past the first 3, as canonical IDs in request order; not fetched in this call. Pass them as dataset_ids with the same filters to get them. Absent when every requested dataset was queried.", + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / row_count / descriptionPrevious value: -"Rows the request returned — exact when the result is inline or staged; when reading stopped early (truncated), the rows read."New value: +"Rows the request returned — exact unless reading stopped early at the inline preview (summary.complete false), when it counts the rows read." - changed
Output schema / properties / summary / properties / complete / descriptionPrevious value: -"False when reading stopped early, so the counts cover only the rows read."New value: +"False when reading stopped early at the inline preview, so the counts cover only the rows read."
- Changed
ilostat_search_indicators6 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Hits per page (1–50)."New value: +"Hits per page, at most 50; a larger value, up to 1,000, is applied as 50 and the notice says so." - changed
Input schema / properties / limit / maximumPrevious value: -50New value: +1000 - added
Input schema / properties / measureAdded value: +{ + "description": "Measure code, exactly as ilostat_describe_indicator's measure.code gives it (e.g. EMP_TEMP_NB); case-insensitive. Keeps every indicator measuring that quantity, under any breakdown.", + "maxLength": 32, + "type": "string" +} - changed
Input schema / properties / query / descriptionPrevious value: -"Plain-language terms, e.g. \"youth unemployment\" or \"informal employment rate\". Case, accents, and punctuation are ignored, labor matches labour, and a trailing plural s is tolerated. Omit to browse by filters (results then order by indicator code)."New value: +"Plain-language terms, e.g. \"youth unemployment\" or \"informal employment rate\". Case, accents, and punctuation are ignored, labor matches labour, and a trailing plural s is tolerated; a term no catalog word starts with is matched by its stem (-ity, -ization, -ive, -ment, -ing, -ed, -ly, and a plural s dropped, -ies read as y). Omit to browse by filters (results then order by indicator code)." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `unknown_filter_code`: database, subject, or breakdown is not an ILOSTAT code. `invalid_cursor`: cursor does not decode. `catalog_unavailable`: No catalog snapshot has loaded and the load failed, or the first load has not finished within 30 s. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `unknown_filter_code`: database, subject, breakdown, or measure is not an ILOSTAT code. `invalid_cursor`: cursor does not decode. `catalog_unavailable`: No catalog snapshot has loaded and the load failed, or the first load has not finished within 30 s. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / notice / descriptionPrevious value: -"Why the query was browsed by filters alone, how to widen a search that matched nothing, that the cursor starts past the last match, and how to reach the remaining pages — whichever apply, joined."New value: +"That a limit past the page cap was applied as the cap, why the query was browsed by filters alone, which terms were matched by stem and as which catalog words (check those hits are on topic), how to widen a search that matched nothing, that the cursor starts past the last match, and how to reach the remaining pages — whichever apply, joined."
8 tool updates
- First observed
ilostat_compare_geographies - First observed
ilostat_dataframe_describe - First observed
ilostat_dataframe_query - First observed
ilostat_describe_indicator - First observed
ilostat_get_country_profile - First observed
ilostat_list_reference - First observed
ilostat_query_indicator - First observed
ilostat_search_indicators
Related MCP Connectors
Search and query the Eurostat catalogue — EU economy, demography, trade, and NUTS regional data.
Search and query 1,500+ OECD statistical datasets via SDMX. Keyless.
Labour market data from the ILO (ILOSTAT) by country, year, sex and age, with provenance.
Query 29,500+ World Bank development indicators for 200+ countries across 60+ years.
Related MCP Servers
- AlicenseAqualityAmaintenanceMCP server for accessing ILOSTAT (ILO statistical database) with tools to search indicators, retrieve metadata, list dimension values, and fetch data, with full provenance tracking.6193 npm2MIT
- AlicenseNot gradedqualityBmaintenanceEnables access to global labour statistics from ILOSTAT via the Pipeworx gateway.12 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables searching, exploring, and querying over 1,500 OECD statistical datasets via SDMX, covering national accounts, employment, trade, PISA, health, and more.89 npm2Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to access official statistical and economic indicator datasets from Eurostat, World Bank, OECD, IMF, WHO, and national statistical offices across 150+ countries through natural-language dataset discovery, inspection, time-series retrieval, SQL queries, CSV export, and account/watchlist management.MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.