Repository navigation
[5.x] Support categories, tags, and global sets in elements_* MCP tools - #20469
brandonkelly wants to merge 2 commits into
Conversation
Adds element adapters for categories (read, create, update, delete, duplicate), tags (read, create, update, delete), and global sets (read and update content only). Global sets' canSave() allows everyone, so MCP authorization now requires editGlobalSet for anything but viewing them, as the globals controller does, and never allows restoring deleted sets, which come back through project config. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c93e4e62-8dbe-4153-b265-a57d6fb57ac8
There was a problem hiding this comment.
🟡 Changes recommended
Category deletion lacks a site-access check, allowing users with group delete permission to delete or hard-delete categories in sites they cannot access.
3 open issues
What changed in this PR
Extends Craft 5’s elements_* MCP tools to support categories, tags, and global sets through the existing adapter system.
Changes:
- Registers category, tag, and global-set adapters with type-specific schemas and operations.
- Restricts global-set changes to permitted content updates and blocks restoration of deleted sets.
- Adds integration coverage and changelog entries for the new support.
| File | Description |
|---|---|
tests/unit/mcp/ElementAdaptersTest.php |
Tests category/tag operations and global-set permission restrictions. |
src/mcp/elements/ElementAdapterRegistry.php |
Registers the three new adapters. |
src/mcp/elements/adapters/TagAdapter.php |
Adds tag criteria, attribute schemas, and field-schema context. |
src/mcp/elements/adapters/GlobalSetAdapter.php |
Supports global-set content updates with site authorization. |
src/mcp/elements/adapters/CategoryAdapter.php |
Adds category operations, copy duplication, and site authorization. |
src/mcp/Authorization.php |
Enforces global-set edit permissions and blocks trashed-set mutations. |
CHANGELOG.md |
Documents the expanded MCP support and new adapter classes. |
Review effort: Balanced
| return $this->lifecycle->duplicate($element); | ||
| } | ||
|
|
||
| /** @param Category $element */ |
There was a problem hiding this comment.
Fixed in c3ac2a3: CategoryAdapter::canDelete() now requires editSite for the category's site on multi-site installs, as well as the native delete permission. This matches CategoriesController::_enforceEditCategoryPermissions(). One correction: Category::canView() doesn't check site access, so the check is explicit rather than going through canView().
| return Category::class; | ||
| } | ||
|
|
||
| protected function criteriaProperties(): array |
There was a problem hiding this comment.
Fixed in c3ac2a3: categories now load with drafts(null)->status(null), as EntryAdapter does, and tags with status(null). Listing defaults are unchanged. The test now disables the category and reads it back. It fails without this change ("Category not found.").
- Load disabled categories, tags, and saved category drafts by ID or UID - Require editSite to delete categories, as the control panel does - Accept context.siteId for new categories' field schemas Amp-Thread-ID: https://ampcode.com/threads/T-c93e4e62-8dbe-4153-b265-a57d6fb57ac8 Co-authored-by: Amp <amp@ampcode.com>


Summary
The
elements_*tools rejected categories, tags, and global sets:Unsupported element type [craft\elements\Category]. Supported types: entries, assets, users, addresses.Craft 5 sites still use all three heavily. On craftcom, for example, the Blog'spostCategoryfield relates to categories, so agents could only refer to them by raw ID.Each adapter uses Craft's own element permissions (
saveCategories:<uid>and so on), plus theeditSite:<uid>check the categories and globals controllers make on multi-site installs, for saving and for deleting categories. Like entries, categories load by ID or UID even when disabled, including saved drafts; tags load even when disabled. Tags allow everyone, which matchesTag::canSave()/canDelete().Global sets need an extra rule.
GlobalSet::canSave()returnstruefor every user, becauseGlobalsControllerenforceseditGlobalSet:<uid>itself.elements_restoreandelements_validatefind elements throughElementLifecyclewith plainAuthorizationchecks, so the rule lives inAuthorizationrather than only in the adapter:Deleted global sets are config-backed. They come back through project config, so restoring the element by itself would leave an orphan, and
elements_restorerefuses it even for admins. Creating, renaming, and deleting sets is left to Settings → Globals, andelements_schemanotes say so.Categories and tags are now summarized as
{type, id, status}in nested dot-path output, like other fetchable types. Before, they were fully serialized.Craft 6: not applicable. In
craftcms/cms6.x, categories, tags, and global sets exist only inyii2-adapter/legacy, and the Craft 6 MCP server has no adapters for them.Evidence
New
ElementAdaptersTest:ToolCallException: Unsupported element type [craft\elements\Category]. Supported types: entries, assets, users, addresses.(and the same forglobalSets)After:
OK (2 tests, 10 assertions)Authorizationrule, a user without permission can update the global set. Without the trashed check, an admin can restore a deleted set. The test fails in both cases.vendor/bin/codecept run unit mcppasses (72 tests). ECS, Rector, and PHPStan are clean.