Framework Guide · WebMCP · Directus
WebMCP for Directus: database-mirrored APIs and the agent surface you get almost free
Directus’s founding bet — your database is the API, mirrored with row-level permissions, no-code Flows and a Vue admin — means most of the governance work WebMCP forces on other stacks already exists as a toggle in the Data Studio. This article, written for data-platform teams, covers the custom-endpoint keystone that keeps agent privileges identical to user privileges, the Flows-as-tool-targets pattern, the Vue and admin-app caveats, and the honest list of what you still must build.
Fact-checked and updated on September 2, 2026 (Directus extensions & Flows documentation). Primary sources are cited at the end of the article.
Geography first, again — and Directus makes it unusually pleasant
WebMCP is a browser standard: tools register via document.modelContext.registerTool() in a live tab, invocable by an in-browser agent — Chrome 149–156 under origin trial today, token-gated per origin ([specification](https://webmachinelearning.github.io/webmcp); [Chrome for Developers](https://developer.chrome.com/docs/ai/webmcp)). Directus is a server. As with every headless platform in this series, the tools register wherever your Directus data becomes a page — and the server-side work is exposing sanctioned, permission-identical endpoints for those tools to call.
What Directus contributes that its peers don’t: the permission model is the query engine. Every read and write flows through role-based, field-level, row-level rules enforced at the API layer — so a tool whose path stays inside that layer inherits the entire governance story for free. The engineering discipline is one line deep: never let a tool step outside it.
The keystone: a custom endpoint that reads as the caller
Directus API extensions mount Express-style routers on the app itself:
// extensions/agent-tools/src/index.ts
export default (router, { services, getSchema }) => {
const { ItemsService } = services;
router.get('/tools/catalog', async (req, res) => {
const items = new ItemsService('products', {
schema: await getSchema({ forUser: req.accountability }),
accountability: req.accountability, // ← the whole security model, right here
});
const rows = await items.readByQuery({
fields: ['id', 'name', 'price', 'stock'],
limit: 20,
});
res.json({ data: rows });
});
};
Compare that to the equivalent code in any other stack: this endpoint answers for whoever is calling — a visitor’s session, an admin’s token, your MCP bridge’s service account — and Directus filters every query through that identity’s rules automatically ([API extensions docs](https://directus.io/docs/guides/extensions/api-extensions/endpoints); [Data Model & Permissions guide](https://directus.io/docs/guides/data-model/permissions)). One implementation therefore serves three transports without privilege divergence: the browser tool’s fetch (visitor cookie), a server-side MCP endpoint (service token), and your own app. “What can the agent see” stops being a WebMCP question and remains the question you already answer in the studio: which role, which policy?
req.accountability is the load-bearing line. Every agent-readiness debate on this platform reduces to whether each tool’s request carries the caller’s identity or someone else’s. Audit nothing else first.Mapping collections to tools without going feral
The temptation, noted in our Strapi guide with different nouns, recurs here in sharper form because Directus’s filter syntax is genuinely pleasant to expose: one query-data(filter) tool and you’re done. Resist it. LLM callers hallucinate collection and field names, succeed often enough to defeat retry logic, and fail in ways nobody can reproduce from a transcript. Chrome’s guidance of well under fifty tools per page is a design forcing function, not a limitation ([developer.chrome.com/docs/ai/webmcp](https://developer.chrome.com/docs/ai/webmcp)):
| Intent | Tool shape | Backing |
|---|---|---|
| Read catalog | list-products(category?) — bounded, 20 max, readOnlyHint |
ItemsService as caller |
| Order status | check-order-status(order_number) |
Filtered read, ownership rule enforced by policy |
| Support intake | submit-ticket(name, email, message) |
Flow trigger, not direct insert |
| Knowledge lookup | search-docs(query) |
Search endpoint on a collection view |
Registration on the consuming front end follows the framework-standard guards catalogued in the MCP-B guide — Vue’s onMounted/onUnmounted pair with an AbortController signal (aborting the signal is the specification’s sole unregistration mechanism; there is no unregisterTool()), Next’s 'use client', Astro’s plain <script>, declarative form annotations for zero-JS cases ([docs.mcp-b.ai](https://docs.mcp-b.ai/how-to/frameworks); [declarative explainer](https://github.com/webmachinelearning/webmcp/blob/main/declarative-api-explainer.md)). A minimal Vue example, since Directus’s own runtime is Vue:
<script setup>
import { onMounted, onUnmounted } from 'vue';
let controller;
onMounted(async () => {
const ctx = document.modelContext ?? navigator.modelContext;
if (!ctx) return;
controller = new AbortController();
await ctx.registerTool({
name: 'list-products',
description: 'List up to 20 in-stock products with name, price, availability. Read-only.',
inputSchema: { type: 'object', properties: { category: { type: 'string' } } },
annotations: { readOnlyHint: true },
async execute({ category }) {
const r = await fetch(`/tools/catalog${category ? `?cat=${encodeURIComponent(category)}` : ''}`);
return { content: [{ type: 'text', text: await r.text() }] };
},
}, { signal: controller.signal });
});
onUnmounted(() => controller?.abort());
</script>
Flows as the action layer: no-code automations become agent-callable
Flows are Directus’s no-code automations — trigger, typed steps, optional manual invocation via POST /flows/trigger/:id. For write-side tools they are the ideal target: validation, notification, audit logging and any human approval you modelled already live inside the Flow, so the tool is a single-purpose caller rather than a reimplementation of your business rules in JavaScript:
async execute({ name, email, message }) {
const r = await fetch('/support/trigger-ticket', { // custom endpoint, caller identity carried
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name, email, message }),
});
return { content: [{ type: 'text', text: await r.text() }] };
}
One Directus-specific rule: manual-trigger flows are documented as public or private, and private ones require a static token in the request. Keep every trigger flow private, and let the custom endpoint hold that token server-side — a flow token embedded in browser-registered code is a public API for anyone who views source, which is exactly the shortcut the permissions model exists to prevent.
inputSchema blocks from flows, not from the whole database — and your agent surface automatically equals your intended automations, nothing more. Schema drift detection is then a collection-metadata diff in CI.The admin app: party trick and trap in one
Directus’s admin interface is a Vue 3 SPA; under the testing flag, document.modelContext.registerTool works inside it — an impressive demo, and a genuinely bad idea. Admin-session tools run with full staff privileges; the specification ships no approval or audit layer of its own ([spec’s security discussion](https://webmachinelearning.github.io/webmcp)), so a prompt-injected agent riding an authenticated admin tab is an admin’s hands on your data. Copilot-style “operate the CRM for me” is a legitimate product category — on Directus it means Flows with explicit confirmation steps, per-user session scoping, and server-side logging before it means any tool registry at all. Keep public-surface tools on public front ends; keep the Data Studio out of the agent’s lane.
What you still must build: the honest list
- The MCP bridge — desktop clients never render your Vue app, and they are most deployments’ dominant agent traffic today. An always-on
tools/list/tools/callendpoint sharing the same custom-endpoint implementations, authenticated with service tokens ([decision analysis with code](https://mcptrail.com/blog/how-to-add-webmcp-to-a-website/)). - Call logging — Directus logs API traffic; tag your
/tools/*namespace so agent calls are queryable, and alert on anomalies the way you would for any untrusted client. - Rate limits at your proxy — agents exercise reads at machine speed; policies bound what, they don’t bound how often.
- Manifest versioning — the spec’s root object already moved (
navigator.modelContext→document.modelContext) between Chromium builds; version your toolset so integrators can pin.
Verification
Flag on, DevTools on the rendered app: (await document.modelContext.getTools()).map(t => t.name) and await document.modelContext.executeTool('list-products', '{}') — then the Directus-specific assertion no other platform lets you make cheaply: call the same tool-backed endpoint as two roles with different policies and diff the responses. If they’re identical, your tool leaked privilege above the caller. The Model Context Tool Inspector covers schema and routing validation.
FAQ
Can Directus Flows trigger WebMCP tools in a user’s open tab?
No — the direction is server→browser, and the specification has no push channel from server events into an agent session. A flow can call your MCP bridge, which surfaces new tool state on the next page interaction; live server-initiated agent actions are simply not in the draft.
Does GraphQL change anything?
Nothing structural: tools call the same permission-checked resolvers. The practical advice stays “don’t give agents raw GraphQL” — typed tools per intent, same as collections.
Self-hosted vs Directus Cloud — token delivery differences?
Self-hosted behind your own proxy: deliver the origin-trial token as an HTTP response header so it never enters cache keys or template logic; Cloud, use the front-end meta-tag path — you control the HTML, not the edge.
Conclusion
Directus aimed its whole design at “humans should trust the data layer”; WebMCP is the first mainstream protocol that tests that sentence against machines. The platform’s accountability plumbing, typed flows and mirrored schema mean the agent surface you build is small by construction: a custom endpoint carrying the caller’s identity, a handful of intent-shaped tools, flows for every write, and a bridge for the clients that never render a page. Build it in that order and the governance conversation with your security team becomes the easiest one you’ll have this year — because you can show them a permissions screen instead of a slide.
Primary sources
- directus.io/docs — REST & GraphQL API reference.
- directus.io/docs — custom endpoint extensions.
- directus.io/docs — Flows and manual triggers.
- directus.io/docs — Data Model & Permissions.
- webmachinelearning.github.io/webmcp — W3C CG specification.
- developer.chrome.com/docs/ai/webmcp — origin trial.
- docs.mcp-b.ai — framework guards.