rforssen.net · internal reference

Giving an AI assistant real access to my systems

How MCP tool exposure is built, secured, and administered across genealogy, Kubernetes, and (soon) a shared notepad.

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.

How it fits together

Authorization Admin Policies · Groups · MCP Tools mcp_tools database tools (desired) · tools_active (live) genealogy-mcp pod FastMCP server reads tools at startup MCP client Claude Desktop, etc. genealogy-api (Flask) /genealogy/* public /k3/* group: admin /notepad/* (planned) group: admin edits reads on boot, snapshots active MCP httpx.get() + X-MCP-Service-Key
One database decides which endpoints are tools. One process loads them. Any MCP client can then call them.

What each piece actually does

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).

Security model

Every request into the Flask app passes through one policy check (enforce_policy) before it reaches any handler. Each endpoint has a require level:

LevelMeaning
publicAnyone — this is the level genealogy data is exposed at.
authenticatedAny logged-in Google account.
groupLogged in and in a specific group — this is where the k3 cluster-introspection endpoints sit.
disabledNobody, 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:

The service-key bypass

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:

  1. That specific endpoint has been explicitly flagged mcp_service_ok: true — opt-in, per endpoint, nothing is bypassable by default.
  2. The request carries the correct key in an 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.

What's exposed today

GroupLevelToolsNotes
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.

Administering it

Everyday editing

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.

Desired vs. live

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.

Better descriptions, on request

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.

What this cost to learn

Worth keeping honest, since some of this took real debugging, not just design:

Open ends

ItemStatus
k3 tools: logs (with a "previous"/crash option), and a filterable events endpointdesigned, not built
A third domain (budget, invoices, or IoT) to actually prove this generalizes past genealogy + k3not started
Shared notepad — human writes, AI proposes, human approves; first write-capable tool in the systemschema designed
Letting the assistant actually trigger a restart, not just read state — deliberately deferred, more trust requirednot started, on purpose
A leftover internal-detail leak in some raw endpoint responses (already stripped before reaching any MCP tool call)low priority
Written as a working reference, not a pitch — it's meant to be corrected and extended as the system grows, the same way the code underneath it is.