HGB Basel MCP Server
Click on "Install 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., "@HGB Basel MCP Serversearch for person named Hans Müller"
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.
Königsfelden — MCP Server
An MCP server that exposes the Königsfelden corpus — the records of the Cistercian convent and the Hofmeisterei Königsfelden (1300–1658), edited by Die Urkunden und Akten des Klosters und der Hofmeisterei Königsfelden — to Claude and other MCP-compatible clients.
Architecture
data/docs/*.xml (TEI, one file per register entry) ─┐
data/registers/ ├─► build_db.py ──► kf.db
people.xml places.xml organizations.xml ─┘ (SQLite + FTS5)
│
server.py
(mcp 2.0 MCPServer,
streamable HTTP)
│
http://<host>:8001/mcpThe TEI sources are parsed once into a SQLite database with two FTS5 indexes. The server
then runs stateless read-only queries against it (PRAGMA query_only).
The server targets mcp 2.0, which renamed the high-level server class
(FastMCP → MCPServer), removed mcp.server.fastmcp, and moved the bind address from
the constructor into run(); requirements.txt pins the major version accordingly.
The transport is streamable HTTP (/mcp), not the legacy HTTP+SSE (/sse) this
server used previously. SSE is deprecated, and — more practically — its handshake hands
the client an absolute /messages/ path computed from the app's own mount point, which
a client cannot reach when the server sits under a reverse-proxy sub-path. Streamable
HTTP has one endpoint and no such handshake. Existing clients pointed at /sse must
be repointed at the new endpoint; it is a deliberate cutover, not a compatible change.
Entity identifiers come from the TEI xml:id attributes — persons perXXXXXX, places
locXXXXXX, organisations orgXXXX. Person and place records additionally carry HLS
identifiers (and GND, for places) where the edition supplies them.
Related MCP server: barracuda-mcp
Setup
1. Install dependencies
pip install -r requirements.txt2. Build the database
python build_db.py --docs ../data/docs --registers ../data/registers --db kf.db--docs is a directory of per-entry TEI files; the entry id is the filename without
its extension. --registers must contain people.xml, places.xml, and
organizations.xml. Both default to ../data/docs and ../data/registers; --batch
(default 200) controls the commit batch size.
Run it once, and again whenever the TEI changes. Rebuilding is destructive: the five
tables the script owns (entries, spans, persons, places, orgs) and both FTS
indexes are cleared and repopulated, so a rebuild always mirrors the current sources
rather than accumulating duplicates. It prints Existing database: clearing N entries
when it does this. Nothing else in the file is touched.
Malformed records are skipped individually and reported on stderr as
WARNING: N record(s) skipped — check for that line, since the build otherwise
completes normally.
3. Start the server
python server.py --db kf.db --host 0.0.0.0 --port 8001Each flag also has an environment variable — KF_DB, KF_HOST, KF_PORT,
KF_HTTP_PATH — which the flags override. Importing server.py never reads sys.argv,
so it is safe to import from tests or an ASGI loader.
--http-path (default /mcp) is the path the MCP endpoint is served at. Behind a
reverse proxy, set it to the public path — see Reverse proxy.
4. Connect a client
Claude Code — the name and URL are positional; there is no --url flag:
claude mcp add --transport http kf http://<server-ip>:8001/mcp -s user-s user makes the server available in every project; -s project writes it to
.mcp.json to share with a repository; the default local scope is just you, in the
current project. claude mcp list then reports the connection status.
Claude Desktop, Cowork, claude.ai — Customize → Connectors → + → Add custom
connector, and paste the same URL. These clients connect from Anthropic's cloud rather
than from your machine, so the server has to be reachable over the public internet;
claude_desktop_config.json only configures local stdio servers, not remote URLs.
Project-scoped .mcp.json:
{
"mcpServers": {
"kf": {
"type": "http",
"url": "http://<server-ip>:8001/mcp"
}
}
}type is required, and streamable-http is accepted as an alias for http. An entry
with a url but no type is read as a stdio server and skipped with an error.
Docker deployment
Build image
docker compose buildFirst-time: build the database
Copy the TEI sources onto the server (the compose file mounts /home/dh/kf_data as
/data), then:
docker run --rm -v /home/dh/kf_data:/data kf-mcp python build_db.py --docs /data/kf_raw/docs --registers /data/kf_raw/registers --db /data/kf.dbRun
docker compose up -dThe container serves on port 8001 and expects kf.db at /data/kf.db. Adjust the volume
path in docker-compose.yml if your data lives elsewhere.
Reverse proxy (nginx)
Serving under a sub-path (https://tei.example.ch/mcp/kf/mcp) has exactly one rule:
the app's --http-path and the nginx location must be the same string. The
endpoint is one path that answers POST (requests), GET (the server→client stream),
and DELETE (session teardown); it builds no URLs of its own, so all nginx has to do is
forward the path unchanged.
server {
listen 443 ssl;
server_name tei.example.ch;
# KF_HTTP_PATH=/mcp/kf/mcp — same string, no trailing slash on proxy_pass,
# so the path reaches the app unrewritten.
location /mcp/kf/mcp {
proxy_pass http://127.0.0.1:8001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# The GET stream must not be buffered or timed out mid-session.
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
chunked_transfer_encoding on;
}
}Two failure modes worth knowing, both of which return a bare Not Found or 405:
A trailing slash on
proxy_pass(http://127.0.0.1:8001/) strips the location prefix, so the app sees/and no route matches.locationand--http-pathdisagree — the app 404s every request. Check the startup line, which prints the exact path being served:Starting KF MCP server on 0.0.0.0:8001/mcp/kf/mcp.
Verify from outside before wiring up a client:
curl -sS -o /dev/null -w '%{http_code}\n' -X POST https://tei.example.ch/mcp/kf/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'200 means the endpoint is live. 404 is a path mismatch, 405 means nginx is not
passing POST to the app (a static location or a limit_except is shadowing it).
Note: the server has no authentication. By default
docker-compose.ymlpublishes port 8001 on all interfaces; if a proxy fronts it, bind it to loopback instead so the corpus is not reachable directly:KF_BIND=127.0.0.1 docker compose up -dOtherwise restrict access at the firewall.
Available tools
Tool | Description |
| Entry/span/person/place/org counts and year range |
| Paginated list of register entries, ordered by year |
| Full entry: title, year, source, pages, transcription, all spans |
| Person authority file by name (substring match) |
| Person record with HLS id, occupation, life dates, and mentions |
| Place authority file by name (German or French) |
| Place record with geo, HLS id, GND id, and mentions |
| Organisation authority file by name or description |
| Full-text search over transcriptions, with snippets |
| Entries mentioning a person, by authority id |
| Entries mentioning a place, by authority id |
| Entries in a year range (max span 300 years) |
Available resources
URI | Description |
| Corpus statistics (JSON) |
| Person index — |
| Single entry (JSON) |
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 rather than returning the whole table. Use
list_entries(limit, offset) to page through the full corpus.
Result size. Claude.ai and Claude Desktop truncate a tool or resource result at
roughly 150,000 characters. kf://persons is capped at 1000 rows (about 100 KB) for
that reason and reports its own truncation; the 500-row tool ceiling stays comfortably
under the limit too.
Full-text search. search_fulltext passes the query to FTS5, so operators work —
Brugg OR Königsfelden, Heinr*, NEAR(...). If the query isn't valid FTS5 syntax
(a stray quote, a dangling AND), it silently falls back to a literal word search
instead of erroring. Only a query with no usable words returns {"error": ...}.
Name search. search_persons, search_places, and search_orgs do a plain
case-insensitive substring match. SQL wildcards in the query are escaped, so searching
for 100% finds a literal "100%" rather than matching every record.
Missing records. get_entry, get_person, and get_place return
{"error": "... not found."} rather than raising.
Spans. get_entry returns every span in the entry — persName, placeName,
orgName, date, measure. The ref field holds the authority id and is empty for
unlinked mentions and for dates/measures; norm holds the normalised @when or
@quantity value.
How entry years are assigned. The year comes from the first <date when="..."> in the
document <body>. If the body has no date, the <sourceDesc> in the header is used as a
fallback. Dates in publicationStmt or revisionDesc are never used — they describe the
edition, not the charter. Years outside 1000–1800 are ignored, and entries with no usable
date have year = NULL.
Database schema
Table | Contents |
| id, title, short_id, year, source, pages, text_raw |
| entry_id, span_id, class, ref, text, norm |
| id, forename, surname, full_name, main_name, occupation, birth, death, org_ref, hls_id, note |
| id, name_de, name_fr, country, region, geo, hls_id, gnd_id, place_type |
| id, name, desc_de, desc_fr |
| FTS5 indexes (external content, populated by AFTER INSERT triggers at build time — there are no update/delete triggers, which is why a rebuild clears and repopulates) |
Deployment
This server runs on tei.dh.unibe.ch at
https://tei.dh.unibe.ch/mcp/kf/mcp, alongside four sibling MCP servers:
SSRQ, HLS, HBLS, 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 -r requirements-dev.txtpytest test_kf_mcp.pyUnit tests (TEI parsing, authority registers, rebuild idempotency) run with no setup. The DB and server tests skip unless you point them at a built database and a running server:
KF_DB=/home/dh/kf_data/kf.db KF_SERVER=http://localhost:8001 pytest test_kf_mcp.pyThe suite also runs standalone, with grouped output and a non-zero exit on failure:
python test_kf_mcp.py --unit --db /home/dh/kf_data/kf.db --server http://localhost:8001Note that the DB tests assert corpus-size floors (≥1550 entries, ≥5000 persons, ≥1300 places, ≥2000 orgs) — they will fail against a small sample database.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables AI-native access to Swiss cultural heritage data from SIK-ISEA (artists), Nationalmuseum (collections), and Nationalbibliothek (bibliography) via MCP tools, without authentication.11MIT
- Alicense-qualityDmaintenanceEnables searching OpenAleph entities and documents using natural language queries through the MCP protocol.121MIT
- AlicenseAqualityFmaintenanceEnables querying Swiss data protection regulations, FDPIC/EDOB decisions, and guidelines directly from MCP-compatible clients like Claude.6Apache 2.0
- AlicenseAqualityAmaintenanceMCP server for Switzerland's national metadata catalogue, enabling AI agents to discover datasets, APIs, public services, and publishers through free-text search and structured queries.13MIT
Related MCP Connectors
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
Agentic search over your Dewey document collections from any MCP-compatible client.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/thodel/kf_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server