Skip to main content
Glama
LINDAS MCP Server

LINDAS MCP Server

by BFH-JTF

LINDAS MCP Server

A Model Context Protocol server that enables LLMs to query structured data from LINDAS — the Swiss Federal Archives' Linked Data platform at https://ld.admin.ch.

LINDAS stores multi-dimensional statistical data as RDF cubes using the cube.link vocabulary, accessible via a SPARQL endpoint backed by Stardog. This server translates high-level tool calls into SPARQL queries and returns clean, LLM-friendly JSON.

What this enables

Ask an LLM questions about Swiss federal data and let it discover, inspect, and query datasets automatically:

  • "Show me forest fire danger warnings in the last week"

  • "Compare population across all cantons for 2023"

  • "Find datasets about unemployment"

The LLM uses the tools below to discover cubes, inspect their structure, find valid dimension values, and query observations — all without writing SPARQL.

Related MCP server: mcp-i14y

Prerequisites

  • Node.js ≥ 20

  • npm or pnpm

Installation

npm install
npm run build

Configuration

Environment variables (all optional):

Variable

Default

Description

LINDAS_SPARQL_ENDPOINT

https://ld.admin.ch/query

SPARQL endpoint URL

LINDAS_DEFAULT_LANGUAGE

de

Default language for labels (de, fr, it, en)

LINDAS_TRANSPORT

stdio

Transport mode: stdio or http

LINDAS_PORT

3000

HTTP port (only used when transport is http)

LINDAS_HOST

0.0.0.0

HTTP bind address (only used when transport is http)

Command-line flags override environment variables:

lindas-mcp [--transport stdio|http] [--port PORT]

Usage with Claude Desktop

Add the server to your claude_desktop_config.json:

{
  "mcpServers": {
    "lindas": {
      "command": "node",
      "args": ["C:/path/to/lindas-mcp/dist/index.js"],
      "env": {
        "LINDAS_DEFAULT_LANGUAGE": "de"
      }
    }
  }
}

For development with hot reload, use tsx:

{
  "mcpServers": {
    "lindas": {
      "command": "npx",
      "args": ["tsx", "C:/path/to/lindas-mcp/src/index.ts"]
    }
  }
}

HTTP Transport (Streamable HTTP)

For use with the MCP Inspector, web-based clients, or remote access, start the server in HTTP mode:

# Using npm scripts
npm run start:http          # node dist/index.js --transport http
npm run dev:http            # tsx src/index.ts --transport http

# Or directly
node dist/index.js --transport http --port 3000

The server exposes the MCP Streamable HTTP endpoint at http://127.0.0.1:3000/mcp.

In HTTP mode, each client session gets its own MCP server instance. The server tracks sessions via the Mcp-Session-Id header.

MCP Inspector

To inspect the server interactively, open the MCP Inspector and connect to:

http://127.0.0.1:3000/mcp

Or launch the Inspector with the server:

npx @modelcontextprotocol/inspector node dist/index.js --transport stdio

opencode

For HTTP mode in opencode, configure opencode.json:

{
  "mcp": {
    "lindas": {
      "type": "remote",
      "url": "http://127.0.0.1:3000/mcp",
      "enabled": true
    }
  }
}

LibreChat / Docker

Build the Docker image and add it to your docker-compose.yml:

services:
  lindas-mcp:
    image: lindas-mcp
    container_name: lindas-mcp
    environment:
      - LINDAS_TRANSPORT=http
      - LINDAS_PORT=8000
      - LINDAS_DEFAULT_LANGUAGE=de
    restart: unless-stopped
    # ports:                    # Only needed for host access
    #   - "8000:8000"

Then in LibreChat's config:

mcpServers:
  lindas:
    type: streamable-http
    url: http://lindas-mcp:8000/mcp

Make sure both containers share the same Docker network.

Available Tools

Tool

Description

Key Parameters

list_cubes

List available data cubes

limit, offset

search_datasets

Full-text search across cube titles/descriptions

query, limit

get_cube_metadata

Get publisher, license, status, temporal coverage, and other metadata for a cube

cube_uri

get_cube_versions

List all versions of a cube

cube_uri

get_cube_structure

Get dimensions/measures/datatypes of a cube

cube_uri

get_dimension_summary

Get all dimensions with value counts and available ranges — single-call overview instead of calling get_dimension_values for each dimension separately

cube_uri, language

Querying

Tool

Description

Key Parameters

query_observations

Query observations with filters, pagination, and optional label resolution

cube_uri, dimensions, measures, filters, resolve_labels, limit, offset, language

count_observations

Count observations (check size before query)

cube_uri, filters

count_observations_by_dimension

Break down observation counts by dimension values (e.g., how many per canton per year)

cube_uri, dimension, filters, limit, language

get_page_info

Get pagination metadata for a query — total count, hasMore, nextPageOffset

cube_uri, dimensions, measures, filters, limit, offset

Geography

Tool

Description

Key Parameters

get_cantons

List all 26 Swiss cantons with IRIs and names

language

get_municipalities

List Swiss municipalities with IRIs and names (optionally filtered by canton)

canton_iri, language

get_districts

List Swiss districts with IRIs and names (optionally filtered by canton)

canton_iri, language

resolve_geography

Resolve a place name to its LINDAS IRI

name, language

resolve_iri

Look up a LINDAS IRI to get its label and type

iri, language

Resources

  • lindas:///cubes — Catalogue of available data cubes (Markdown)

Prompts

  • data_exploration — Step-by-step guide for exploring LINDAS data

  • canton_comparison — Compare a topic across cantons for a given year (args: topic, year)

Typical Workflow

The recommended workflow for an LLM using this server:

  1. search_datasets or list_cubes — Find cubes matching the user's topic

  2. get_cube_structure — Inspect the cube's dimensions and measures

  3. get_dimension_summary — Get a quick overview of all dimensions with value counts (replaces calling get_dimension_values for each dimension separately)

  4. resolve_geography — If the user mentions a place name, resolve it to an IRI

  5. resolve_iri — If query results contain opaque IRIs, look up their labels

  6. count_observations — Check how many results the query will return

  7. query_observations — Retrieve the data (use resolve_labels: true to get human-readable labels instead of IRIs)

For geographic comparisons, use get_cantons, get_municipalities, or get_districts to list geographic entities with their IRIs.

resolve_labels Feature

When query_observations is called with resolve_labels: true, IRI-valued dimensions are automatically joined to their schema:name labels. Instead of receiving:

{ "canton": { "value": "https://ld.admin.ch/canton/1", "label": "https://ld.admin.ch/canton/1" } }

You receive:

{ "canton": { "value": "https://ld.admin.ch/canton/1", "label": "Zürich" } }

This makes results immediately understandable without additional lookups.

Development

npm run dev          # Start stdio transport with tsx (hot reload)
npm run dev:http     # Start HTTP transport with tsx (hot reload)
npm run build        # Compile with tsc
npm start            # Run compiled stdio server
npm run start:http   # Run compiled HTTP server
npm test             # Run unit tests (vitest)
npm run test:watch   # Watch mode

Project Structure

src/
├── index.ts              # MCP server entry point (stdio + HTTP)
├── config.ts             # Configuration constants
├── sparql/
│   ├── client.ts         # SPARQL HTTP client + SparqlError
│   ├── queryBuilder.ts   # Pure SPARQL query builder functions
│   └── resultParser.ts   # SPARQL JSON → domain object parsers
├── tools/
│   ├── index.ts          # Tool registration + dispatch
│   └── *.ts              # Individual tool handlers
├── resources/
│   └── catalogue.ts      # lindas:///cubes resource
└── prompts/
    └── templates.ts      # Prompt templates
tests/
├── sparql.test.ts        # Query builder unit tests
└── resultParser.test.ts  # Parser unit tests

Notes

  • All logging goes to stderr (stdout is reserved for the MCP protocol in stdio mode).

  • User input interpolated into SPARQL is escaped to prevent injection.

  • Result limit is capped at 500 to protect LLM context windows.

  • Labels are fetched via schema:name with language filtering; IRI-valued dimensions fall back to the IRI itself if no label is found.

  • The search_datasets tool uses CONTAINS filters on schema:name and schema:description (the Stardog textMatch predicate is not supported on the public LINDAS endpoint).

  • In HTTP mode, each client session gets its own MCP server instance. Sessions are tracked via the Mcp-Session-Id header and cleaned up on disconnect.

License

MIT

Available Tools

8 tools
count_observationsA

Count the number of observations in a cube, optionally filtered. Use this BEFORE query_observations to check if a query will return a manageable number of results. If count is large, use filters to narrow down or use a smaller limit.

Inputs: cube_uri (required), filters.

get_cantonsB

List all 26 Swiss cantons with their LINDAS IRIs and names. Use the returned IRIs to filter observations by canton in query_observations.

Inputs: language.

get_cube_structureA

Get the structure of a specific data cube: its dimensions, measures, datatypes, and constraints. ALWAYS call this before query_observations to understand what dimensions and measures are available. The 'path' field in the result is the property URI you pass to query_observations, get_dimension_values, and as filter dimensions.

Inputs: cube_uri (required).

get_dimension_valuesA

Get the distinct values for a dimension of a cube, with human-readable labels. Use this after get_cube_structure to discover what values you can filter on (e.g., which cantons, which years, which categories). Pass the 'path' value from get_cube_structure as dimension_path.

Inputs: cube_uri (required), dimension_path (required), limit, language.

list_cubesB

List available data cubes on LINDAS with their titles and descriptions. Use this to discover what datasets are available. Call get_cube_structure next to understand a cube's dimensions.

Inputs: limit, offset.

query_observationsA

Query observations from a data cube with optional filtering and pagination. ALWAYS call get_cube_structure first to learn the cube's dimension and measure paths. Pass dimension paths in the 'dimensions' array and measure paths in the 'measures' array. Use get_dimension_values to find valid filter values.

Inputs: cube_uri (required), limit, offset, filters, +3 more.

resolve_geographyA

Resolve a place name to its LINDAS IRI. Works for cantons, municipalities, and districts. Use this when a user mentions a Swiss place name and you need its IRI to filter cube observations.

Inputs: name (required), language.

search_datasetsA

Full-text search across LINDAS cubes by title and description. Use this when looking for datasets about a specific topic (e.g., 'population', 'forest', 'unemployment').

Inputs: query (required), limit.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: discovery (list_cubes, search_datasets), schema exploration (get_cube_structure, get_dimension_values, get_cantons), counting (count_observations), querying (query_observations), and geography resolution (resolve_geography). No two tools overlap in function.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern in snake_case (e.g., count_observations, get_cube_structure, list_cubes). The naming is predictable and easy to parse.

    Tool Count5/5

    With 8 tools, the server is well-scoped for its purpose of exploring and querying Swiss open data cubes. Each tool earns its place, covering discovery, schema analysis, and data retrieval without unnecessary redundancy.

    Completeness5/5

    The tool set covers the full workflow for a read-only data cube explorer: discovering cubes, understanding structure and dimensions, filtering with valid values, counting results, and fetching observations. The inclusion of geography resolution and text search fills common needs.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      A
      maintenance
      Provides AI-native access to Swiss Federal Statistical Office datasets through 9 tools for querying education, population, and cross-cantonal comparisons without authentication.
      3
      15
      42 PyPI
      2
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Connects AI assistants to the Swiss I14Y Interoperability Platform, enabling natural language exploration of government datasets, APIs, codelists, and public services.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables reproducible analysis of 100 curated Swiss open-data resources by searching profiles, materializing PXWeb data, validating and executing SQL, and formatting reproduction details, with reasoning delegated to the MCP client.
      Apache 2.0
    • A
      license
      A
      quality
      B
      maintenance
      MCP server for LINDAS, the linked-data knowledge graph of the Swiss administration, enabling querying of Swiss public data cubes via guarded SPARQL tools.
      7
      28 PyPI
      MIT