# Add your plugin’s tools to Royal MCP

> Plugin developers can add their own tools to Royal MCP with two filters. A working example, the rules every tool follows, and how to partner with us.

- Canonical: <https://royalplugins.com/support/royal-mcp/add-your-plugin-tools/>
- HTML version: <https://royalplugins.com/support/royal-mcp/add-your-plugin-tools/>

---

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](mailto: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](https://royalplugins.com/affiliates/) 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/](https://wordpress.org/support/plugin/royal-mcp/), or open an issue on [GitHub](https://github.com/royalplugins/royal-mcp/issues).

### Premium support (paid)

For direct one-on-one help with a 24-hour SLA, see [Premium Support](https://royalplugins.com/premium-support/).

## Related

[Safety and recovery

Read-only mode, drafts-only mode and undo for AI changes.](https://royalplugins.com/support/royal-mcp/safety-and-recovery/)
[Add customer emails to replies

Shape what Royal MCP returns with filters.](https://royalplugins.com/support/royal-mcp/exposing-customer-email-via-filter/)
[API keys

How connections sign in, and how to rotate a key.](https://royalplugins.com/support/royal-mcp/api-keys/)

[Back to Royal MCP Support](https://royalplugins.com/support/royal-mcp/)
[Safety and recovery](https://royalplugins.com/support/royal-mcp/safety-and-recovery/)
