Analysis: applying MCP to the 7 Directus-inspired specs
Purpose: the place to come back to when deciding which features should be exposed over MCP, as what tools, at what risk and priority. Based on the existing MCP infrastructure (
index.md). The tables/sections below are the original design analysis; the current implementation state lives in the "MCP roadmap" section (Wave 1/2 + the small patches = shipped). Each entry states its prerequisite (the REST/service must exist first).Source label: everything below is [Inference] based on the architecture as read in the codebase, not on observed runtime behaviour.
Decision principles (applied to every spec)
- MCP comes after REST. An MCP tool calls through
AISecureHarness/a skill, or calls REST internally — the feature's REST/service must exist first. So MCP for these 7 features is phase 2, after the original spec is implemented. - The permission floor is invariant. An MCP tool never exceeds the token's rights. A tool that mutates the schema or is named
delete*automatically getsrisk = dangerous→ goes throughagent_approvals(HITL). Good for safety, but it means write tools carry approval latency. - Criteria for "worth putting on MCP": a task that is (a) repetitive/automatable, (b) something an agent/LLM benefits from being able to call, (c) more read than write (reads are safe, writes need guards). Purely-UI tasks (drag-and-drop, pickers) do not belong on MCP.
- Two ways to add a tool: (A) add it to
packages/mcp-server(stdio, editor) — fast, CRUD-style; (B) register a skill in the harness +agentTools→ it appears in the HTTPtools/listautomatically — the right path for tools with risk/approval/autonomy.
MCP priority summary
| Spec | MCP value | MCP priority | Direction | Risk note |
|---|---|---|---|---|
| content-versioning | High | P1 | B (harness skill) | promote = dangerous → HITL |
| insights-dashboard | High (read) | P1 | A + B | run-panel is read-only and safe; query injection already blocked in the service |
| visual-flow-builder | Medium-high | P2 | B | triggering a flow is a side effect; needs its own capability |
| translation-memory-ui | High | P1 | A | lookup/translate are read-mostly, ideal for an agent |
| image-transform-dsl | Low | P3 | A (get URL only) | transform is delivery, low agent value |
| realtime-subscriptions | Low/n.a. | P3 | — | MCP is request/response, not streaming; a poor fit |
| presets-inheritance | Low | P3 | A | a small convenience; mostly UI state |
1. content-versioning → P1
Why: a content agent benefits a lot from creating version branches, comparing them, and promoting — this is the "draft → review → publish" loop agents already run through revisions.
Proposed tools (direction B — a harness skill, because it writes + needs HITL):
version.list(collection, itemId)— read,safe.version.create(collection, itemId, key, name)— write,review_required.version.compare(collection, itemId, key)— read,safe, returnsChange[].version.promote(collection, itemId, key)—dangerous(applies to main) →agent_approvals(matches the existing HITL; promote already goes throughItemService.update).
Prerequisite: the content-versioning spec (service + routes + SDK) finished.
Risk: promote overwrites main; HITL is mandatory, plus the mainDiverged warning. Keep the autonomy cap at ≤ L2 for version.promote.
2. insights-dashboard → P1 (read-only)
Why: "ask about the numbers" is the classic MCP use case — an agent/LLM queries an aggregate to answer an operational question without a human opening a dashboard.
Proposed tools (A for reads, via internal REST):
dashboard.list(),dashboard.getPanels(id)— read.panel.run(dashboardId, panelId, { dateRange?, filter? })— read, returnsPanelResult. Safe, because the service already whitelists fields, blocks injection, and filters by siteId.- (Optional)
insights.query({ collection, aggregate, field, groupBy, filter })— an ad-hoc aggregate reusingpanelQuerySchema→ answers questions without a pre-saved panel. This is the highest-value MCP tool across all 7 features (an agent asking the data itself).
Prerequisite: insights-service.ts + panelQuerySchema (shared) finished.
Risk: low (read). Still apply collection permissions and a limit cap so it cannot pull huge amounts of data.
3. visual-flow-builder → P2
Why: an agent can trigger automation in context (run a flow), or read run history to diagnose.
Proposed tools (B — because triggering has side effects):
flow.list(),flow.getRuns(id),flow.getRun(id, runId)— read,safe.flow.run(id, input)— write/side effect,review_requiredordangerousdepending on the flow (a flow can do http/mail/item mutation). Needs its ownflow:triggercapability.
Prerequisite: the flow-gap spec (run history endpoint + trigger) finished.
Risk: a flow can cause external side effects (http/mail) → triggering over MCP must carry a narrow capability + low autonomy; consider forbidding flow.run for low-capability tokens.
4. translation-memory-ui → P1
Why: translation is an LLM-native task; an agent calling TM lookup/translate is a natural flow. Read-mostly, low risk.
Proposed tools (A — the REST already exists):
tm.lookup({ sourceLang, targetLang, text, threshold? })— read,safe.tm.translate({ ... })— the TM→glossary→MT pipeline;safe(orreview_requiredif it calls a paid MT).tm.upsert({ ... })— write,review_required(it writes into the TM store).tm.list/update/delete— management, at the usual risk levels.
Prerequisite: only the current TM backend (already there!) plus the PATCH/DELETE endpoints from the translation-memory-ui spec. ⇒ MCP for TM can ship earliest because the backend is already sufficient.
Risk: low; tm.translate may incur MT cost → rate-limit it.
5. image-transform-dsl → P3
Why: transform is delivery (an image to a browser); an agent gains little. The MCP value is mainly getting a transform URL to embed in content.
Proposed tools (A, small):
media.url(key, { preset | dsl })— returns the signed URL (if signing is on). Read,safe.transformPreset.list()— read.
Prerequisite: the image-transform-dsl spec finished.
Risk: low; do not expose transform compute over MCP (it would be pointless).
6. realtime-subscriptions → P3 / a poor fit
Why: the MCP HTTP endpoint is one-shot request/response, with no server-initiated stream (SSE is off). Realtime/subscriptions do not map onto that model.
Conclusion: do not put subscriptions on MCP. If an agent needs to "know an item just changed", use a polling tool items.listSince(collection, since) (read) rather than subscribing. Recorded here so the effort is not wasted.
Future: revisit if the MCP spec/implementation gains stable streaming notifications.
7. presets-inheritance → P3
Why: a preset is UI view state; low agent value. It could help an agent "open a collection in the role's standard view".
Proposed tools (A, optional):
preset.effective(collection)— read, returns the default view.safe.
Prerequisite: the presets-inheritance spec finished.
Risk: low.
Proposed MCP roadmap (after the original specs are implemented)
- Wave 1 (high value, low risk, read-first): ✅ shipped
insights.query/panel.run(read aggregate — the highest-value MCP tool) →packages/mcp-server/src/tools/insights.ts(query_insights,run_panel,list_dashboards,get_dashboard,list_dashboard_panels).tm.lookup/tm.translate(already present beforehand —lookup_tm,translate_text).
- Wave 2 (guarded writes): ✅ shipped (
version.*)version.*via direction B (harness skill) — not a stdio passthrough — sopromoteVersiongoes through HITL/autonomy. Skills:listVersions,compareVersion(safe);createVersion,updateVersion,deleteVersion,promoteVersion(dangerous). Defined inapps/cms/src/services/ai-harness.ts+packages/ai-skills/src/skills.ts; exposed viaPOST /api/v1/mcp(tools/list).tm.upsert— already there (upsert_tm).
- Wave 3 (side effects, proceed carefully):
flow.run(narrow capability + low autonomy) —run_flowexists today as a stdio passthrough; a governed version (therunFlowharness skill, dangerous) also exists. Addsget_flow_run(read one run in detail, for diagnosis).
- Small completing patches: ✅ shipped (stdio passthrough, safe read/CRUD):
- TM:
update_tm(PATCH/tm/:id),delete_tm(DELETE,confirm=true). - Flow:
get_flow_run(GET/flows/:id/runs/:runId). - Image transform:
list_transform_presets(GET/transform-presets).media.urlis deliberately NOT exposed — the signed delivery URL is built at the edge with a server secret (transformKey/HMAC), and no REST endpoint returns a signed URL;/files/presigned-urlis upload-only. Putting it on MCP would either return a wrong URL (no signature) or require duplicating the secret — neither is acceptable. - Presets:
get_effective_preset(GET/presets/effective),list_preset_bookmarks(GET/presets/bookmarks).
- TM:
- Skip / watch: realtime (a poor fit for request/response — use
cdc_events_readto poll rather than subscribe).
Conclusion: all 7 Directus-inspired specs are now covered at the MCP level exactly as analysed, plus governed versioning. Beyond those 7, a sweep of every /api/v1/* route added the content-ops surfaces that had not previously reached MCP: editorial (list_reviews/submit_review/approve_content/reject_content), releases (CRUD + publish_release), deployments (read-only; triggering stays governed), shares (create_share/revoke_share), and get_site. The only remaining MCP gaps are the deliberate exclusions — see the "Deliberately NOT on MCP" table in index.md (realtime streaming, signed media URLs, binary up/download, triggering a deploy, security/GDPR admin, auth/self-service, dev/infra tooling).
Why does
version.*take direction B rather than a stdio passthrough?promoteVersionoverwrites main; principle #2 (permission floor + HITL for dangerous writes) requires it to flow throughAISecureHarnessso it reachesagent_approvals. The stdio server (@lumibase/mcp-server) is a passthrough with no HITL, so it only suits safe read/CRUD — not promote. Versioning is therefore deliberately absent from the stdio server; it appears only on the governed endpoint. Seeindex.md§Content versions.
Common preconditions before opening any write tool on MCP
- A write tool → the correct
riskPolicy.level(review_required/dangerous) so it reachesagent_approvals. - Set a
rateLimitfor tools that call an LLM/MT or are resource-hungry (tm.translate, a heavyinsights.query). - Parity test: add a case to
mcp-parity.property.test.tsproving the MCP decision == the harness decision. - Update the tool table in
index.mdwhen a new tool appears intools/list.