The knowledgebase’s semantic search, exposed to Claude as MCP tools on its own tailnet node and through the Aperture connector telebkb. This note is the architecture, the tool contract, and the one non-obvious failure (421 Invalid Host header) that eats an afternoon if you don’t know it.
Reshaped this session to mirror the historian subagent’s “named sources, explicit selection” pattern — the model asks what exists, then picks, rather than being handed a single opaque search().
get_collections() → the named sources + their shape
search(collection, query, limit, folder) → hits from ONE named collection
get_note(path) → full text of an indexed note
list_topics() → topic index
For Agents
An unknown collection name returns {error, available} — it does not throw. The recovery loop is: call search with a guess, read available from the error, retry. You never need get_collections() first, but it’s cheaper if you’re unsure.
hybrid.py holds its BM25 index in module globals (_bm25, _docs, _facets), so the process can serve exactly one collection’s keyword index. Giving notes hybrid retrieval means refactoring that module state into per-collection instances. Deliberately deferred — semantic-only is adequate for prose notes, whereas the manuals genuinely need keyword matching for part numbers and section codes.
Symptom: every tailnet request to /mcp returns 421; loopback 127.0.0.1:9099 works fine. It reads like a proxy misconfiguration or an auth failure — the response body names no layer, so nothing points at the app.
Cause:mcp≥ 1.29 turns on DNS-rebinding protection with an empty allowed_hosts, which admits only 127.0.0.1. tailscale serve forwards the originalHost header (knowledgebase.taild4189d.ts.net:8443), so every tailnet request fails the check.
Fix: pass transport security explicitly when constructing the server —
overridable at runtime via TELEP_KB_MCP_ALLOWED_HOSTS.
Generalizes: any reverse proxy that preserves Host (which tailscale serve does by design) will trip host-allowlist defaults in the app behind it. Check the app before the proxy when a 4xx has no matching proxy log line.