892 rows, 8 of them MCP: the catalogue is not what the cost articles measure
The directory table has 892 rows. Eight carry category = 'mcp'. That is the whole gap between what the catalogue-cost articles on this site measure and what the catalogue actually is.
What the 892 rows are, by the columns that describe them
Every row in directory shares one schema: key, type, target, auth, content, category, runner, includes, input_schema, sensitive, enabled. type is one of four values — fn, http, agent, flow. runner names what actually executes the row: edge (184 rows — Cloudflare Workers code), http (58 — a third-party API call), mac (43 — a command on the operator's own machine, dispatched through a local bridge), apps_script (9 — Google Apps Script), sibling (5 — another deployed instance of this build). 591 rows carry no runner at all — they resolve inside the request handler itself, as a direct function call or a model invocation, with no external process to hand off to.
category spans at least 30 distinct values with real counts behind them: blooio (63, a messaging platform), marketing (49), cli (34, shell tools like git, wrangler, ffmpeg), content-ops (31), cf_bindings (23), agent (22), stripe (20), governance (19), cloudflare (14), local (12), phone (11), payments (11), google (9), mcp (8), computer (8). 152 rows have no category set.
So the plain count contradicts the frame the site's own catalogue-cost articles use. tooling-as-data and mcp-as-a-projection measure what it costs a model to receive this catalogue as MCP tool definitions versus as database rows read at call time — a real, first-party measurement. But the row set they are measuring is not an MCP tool catalogue that happens to live in a database. It is a catalogue of API calls, shell commands, Mac-local actions, Google Apps Script jobs, and agent invocations, of which a specific named eight are about MCP itself. MCP is one of at least six runners and one of at least thirty categories represented in the same table, not the subject the table exists to serve.
The table is not the whole object system — there are at least two
directory is resolved by one function: dispatch(env, key, ...), which takes a key and returns or executes the matching row. Content — articles, their claim graphs, their revision history — lives in a separate pair of tables, articles and article_slots, resolved by a separate route keyed on slug, not key. The two resolvers do not merge. A directory row can point at an article by URL or reference; an article is never itself a directory row. This build has at least two distinct object families, each with its own table, its own primary key, and its own resolver function — unified by a shared pattern (a stable identifier, a contract describing it, an append-only ledger of what happened when it was invoked or read, a receipt), not by one shared table.
That distinction matters for a specific reason: a claim that "everything is one row in one table" is falsified by opening functions/api/articles/[[path]].js next to functions/api/dispatch.js and finding two different key spaces. The accurate claim is narrower and still real — this build gives unlike kinds of object (an API call, a shell command, a piece of published content, an agent, a stored instruction block) a common shape of contract and a common audit trail, using more than one underlying table to do it.
What changes live, and by what mechanism
prompts/blocks/BLOCK_VOICE.md is a directory row, category unset in the count above but tagged target = 'prompt_block'. It is not read once at deploy time. functions/api/dispatch.js calls loadPromptBlockMap(env) — a fresh SELECT key, content FROM directory WHERE target = 'prompt_block' — on every single call to dispatch(), for every agent that lists it in its includes column (ROUTER, OPS, ARCADS, VOICE, CLOUDFLARE, GITHUB). Edit that row, and the next call to any of those six agents is assembled from the new text. No redeploy, no restart, no new session. Three real edits of exactly this kind — to prompts/ROUTER.md, to the post-to-x skill files, and to BLOCK_VOICE.md itself — are documented with commit hashes and diffs in The agent can rewrite what governs its next turn. That article is the control-plane case. This one is the broader shape it is one instance of: a table whose rows are read fresh on every request, so that editing a row is not a deploy, it is a write.
What this does not establish
It does not establish that every kind of thing in this build passes through one literal table, one literal key, or one literal resolver — it does not, and the two-resolver split above is the falsifier for that stronger claim. It does not establish that MCP is a minor feature; eight rows is not zero, and MCP remains one live projection of the catalogue for any client that wants it. It establishes a narrower, checkable fact: the catalogue the cost-comparison articles measure is overwhelmingly not MCP-shaped tools that happen to sit in a database — it is APIs, shell commands, local-machine actions, and agents, and the database rows that describe them are read fresh per request rather than fixed at deploy, which is the property that makes the control-plane edits in the companion article possible at all.
What would falsify this
- A count showing
directoryrows are overwhelmingly MCP-shaped (tool name, JSON Schema, nothing else) rather than spanning the six runners and thirty-plus categories reported above, reproduced against the live table. - Evidence that
articlesanddirectoryshare one resolver or one key space, contradicting the two-function split named above. - Evidence that
loadPromptBlockMapis cached or precompiled rather than queried fresh on everydispatch()call, which would remove the live-edit property this piece rests on.
Ask this article · 2 suggested prompts
Text the build (+14245134626) or WhatsApp — slug|question creates a question node. Paste evidence with ingest slug|q:NODE_ID|your paste.