← all cases

kb-api

A service that puts one knowledge base behind two front doors — a REST API for apps and the Model Context Protocol for agents — over the same Postgres store and the same search query, so the two transports cannot drift apart.

FastAPIPostgreSQLMCP full-text searchpg_trgm

Problem

A knowledge base is only useful to an agent if the agent can query it the way it queries everything else — as a tool, over MCP — while humans and other services still reach it over plain REST. Standing up two separate services would mean two query paths that drift apart. The goal was one search implementation, two transports, with namespace and visibility scoping enforced server side so a lower-privilege caller can never widen its own scope.

Architecture

REST clients/kb/v1/… MCP agents/kb/mcp/ — 4 tools FastAPI appone ranked query Postgres rag.chunksfts + pg_trgm · scoped
Both transports run inside one process. The MCP mount exposes search, article-fetch, stats and random-sample tools; a bot-scoped caller is hard-limited to public content server side. Search is a single SQL query, so REST and MCP results are identical by construction.

Design decisions

Numbers

Live figures, measured on 2026-09-26 against the running service and its Postgres store:

2,559indexed chunks · 368 articles
1,822 / 737chunks in the lib / prod namespaces
0.10–0.32 ssearch latency, 5 real queries (warm)
43 MBtable + indexes on disk

Search is hybrid lexical, not vector: the score blends a Postgres full-text rank (ts_rank_cd over a tsvector column, parsed with websearch_to_tsquery) with a trigram title-similarity from pg_trgm. There is no embedding column and no semantic vector step — verified against information_schema.columns on the live table, not assumed.

Failure modes found in production

Limits

What's next

Links