hbls_mcp
by thodel
README.md
# 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
- **18,244 articles** (3,718 biographies), 19,707 persons
- 8 volumes covering Swiss history, families, geography, and topics
- Source PDF: [digibern.ch](https://www.digibern.ch/katalog/historisch-biographisches-lexikon-der-schweiz)
- Predecessor to the [Historisches Lexikon der Schweiz (HLS)](https://hls-dhs-ddsg.ch)
## Quick start
```bash
# 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](#transport).
Connect Claude Code with — note the name and URL are positional, there is no `--url`
flag:
```bash
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 |
## 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.
## Transport
<a id="transport"></a>
**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:
```nginx
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 |
## 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](https://github.com/thodel/kf_mcp), [SSRQ](https://github.com/thodel/ssrq_mcp), [HLS](https://github.com/thodel/hls_mcp), [EOS / HGB Basel](https://github.com/thodel/eos_mcp).
What they share — the nginx routing, the landing pages, and the deploy sequence —
lives in **[tei_mcp_ops](https://github.com/thodel/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
```bash
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:
```bash
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`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues