Search by

Secure MCP server for Craft: audited, permission-gated access for AI clients to content and operations.

dev-main 2026-09-06 13:31 UTC

This package is auto-updated.

Last update: 2026-09-07 08:01:03 UTC


README

A security-hardened MCP server for Craft CMS 5. It gives AI clients — Claude, Cursor, ChatGPT, n8n and any other client that speaks MCP over Streamable HTTP — audited, permission-gated access to your content and operations.

Built for the live site. Every call needs a token that belongs to a Craft user and carries that user's permissions. Every tool call is written to an audit log. Tools that change state are switched off until you switch them on, one by one. A read-only switch sits above everything.

Content and operations, under control. Entries, drafts, categories, globals, assets and the schema — plus logs (trimmed, without request context), caches, the queue, routes and one narrow database query behind a guard. Deliberately without arbitrary PHP execution, without deployment, without plugin lifecycle and without writes to project config or users. Each of those absences is a deliberate decision.

For agencies that run client sites. Several sites, several editors, one log — the AI can never do more than the user its token belongs to.

37 tools in 11 groups. Requirements: Craft CMS 5.9+, PHP 8.2+, MySQL 8.0.17+ / MariaDB 10.4.6+ or PostgreSQL 13+.

Installation

composer require alphabridge/mcp
php craft plugin/install alphabridge-mcp

The migration creates two tables, alphabridge_tokens and alphabridge_audit. php craft plugin/uninstall alphabridge-mcp removes them again without leaving anything behind.

Create a token

In the control panel, open AlphaBridge MCP in the sidebar (administrators only): choose the user the token belongs to, a scope, a label and an optional lifetime. The same works from the console:

php craft alphabridge-mcp/tokens/create --user=admin --scope=read --label="Claude Desktop"
php craft alphabridge-mcp/tokens/list
php craft alphabridge-mcp/tokens/revoke 1

The plain-text token is shown exactly once, at creation. Only its SHA-256 hash is stored. A token is mapped to exactly one Craft user and works with that user's permissions; the scope narrows further:

Scope Allows
read The reading tools, without the administrator tools. Changes nothing.
content The reading tools, plus writing entries, drafts, categories and globals. Still without the administrator tools.
full Everything the mapped user may do, including the administrator tools (schema fields, system info, audit log, database, logs, caches, routes, queue, plugins, user groups).

--expires-in-days=30 limits a token's lifetime; 0 (the default) means no expiry.

Connect a client

The endpoint is POST https://your-site.example/alphabridge/mcp (JSON-RPC 2.0, Streamable HTTP, protocol versions 2025-03-26 and 2025-06-18). Send the token in a header:

Authorization: Bearer abmcp_…

X-Api-Key: abmcp_… works as well. With Claude Code, for example:

claude mcp add --transport http craft https://your-site.example/alphabridge/mcp \
  --header "Authorization: Bearer abmcp_…"

Any MCP client that supports Streamable HTTP with a custom Authorization header can connect the same way. Clients that can only send the token in the URL need the setting Allow the token in the URL path — it is off by default because a token in a path ends up in server logs, proxy logs and referer headers far more easily.

Settings

In the control panel under Settings → Plugins → AlphaBridge MCP:

  • Read-only mode — while on, no tool that changes anything will run.
  • Tools — every tool is Default, On or Off. Tools that change state are off by default; a later version may reclassify a tool, which is why Default is a state of its own and not a synonym for one of the other two.
  • Calls per minute and token — a brake against a looping model, not a defence against a determined caller with a valid token.
  • Allow the token in the URL path — see above.
  • Additional allowed origins — for browser-based clients; see below.

The same settings can be set in config/alphabridge-mcp.php; a value set there wins over the control panel, and the settings page says so for each affected field.

return [
    'readOnly' => false,
    'rateLimit' => 120,
    'connectorUrlAuthEnabled' => false,
    'allowedOrigins' => ['https://client.example', '$MCP_ALLOWED_ORIGINS'],
    'toolState' => ['craft_create_entry' => true],
];

Tokens are not part of the settings: they are secrets, and plugin settings live in the project config, which is committed to Git. They have their own page, AlphaBridge MCP in the sidebar, which only administrators can open. Craft registers a user permission "Access AlphaBridge MCP" for every plugin with a sidebar entry; granting it does not open the token page to non-administrators.

What every call goes through

Rate limit → token scope → edition → read-only mode → tool enablement → administrator check → argument schema. Then the tool itself asks Craft, per element, what the mapped user may see, save or delete — with Craft's own checks plus the site permission.

The database tool runs one SELECT against one table: no joins, no subqueries, no CTEs, no UNION, no functions beyond count/min/max/avg/sum, no protected tables (users, sessions, tokens, project config, …), no server-account or sequence expressions. Logs are returned without Craft's request context, which contains $_POST, $_COOKIE and $_SESSION; SQL statements and stack traces are cut off.

Origins and DNS rebinding

If a client sends an Origin header, it must be on the allowlist, otherwise the endpoint answers with HTTP 403. The allowlist consists of the configured allowedOrigins and the site URL — but only where these can be determined statically: a fixed URL with scheme and host, possibly held in an environment variable whose content is such a URL. Exactly one level is resolved. A site URL built from a Craft alias such as @web does not count, not even when the alias sits inside an environment variable: Craft builds @web from the Host header of the incoming request, so an attacker would pass the comparison against themselves. In such installations allowedOrigins is the only source; if it is empty, every request that carries an Origin is refused. Server-to-server clients send no Origin and are unaffected.

Licence

This plugin is sold through the Craft Plugin Store under the Craft License: one licence per production environment, free to try in development and staging environments.

Support

Questions, bugs and feature requests: mail@cultureclub.dev. Please report security issues to that address only — not in public.

Development and testing

This published package contains the runtime code. The project is verified in CI on every change: composer validate --strict, the unit suite (phpunit, no Craft bootstrap needed), PHPStan at level 6 without a baseline, and an end-to-end run of tests/integration/lifecycle.sh against PostgreSQL 16 and MySQL 8.4. That integration run builds a fresh Craft installation, installs the plugin and walks through migration and rollback, token handling, the real HTTP answers of the endpoint, the permission filter with a non-admin editor, and the settings page.