Developer Guide
MCPify is built to be extended without editing core files. You register your own tools with a documented PHP API, and every tool you add automatically gets the same security gate, validation, rate limiting, and all three delivery surfaces (browser, REST, OpenAPI).
Register a custom tool
Hook into mcpify_register_tools and add a tool definition to the registry:
use MCPify\Core\Dispatch\ToolRequest;
use MCPify\Core\Registry\AuthLevel;
use MCPify\Core\Registry\ToolDefinition;
use MCPify\Core\Registry\ToolRegistry;
use MCPify\Core\Registry\ToolType;
add_action( 'mcpify_register_tools', function ( ToolRegistry $registry ) {
$registry->register( new ToolDefinition(
name: 'myplugin.get_featured',
title: 'Get featured items',
description: 'Return the current featured items for this site.',
inputSchema: [
'type' => 'object',
'properties' => [
'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 20, 'default' => 5 ],
],
'additionalProperties' => false,
],
handler: function ( ToolRequest $request ): array {
$limit = (int) ( $request->params['limit'] ?? 5 );
// ... your logic ...
return [ 'items' => [] ];
},
type: ToolType::Read,
auth: AuthLevel::Anonymous,
) );
} );
type and auth are enums, not strings, and your handler receives a ToolRequest, not a plain array - read your validated input from $request->params. Return an array (or a ToolResult) and the dispatcher wraps it in the standard envelope.
Your tool immediately appears in the manifest, is callable over REST and OpenAPI, and is registered in the browser for WebMCP agents.
Use your own prefix (for example myplugin.) so your tools never collide with the built-in wordpress.*, woocommerce.*, or cpt.* tools.
Choose the right type and tier
- Mark a tool
ToolType::Readonly if it never changes state. Anything that writes must beToolType::Writeso the nonce requirement is enforced. - Pick the narrowest tier that still works:
AuthLevel | Use it for |
|---|---|
Anonymous | Content already public on your site |
Session | Something a guest must be able to do in their own browser session, like a cart |
Authenticated | Anything scoped to a specific person's own data |
Capability | Anything a normal customer should never reach (pass capability: too) |
- Return the smallest useful, public shape. Never return emails, private meta, or secrets - the security model applies to your tools too.
- Validate with a strict JSON Schema (
additionalProperties: false) so bad input is rejected before your handler runs. - Set
rateLimit:on anything expensive, or on anything whose success and failure responses differ enough to be guessed at. - Re-check ownership inside the handler for anything user-scoped. The tier says someone is logged in; only your handler knows whose record this is.
Hooks and filters
MCPify uses the mcpify_ prefix for all its extension points. Common ones:
| Hook | Type | Purpose |
|---|---|---|
mcpify_register_tools | action | Register custom tools |
mcpify_before_execute | filter | Inspect or rewrite params before a tool runs |
mcpify_tool_response | filter | Shape the response envelope before it is returned |
mcpify_after_execute | action | Observe every completed call (this is what webhooks use) |
mcpify_manifest | filter | Adjust the published manifest |
mcpify_rate_limit / mcpify_rate_window | filter | Change the per-caller limit and window |
mcpify_is_pro | filter | The edition gate; the license bridge hooks in here |
mcpify_abilities_enabled | filter | Turn the WordPress Abilities API bridge on/off (Pro) |
Two constants that were documented in earlier drafts, mcpify_permission_check and mcpify_protected_tools, were removed rather than left in place, because they were never fired. A hook advertised as a security extension point that silently does nothing is worse than no hook at all - it manufactures confidence. If you had planned to use them, tighten access with the per-tool overrides in Settings instead, or gate inside your own handler.
Both mcpify_before_execute and mcpify_after_execute fire for the MCP endpoint as well as the REST one, so hardening or logging you hang on them sees server-side agents too.
Enable the Abilities bridge (WordPress 6.9+, Pro):
add_filter( 'mcpify_abilities_enabled', '__return_true' );
Drive the REST API
The REST surface is the simplest way to call MCPify from your own agent or backend.
Discover the tools:
curl "https://example.com/wp-json/mcpify/v1/manifest"
Run a read tool:
curl -X POST "https://example.com/wp-json/mcpify/v1/execute" \
-H "Content-Type: application/json" \
-d '{ "tool": "wordpress.search", "params": { "query": "returns policy" } }'
Run a write tool (needs a logged-in session and the REST nonce):
curl -X POST "https://example.com/wp-json/mcpify/v1/execute" \
-H "Content-Type: application/json" \
-H "X-WP-Nonce: THE_NONCE" \
--cookie "session-cookies" \
-d '{ "tool": "woocommerce.add_to_cart", "params": { "product_id": 42, "quantity": 1 } }'
Connect a server-side agent over MCP (Pro)
Pro adds a JSON-RPC 2.0 endpoint at /wp-json/mcpify/v1/mcp speaking protocol version 2024-11-05:
curl -X POST "https://example.com/wp-json/mcpify/v1/mcp" \
-H "Content-Type: application/json" \
-H "X-MCPify-Key: YOUR_AGENT_KEY" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }'
It supports initialize, tools/list, tools/call and ping, over the same registry, gate and rate limits as every other surface. Your custom tools appear here automatically, with no extra work.
Use with Custom GPT Actions
The /wp-json/mcpify/v1/openapi endpoint returns a ready OpenAPI 3 document. In a Custom GPT (or any OpenAPI-driven agent), import that URL as an Action and the assistant gets every enabled tool as a typed operation - no manual schema mapping.
Good practice
- Keep handlers small and delegate to services you can unit-test.
- Fail with a clear message; the dispatcher turns thrown errors into the standard
{ success: false, error }envelope. - Treat all input as untrusted and re-check permissions in sensitive handlers - do not rely on the tier alone for anything high-value.