Add your plugin’s tools to Royal MCP
If you build a WordPress plugin, you can let AI clients work with it through Royal MCP. Two filters add your tools to the list Claude, ChatGPT and every other connected client sees, and run them when they’re called. Royal MCP handles sign-in, rate limits, logging and its safety switches; you write the tool. Requires Royal MCP 1.5.11 or later.
How it works
| Filter | What you do | Arguments |
|---|---|---|
royal_mcp_tools | Append your tool definitions: name, description, inputSchema (JSON Schema) and annotations. | $tools, the full tool list |
royal_mcp_execute_tool_external | Run the call when the name is yours and return the result. Leave $result alone for any other name. | $result (null unless an earlier callback answered), $name, $args |
Return an array and it becomes the tool’s result. Return a WP_Error (or throw an Exception) and the client gets its message as a tool error. The second filter only runs for names Royal MCP doesn’t define itself, so your callback can never replace one of Royal MCP’s own tools.
A complete example
This plugin adds one tool that reads and one that changes the site. Save it as a plugin (or in wp-content/mu-plugins/), connect an AI client, and ask it to list your widgets.
<?php
/**
* Plugin Name: Acme Widgets for Royal MCP
* Description: Adds two Acme Widgets tools to Royal MCP: one that reads, one that changes the site.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// 1. List your tools. Royal MCP shows them to every connected AI client.
add_filter( 'royal_mcp_tools', function ( $tools ) {
$tools[] = [
'name' => 'acme_get_widgets',
'description' => 'List Acme widgets with their IDs and labels.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'description' => 'How many widgets to return (default 20).' ],
],
],
// Only for tools that never change anything.
'annotations' => [ 'readOnlyHint' => true ],
];
$tools[] = [
'name' => 'acme_rename_widget',
'description' => 'Change the label of one Acme widget.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'id' => [ 'type' => 'integer', 'description' => 'Widget ID from acme_get_widgets.' ],
'label' => [ 'type' => 'string', 'description' => 'New label.' ],
],
'required' => [ 'id', 'label' ],
],
'annotations' => [ 'readOnlyHint' => false, 'destructiveHint' => false, 'idempotentHint' => true ],
];
return $tools;
} );
// 2. Run them. Leave $result alone for tool names that aren't yours.
add_filter( 'royal_mcp_execute_tool_external', function ( $result, $name, $args ) {
if ( 0 !== strpos( $name, 'acme_' ) ) {
return $result;
}
$widgets = get_option( 'acme_widgets', [] );
$widgets = is_array( $widgets ) ? $widgets : [];
switch ( $name ) {
case 'acme_get_widgets':
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'acme_forbidden', 'You do not have permission to list widgets.' );
}
$limit = isset( $args['limit'] ) ? max( 1, min( 100, (int) $args['limit'] ) ) : 20;
$rows = [];
foreach ( array_slice( $widgets, 0, $limit, true ) as $id => $label ) {
$rows[] = [ 'id' => (int) $id, 'label' => (string) $label ];
}
return [ 'widgets' => $rows, 'total' => count( $widgets ) ];
case 'acme_rename_widget':
if ( ! current_user_can( 'manage_options' ) ) {
return new WP_Error( 'acme_forbidden', 'You do not have permission to rename widgets.' );
}
$id = isset( $args['id'] ) ? (int) $args['id'] : 0;
$label = isset( $args['label'] ) ? sanitize_text_field( (string) $args['label'] ) : '';
if ( ! isset( $widgets[ $id ] ) ) {
return new WP_Error( 'acme_not_found', 'No widget with that ID.' );
}
if ( '' === $label ) {
return new WP_Error( 'acme_bad_label', 'The label cannot be empty.' );
}
$old = $widgets[ $id ];
$widgets[ $id ] = $label;
update_option( 'acme_widgets', $widgets );
return [ 'id' => $id, 'old_label' => $old, 'label' => $label ];
}
return $result;
}, 10, 3 );
With read-only mode on, acme_get_widgets still runs and acme_rename_widget is refused before your code is called.
Rules every tool follows
Check permissions yourself
The call reaches your callback already signed in as a WordPress user, but Royal MCP’s per-tool permission checks cover its own tools only. Check current_user_can() in every tool, with the capability you’d require in wp-admin for the same action, and check object-level permissions (for example edit_post for a specific post) where they apply.
Say whether the tool changes anything
Set 'readOnlyHint' => true only on tools that never change anything. Every other tool from another plugin, including one with no annotations or with a value other than boolean true, counts as a change. Changes are refused when the site owner turns on read-only mode, drafts-only mode, or a read-only or drafts-only access level for the connection. Add destructiveHint and idempotentHint so AI clients know which calls to confirm with the user first.
Use your own name prefix
Start every tool name with your plugin’s prefix (acme_ in the example) and don’t use a prefix Royal MCP uses for its own tools: wp_, mcp_, royal_mcp_, ext_, wc_, elementor_, divi_, acf_, yoast_, wordfence_, solid_, gp_, sv_, fc_, rl_, rlinks_, raif_, bp_, cf7_, duplicator_, litespeed_, monsterinsights_, redirection_, snippets_, tablepress_, updraftplus_, w3tc_, wpcode_, wpforms_.
Validate input and keep results small
Treat $args as untrusted: cast, sanitize and bound every value, as the example does with limit. Return only the fields the AI needs, page long lists, and never return secrets such as API keys, passwords or tokens.
Write clear descriptions
The description and the schema’s property descriptions are what the AI reads to decide when and how to call your tool. Say what it does, what it returns and where an ID comes from.
What Royal MCP does for your tools
- Sign-in: OAuth and API-key connections, for every MCP client.
- Switches: nothing runs while Royal MCP is switched off, and read-only mode, drafts-only mode and per-connection access levels apply as described above.
- Rate limits: the same per-IP limits as every other call.
- Activity log: every call is logged with the tool name, the argument keys (never their values) and the result.
Royal MCP keeps no undo snapshot for tools from other plugins. If a tool makes a change that can’t be reversed, mark it destructiveHint so the AI client asks first.
Already registering WordPress abilities?
If your plugin registers abilities with the WordPress Abilities API, Royal MCP Pro can list them as tools on the same connection once the site owner turns on your plugin’s abilities in its settings. On the free plugin, add your tools with the two filters above.
Partner with us
Want your plugin listed among Royal MCP’s integrations, or would you rather we build the integration with you? Email devs@royalplugins.com with your plugin and what you’d like AI clients to do with it. We review every request; we can’t promise a timeline for each one.
You can also earn from the plugins you recommend: the Royal Plugins affiliate program pays 30% on first-year sales and 15% on renewals.
Still stuck?
Community support (free)
Post a new thread at wordpress.org/support/plugin/royal-mcp/, or open an issue on GitHub.
Premium support (paid)
For direct one-on-one help with a 24-hour SLA, see Premium Support.