Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ Configurable via admin panel:
- **Anonymous Comments** - Allow posting comments via REST API without authentication
- **Comment Notifications** - Automatic email notifications to moderators when new comments are created via REST API

### MCP Abilities (Optional, requires WordPress 6.9+)
Registers WordPress 6.9 Abilities API endpoints for headless content workflows. Pair with [WordPress/mcp-adapter](https://github.com/WordPress/mcp-adapter) to expose them over MCP so AI tools (Claude Code, Cursor) and VS Code can manage content programmatically.

Eleven abilities under category `spoko-content`:

| Ability | Capability | Notes |
|---|---|---|
| `spoko/posts-create` | `publish_posts` for publish/private/future, else `edit_posts` | |
| `spoko/posts-update` | `edit_post` on the target ID | post_type guard; extra `publish_posts` check when transitioning to publish |
| `spoko/posts-delete` | `delete_post` on the target ID | post_type guard |
| `spoko/pages-create` | `publish_pages` for publish/private/future, else `edit_pages` | |
| `spoko/pages-update` | `edit_page` on the target ID | post_type guard; extra `publish_pages` check when transitioning to publish |
| `spoko/pages-delete` | `delete_page` on the target ID | post_type guard |
| `spoko/terms-create` | `manage_categories` / `manage_post_tags` | |
| `spoko/terms-update` | same | rejects when term ID is not in the given taxonomy |
| `spoko/terms-delete` | same | |
| `spoko/translations-link` | `edit_posts` + `edit_post` on every input ID | additive; rejects multi-group fusion; validates language codes against Polylang |
| `spoko/translations-unlink` | `edit_post` on the target ID | |
Comment on lines +67 to +81

Each ability validates input/output via JSON Schema, enforces WordPress capabilities in `permission_callback`, and carries MCP annotations (`readonly` / `destructive` / `idempotent`). Toggleable from **SPOKO REST API → Features Management → MCP Abilities**. Silently no-ops on WordPress < 6.9.

### Headless Mode (Optional)
Complete headless WordPress functionality:
- **Frontend Redirect** - Automatically redirects all visitors to your headless frontend application
Expand Down
27 changes: 24 additions & 3 deletions readme.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
=== SPOKO Enhanced WP REST API ===
Contributors: spoko
Tags: rest-api, headless, cms, api, polylang, multilingual, astro, nextjs
Tags: rest-api, headless, cms, api, polylang, multilingual, astro, nextjs, mcp, abilities-api, ai
Requires at least: 5.0
Tested up to: 6.7
Tested up to: 6.9
Requires PHP: 8.3
Stable tag: 1.0.8
Stable tag: 1.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -82,6 +82,19 @@ Configurable via admin panel:
* Anonymous Comments - Allow posting comments via REST API without authentication
* Comment Notifications - Automatic email notifications to moderators when new comments are created via REST API

**MCP Abilities (Optional, requires WordPress 6.9+)**

Registers WordPress 6.9 Abilities API endpoints for headless content workflows. Pair with [WordPress/mcp-adapter](https://github.com/WordPress/mcp-adapter) to expose them over MCP so AI tools (Claude Code, Cursor) and VS Code can manage content programmatically.

Eleven abilities under category `spoko-content`:

* `spoko/posts-create`, `posts-update`, `posts-delete` &mdash; CRUD on posts
* `spoko/pages-create`, `pages-update`, `pages-delete` &mdash; CRUD on pages
* `spoko/terms-create`, `terms-update`, `terms-delete` &mdash; categories and tags
* `spoko/translations-link`, `translations-unlink` &mdash; Polylang translation pairing (registered only when Polylang is active)
Comment on lines +89 to +94

Each ability validates input/output via JSON Schema, enforces WordPress capabilities in `permission_callback` (status-aware for posts/pages), and carries MCP annotations (`readonly` / `destructive` / `idempotent`). The feature is toggleable from the admin settings page and silently no-ops on WordPress &lt; 6.9.

**Headless Mode (Optional)**

Complete headless WordPress functionality:
Expand Down Expand Up @@ -148,6 +161,14 @@ No, the plugin is optimized for performance and only adds minimal processing to

== Changelog ==

= 1.2.0 =
* Added: MCP Abilities feature &mdash; eleven Abilities API endpoints (posts/pages/terms CRUD + Polylang translation pairing) for headless content workflows
* Added: Admin toggle "MCP Abilities" in Features Management
* Feature: Status-aware capability checks on post/page create and update
* Feature: Post-type guard prevents cross-post-type mutation
* Feature: Polylang translation linking is additive and rejects multi-group fusion
* Requires WordPress 6.9+ for MCP features (silently disabled on older versions); rest of plugin unchanged

= 1.0.8 =
* Added: Headless Mode - Complete headless WordPress functionality
* Added: Frontend redirect with URL path preservation
Expand Down
4 changes: 2 additions & 2 deletions spoko-enhanced-rest-api.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Plugin Name: SPOKO Enhanced WP REST API
* Description: Extends WordPress REST API with additional fields, optimizations and GA4 Popular Posts integration
* Version: 1.1.0
* Description: Extends WordPress REST API with additional fields, optimizations, GA4 Popular Posts integration, and MCP Abilities for headless content workflows
* Version: 1.2.0
* Author: spoko.space
* Author URI: https://spoko.space
* Requires at least: 5.0
Expand Down
10 changes: 6 additions & 4 deletions src/Core/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
PostCounters,
MenusEndpoint
};
use Spoko\EnhancedRestAPI\Features\Mcp\AbilitiesFeature;
use Spoko\EnhancedRestAPI\Services\{
TranslationCache,
ErrorLogger,
Expand Down Expand Up @@ -63,7 +64,8 @@ private function initFeatures(): void
new AdminInterface($this->cache),
new CategoryFeaturedImage(),
new PostCounters($this->logger),
new MenusEndpoint()
new MenusEndpoint(),
new AbilitiesFeature(),
];
}

Expand All @@ -89,7 +91,7 @@ public function registerGlobalFeatures(): void
foreach ($this->features as $feature) {
// Only register HeadlessMode, AdminInterface and CategoryFeaturedImage here
// Other features will be registered via their specific hooks
if ($feature instanceof HeadlessMode || $feature instanceof AdminInterface || $feature instanceof CategoryFeaturedImage) {
if ($feature instanceof HeadlessMode || $feature instanceof AdminInterface || $feature instanceof CategoryFeaturedImage || $feature instanceof AbilitiesFeature) {
if (method_exists($feature, 'register')) {
$feature->register();
}
Expand All @@ -101,7 +103,7 @@ public function registerRestFields(): void
{
foreach ($this->features as $feature) {
// Skip features already registered globally
if ($feature instanceof HeadlessMode || $feature instanceof AdminInterface || $feature instanceof CategoryFeaturedImage) {
if ($feature instanceof HeadlessMode || $feature instanceof AdminInterface || $feature instanceof CategoryFeaturedImage || $feature instanceof AbilitiesFeature) {
continue;
}

Expand All @@ -127,7 +129,7 @@ public function registerAdminFeatures(): void
{
foreach ($this->features as $feature) {
// Skip features already registered globally
if ($feature instanceof HeadlessMode || $feature instanceof AdminInterface || $feature instanceof CategoryFeaturedImage) {
if ($feature instanceof HeadlessMode || $feature instanceof AdminInterface || $feature instanceof CategoryFeaturedImage || $feature instanceof AbilitiesFeature) {
continue;
}

Expand Down
24 changes: 23 additions & 1 deletion src/Features/AdminInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Spoko\EnhancedRestAPI\Features;

use Spoko\EnhancedRestAPI\Features\Mcp\AbilitiesFeature;
use Spoko\EnhancedRestAPI\Services\TranslationCache;

class AdminInterface
Expand Down Expand Up @@ -152,7 +153,8 @@ private function saveFeatureSettings(): void
'spoko_rest_relative_urls_enabled',
'spoko_rest_anonymous_comments_enabled',
'spoko_rest_comment_notifications_enabled',
'spoko_rest_post_counters_enabled'
'spoko_rest_post_counters_enabled',
AbilitiesFeature::OPTION_ENABLED,
];

foreach ($features as $feature) {
Expand Down Expand Up @@ -399,6 +401,26 @@ public function renderAdminPage(): void
</p>
</td>
</tr>

<tr>
<th scope="row">MCP Abilities</th>
<td>
<label>
<input type="checkbox"
name="mcp_abilities_enabled"
value="1"
<?php checked('1', get_option(AbilitiesFeature::OPTION_ENABLED, '1')); ?>>
Register Abilities API endpoints for headless content workflows
</label>
<p class="description">
Exposes content CRUD (posts, pages, categories, tags) and Polylang translation pairing as
<a href="https://make.wordpress.org/core/2025/11/10/abilities-api-in-wordpress-6-9/" target="_blank">WordPress Abilities API</a>
endpoints under category <code>spoko-content</code>. Pair with
<a href="https://github.com/WordPress/mcp-adapter" target="_blank">mcp-adapter</a> to expose them over MCP for Claude Code / Cursor / VS Code.
Requires WordPress 6.9+ &mdash; silently no-ops on older versions.
</p>
</td>
</tr>
</table>

<?php submit_button('Save Features', 'primary', 'save_features', false); ?>
Expand Down
58 changes: 58 additions & 0 deletions src/Features/Mcp/AbilitiesFeature.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Spoko\EnhancedRestAPI\Features\Mcp;

final class AbilitiesFeature
{
public const OPTION_ENABLED = 'spoko_rest_mcp_abilities_enabled';

public function register(): void
{
if (!self::isEnabled()) {
return;
}

add_action('wp_abilities_api_categories_init', [$this, 'registerCategory']);
add_action('wp_abilities_api_init', [$this, 'registerAbilities']);
}

public static function isEnabled(): bool
{
return get_option(self::OPTION_ENABLED, '1') === '1' && function_exists('wp_register_ability');
}

public function registerCategory(): void
{
if (!function_exists('wp_register_ability_category')) {
return;
}

wp_register_ability_category(
'spoko-content',
[
'label' => __('SPOKO content workflows', 'spoko-enhanced-rest-api'),
'description' => __('Headless content management: read/list/CRUD for posts/pages/terms, Rank Math SEO, featured images, and Polylang translation pairing.', 'spoko-enhanced-rest-api'),
]
);
}

public function registerAbilities(): void
{
if (!function_exists('wp_register_ability')) {
return;
}

Read::register();
Posts::register();
Pages::register();
Terms::register();

if (function_exists('pll_set_post_language') && function_exists('pll_save_post_translations')) {
Translations::register();
Translate::register();
Rewire::register();
}
}
}
Loading