Every Devani site is an MCP server, served at /devani/mcp/?api_key=<key>. The implementation lives in devani/mcp/ — index.php (transport), auth.php, and ToolService.php (the tool catalog and all handlers).
Authentication & roles
Keys are created in Settings, stored hashed in the mcp_api_keys table, and verified per request. Keys carry a role; write operations are gated by an explicit writeTools list in ToolService — any tool that mutates state must be on that list or non-editor keys get refused. When you add a mutating tool, adding it to writeTools is part of the change, not an afterthought (granular style editors were once missed here; the gap was a real write-gate bypass).
Safety invariants (don't break these)
- Snapshot before write. Every content mutation calls the snapshot helper before writing. Snapshots land in
devani/snapshots/mcp/<timestamp-id>/, pruned to the newest few per file group.list_snapshots/restore_snapshotexpose them; restore takes a safety snapshot of current state first, so restores are reversible. - Integration settings are write-only.
update_integration_settingsaccepts only an allowlisted set of keys (allowedIntegrationSettings), and no tool ever returns a stored secret.get_integration_settingsreports configured/not-configured, never values. - Restricted paths. Page tools refuse reserved slugs (
devani,header,footer,blog,products, …) so a generic page write can never clobber the header, the blog tree, or the application. - Audit everything. Calls are logged with key identity; recent activity is visible in Settings.
The catalog, briefly
Tools cover pages/blog (CRUD + meta incl. Open Graph/Twitter/canonical/robots as of 2.4), regions (header, footer, navigation, global styles with line/region-level editors), media (upload registration, Tonta resize, AltText triggers), commerce (products, inventory, discounts, orders, fulfillment), redirects, users (admin-only), reviews, snapshots, and integration modules (Sendl form embed, Pinpic interactive images). Call the server's tools/list for the authoritative, versioned catalog.
Adding a tool
- Schema in
listTools()viatoolSchema()— description written for an AI caller: say when to use it, not just what it does. - Dispatch case + handler. Validate inputs with
InvalidArgumentException; snapshot before writes. - Gate it:
writeToolsif it mutates; required-args entry for audit summaries. - If it inserts content, emit module-convention markup (see the sanitizer doc) and mirror it in the editor's block picker.