The goal isn't a genealogy chatbot or a cluster-monitoring bot. It's one assistant that can move across genuinely different domains — a family history database, a home Kubernetes cluster, eventually a shared writing space — the same way a person would, by being handed the right tool for whatever's being asked. Genealogy came first because it was low-stakes: safe to get wrong, useful to test on. Everything below is the infrastructure that makes handing over a new domain fast and safe, not a one-off integration.
The database is the source of truth. A tools table — tool_group, name, api_base, path, fixed_params, params (per-argument mapping: real name, query-string name, required or optional-with-a-default), enabled — defines every tool. Nothing is hardcoded in Python.
One loader turns rows into callable tools. genealogy_mcp.py reads enabled rows once at startup and builds a real Python function for each one via exec(), so FastMCP can inspect its actual signature and show the agent a correct schema — not a generic **kwargs blob.
An admin page manages the database. Add, edit, enable/disable, and delete tools without writing SQL — including an AI-assisted first draft of each tool's description (see below).
Every request into the Flask app passes through one policy check (enforce_policy) before it reaches any handler. Each endpoint has a require level:
| Level | Meaning |
|---|---|
public | Anyone — this is the level genealogy data is exposed at. |
authenticated | Any logged-in Google account. |
group | Logged in and in a specific group — this is where the k3 cluster-introspection endpoints sit. |
disabled | Nobody, full stop. |
MCP tool calls don't carry a personal login — genealogy_mcp.py is a background process, not someone sitting at a browser. For endpoints that need to stay group-gated (the k3 cluster tools) but should still be callable by the assistant, there's a separate, narrower mechanism:
A shared secret, generated once, stored only as a Kubernetes Secret — never in any file this admin page serves. Two things have to both be true for it to work on a given endpoint:
mcp_service_ok: true — opt-in, per endpoint, nothing is bypassable by default.X-MCP-Service-Key header, checked with a timing-safe comparison.It's not a master key. Flagging /k3/nodes doesn't touch the login requirement on /k3/pods/logs or anything else — confirmed directly: the same key, sent to an unflagged endpoint, is still rejected exactly as before this existed.
| Group | Level | Tools | Notes |
|---|---|---|---|
| genealogy | public | search_person, get_individual, get_relation, search_tings, get_dom, list_articles, get_article | Fully migrated from hand-written functions to DB-driven; a couple of legacy routes still mid-cutover on purpose. |
| k3 | group + service key | get_k3_nodes (proof of concept) | ~20 more read-only cluster endpoints already exist and are ready to add the same way: node/pod health, storage, images, deployments. |
| notepad | planned | — | Human-writes / AI-proposes / human-approves shared document. Backend designed, not yet built or exposed. |
The MCP Tools tab: add a tool, edit its parameters, toggle it off. The Endpoint Policies tab now has a one-click “+ Add to MCP” per endpoint — it pre-fills the tool group, API base, and path, and, for group-gated endpoints, walks through enabling the service key with an explicit confirmation before anything changes.
Editing a tool doesn't take effect until genealogy-mcp restarts — deliberate, so several changes can be batched instead of restarting after every click. A second table, tools_active, snapshots exactly what's currently loaded, so the admin page can badge anything pending — “not live yet” or “pending restart” — and an Apply Changes button triggers the restart directly.
A tool's description is what the AI reads to decide when to use it — vague ones ("retrieves relevant information") are actively worse than no tool at all. An optional Generate step drafts one from the endpoint's real source code, improved by an optional hint ("same data as get_person, just the new route") — always a first draft to review and edit, never a finished answer.
Worth keeping honest, since some of this took real debugging, not just design:
genealogy-api uses mysql.connector; the standalone genealogy-mcp pod uses pymysql. Assuming they matched caused two separate outright crashes.201 Created with correct-looking data, but the row was never actually in the database — mysql.connector defaults to autocommit=False, unlike pymysql. Fixed by setting it explicitly.docker push run from the wrong project directory overwrote the live API image with an unrelated one. Recovered in about a minute by rolling back to a known-good tag — the incident that's the reason this document is careful to note exactly which service builds from Docker versus which reads its code from a mounted NFS share.| Item | Status |
|---|---|
| k3 tools: logs (with a "previous"/crash option), and a filterable events endpoint | designed, not built |
| A third domain (budget, invoices, or IoT) to actually prove this generalizes past genealogy + k3 | not started |
| Shared notepad — human writes, AI proposes, human approves; first write-capable tool in the system | schema designed |
| Letting the assistant actually trigger a restart, not just read state — deliberately deferred, more trust required | not started, on purpose |
| A leftover internal-detail leak in some raw endpoint responses (already stripped before reaching any MCP tool call) | low priority |