Skip to main content
Glama
markusl

Tilastokeskus StatFin MCP Server

by markusl

Tilastokeskus StatFin MCP Server

Production-grade Model Context Protocol (MCP) server for Statistics Finland's StatFin database. Enables AI assistants like Claude to browse, search, and query Finnish statistical data.

Purpose

This project makes Finland's official statistics accessible to AI assistants through the Model Context Protocol (MCP). It bridges the gap between natural language queries and the structured PxWeb API.

Why This Exists

The Problem: Statistics Finland maintains one of the world's most comprehensive national statistics databases with 4,500+ tables covering population, employment, housing, economy, and more. However, the PxWeb API requires:

  • Knowledge of table IDs, variable codes, and value codes

  • Understanding of the hierarchical data structure

  • Correct query formatting with specific filter types

  • Awareness of API rate limits and response sizes

This makes it difficult for users to answer simple questions like "What is Helsinki's population?" without significant technical knowledge.

The Solution: This MCP server provides 7 tools that allow AI assistants to:

  1. Discover relevant tables through natural language search

  2. Explore the database structure and available variables

  3. Query specific data with proper filtering and pagination

  4. Handle rate limiting, caching, and error recovery automatically

Use Cases

  • Journalists asking "How has unemployment changed since COVID?"

  • Researchers comparing regional population trends

  • Analysts tracking housing price divergence between Helsinki and other cities

  • Citizens curious about birth rates, migration, or energy consumption

  • Developers building applications that need Finnish statistical data

Design Philosophy

  • LLM-first: Tool descriptions, parameter hints, and output schemas are optimized for AI consumption

  • Guided workflow: Each tool guides the LLM to the next logical step

  • Fail-safe: Query size estimation prevents expensive API calls; rate limiting protects against quota exhaustion

  • Cache-smart: Historical data is cached until StatFin updates it, minimizing redundant API calls

Related MCP server: @nor-data/statfin-mcp

Features

  • 7 MCP Tools for comprehensive data access

  • 149 subject areas covering population, employment, education, economy, environment, and more

  • ~4,500+ statistical tables with decades of historical data

  • Smart caching with timestamp validation - historical data stays cached until updated

  • Rate limiting (8 req/min per instance) to respect API limits

  • Multi-language support (Finnish, English, Swedish)

Documentation

  • ARCHITECTURE.md - Technical architecture and design decisions

  • BLOGS.md - Blog post ideas and data stories (English)

  • BLOGIT.md - Blog post ideas and data stories (Finnish)

Data Source & License

Statistics Finland StatFin Database

  • Official statistics of Finland

  • Data updated regularly (varies by table)

  • Free to use, no API key required

Data License: Statistics Finland data is licensed under CC BY 4.0. When using the data, provide attribution: "Source: Statistics Finland"

Installation

Local Installation

Clone and build the server to run on your machine.

git clone https://github.com/your-org/statfin-mcp.git
cd statfin-mcp
npm install
npm run build

Add to your config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "statfin": {
      "command": "node",
      "args": ["/absolute/path/to/statfin-mcp/dist/server.js"]
    }
  }
}
claude mcp add statfin node /absolute/path/to/statfin-mcp/dist/server.js

Verify with:

claude mcp list

Add to ~/.codex/config.toml:

[mcp.statfin]
command = "node"
args = ["/absolute/path/to/statfin-mcp/dist/server.js"]
transport = "stdio"

In Cursor settings, add MCP server with command:

  • Name: statfin

  • Command: node

  • Args: /absolute/path/to/statfin-mcp/dist/server.js


Remote Server

Connect to a hosted instance without local installation.

claude mcp add --transport http statfin https://your-server.example.com/mcp

Verify with:

claude mcp list

Add to your config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "statfin": {
      "type": "http",
      "url": "https://your-server.example.com/mcp"
    }
  }
}

Add to ~/.codex/config.toml:

[mcp.statfin]
url = "https://your-server.example.com/mcp"
transport = "http"

In Cursor settings, add MCP server:

  • Name: statfin

  • URL: https://your-server.example.com/mcp

  • Transport: HTTP


Running the Server

# Development mode (stdio transport, watch mode)
npm run dev

# Production HTTP server
npm start

# With custom port
PORT=8080 npm start

# Docker
docker-compose up --build

MCP Tools

Tool

Description

search_statistics

Search for tables by keyword (primary discovery)

list_subject_areas

Browse all 149 topic areas

list_tables

List tables in a subject area

get_table_metadata

Get table structure and variables

get_variable_values

Get all codes for a variable (regions, years)

query_table

Execute data queries

get_api_status

Check server health and rate limits

Example Workflows

⚠️ Variable codes changed in the 8 June 2026 PxWeb migration. They are now version-stamped and table-specific (e.g. alue_23_20260101, timeperiod_y), so the variable codes shown in the advanced examples below are illustrative — always call get_table_metadata to get the exact codes for your table. Table IDs are now short (11re.px, not statfin_vaerak_pxt_11re.px). Value codes (SSS, KU091, MK01…) are unchanged.

Basic: Helsinki Population Trend

// Codes below are the current codes for table 11re.px (verified).
query_table({
  tableId: "11re.px",
  selections: [
    { variable: "alue_23_20260101", filter: "item", values: ["KU091"] },     // Helsinki
    { variable: "ikaryhma_10_20180101", filter: "item", values: ["SSS"] },    // All ages
    { variable: "sukupuoli_9_20180101", filter: "item", values: ["SSS"] },    // Total
    { variable: "timeperiod_y", filter: "top", top: 10 },                     // Last 10 years
    { variable: "contentscode", filter: "item", values: ["vaerak-vaesto"] }   // Population
  ]
})
// Returns: Helsinki population trend over the last 10 years

Advanced Example Queries

The variable codes in these examples are conceptual (pre-migration names shown for readability). Resolve the real, current codes for each table via get_table_metadata before querying — only the short table IDs and the value codes are guaranteed current.

Education: University Student Employment by Field

Which fields of study have the highest employment rates for students?

// Table: Student employment by education level and field
query_table({
  tableId: "13g2.px",
  selections: [
    { variable: "Koulutusaste", filter: "item", values: ["7"] },  // University level
    { variable: "Sukupuoli", filter: "item", values: ["SSS"] },   // All genders
    { variable: "Maakunta", filter: "item", values: ["SSS"] },    // Whole country
    { variable: "Vuosi", filter: "top", top: 3 }                  // Last 3 years
  ]
})
// Analyze: Compare IT vs humanities employment rates

Track rental market changes in Helsinki city center vs suburbs

// Table: Free-market rental prices by postal code, quarterly
query_table({
  tableId: "13eb.px",
  selections: [
    { variable: "Postinumero", filter: "item", values: [
      "00100",  // Helsinki center (Kruununhaka)
      "00500",  // Sörnäinen
      "02100",  // Espoo Tapiola
      "01300"   // Vantaa Tikkurila
    ]},
    { variable: "Huoneluku", filter: "item", values: ["02"] },  // 2-room apartments
    { variable: "Vuosineljännes", filter: "top", top: 20 }      // 5 years quarterly
  ]
})
// Analyze: Which areas are gentrifying fastest?

Migration: International Migration Flows

Analyze emigration vs immigration patterns over decades

// Table: Migration by month and type
query_table({
  tableId: "119z.px",
  selections: [
    { variable: "Sukupuoli", filter: "item", values: ["SSS"] },
    { variable: "Tapahtumakuukausi", filter: "item", values: ["SSS"] },  // Annual totals
    { variable: "Tiedot", filter: "item", values: [
      "vm41",  // Immigration
      "vm42",  // Emigration
      "vm43"   // Net migration
    ]},
    { variable: "Vuosi", filter: "top", top: 30 }  // 30-year trend
  ]
})
// Analyze: How has Finland's migration balance changed since 1990?

Track reported crimes by category with seasonal patterns

// Table: Reported crimes by month (preliminary data)
query_table({
  tableId: "13jt.px",
  selections: [
    { variable: "Rikosryhmä ja teonkuvauksen tarkenne", filter: "item", values: [
      "101T603",     // All crimes total
      "101T504X406", // Violent crimes
      "101T161"      // Property crimes
    ]},
    { variable: "Tiedot", filter: "item", values: ["rikokset_lkm"] },
    { variable: "Kuukausi", filter: "top", top: 60 }  // 5 years monthly
  ]
})
// Analyze: Seasonal crime patterns, COVID impact on crime rates

Electricity: Power Generation Mix Evolution

How has Finland's electricity production evolved toward renewables?

// Table: Electricity supply and production by source
query_table({
  tableId: "11sr.px",
  selections: [
    { variable: "Tiedot", filter: "item", values: [
      "sahkon_tuot",       // Total production
      "vesivoima",         // Hydropower
      "tuulivoima",        // Wind power
      "ydinvoima",         // Nuclear
      "fossiiliset"        // Fossil fuels
    ]},
    { variable: "Vuosi", filter: "top", top: 25 }  // 25-year transition
  ]
})
// Analyze: Nuclear vs wind growth, fossil phase-out trajectory

Traffic: Road Accident Hotspots by Municipality

Which municipalities have the highest traffic accident rates?

// Table: Traffic accidents with injuries by area and road type
query_table({
  tableId: "12qh.px",
  selections: [
    { variable: "Alue", filter: "item", values: [
      "SSS",    // Whole country (for comparison)
      "KU091",  // Helsinki
      "KU092",  // Vantaa
      "KU049",  // Espoo
      "KU837",  // Tampere
      "KU853"   // Turku
    ]},
    { variable: "Tielaji", filter: "item", values: ["SSS"] },
    { variable: "Osallinen", filter: "item", values: ["SSS"] },
    { variable: "Tiedot", filter: "item", values: [
      "konn",     // Total accidents
      "kuolonn",  // Fatal accidents
      "loukonn"   // Injury accidents
    ]},
    { variable: "Vuosi", filter: "top", top: 5 }
  ]
})
// Analyze: Per-capita accident rates, pedestrian vs vehicle involvement

Housing: Household Size and Building Type Changes

How are Finnish living patterns changing over time?

// Table: Households by size and building type
query_table({
  tableId: "116a.px",
  selections: [
    { variable: "Talotyyppi", filter: "item", values: [
      "1",  // Detached houses
      "2",  // Attached houses
      "3",  // Apartment buildings
    ]},
    { variable: "Asuntokunnan koko", filter: "item", values: [
      "1",  // 1-person households
      "2",  // 2-person
      "3",  // 3-person
      "4"   // 4+ person
    ]},
    { variable: "Vuosi", filter: "top", top: 40 }  // Since 1985
  ]
})
// Analyze: Rise of single-person households, apartment living trends

Electric Vehicles: Adoption Rate by Vehicle Type

Track the EV transition in Finland's vehicle fleet

// Table: New vehicle registrations by fuel type
query_table({
  tableId: "11ck.px",
  selections: [
    { variable: "Ajoneuvoluokka", filter: "item", values: ["01"] },  // Passenger cars
    { variable: "Käyttövoima", filter: "item", values: [
      "00",  // Total
      "01",  // Petrol
      "02",  // Diesel
      "04",  // Electric
      "05",  // Plug-in hybrid
    ]},
    { variable: "Vuosi", filter: "top", top: 15 }
  ]
})
// Analyze: EV market share growth, diesel decline post-2015

Multi-Step Analysis Patterns

Cross-Domain Analysis: Education → Employment → Income

1. Find education completion rates by field
2. Get employment statistics for recent graduates
3. Query income data by education level
4. Compare: Which fields offer best ROI?

Time-Series with Regional Breakdown

1. Get national trend (region variable, value "SSS")
2. Compare major cities (KU091, KU837, KU853)
3. Identify regional divergence patterns
4. Correlate with local economic indicators

Demographic Shift Analysis

1. Query population by age groups (1990-2024)
2. Get migration data for same period
3. Query birth/death rates
4. Model: Aging population impact on workforce

Common Region Codes

Code

Description

SSS

Whole country (KOKO MAA)

MK01-MK19

Regions (maakunta)

KU091

Helsinki

KU092

Vantaa

KU049

Espoo

Development

# Type checking
npm run typecheck

# Run unit tests
npm run test:run

# Run tests with coverage
npm run test:coverage

# Run integration tests (requires API access)
npm run test:integration

# Fetch fresh test fixtures
npm run test:fixtures:fetch

Environment Variables

Variable

Default

Description

PORT

8080

HTTP server port

MCP_TRANSPORT

stdio

Set to "http" for HTTP transport

API_TOKEN

-

Optional authentication token

LOG_LEVEL

info

Logging level (debug, info, warn, error)

API Rate Limits

  • StatFin API: 30 requests/minute total

  • This server: 8 requests/minute per instance

  • Designed for up to 3 concurrent Cloud Run instances

License

This project (code) is licensed under MIT. The statistical data accessed through this server is provided by Statistics Finland under CC BY 4.0.

Available Tools

7 tools
get_api_statusCheck API StatusA
Read-onlyIdempotent

Get server health, rate limit status, and cache statistics.

Use when:

  • Queries are failing or slow

  • Need to check remaining API quota

  • Debugging connection issues

Rate limit: 8 requests per minute per instance.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
cacheYesCache statistics per cache type
configYesServer configuration
healthyYesTrue if server is healthy and accepting requests
rateLimitYesCurrent rate limit status

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, covering safety. The description adds value beyond that by stating the rate limit (8 requests per minute per instance) and the specific data returned (health, rate limit status, cache statistics). This is useful behavioral context that annotations do not provide. No contradictions with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: first a one-line purpose, then a 'Use when' bulleted list, then a rate limit note. Every sentence earns its place – there is no fluff. Information is front-loaded, so an agent quickly understands what the tool does and when to use it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a health-check tool with no parameters and an output schema (which likely defines the return structure), the description covers the purpose, use cases, and rate limit. The siblings are all sufficiently different that there is no ambiguity about selection. Nothing critical is missing for an agent to decide whether and when to call this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters, the schema is trivially complete (100% coverage). The baseline for 0 params is 4, and the description adds no parameter-related meaning because none is needed. The description's mention of what is returned (health, rate limit, cache stats) provides context that would indirectly help understand any potential future parameters, but for now it's appropriately handled.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb and resource: 'Get server health, rate limit status, and cache statistics.' This distinguishes it from all siblings, which deal with statistics, subject areas, metadata, and queries – none of which match a health check. The purpose is unambiguous and immediately actionable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use when:' list with three concrete triggers: queries failing/slow, needing to check remaining API quota, and debugging connection issues. This gives an agent clear conditions for invocation. While it doesn't explicitly say when not to use it, the context is specific enough that misuse is unlikely, and the distinct scope of siblings implicitly excludes alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_table_metadataGet Table StructureA
Read-onlyIdempotent

Get the structure of a table: what variables it has and what values are available.

REQUIRED before querying - shows you:

  • Variable codes (table-specific and version-stamped, e.g. "alue_23_20260101" for region, "timeperiod_y" for the time variable). Always read these here - never assume or reuse codes from another table.

  • Value codes (KU091=Helsinki, SSS=Total, 2024=year 2024)

  • Which variables are required vs optional

  • Total possible data combinations

Example: a region variable may have 300+ values, a year variable 50+.

After understanding the structure, use query_table with the exact codes from this output.

ParametersJSON Schema
NameRequiredDescriptionDefault
tableIdYesTable ID from search_statistics. Example: "11re.px"
languageNoLanguage for variable/value names. Default "fi".fi
includeAllValuesNoIf true, return ALL value codes (can be 300+ for regions). Default false shows first 20.

Output Schema

ParametersJSON Schema
NameRequiredDescription
titleYesHuman-readable table title
tableIdYesThe table ID
variablesYesAll variables in this table
lastUpdatedYesWhen the data was last updated (ISO format)
queryGuidanceYesTips for constructing an efficient query
totalCombinationsYesTotal possible data cells (product of all value counts)

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds valuable behavioral context: it explains that variable codes are table-specific and version-stamped, that value codes can be numerous (300+), and that includeAllValues defaults to showing only the first 20. This goes beyond what annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a clear opening sentence, a bulleted list of what the output shows, and a concrete example. It's slightly longer than strictly necessary but every section earns its place by conveying critical usage information. The 'REQUIRED before querying' callout is front-loaded and effective.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a read-only metadata tool. It explains the purpose, the prerequisite relationship to query_table, the nature of the output (codes, values, required/optional), and the scale of data. The output schema exists, so return values don't need to be described in detail. The only minor gap is not explaining what 'total possible data combinations' means precisely, but this is a minor omission given the overall completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

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 three parameters (tableId, language, includeAllValues). The description adds context about the meaning of the output (variable codes, value codes) and the scale of values, but doesn't add much parameter-specific detail 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.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: retrieving table structure including variable codes, value codes, required vs optional variables, and data combinations. It explicitly distinguishes itself from query_table by positioning this as the prerequisite step before querying. The verb 'get' plus the specific resource 'table metadata/structure' is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says this is REQUIRED before querying, and instructs the agent to use query_table afterward with the exact codes from this output. It also warns against assuming or reusing codes from another table, providing clear when-to-use guidance and routing to the correct sibling tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_variable_valuesGet All Values for a VariableA
Read-onlyIdempotent

Get the complete list of values for a variable when metadata only shows first 20.

Useful for:

  • Finding specific region codes (KU091=Helsinki, MK01=Uusimaa region)

  • Getting all available years (1972-2024)

  • Finding specific category codes

Common region codes:

  • SSS = Whole country (Finland)

  • MK01-MK19 = Regions (maakunta)

  • KU091 = Helsinki, KU049 = Espoo, KU837 = Tampere

Use search parameter to filter: search="Helsinki" returns only matching values.

ParametersJSON Schema
NameRequiredDescriptionDefault
searchNoFilter values containing this text. Example: "Helsinki" returns only Helsinki-related codes.
tableIdYesTable ID. Example: "11re.px"
languageNoLanguage for value names. Default "fi".fi
variableYesExact variable code from get_table_metadata (table-specific and version-stamped, e.g. "alue_23_20260101", "timeperiod_y"). The region variable usually starts with "alue"; the time variable is often "timeperiod_y". Do not guess.

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalYesTotal number of values for this variable
valuesYesAll values for this variable (filtered if search was used)
variableYesThe variable code that was queried
commonCodesNoCommon code patterns for region variables (wholeCountry, regions, municipalities)

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readonly/idempotent/non-destructive behavior, so the description adds value by explaining that it returns a complete list beyond the metadata's first 20. It also provides concrete behavioral context (e.g., common region codes and search filter behavior) that helps set expectations about the output. No contradictions with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the core purpose first. It uses bullet points for readability and groups related information (useful cases, region codes). It is a bit longer than strictly necessary, but every sentence adds practical value—examples and codes are not filler. The structure makes it scannable for an agent.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only value-list tool with an output schema, the description is thorough. It covers the primary use cases, gives concrete examples of how to use the search filter, and provides domain-specific knowledge (region codes, year range 1972–2024) that an agent would otherwise lack. The existence of an output schema reduces the need to describe return structure. No critical information is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already describes all parameters with 100% coverage (baseline 3). The description goes well beyond by explaining variable code patterns (e.g., 'alue_23_20260101', 'timeperiod_y'), providing common region codes (e.g., KU091, MK01), and showing the search parameter example with 'Helsinki'. This enriches the schema's bare definitions and helps the agent choose correct parameter values.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific, actionable purpose: 'Get the complete list of values for a variable when metadata only shows first 20.' This clearly distinguishes it from metadata retrieval (get_table_metadata) which provides a truncated list. The 'Useful for' bullets further clarify the intended use cases (region codes, year lists, category codes), leaving no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description states when to use the tool: when metadata only shows the first 20 values. This implicitly contrasts with get_table_metadata, but it does not name alternatives or explicitly say not to use other tools. It gives clear context for the typical scenarios (finding region codes, years, category codes) but lacks explicit 'when not to use' guidance or reference to sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_subject_areasBrowse Statistical TopicsA
Read-onlyIdempotent

List all 149 subject areas (topics) in StatFin database.

Use this to explore what statistics are available when you don't have a specific search term.

Topic examples:

  • vaerak: Population structure

  • tyti: Labor force

  • ashi: Housing prices

  • synt: Births and deaths

  • muutl: Migration

After finding an area, use list_tables to see all tables in that topic.

ParametersJSON Schema
NameRequiredDescriptionDefault
languageNoLanguage: fi=Finnish, en=English, sv=Swedishfi

Output Schema

ParametersJSON Schema
NameRequiredDescription
areasYesAll available subject areas (topics)
totalYesTotal number of subject areas (149)

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the read-only, idempotent, and non-destructive profile. The description adds useful behavioral context by promising the complete set ('all 149') and giving representative codes and labels. It doesn't mention ordering or language-dependent output, but that is less critical given the output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core message is front-loaded in the first sentence, the examples earn their place by making return values concrete, and the last sentence is actionable. There is no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple, parameterless, read-only list operation with an output schema and complete parameter docs, the description covers purpose, when to use it, and what to do next. Nothing required 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.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The only parameter, language, is fully documented in the input schema with enum values, default, and meaning. The description adds no parameter-level guidance, so it stays at the baseline expected when schema description coverage is high.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource: 'List all 149 subject areas (topics) in StatFin database.' The topic examples make the resource concrete, and the final sentence distinguishes it from list_tables, so an agent can clearly tell what this tool produces.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use it: 'when you don't have a specific search term,' and gives a clear next step to list_tables. It doesn't name search_statistics as the counterpart for specific queries, but the contextual cue is strong enough to guide selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_tablesList Tables in TopicA
Read-onlyIdempotent

List all statistical tables within a subject area.

Each area typically has 20-40 tables with different data views.

Common subject areas:

  • "vaerak" → 30+ population tables (age, gender, region, etc.)

  • "tyti" → 35+ employment tables (employment rate, unemployment, etc.)

  • "ashi" → 15+ housing price tables

Use list_subject_areas first to find the area ID, or use search_statistics for direct search.

ParametersJSON Schema
NameRequiredDescriptionDefault
languageNoLanguage: fi=Finnish, en=English, sv=Swedishfi
subjectAreaYesSubject area ID (e.g., "vaerak", "tyti", "asas")

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalYesNumber of tables in this area
tablesYesAll tables in this subject area
subjectAreaYesThe subject area that was queried

TDQS

A4.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, so the safety profile is well covered. The description adds useful context about typical table volume and data views, but it does not disclose further behavioral traits such as response size limits, pagination, or result variability.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with a one-sentence definition, followed by compact bullets and a short routing note. Every sentence contributes useful information, and there is no redundant restatement of the schema or title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter read-only tool with full schema coverage, an output schema, and strong sibling guidance, the description is complete. It explains how to discover the required argument, what kind of content to expect, and when to choose an alternative tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

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 adds real value beyond the schema by mapping example subject-area IDs to content ('population tables', 'employment tables', 'housing price tables') and expected table counts, making the required subjectArea parameter more meaningful to an agent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'List all statistical tables within a subject area.' It also distinguishes itself from siblings by naming list_subject_areas and search_statistics and clarifying that the scope is subject-area-based, not a general search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells the agent when to use this tool versus alternatives: 'Use list_subject_areas first to find the area ID, or use search_statistics for direct search.' It also gives concrete subject-area examples with expected table counts, which helps the agent decide whether this is the right entry point.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_tableQuery Statistical DataA
Read-onlyIdempotent

Execute a query to retrieve actual statistical data from a table.

WORKFLOW: search_statistics → get_table_metadata → query_table

Selection types:

  • filter: "item" + values: ["KU091", "2024"] → specific values

  • filter: "top" + top: 5 → latest 5 values (good for time variables)

  • filter: "all" → all values (use carefully, can be large!)

Example - Helsinki population for last 5 years (the variable codes below are from table 11re.px; YOUR table's codes WILL differ - always read them from get_table_metadata first, never reuse these): { "tableId": "11re.px", "selections": [ {"variable": "alue_23_20260101", "filter": "item", "values": ["KU091"]}, {"variable": "timeperiod_y", "filter": "top", "top": 5}, {"variable": "sukupuoli_9_20180101", "filter": "item", "values": ["SSS"]}, {"variable": "ikaryhma_10_20180101", "filter": "item", "values": ["SSS"]}, {"variable": "contentscode", "filter": "item", "values": ["vaerak-vaesto"]} ] }

IMPORTANT: Variable codes are table-specific; get them from get_table_metadata. Use VALUE CODES (KU091, SSS), not labels (Helsinki, Total).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows. If query exceeds this, it fails with an error suggesting more specific selections.
tableIdYesTable ID from search_statistics or list_tables. Example: "11re.px"
languageNoLabel language in results. Default "fi".fi
selectionsYesOne entry per variable. IMPORTANT: Include all non-optional variables or query will return too much data.

Output Schema

ParametersJSON Schema
NameRequiredDescription
dataNoQuery results (only present if success=true)
errorNoError message (only present if success=false)
successYesTrue if query succeeded, false if error
metadataNoDataset metadata (source, last updated, label)
rowCountNoNumber of data rows returned
queryInfoYesQuery execution metadata

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnly, idempotent, openWorld, and non-destructive behavior, lowering the burden. The description adds useful behavioral context beyond annotations: the 'all' filter can return large datasets, variable codes are table-specific, and the example shows how selections are structured. It does not contradict annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every section earns its place: purpose, workflow, selection-type semantics, a concrete example, and critical warnings. It is front-loaded with the core action and workflow, and the example is highly instructive rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the 100% schema coverage, and the presence of an output schema, the description is complete. It covers the required workflow, selection semantics, the need for table-specific codes, and caution around large result sets. Nothing essential for an agent to call this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

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 significant meaning beyond the schema by explaining the three selection types with concrete examples and emphasizing value codes over labels. The example JSON demonstrates exactly how to populate selections, which is valuable for correct invocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool executes a query to retrieve actual statistical data from a table, using a specific verb and resource. It also positions the tool within a workflow (search_statistics → get_table_metadata → query_table), which differentiates it from sibling tools like search_statistics and get_table_metadata.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The WORKFLOW line explicitly places query_table as the final step after search_statistics and get_table_metadata, giving clear sequencing. The description also warns to read variable codes from get_table_metadata first and never reuse codes, but it does not explicitly state when not to use query_table or name alternative tools for other needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_statisticsSearch Finnish StatisticsA
Read-onlyIdempotent

Search Statistics Finland's StatFin database for statistical tables by keyword.

USE THIS FIRST when looking for data. Returns ranked results with relevance scores.

Examples:

  • "väestö Helsinki" → population tables for Helsinki

  • "unemployment" → employment/labor market tables

  • "housing prices" → real estate statistics

Returns: tableId (needed for query_table), title, relevance score, publication date.

After finding a table, use get_table_metadata to see its structure before querying.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (1-50). Default 10. Increase if first results are not relevant.
queryYesSearch term. Examples: "väestö Helsinki", "unemployment rate", "asuntojen hinnat". Finnish terms often work better.
languageNoResponse language. Default "fi" (Finnish). Use "en" for English labels.fi

Output Schema

ParametersJSON Schema
NameRequiredDescription
queryYesThe search query that was executed
totalYesNumber of results returned
hasMoreYesTrue if more results available (increase limit)
resultsYesRanked search results

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false. The description adds useful behavior beyond that: results are ranked by relevance, return fields are enumerated, and Finnish terms often work better. This gives the agent realistic expectations about search behavior without contradicting annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the most important instruction: 'USE THIS FIRST.' Every section earns its place: the one-line purpose, usage priority, examples, return fields, and next-step workflow. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only search tool, the description covers the essential agent workflow: search, interpret results, and proceed to metadata before querying. The output schema exists to document return structure, and the description still enumerates key return fields. Nothing critical is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

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 three parameters. The description adds examples and notes about Finnish terms, but those complement rather than substantially extend the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb and resource: 'Search Statistics Finland's StatFin database for statistical tables by keyword.' It also differentiates the tool from siblings by emphasizing keyword search and ranked results, which is distinct from list_tables, list_subject_areas, or query_table.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit contextual guidance: 'USE THIS FIRST when looking for data.' It also provides a follow-up workflow: use get_table_metadata before querying. However, it does not explicitly state when not to use this tool or compare it against list_tables and list_subject_areas, so it stops short of full when/when-not coverage.

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.

  1. 7 tool updatesv1.0.0
    • First observedget_api_status
    • First observedget_table_metadata
    • First observedget_variable_values
    • First observedlist_subject_areas
    • First observedlist_tables
    • First observedquery_table
    • First observedsearch_statistics

TDQS

A4.6/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: search, list topics, list tables, get metadata, get variable values, query data, and check status. No overlaps or ambiguous boundaries; the workflow is explicitly sequential and each step is unique.

Naming Consistency5/5

All tool names follow a consistent verb_noun snake_case pattern: search_statistics, list_subject_areas, list_tables, get_table_metadata, get_variable_values, query_table, get_api_status. Predictable and uniform.

Tool Count5/5

With 7 tools, the server is well-scoped for a statistical database access. Each tool is essential and earns its place—no redundant or missing operations. The count is comfortably within the optimal 3-15 range.

Completeness5/5

The toolset covers the entire lifecycle from discovery (search, list subject areas, list tables) to detailed exploration (metadata, variable values) to data retrieval (query) and system status. No significant gaps; the workflow is fully supported.

Maintenance

ActivityInactive
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    MCP server for official statistics from Statistics Finland (Tilastokeskus) — the StatFin database, exposed through the PxWeb API. Search 3000+ tables, inspect their dimensions, and pull data as JSON-stat2.
    4
    35 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Local MCP server for querying Polish socioeconomic statistics from the GUS Bank Danych Lokalnych (BDL), letting AI find variables and units and pull data.
    5
    MIT