SourceVahti
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SourceVahtisearch indicators for female lung cancer mortality in Finland"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
SourceVahti
Reliable AI access to public health, government and research data.
SourceVahti is a Model Context Protocol (MCP) server that makes statistical definitions explicit before returning a number. Version 0.3 exposes four source-specific adapters and three read-only tools over local stdio:
search_indicatorsget_observationsget_latest_observation
The product is the ambiguity guard. “Mortality rate” may mean a crude rate or
one of several age-standardised rates, and two publishers may produce valid but
non-interchangeable values. SourceVahti returns a structured
ambiguous_indicator error with the valid candidates instead of silently
choosing one.
Current sources
Source | Narrow supported scope | Role |
Finnish Cancer Registry | Female Finnish lung/tracheal cancer mortality, 2024 | Exact registry acceptance case |
NORDCAN 9.6 | Nordic lung-cancer mortality by country and rate definition, 2023–2024 | Nordic cancer outcomes |
WHO Global Health Observatory | Current tobacco-use prevalence, age 15+, Nordic countries, 2000–2025 | Modelled risk-factor context |
Eurostat | Lung-cancer mortality, C33-C34, Nordic countries, 2011–2023 | Independent mortality source |
All bundled data are frozen, reviewed snapshots. The MCP server makes no upstream requests during a tool call.
Finnish Cancer Registry
The snapshot contains the 2024 female lung and tracheal cancer mortality export. The public application exposes three rates for the same population and year:
Rate definition | Standard population | Value per 100,000 person-years |
Crude | None | 29.96 |
Age-standardised, world | World standard population (1966) | 9.04 |
Age-standardised, Finland | Finland population 2014 | 23.28 |
It was retrieved on 2026-07-30 from the Finnish Cancer Registry statistics application. The latest official year was 2024 and the release date was 2026-04-24.
NORDCAN
The NORDCAN snapshot contains version 9.6 female lung-cancer mortality tables for 2023–2024. It covers Denmark, Finland, Greenland, Iceland, Norway, Sweden, the Faroe Islands' latest 2023 observation and two Nordic aggregates. Every geography retains crude, World, Nordic 2000, European 1976 and European 2013 rate definitions. NORDCAN 9.6 was released on 2026-06-30. See the database and statistical definitions.
WHO GHO
The WHO adapter contains 150 observations: female, male and both-sex modelled estimates for five Nordic countries at ten published years from 2000 through 2025. The indicator is current tobacco-use prevalence among people aged 15 years and over, standardised to the WHO standard population. Each value retains its 95% uncertainty bounds, modelled/projected status and source note.
This is contextual risk-factor data, not a cancer outcome or causal estimate. See the WHO indicator metadata.
Eurostat
The Eurostat adapter contains 195 annual observations for Denmark, Finland,
Iceland, Norway and Sweden: female, male and both-sex rates from 2011 through
2023. It selects dataset HLTH_CD_ASDR2, unit RT, all ages and cause
C33_C34. Rates are directly standardised to the European Standard Population
2013 and retain Eurostat status flags.
See the Eurostat causes-of-death metadata. Eurostat and NORDCAN are separate publishers; matching definitions make a source comparison possible but do not make their observations identical.
Related MCP server: Kolada MCP Server
Install and run
Requirements: Python 3.11 or newer and uv.
git clone https://github.com/heidihelena/sourcevahti.git
cd sourcevahti
uv sync --locked --all-extras --dev
uv run sourcevahtisourcevahti starts a local stdio server. It should stay silent and wait for an
MCP host because stdout carries the protocol.
For MCP Inspector:
uv run mcp dev src/sourcevahti/server.py:mcpGeneric MCP host configuration:
{
"mcpServers": {
"sourcevahti": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/sourcevahti",
"run",
"sourcevahti"
]
}
}
}Use an absolute path because an MCP host may start the server from a different working directory.
Examples
First search without selecting a Finnish rate definition:
{
"query": "female lung cancer mortality rate",
"source": "finnish_cancer_registry",
"sex": "female",
"unit": "per_100_000_person_years"
}search_indicators returns three candidates. Retry
get_latest_observation with the required definition:
{
"query": "female lung cancer mortality rate",
"source": "finnish_cancer_registry",
"sex": "female",
"rate_type": "age_standardised_finland_2014",
"unit": "per_100_000_person_years"
}The result includes the native source code, cancer definition, year, value, unit, sex, geography, rate type, standard population, observation status, citation URL, source version and release date, retrieval date and snapshot ID. The acceptance value is 23.28 per 100,000 person-years in 2024.
WHO risk-factor context:
{
"query": "female Finland current tobacco use prevalence",
"source": "who_gho",
"sex": "female",
"geography": "Finland",
"unit": "percent"
}This returns the 2025 projected estimate, 17.1%, with a 14.0–20.2% uncertainty interval and the WHO indicator definition.
Eurostat source comparison:
{
"query": "female Finland lung cancer mortality European 2013",
"source": "eurostat",
"sex": "female",
"geography": "Finland"
}If source is omitted, a European-2013-standardised Nordic lung-cancer
mortality query may correctly remain ambiguous between NORDCAN and Eurostat.
Tool contracts
search_indicators
Lexically searches indicator names, descriptions, definitions, native codes, health topic, cancer codes, source, geography, sex, unit, rate type and standard population. Recognised dimensional terms in the query become constraints. Explicit filters are validated and cannot contradict those terms. Epidemiologically distinct series are never collapsed.
get_observations
Returns one resolved series, optionally bounded by inclusive start_year and
end_year. Supply an exact indicator_id, or a query plus sufficient
dimensions to leave exactly one candidate.
get_latest_observation
Returns the highest-year observation in one resolved series. In addition to the shared provenance fields, an observation may include uncertainty bounds and a publisher note.
Supported canonical values:
source:finnish_cancer_registry,nordcan,who_gho,eurostatsex:female,male,allunit:per_100_000_person_years,percentrate_type:crude,age_standardised_world,age_standardised_world_1966,age_standardised_finland_2014,age_standardised_nordic_2000,age_standardised_europe_1976,age_standardised_europe_2013,age_standardised_who
count remains reserved for future count indicators. Source-specific geography
and semantic constraints are validated. Unknown categories and dimensional
mismatches are rejected.
Architecture
MCP typed tools
└── SourceCatalog
├── cross-source query resolution and ambiguity checks
├── FinnishCancerRegistryAdapter
├── NordcanAdapter
├── WhoGhoAdapter
└── EurostatAdapter
└── frozen source snapshots
└── strict Pydantic models + provenancesrc/sourcevahti/models.py is the public schema. Version 0.3 adds a general
health topic and indicator definition, measure type, percent units,
uncertainty bounds, publisher notes, modelled/projected status and optional
cancer fields. Cancer mortality observations still require an exact cancer
definition and a per-100,000 rate.
The official MCP SDK derives JSON schemas from the typed functions and Pydantic models. Domain exceptions become MCP tool errors so a model can correct its request. Adapters remain source-specific; shared code handles normalised search, validation, duplicate detection and ambiguity.
Data refresh
Bundled snapshot files:
src/sourcevahti/data/finnish_cancer_registry_2024.csvsrc/sourcevahti/data/nordcan_lung_mortality_9_6.csvsrc/sourcevahti/data/who_gho_tobacco_use_2026_01_15.csvsrc/sourcevahti/data/eurostat_lung_mortality_2026_06_08.csv
WHO and Eurostat have documented machine endpoints. Refresh their deliberately narrow matrices outside server startup:
uv run python scripts/refresh_context_snapshots.pyThe script requests only the reviewed indicators/dimensions, rejects missing countries, short series, duplicate country-sex-year keys and negative values, then writes deterministic CSV files. Review diffs, metadata, terms and publisher flags before accepting them.
The Finnish Cancer Registry and NORDCAN interactive applications remain a manual trust boundary. Preserve raw exports outside the repository, verify all rate definitions and standard populations, update provenance with the normalised file, and confirm that ambiguity tests still expose every valid definition.
SourceVahti code is Apache-2.0. Source data retain publisher terms; every
provenance object carries a separate license_note.
Deployment and DNS
The public endpoints are sourcevahti.vahtian.com for the hosted HTTP/MCP
service and trends.ntog.org for the NTOG Shiny application. DNS records should
only be created after each deployment supplies its canonical target hostname.
See docs/DNS.md for Cloudflare records, provider-side custom domain steps and verification commands.
Development
uv sync --locked --all-extras --dev
uv run --frozen ruff check .
uv run --frozen ruff format --check .
uv run --frozen mypy src
uv run --frozen pytest
uv buildTests use frozen official responses and the SDK's in-memory MCP client. CI runs linting, type checking, tests and package builds on Python 3.11 and 3.13.
See CONTRIBUTING.md for source-update rules and SECURITY.md for responsible disclosure.
Licence
SourceVahti code is licensed under the Apache License, Version 2.0. No medical advice is provided. Always cite the source and preserve the returned epidemiological definition when reporting a value.
Available Tools
3 toolsget_latest_observationGet latest health observationBRead-only
Return the latest observation with definition and full provenance.
| Name | Required | Description | Default |
|---|---|---|---|
| sex | No | Optional sex filter: female, male, or all. | |
| unit | No | Optional canonical unit filter. | |
| query | No | Indicator query used when indicator_id is omitted. | |
| source | No | Optional source identifier. | |
| geography | No | Optional published geography. | |
| rate_type | No | Explicit epidemiological rate definition. Omission produces an ambiguity error when several definitions are valid. | |
| indicator_id | No | Exact identifier returned by search_indicators. |
Output Schema
| Name | Required | Description |
|---|---|---|
| indicator | Yes | |
| observation | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds that the result includes definition and full provenance, but it does not mention error conditions, how 'latest' is determined, or what happens when no observation exists. The readOnlyHint=true annotation covers safety, so the description doesn't need to restate that, but more behavioral detail would be helpful.
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 a single sentence that efficiently states the action, resource, and key added value ('definition and full provenance'). No fluff or unnecessary details are present.
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?
The presence of an output schema and fully described parameters covers the return value and argument semantics. However, the description lacks usage context and does not differentiate from sibling tools, which is a notable gap for a tool with this many optional filters.
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?
The input schema covers 100% of parameters with descriptive text, so the baseline is 3. The description itself does not add parameter-level meaning, but the schema already provides clarity, including important warnings like rate_type's ambiguity error.
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 uses a specific verb ('Return') and resource ('latest observation'), and adds 'with definition and full provenance' to clarify scope. It distinguishes from siblings by focusing on the singular latest result, though it doesn't explicitly contrast with get_observations.
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 description provides no guidance on when to use this tool versus get_observations or search_indicators. It does not mention scenarios like retrieving a single most recent record versus a full time series, leaving the agent to infer the intended use from context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_observationsGet health observationsBRead-only
Return a normalised series only after resolving its full definition.
| Name | Required | Description | Default |
|---|---|---|---|
| sex | No | Optional sex filter: female, male, or all. | |
| unit | No | Optional canonical unit filter. | |
| query | No | Indicator query used when indicator_id is omitted. | |
| source | No | Optional source identifier. | |
| end_year | No | Inclusive final year. | |
| geography | No | Optional published geography. | |
| rate_type | No | Explicit canonical rate definition returned by search_indicators. Required when several definitions match. | |
| start_year | No | Inclusive first year. | |
| indicator_id | No | Exact identifier returned by search_indicators. |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | |
| indicator | Yes | |
| observations | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is known. The description adds the notion of normalization and the requirement to resolve the full definition, which provides some context about behavior. No contradiction found, but no additional details on rate limits, pagination, or response characteristics.
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 a single concise sentence with no redundant words. It is front-loaded with the core function, but the second clause is somewhat cryptic, reducing clarity slightly.
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?
The description is too minimal for a tool with 9 optional parameters. It does not explain the relationship with search_indicators (which supplies indicator_id and rate_type), nor does it mention any prerequisites or typical usage scenarios. The output schema covers return values, but usage context 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 every parameter has an individual description. The tool description adds no parameter-level information beyond what the schema provides, keeping this at baseline.
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 says 'Return a normalised series', which gives a specific verb and resource. It distinguishes from sibling get_latest_observation (which returns a single observation) by explicitly returning a series. However, the phrase 'only after resolving its full definition' is vague about what that entails.
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?
No explicit when-to-use or alternative tool guidance. The phrase 'only after resolving its full definition' hints that some prerequisite must be met but does not explain how or when to use this tool vs search_indicators. Missing clear exclusions or alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_indicatorsSearch health indicatorsARead-only
Search precisely defined indicators and expose all valid rate variants.
| Name | Required | Description | Default |
|---|---|---|---|
| sex | No | Optional sex: female, male, or all. | |
| unit | No | Optional unit. Canonical values include per_100_000_person_years and percent. | |
| limit | No | Maximum matches to return. | |
| query | Yes | Plain-language indicator query, for example 'female lung cancer mortality rate'. | |
| source | No | Optional source: finnish_cancer_registry, nordcan, who_gho, or eurostat. Source names in the query are also enforced. | |
| geography | No | Optional published geography, for example Finland, Denmark, Norway, Sweden, Iceland, Greenland, or Faroe Islands. |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | |
| query | Yes | |
| matches | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses that the search is precise ('precisely defined indicators') and returns all valid rate variants, which goes beyond the readOnlyHint annotation. It does not contradict the annotation, and adds behavioral nuance about the comprehensiveness of results. However, it does not elaborate on additional behaviors like pagination or how results are ordered, but with the annotation providing the read-only profile, this is adequate.
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 a single, 11-word sentence that front-loads the action and purpose. It contains no filler or redundant information, and every word contributes to meaning. This is a model of concise writing.
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?
The tool has six parameters and an output schema, but the schema descriptions are comprehensive and the annotations supply the read-only safety context. The description covers the core purpose and a key behavioral quirk (exposing all rate variants), which is enough given the structured data. It could be slightly more explicit about how it fits into the workflow with observations, but overall it is complete.
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?
The input schema descriptions cover 100% of the parameters, providing detailed explanations for each field such as examples for query and source. The tool description adds no additional parameter-specific semantics beyond the schema, so the baseline of 3 applies. The description's mention of 'valid rate variants' tangentially relates to the unit/geography filtering but does not clarify their syntax.
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 clearly states the tool performs a search over health indicators, using the specific verb 'Search' and resource 'precisely defined indicators.' It also adds a unique characteristic ('expose all valid rate variants') that distinguishes it from sibling tools like get_observations, which fetch observation data. This makes the purpose unambiguous and well-scoped.
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 description implies the tool is used for searching indicator definitions, but it does not explicitly contrast it with the sibling tools get_observations or get_latest_observation. There is no when-not-to-use guidance or alternative recommendations. The context is inferred from the verb 'Search' and the sibling names, but not explicitly stated.
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.
3 tool updates
v0.3.0- First observed
get_latest_observation - First observed
get_observations - First observed
search_indicators
TDQS
Scored across 3 tools
The three tools target distinct operations: searching indicators, retrieving full observation series, and fetching the latest observation. Search vs. retrieval is clear, and the latest observation is a specific convenience function, though it could be seen as a subset of get_observations.
All tool names follow the verb_noun pattern consistently: search_indicators, get_observations, get_latest_observation. The verbs (search/get) and noun phrases are clear and predictable.
With only 3 tools, the server is minimal but well-suited for a focused data-access purpose. It feels slightly thin but not under-scoped, and each tool serves a distinct and necessary function.
The domain is read-only indicator/observation data access. The tools cover searching for definitions, retrieving time series, and getting the latest value. A possible gap is a direct 'get_indicator' by exact ID, but search_indicators likely handles that. Core workflows appear covered.
Related MCP Connectors
Normalized official data with provenance, aggregations, insights, free samples and agent access.
Normalized official data with provenance, aggregations, insights, free samples and agent access.
Finnish postal-area and municipality statistics; rank, compare, history, air quality; paywall-aware
Official data with free samples, provenance, aggregations, freshness and agent-ready insights.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables natural language access to Denmark's Statistics API (Danmarks Statistik), allowing users to query and analyze Danish statistical data without coding knowledge through AI-powered interactions.MIT
- AlicenseAqualityDmaintenanceProvides access to 5,000+ Key Performance Indicators across 264 operating areas for all Swedish municipalities and regions, enabling statistical analysis, comparisons, and trend tracking of Swedish public sector data.2134 npm12MIT
- AlicenseNot gradedqualityCmaintenanceProvides access to Sweden's comprehensive municipal and regional statistics database with semantic search capabilities. Enables natural language queries against thousands of Key Performance Indicators covering various aspects of Swedish public sector data.16Apache 2.0
- AlicenseNot gradedqualityCmaintenanceProduction-grade MCP server for Statistics Finland's StatFin database. Enables AI assistants like Claude to browse, search, and query Finnish statistical data.11MIT