Skip to content

Expose endpoints as abilities via WordPress/mcp-adapter (Abilities API integration) #4

@zackkatz

Description

@zackkatz

Goal

Expose Block MCP's REST endpoints as registered WordPress abilities (wp_register_ability()), so the official WordPress/mcp-adapter can publish them as MCP tools without our standalone TypeScript stdio server.

Why

Two things are converging:

  1. Abilities API shipped in WordPress 6.9 core. It's the canonical way to register a capability that an AI agent (or any other consumer) can discover and call.
  2. WordPress/mcp-adapter (1k+ stars, official WP org) automatically converts registered abilities into MCP tools/resources/prompts, with HTTP + STDIO transports, permission gates, error handling, and observability hooks built in.

Today our TypeScript MCP server reimplements pieces the adapter already gives us for free (transport, error envelope, capability gates). Once the adapter is stable, registering our endpoints as abilities means:

  • Less code to maintain. Drop the stdio bridge, axios client, tool-input schemas duplicated from the PHP route definitions.
  • Blessed transport. Inherit MCP-spec compliance and future protocol upgrades from the official adapter.
  • Native discoverability. Other WordPress AI tooling that consumes the Abilities API (WP-CLI, future core surfaces, third-party plugins) gets our endpoints for free.
  • Per-ability permissions. The Abilities API has structured permission callbacks; we currently roll capability checks per route.

Scope

Each REST endpoint becomes a registered ability. Approximate mapping:

Current REST endpoint Ability slug
GET /posts/{id}/blocks gk-block-api/get-page-blocks
PATCH /posts/{id}/blocks/{index} gk-block-api/update-block
PATCH /posts/{id}/blocks/by-ref/{ref} gk-block-api/update-block-by-ref
POST /posts/{id}/blocks gk-block-api/insert-blocks
DELETE /posts/{id}/blocks/{index} gk-block-api/delete-block
DELETE /posts/{id}/blocks/by-ref/{ref} gk-block-api/delete-block-by-ref
POST /posts/{id}/mutate gk-block-api/edit-block-tree
POST /posts/{id}/blocks/replace gk-block-api/replace-block-range
PUT /posts/{id}/blocks gk-block-api/rewrite-post-blocks
POST /posts/{id}/insert-pattern gk-block-api/insert-pattern
POST /posts/{id}/revert gk-block-api/revert-to-revision
GET /block-types gk-block-api/list-block-types
GET /patterns gk-block-api/list-patterns
GET /site-usage gk-block-api/get-site-usage
GET /resolve gk-block-api/resolve-url
POST /posts gk-block-api/create-post
PATCH /posts/{id} gk-block-api/update-post
GET /terms gk-block-api/list-terms
POST /media gk-block-api/upload-media
Yoast gravitykit/v1/yoast-seo/* gk-block-api/yoast-*

Each registration provides:

  • label, description (the existing tool descriptions are a starting point)
  • input_schema / output_schema (already defined per route — port from register_rest_route args)
  • permission_callback (existing check_post_edit_permission pattern)
  • execute_callback (delegates to the same Block_CRUD / Block_Mutator / Post_Manager / etc. service objects)

Phased plan

  1. Phase 1 — Wrap existing REST handlers as abilities. Keep the REST routes for backwards compatibility. Add Abilities_Registrar class that registers each endpoint as an ability whose execute_callback calls the same service-class method. Behind a feature flag (gk_block_api_register_abilities filter, default off until stable).
  2. Phase 2 — Adapter compatibility check. Install WordPress/mcp-adapter in a test site, confirm our abilities are discovered, run the existing PHP smoke against the adapter's STDIO transport.
  3. Phase 3 — TypeScript thin-client deprecation path. Decide whether to keep the standalone TS MCP for backwards compatibility or document the adapter as the primary surface. Likely: keep TS as a supported entry point (some users won't want the adapter), but the adapter becomes the recommended path.
  4. Phase 4 — Drop the abilities feature flag. Default-on once we trust the surface.

Open questions

  • Schema parity. Our register_rest_route args arrays describe input shape but not output shape. Abilities want both. We'll need to formalize output schemas — useful regardless of this issue.
  • Ref-based vs index-based abilities. Should update_block and update_block_by_ref be one ability with an XOR'd input, or two abilities? Current REST has them as two routes. Two abilities is more discoverable; one is cleaner. Probably two.
  • Preference scoring on the discovery side. Our list_block_types returns enriched data (tier, replacement, usage). The Abilities API has its own discovery channel — is there value in exposing block types as abilities, or do they stay as ability inputs/outputs?
  • Yoast endpoints live on a different REST namespace (gravitykit/v1/yoast-seo/* in the Block-Theme mu-plugin). Either co-locate the ability registration or keep Yoast as a separate sub-package.
  • MCP adapter dependency. Adapter requires WP 6.9+ for built-in Abilities API; WP 6.8 needs the (now-archived) plugin. Our minimum is currently WP 6.0. Either bump the floor for ability registration only, or keep dual paths.

Out of scope

  • Replacing the standalone TS server. Phase 3 will revisit; for v1 of this work the TS client stays.
  • Resources and prompts. The adapter supports both, but our endpoints are tools-shaped. Could revisit later (e.g., expose the ref system as a resource).

Acceptance

  • All current REST endpoints registered as abilities behind the feature flag
  • WordPress/mcp-adapter test site successfully discovers and invokes each ability
  • New PHPUnit test file AbilitiesRegistrarTest.php covering registration + permission + execute paths
  • README updated with adapter setup instructions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions