Skip to content
Back to skills

Lw Site Manager Extend Abilities

ASecurity

Add companion-plugin abilities to LW Site Manager 1.5.x through lw_site_manager_register_categories and lw_site_manager_register_abilities. Covers namespaces, REST and MCP exposure metadata, PermissionManager, object-level authorization, schemas, annotations, WP_Error failure contracts, and service-layer registration. Use when a plugin should expose its own operations through the Site Manager Abilities or MCP surface. Use lw-site-manager-overview when consuming existing abilities.

  • 22 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added June 5, 2026
documentationgophpsqlgitapidatabasesecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add Lonsdale201/wp-agent-skills --skill lw-site-manager-extend-abilities --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Lw Site Manager Extend Abilities?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Lw Site Manager Extend Abilities
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lonsdale201-lw-site-manager-extend-abilities/badge)](https://www.skillsdirectory.com/skills/lonsdale201-lw-site-manager-extend-abilities)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: lw-site-manager-extend-abilities
description: >-
  Add companion-plugin abilities to LW Site Manager 1.5.x through
  lw_site_manager_register_categories and lw_site_manager_register_abilities.
  Covers namespaces, REST and MCP exposure metadata, PermissionManager,
  object-level authorization, schemas, annotations, WP_Error failure contracts,
  and service-layer registration. Use when a plugin should expose its own
  operations through the Site Manager Abilities or MCP surface. Use
  lw-site-manager-overview when consuming existing abilities.
metadata:
  wp-skills-author: "Soczó Kristóf"
  wp-skills-contact: "https://github.com/Lonsdale201"
  wp-skills-plugin: "lw-site-manager"
  wp-skills-plugin-version-tested: "1.5.1"
  wp-skills-wp-version-tested: "7.1.2"
  wp-skills-php-min: "8.2"
  wp-skills-last-updated: "2026-09-26"
---

# Extend LW Site Manager with companion abilities

Register a companion plugin's operations through LW Site Manager's two action hooks. Keep the companion's domain logic in its own service classes and expose only stable, capability-checked operations.

## Extension hooks

| Hook | Arguments | Purpose |
|---|---:|---|
| `lw_site_manager_register_categories` | 0 | Register ability categories before abilities reference them. |
| `lw_site_manager_register_abilities` | 1 | Register abilities; receives the central `PermissionManager`. |

Attach handlers before `wp_abilities_api_init` runs. A normal plugin bootstrap on `plugins_loaded` is early enough.

```php
use LightweightPlugins\SiteManager\Abilities\PermissionManager;

add_action( 'lw_site_manager_register_categories', 'acme_register_categories' );
add_action( 'lw_site_manager_register_abilities', 'acme_register_abilities', 10, 1 );

function acme_register_categories(): void {
	wp_register_ability_category(
		'acme-newsletter',
		[
			'label'       => __( 'Newsletter', 'acme-newsletter' ),
			'description' => __( 'Newsletter management operations.', 'acme-newsletter' ),
		]
	);
}

function acme_register_abilities( PermissionManager $permissions ): void {
	wp_register_ability(
		'acme-newsletter/list-subscribers',
		[
			'label'               => __( 'List subscribers', 'acme-newsletter' ),
			'description'         => __( 'List newsletter subscribers.', 'acme-newsletter' ),
			'category'            => 'acme-newsletter',
			'input_schema'        => Acme\Abilities\Schemas::list_input(),
			'output_schema'       => Acme\Abilities\Schemas::subscriber_list(),
			'execute_callback'    => [ Acme\Services\Subscribers::class, 'list' ],
			'permission_callback' => $permissions->callback( 'can_manage_options' ),
			'meta'                => [
				'show_in_rest' => true,
				'annotations'  => [
					'readonly'    => true,
					'destructive' => false,
					'idempotent'  => true,
				],
				'mcp' => [
					'public' => true,
					'type'   => 'tool',
				],
			],
		]
	);
}
```

## Own the namespace

Use the companion plugin's namespace, such as `lw-cookie/*` or `acme-newsletter/*`. This prevents collisions and makes ownership visible in discovery.

LW Site Manager automatically adds MCP metadata only to `site-manager/*`. A foreign namespace must set:

```php
'mcp' => [
	'public' => true,
	'type'   => 'tool',
],
```

`show_in_rest => true` and `mcp.public => true` solve different problems. The first exposes the ability through the WordPress Abilities REST API. The second lets the built-in MCP server discover it.

Do not borrow `site-manager/*` merely to gain automatic MCP exposure. Use explicit metadata and retain namespace ownership.

## PermissionManager is the baseline

Reuse the central manager where its primitive capability matches the operation:

| Method | Capability or rule |
|---|---|
| `can_manage_updates` | `update_plugins` and `update_themes` |
| `can_install_plugins` | `install_plugins` |
| `can_install_themes` | `install_themes` |
| `can_manage_plugins` | `activate_plugins` |
| `can_manage_themes` | `switch_themes` |
| `can_manage_backups`, `can_manage_database`, `can_manage_cache`, `can_manage_options` | `manage_options` |
| `can_view_health` | `view_site_health_checks` |
| `can_manage_users` | `list_users` |
| `can_create_users`, `can_edit_users`, `can_delete_users` | matching user capability |
| content/media/taxonomy methods | matching primitive capability |

The registration callback cannot authorize a target object it has not seen. Apply the object's meta capability inside the execute callback:

```php
public static function update( array $input ): array|WP_Error {
	$post_id = (int) ( $input['id'] ?? 0 );

	if ( $post_id < 1 || ! current_user_can( 'edit_post', $post_id ) ) {
		return new WP_Error(
			'forbidden_post',
			__( 'You cannot edit this post.', 'acme-newsletter' ),
			[ 'status' => 403 ]
		);
	}

	// Validate the requested transition, then mutate.
}
```

Use WooCommerce meta capabilities and CRUD objects for orders and products. A primitive `edit_posts` check is not order authorization.

## Build truthful schemas

Define every accepted input and every returned field. Keep `required` at the object schema level:

```php
'input_schema' => [
	'type'       => 'object',
	'properties' => [
		'id' => [
			'type'    => 'integer',
			'minimum' => 1,
		],
		'status' => [
			'type' => 'string',
			'enum' => [ 'draft', 'ready', 'sent' ],
		],
	],
	'required'             => [ 'id', 'status' ],
	'additionalProperties' => false,
],
```

If a client depends on a field, include it in `output_schema`. WordPress validates output; drift becomes a runtime failure instead of an undocumented response.

The plugin's `AbstractAbilitiesRegistrar` currently offers protected helpers such as `readOnlyMeta()`, `writeMeta()`, `destructiveMeta()`, `listingSchema()`, `idSchema()`, `listOutputSchema()`, and `entityOutputSchema()`. Subclass it only when the companion explicitly depends on LW Site Manager 1.5.x. It is an internal implementation class, so re-verify it on every version bump. A local schema/meta builder creates a looser dependency.

## Mark annotations by behavior

| Annotation | Set true when |
|---|---|
| `readonly` | The operation does not mutate state or trigger external effects. |
| `destructive` | It can remove data, send irreversible messages, charge/refund, restore over state, or otherwise cause hard-to-reverse effects. |
| `idempotent` | Repeating the same request produces the same intended state without duplicate side effects. |

Examples:

- list/get: readonly, non-destructive, idempotent;
- create: write, non-destructive, usually non-idempotent;
- set an existing resource to an exact value: write, often idempotent;
- send campaign or email: write, destructive/external, non-idempotent;
- delete by stable ID: destructive and often idempotent, provided repeated deletion is a clean no-op.

Do not call a scanner or outbound probe readonly merely because it does not change WordPress content. Network traffic, refreshed scan state, and rate-limit consumption are side effects.

## Return errors that transports understand

Return `WP_Error` for a failed whole operation:

```php
return new WP_Error(
	'newsletter_send_failed',
	__( 'The campaign could not be queued.', 'acme-newsletter' ),
	[ 'status' => 500 ]
);
```

Do not return `[ 'success' => false ]` with HTTP 200 for a hard failure. REST clients and MCP agents can otherwise misread the transport as successful.

For a batch, return a normal result when part of the batch completed. Include processed IDs and failed IDs, and make the output schema match those types exactly.

## Keep registrars thin

The registrar should describe the interface. Put validation, object authorization, transactions, and domain logic in a service:

```php
'execute_callback' => [ Acme\Services\Campaigns::class, 'send' ],
```

The service should:

1. normalize and validate input;
2. authorize the specific target;
3. check current state and transition rules;
4. perform the operation once;
5. return a schema-conforming result or `WP_Error`.

## Verify the integration

Check all relevant surfaces after implementation:

1. The category and ability appear in the live Abilities registry.
2. The REST metadata endpoint shows the declared input/output schemas.
3. An allowed user can execute the ability; a lower role cannot.
4. Object-level denial works against another user's private object.
5. A hard failure becomes a non-2xx REST response and an MCP error.
6. If MCP exposure is intended, discovery includes the foreign namespace.
7. Readonly, destructive, and idempotent annotations match observed behavior.

## Cross-references

- Use `lw-site-manager-overview` for MCP state, domain lock, authentication, adapter diagnostics, and consumer calls.
- Use `wp-abilities-api` for the WordPress core registration lifecycle and schema rules.
- Use `wp-security-audit` for nonce, capability, input, output, SQL, file, and remote-request review of the companion implementation.
- Follow the plugin's `docs/extending-skills.md` separately when adding runtime skill sources; ability registration and skill-source registration are different contracts.

## References

- Plugin repository: <https://github.com/lwplugins/lw-site-manager>
- Official extension guide: <https://github.com/lwplugins/lw-site-manager/blob/main/docs/extending-abilities.md>
- MCP exposure rules: <https://github.com/lwplugins/lw-site-manager/blob/main/docs/mcp-server.md>
- Verified source paths:
  - `lw-site-manager.php`
  - `src/Abilities/Registrar.php`
  - `src/Abilities/PermissionManager.php`
  - `src/Abilities/Registrars/AbstractAbilitiesRegistrar.php`
  - `src/Mcp/AbilityExposer.php`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.