You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**Ability slugs:**`<category>/<verb>-<object>`, e.g. `jetpack-monitor/get-monitor-status`.
86
+
-**Category slug — prefer a core / shared category over a plugin-scoped one when the abilities are general site management.** Think of categories as broad surface labels for the agent, not as plugin namespaces. If the abilities act on the *site* (backup/restore, modules, connection state, content), use the upstream-registered `site` category. Reach for a plugin-scoped slug only when the abilities are genuinely product-specific and don't fit any shared bucket.
87
+
-**Use a shared/core category** (`site`, etc.) for: backup/restore, module toggles, connection-state readers, content-management actions, anything an agent would discover under "manage this site".
88
+
-**Use a plugin-scoped slug** (`jetpack-forms`, `jetpack-boost`) when the abilities cover a product-specific surface that wouldn't sit naturally next to another plugin's tools.
89
+
- Bad either way: bare single words you invent (`monitor`, `cache`) — pick a real shared category or namespace it.
90
+
-**If the category you pick is registered upstream**, override `register_category()` to a no-op and skip `wp_register_ability_category()`. The base `Registrar` still hooks the categories-init callback; making it a no-op avoids "already registered" notices. `get_category_definition()` stays on the class to satisfy the abstract contract but isn't called.
91
+
-**Ability slugs:**`<product-or-feature>/<verb>-<object>`, e.g. `jetpack-monitor/get-monitor-status`, `jetpack-backup/get-backup-overview`. The slug prefix is a product/feature namespace and is independent of the category — abilities under the shared `site` category still use `jetpack-<feature>/...` slugs.
-**Namespace:** plugin context (Case A, Case C) → `Automattic\Jetpack\Plugin\Abilities`. Package context (Case B) → `Automattic\Jetpack\<Pkg>\Abilities`.
90
94
@@ -115,6 +119,7 @@ For every entry in `get_abilities()`:
115
119
-`execute_callback` — `[ __CLASS__, 'method_name' ]`. Method signature is `( $input = null )`. Return value is a plain array (or `WP_Error`). Keep the shape **high-signal** — don't return raw `$mod` / `WP_Post` / option dumps; summarize. See the anti-patterns section of `references/agent-ergonomics.md`.
116
120
-`permission_callback` — `[ __CLASS__, 'can_method' ]`. See the permissions table below.
117
121
-`meta.annotations` — `readonly`, `destructive`, `idempotent` booleans. These must match execute behavior; `wp-abilities-verify` will flag mismatches.
122
+
-`meta.mcp` — every ability gets `'mcp' => array( 'public' => true, 'type' => 'tool' )`. This is what surfaces the ability to MCP-bridged AI agents as a tool. Matches the convention used by Stats / Boost / Modules abilities; leaving it off makes the ability invisible to MCP consumers even though it shows up in `/wp-abilities/v1/abilities/`. Flip `public => false` only when the ability is for internal callers and explicitly shouldn't be agent-discoverable.
118
123
-`meta.show_in_rest` — `true` when the ability should be callable via REST.
119
124
-**Do NOT set `category`** in the spec. `Registrar` auto-injects it. Setting it duplicates info that drifts.
120
125
-`WP_Error` codes — use the shared vocabulary in `wp-abilities-api/references/error-code-vocabulary.md` (`<plugin>_not_initialized`, `<plugin>_missing_<field>`, `<plugin>_invalid_<field>`, `<plugin>_<resource>_data_unavailable`). The **message** should tell the agent *what to do next* — e.g. "Unknown module slug. Call jetpack-modules/get-modules to enumerate." — not just restate the error condition.
Copy file name to clipboardExpand all lines: .agents/skills/ship-wp-ability/references/class-skeleton.md
+35Lines changed: 35 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,33 @@ Do not re-implement any of this in the subclass:
16
16
-**WP < 6.9 compatibility** — guards every call to `wp_register_ability_category()` and `wp_register_ability()` with `function_exists()`.
17
17
-**Category auto-injection** — if a spec in `get_abilities()` omits the `category` key, the base class injects `get_category_slug()`. Don't set `category` manually.
18
18
19
+
### When the category is registered upstream (preferred for site-wide abilities)
20
+
21
+
The skill steers you toward shared/core categories (e.g. `site`) when the abilities are general site management. In that case **you don't register the category** — WordPress core / wpcom already did. Override `register_category()` to a no-op:
22
+
23
+
```php
24
+
public static function get_category_slug(): string {
25
+
return 'site';
26
+
}
27
+
28
+
// Required by the abstract parent but unused — the `site` category is
29
+
// already registered upstream so we don't re-declare it.
30
+
public static function get_category_definition(): array {
// `Registrar::init()` still hooks this onto `wp_abilities_api_categories_init`;
38
+
// making it a no-op avoids "already registered" notices.
39
+
public static function register_category() {
40
+
// No-op: `site` is registered upstream.
41
+
}
42
+
```
43
+
44
+
`register_abilities()` keeps its normal behavior. Ability slugs are still product-namespaced (`jetpack-backup/get-backup-overview`) — the category and the slug prefix are independent.
45
+
19
46
The subclass just needs the three abstract getters plus execute + permission callbacks.
20
47
21
48
## Plugin-context template
@@ -110,6 +137,10 @@ class <Name>_Abilities extends Registrar {
110
137
'destructive' => false,
111
138
'idempotent' => true,
112
139
),
140
+
'mcp' => array(
141
+
'public' => true,
142
+
'type' => 'tool',
143
+
),
113
144
'show_in_rest' => true,
114
145
),
115
146
),
@@ -152,6 +183,10 @@ class <Name>_Abilities extends Registrar {
0 commit comments