Subchapter 3.2
references/domain-vs-projection.mdMarkdown9 KBView on GitHub
Three layers in this model: domain, projection, and an optional workflow layer that composes abilities into multi-step tasks. The title names the two primary decisions; workflow is introduced where it earns its keep.
The Abilities API can be used at two heights. You can register abilities as the surface an MCP/REST/Command-Palette client will consume directly. Or you can register abilities at the domain layer — “what can this plugin do, transport-neutrally?” — and let each consumer see a projection of that surface that suits its constraints. This reference covers why the second framing pays off, the three-layer model that operationalizes it, and how to use it when deciding what to register.
Treating MCP exposure as the registration target conflates two decisions:
The first decision is stable. The second is volatile — token budgets shift, MCP clients improve, Command Palette gains new conventions, large plugins outgrow flat tool lists and adopt nested-discovery patterns. Coupling the two means re-registering abilities every time the projection question is reopened.
Decoupling them means registrations stay where they are while projections evolve underneath.
| Layer | Purpose | Examples |
|---|---|---|
| Domain capability | Stable, transport-neutral, permission-checked. The plugin’s contract for “what can this do.” | myplugin/list-things, myplugin/update-thing, myplugin/get-overview |
| Workflow (optional) | Compositions of one or more abilities into a human- or agent-friendly task. | “Onboard a new merchant” = verify-account → enable-feature → send-welcome |
| Projection | Consumer-specific view of the domain layer. Token-efficient compression for MCP, curated workflows for Command Palette, discoverable execution surface for REST, forms for admin UI, chainable commands for CLI. | The bundled WordPress MCP adapter projects every published ability through three meta-abilities (mcp-adapter/discover-abilities, mcp-adapter/execute-ability, mcp-adapter/get-ability-info) — agents traverse those three rather than seeing the full registry flat. Command Palette, by contrast, can show the same domain abilities flat because token budget is not a constraint. |
The domain layer is what the registry holds. The projection layer is what each consumer sees. The workflow layer is optional and exists when a user-or-agent-meaningful task needs more than one ability.
A registered ability is a use-case contract — a natural-language shortcut to an action a human can already perform in the UI. REST endpoints and CLI commands are transport contracts; they expose plumbing. Abilities expose actions.
Two consequences fall out of that framing:
If yes, the operation is a candidate for an ability. The lens is broader than wp-admin alone: a “supported UI or workflow” covers admin screens, public-facing UIs (storefront, account dashboard, course viewer, appointment booker), end-user self-service flows on the site front-end, and supported workflows in which another plugin or an agent calls the operation as part of a chain of actions. Abilities like store/get-my-orders, events/list-available-tickets, or profile/update-public-profile qualify just as much as admin-side abilities like myplugin/list-pending-orders or myplugin/approve-submission.
If no, the operation stays a REST endpoint, a CLI command, or an internal hook. Internal-only plumbing — cache invalidation, scheduler ticks, debug snapshots, lifecycle bookkeeping — does not belong as an ability even when it has a clean schema. There is no meaningful human invocation point, so there is no use-case contract to register.
The test is asymmetric: not everything a UI exposes should be an ability either (rule 1 in grouping-heuristic.md covers grouping). But anything that has no human-meaningful invocation surface almost certainly is not ability-meaningful.
If the ability mirrors a UI action, it must share the UI’s permissions, validation, and business rules. The mechanism for keeping that promise is in shared-core-service.md. The framing is here: the ability is the use-case; UI / REST / CLI / MCP are thin adapters over it.
The Abilities API is a WordPress core primitive. It establishes a formal contract for “run this code with these inputs under this permission check.” MCP, REST, and Command Palette are exposure layers on top. You opt in.
This means a plugin can register abilities that the Command Palette uses but MCP does not see, or vice versa. The registration is transport-neutral; exposure is per-consumer.
A small plugin might expose its three abilities flat in MCP and flat in Command Palette. A larger plugin might keep the same three domain abilities but project them through a nested-discovery facade for MCP (three meta-tools that traverse to the underlying registrations) while showing them flat in the Command Palette where token budget is not a constraint.
The domain layer does not change. The projection layer does the consumer-specific work.
The choice between flat-with-full-schemas, semantic-grouping, single-tool-facade, and nested-discovery is a projection decision. Different shapes serve different consumer constraints. None of those shapes require changing what abilities are registered at the domain layer; they change how a consumer sees the registered set.
This is what “pattern-agnostic” means in practice: the registration is one decision, the projection is another, and a redesign of the second does not invalidate the first.
When designing a plugin’s ability surface, work in this order:
grouping-heuristic.md to pick the right granularity (semantic-intent over REST-atomization).Most plugins never need step 3.
A “Notifications” plugin manages a per-user notification feed. It supports listing, marking read, and dismissing all.
Three abilities, registered once:
notifications/list — read; filter by unread_only, since, category.notifications/mark-read — write; takes a notification_id.notifications/dismiss-all — write; zero-arg.These are use-case contracts. They are transport-neutral. They do not assume MCP or Command Palette or REST.
Small surface, flat works. The MCP projection exposes the three abilities as-is. No nested-discovery needed.
The Command Palette projection adds a small workflow that wraps notifications/list with default filters (unread_only=true) and opens the admin notifications page on selection. The other two abilities remain visible as flat commands. The domain registration did not change; the Command Palette layer added the workflow on top.
The REST projection exposes only the read ability. Writes go through admin UI for security-review reasons. The domain registration is unchanged; the REST layer just doesn’t expose two of the three.
Same three registrations, three different projections, no re-registration as projection decisions evolve.
For plugins with one to three admin-only abilities and no other surfaces, the projection layer is trivially the identity function. You don’t need to implement layers; you need to think in layers so you don’t paint yourself into a corner when the plugin grows or when MCP token budgets become a constraint.
Concretely: name the abilities at the domain level (myplugin/get-thing, not myplugin-mcp/get-thing), keep the registration permission-checked and transport-neutral, and skip the projection layer until something needs it.
grouping-heuristic.md — within-domain decisions: how many abilities, where to put filters.shared-core-service.md — the implementation mechanism for “same code path as the UI.”