Skip to main content
Glama
hbls_mcp
by thodel

hbls_mcp

Model Context Protocol (MCP) server for the Historisches Biographisches Lexikon der Schweiz (HBLS), offering full-text search and structured access to the 8-volume encyclopedia published 1921–1934.

Corpus

Related MCP server: HGB Basel MCP Server

Quick start

# Requires hbls.db at /data/hbls.db
docker compose up --build -d

The server listens on port 8003 with streamable HTTP transport. The endpoint is http://localhost:8003/mcp by default, and whatever --http-path says otherwise — see Transport.

Connect Claude Code with — note the name and URL are positional, there is no --url flag:

claude mcp add --transport http hbls http://<server-ip>:8003/mcp -s user

-s user makes the server available in every project; -s project writes it to .mcp.json to share with a repository. In Claude Desktop, Cowork or claude.ai, use Customize → Connectors → + → Add custom connector with the same URL; those clients connect from Anthropic's cloud, so the server must be reachable over the public internet. In a .mcp.json the entry is {"type": "http", "url": "…"} — a url without a type is read as a stdio server and skipped.

Tools

Tool

Description

corpus_stats

Corpus summary: article/member counts, text size, volumes

search

FTS5 full-text search across headwords + article text

get_article

Full article by headword + volume

get_article_by_page

Article at a given volume + page

list_volume

Paginated list of all articles in a volume

get_family_members

All persons listed under a family article

search_persons

Search persons by forename or family name

search_bio

Search biographies only (bio category)

get_pdf_url

Direct PDF URL for headword/volume/page

get_articles_by_category

List articles by category (fam/bio/geo/tem)

get_category_stats

Article counts per category

search_semantic

Passages matched by meaning; per_parent caps the passages per article

semantic_index_stats

Coverage of the semantic index (n_chunks, n_indexed, n_total, coverage, runs)

Resources

  • hbls://stats — static corpus statistics snapshot

  • hbls://volume/{n} — article index for volume n: {volume, total, returned, truncated, articles: [...]}, capped at 1000 rows and flagged when truncated

  • hbls://article/{headword}/{volume} — single article

Database

The server expects the SQLite database at /data/hbls.db inside the container. Mount the host directory containing hbls.db to /data. db.SCHEMA_SQL holds the schema the server expects — the contract between the build pipeline and this server, and what the tests build their fixtures from.

Semantic index

search_semantic answers from passage vectors stored in hbls.db next to the corpus (tables chunks, embeddings, embedding_runs). The index is built and searched by files shared across the fleet (ch-h-bot docs/MCP_PROVIDER_PATTERN.md), vendored byte-identical from eperiodica_mcp:

File

Role

embeddings.py

GPUStack client, chunking, vector packing

vectorstore.py

vector schema (chunks.parent_id), matrix, search, stats

embed_db.py

builds the index: python embed_db.py --db hbls.db, --status, --limit, --recompute, --reuse

What is this corpus's lives in corpus.py: iter_documents yields every article with the head line headword volume that goes on the first line of every passage (for the vector; a hit's snippet is the passage without it), and clean resolves the edition's abbreviations with a vocabulary built over the whole table. db.JOIN_SQL and db.row_to_hit say how a chunk joins its article and what a hit carries: id, chunk_id, headword, volume, page, pdf_url, snippet, score, chunk_index, char_start, char_end.

Migrating an index built before #9

An index written by the repo's former embed_db.py names the parent column chunks.doc_id and the run counter embedding_runs.n_articles; the shared schema says parent_id and n_parents. The passages themselves are identical (measured over all 54,015 in thodel/ch-h-bot#555), so the move is a rename and no re-embed. With the server stopped and a backup made by hand:

cp hbls.db hbls.db.pre-parent-id
python migrate_parent_id.py --db hbls.db      # one transaction, then integrity_check
python embed_db.py --db hbls.db --status      # n_chunks unchanged, nothing pending

The script is idempotent and prints the counts before and after. Code and rename have to go together: the old db.py asks for doc_id, the new one for parent_id, and a mismatch answers every search_semantic with «no such column».

Transport

Streamable HTTP — one endpoint answering POST (requests), GET (the server→client stream), and DELETE (session teardown). A /health endpoint returns {"status":"ok"}.

The endpoint path was previously hard-coded to /messages/, which belongs to the SSE transport and made the endpoint impossible to guess from any client configuration. It is now --http-path (default /mcp).

Behind a reverse proxy

Set --http-path (or HBLS_HTTP_PATH) to the public path, and give nginx a location with the same string. Then nginx forwards the path unchanged:

location /mcp/hbls/mcp {
    proxy_pass         http://127.0.0.1:8003;   # no trailing slash
    proxy_http_version 1.1;
    proxy_set_header   Connection '';
    proxy_buffering    off;
    proxy_read_timeout 3600s;
    chunked_transfer_encoding on;
}

The app's path and the nginx location must agree exactly or every request 404s. The startup line prints what is actually being served:

Starting HBLS MCP server on 0.0.0.0:8003/mcp/hbls/mcp

Environment variables

Variable

Default

Description

HBLS_DB

/data/hbls.db

Path to SQLite DB

HBLS_HOST

0.0.0.0

Listen address

HBLS_PORT

8003

Listen port

HBLS_HTTP_PATH

/mcp

Path the MCP endpoint is served at

HBLS_EMBED_MODEL

qwen3-embedding-4b

Embedding model, for queries and embed_db.py

HBLS_CHUNK_CHARS / HBLS_CHUNK_OVERLAP

1000 / 150

Passage window for embed_db.py

GPUSTACK_BASE_URL / GPUSTACK_API_KEY

Embedding service

Query behaviour

Limits. Every limit is clamped to at most 500; a negative, zero, or non-numeric value falls back to that tool's own default. The previous min(limit, 200) guard let every negative through, and SQLite reads LIMIT -1 as unbounded.

Name search. SQL wildcards in a query are escaped, so searching for 100% finds a literal "100%" rather than matching every record.

Full-text search. search passes the query to FTS5, so operators work — Bern OR Brugg, Zwing*, NEAR(...). An invalid FTS5 query falls back to quoted phrases and then to a literal headword search instead of raising.

Result size. Claude.ai and Claude Desktop truncate a tool or resource result at roughly 150,000 characters. Search results therefore carry a snippet rather than the full article_text — 20 full articles ran well past that limit. Use get_article for the text of one article.

Deployment

This server runs on tei.dh.unibe.ch at https://tei.dh.unibe.ch/mcp/hbls/mcp, alongside four sibling MCP servers: Königsfelden, SSRQ, HLS, EOS / HGB Basel.

What they share — the nginx routing, the landing pages, and the deploy sequence — lives in tei_mcp_ops. Start there for anything that spans the fleet; in particular, the app's --http-path and the nginx location have to be the same string, which is the rule a sub-path deployment turns on.

Tests

pip install pytest
pytest test_hbls_mcp.py

Unit tests build their own throwaway database and need no setup. DB and server tests skip unless pointed at them:

HBLS_DB=/data/hbls.db HBLS_SERVER=http://localhost:8003 pytest test_hbls_mcp.py

Requires Python 3.10+ (X | None annotations); the container image is python:3.12-slim.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    MCP server for searching Swiss court decisions from federal and cantonal courts via entscheidsuche.ch. Enables full-text search, law reference lookup, and filtering by canton, court level, and date without API keys.
    8
    34 PyPI
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables querying the Historisches Grundbuch Basel corpus, including full-text search, person lookups, and property dossier retrieval, through MCP-compatible clients like Claude.
    -
  • A
    license
    A
    quality
    A
    maintenance
    MCP server for Swiss federal legislation metadata via Fedlex, enabling search and retrieval of act details with ELI URIs, SR numbers, and multilingual support.
    3
    33 PyPI
    Apache 2.0
  • A
    license
    A
    quality
    D
    maintenance
    MCP server for searching and retrieving full-text pages from the Library of Congress, including newspapers, books, and manuscripts, via the loc.gov API.
    3
    Apache 2.0