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
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@ 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.

Sixteen abilities under category `spoko-content`:

| Ability | Capability | Notes |
|---|---|---|
| `spoko/posts-get` | `read_post` on the target ID | returns title, content, taxonomies, featured image, Rank Math SEO fields |
| `spoko/pages-get` | `read_post` on the target ID | returns title, content, parent, featured image, Rank Math SEO fields |
| `spoko/posts-list` | `edit_posts` | paginated; authors without `edit_others_posts` see public posts + their own non-public; respects `read_private_posts` |
| `spoko/posts-create` | `publish_posts` for publish/private/future, else `edit_posts` | accepts categories/tags/featured image/SEO; validates term and attachment IDs |
| `spoko/posts-update` | `edit_post` on the target ID | post_type guard; extra `publish_posts` check when transitioning to publish; rejects no-op updates |
| `spoko/posts-delete` | `delete_post` on the target ID | post_type guard; no-ops on already-trashed posts unless `force=true` |
| `spoko/pages-create` | `publish_pages` for publish/private/future, else `edit_pages` | validates `parent_id` is a page |
| `spoko/pages-update` | `edit_page` on the target ID | post_type guard; extra `publish_pages` check when transitioning to publish; rejects no-op updates |
| `spoko/pages-delete` | `delete_page` on the target ID | post_type guard; no-ops on already-trashed pages unless `force=true` |
| `spoko/terms-create` | `manage_terms` on the taxonomy | resolved via the taxonomy cap object |
| `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, duplicate post IDs, and language-slot conflicts; validates language codes against Polylang |
| `spoko/translations-unlink` | `edit_post` on the target ID | |
| `spoko/posts-translate` | `edit_post` on source + `edit_posts` (plus `publish_posts` when `overrides.status` is publish/private/future) | duplicates source into a new language, links via Polylang, optionally translates referenced media |
| `spoko/attachments-rewire` | `edit_post` on the target post | rewires existing image references to language-correct attachment copies; backfill helper for posts that pre-date media translation |

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
31 changes: 28 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,23 @@ 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.

Sixteen abilities under category `spoko-content`:

* `spoko/posts-get`, `pages-get` &mdash; single-item read with Rank Math SEO fields
* `spoko/posts-list` &mdash; paginated post listing (capability-filtered: authors without `edit_others_posts` see public posts plus their own non-public)
Comment on lines +85 to +92
* `spoko/posts-create`, `posts-update`, `posts-delete` &mdash; CRUD on posts (with categories, tags, featured image, Rank Math SEO)
* `spoko/pages-create`, `pages-update`, `pages-delete` &mdash; CRUD on pages (with parent validation, featured image, Rank Math SEO)
* `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)
* `spoko/posts-translate` &mdash; duplicate a post into a new language and translate referenced media (Polylang only)
* `spoko/attachments-rewire` &mdash; backfill helper that rewires image references on an existing post to language-correct attachment copies (Polylang only)

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 +165,14 @@ No, the plugin is optimized for performance and only adds minimal processing to

== Changelog ==

= 1.2.0 =
* Added: MCP Abilities feature &mdash; sixteen Abilities API endpoints (read/list, posts/pages/terms CRUD with Rank Math SEO + featured images, Polylang translation pairing, post translation with media duplication, and attachment rewire) 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