Skip to content

Commit 3150567

Browse files
authored
ship-wp-ability skill: prefer shared categories and require mcp tool meta (#48838)
1 parent c7aab80 commit 3150567

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

.agents/skills/ship-wp-ability.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ Do NOT consolidate across these hard boundaries:
8383

8484
Then the rest of the naming:
8585

86-
- **Category slug:** kebab-case, plugin-scoped. Good: `jetpack-monitor`, `jetpack-forms`, `jetpack-modules`. Bad: `monitor`, `forms`.
87-
- **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.
8892
- **Class name:** `<Name>_Abilities` (e.g. `Monitor_Abilities`, `Forms_Abilities`, `Modules_Abilities`).
8993
- **Namespace:** plugin context (Case A, Case C) → `Automattic\Jetpack\Plugin\Abilities`. Package context (Case B) → `Automattic\Jetpack\<Pkg>\Abilities`.
9094

@@ -115,6 +119,7 @@ For every entry in `get_abilities()`:
115119
- `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`.
116120
- `permission_callback``[ __CLASS__, 'can_method' ]`. See the permissions table below.
117121
- `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.
118123
- `meta.show_in_rest``true` when the ability should be callable via REST.
119124
- **Do NOT set `category`** in the spec. `Registrar` auto-injects it. Setting it duplicates info that drifts.
120125
- `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.

.agents/skills/ship-wp-ability/references/class-skeleton.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,33 @@ Do not re-implement any of this in the subclass:
1616
- **WP < 6.9 compatibility** — guards every call to `wp_register_ability_category()` and `wp_register_ability()` with `function_exists()`.
1717
- **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.
1818

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 {
31+
return array(
32+
'label' => __( 'Site', 'jetpack-<pkg>' ),
33+
'description' => __( 'Site-wide management abilities (registered upstream).', 'jetpack-<pkg>' ),
34+
);
35+
}
36+
37+
// `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+
1946
The subclass just needs the three abstract getters plus execute + permission callbacks.
2047

2148
## Plugin-context template
@@ -110,6 +137,10 @@ class <Name>_Abilities extends Registrar {
110137
'destructive' => false,
111138
'idempotent' => true,
112139
),
140+
'mcp' => array(
141+
'public' => true,
142+
'type' => 'tool',
143+
),
113144
'show_in_rest' => true,
114145
),
115146
),
@@ -152,6 +183,10 @@ class <Name>_Abilities extends Registrar {
152183
'destructive' => false,
153184
'idempotent' => true,
154185
),
186+
'mcp' => array(
187+
'public' => true,
188+
'type' => 'tool',
189+
),
155190
'show_in_rest' => true,
156191
),
157192
),

0 commit comments

Comments
 (0)