Plain-language control
Describe the discount you want; the agent assembles a valid rule, previews it, and publishes it — no wizard clicks.
New in v4.59.8: Dino Discounts can be driven by an AI agent. The plugin
publishes its discount engine as a set of agent-callable abilities — through
the WordPress Abilities API
(WordPress 6.9) and, via the MCP Adapter,
the Model Context Protocol. An AI assistant
connected to your store can read your discounts, dry-run a change against a sample
cart, publish it, and roll it back — all through the exact same REST routes (and
the exact same manage_woocommerce permission gate) your admin team already uses.

Same engine, new driver.
An ability is a thin, capability-gated wrapper over a route the admin app already calls. The agent doesn't get a private back door — it gets a steering wheel for the car you already drive.
If your store runs WordPress 6.9+ and you've installed an MCP host (the small connector layer described below), you can talk to your discounts. For example:
The assistant turns each request into one or more abilities (the verbs below), runs them against your store, and reports back. Nothing happens that your team couldn't do by hand in the Dino Discounts admin — and write actions are always reversible.
Plain-language control
Describe the discount you want; the agent assembles a valid rule, previews it, and publishes it — no wizard clicks.
Safe by construction
Writes validate before they touch anything, publish atomically snapshots the prior state, and every change is one restore-snapshot away from undone.
One source of truth
Abilities call the same dino-discounts/v1 REST routes as the admin React app. An agent's edit and a human's edit land on identical code paths.
Zero new attack surface
Every ability is gated on manage_woocommerce — twice. There are no public abilities and no separate auth.
The agent layer is inert by default. It wires itself onto two upstream hooks and does nothing at all unless the host that fires them is present — no fatal error, no new dependency, no behaviour change on a store that doesn't want it.
To turn it on you need:
wp_abilities_api_init). With just this, the abilities become callable over
the Abilities REST API.mcp_adapter_init)
— the WordPress feature plugin that exposes registered abilities to MCP
clients. With this installed, the same abilities are published as MCP tools
under an MCP server named dino-discounts, ready for any MCP-capable
assistant to discover and call.Dino Discounts itself needs no configuration: as soon as those hosts are active, the catalogue registers automatically. If either is absent, that part of the layer simply doesn't register.
Ten abilities ship, in two categories. Seven are read-only or compute-only
(safe to call freely); three mutate and are clearly marked. Every one is
gated on the manage_woocommerce capability.
dino-discounts-rules — read, preview, publish, roll back| Ability | Does | Effect |
|---|---|---|
| dino-discounts/get-rule-schema | Returns the live JSON Schema for a discount rule (the same schema the on-save sanitiser validates against). Read this first to learn the rule shape. | Read-only |
| dino-discounts/list-rules | Returns the full active rule set, with each rule's store-wide redemption count. | Read-only |
| dino-discounts/get-rule | Returns one active rule by its id (UUID or integer). | Read-only |
| dino-discounts/preview-cart | Prices a hypothetical cart against a supplied rule set — the same dry-run the admin Cart Preview uses. Optionally returns a debug trace of which rules matched. | Compute-only (no writes) |
| dino-discounts/publish-rule | Safely creates or updates a single rule: validate → upsert → optional preview → publish → report rollback snapshot. See the worked flow. | Mutating |
| dino-discounts/list-snapshots | Returns the rule history: every audit snapshot (id, name, comment, timestamp, diff summary) written on publish or reset. | Read-only |
| dino-discounts/restore-snapshot | Atomically restores the active rules to a previous snapshot. Writes a fresh snapshot of the current state first, so the restore is itself reversible. | Mutating |
dino-discounts-insights — analytics & performance (read-only)| Ability | Does | Effect |
|---|---|---|
| dino-discounts/read-discount-analytics | Per-rule discount performance from completed orders: how often each rule applied and the total discounted, over an optional date range. | Read-only |
| dino-discounts/read-coupon-analytics | Coupon-code redemption analytics over an optional date range. | Read-only |
| dino-discounts/read-rule-performance | The discount-engine performance log: recent rule-evaluation timing traces, to spot slow rules. | Read-only |
manage_woocommerce — the same
capability AbstractController::check_permission()
enforces on every admin REST route.permission_callback before executing, and the
underlying REST controller re-checks it when the call dispatches. A request
that slips one gate still meets the other.dino_discounts_ability_forbidden
error (HTTP 401/403), not a silent failure.dino-discounts/v1 route in-process
with rest_do_request(). The route still owns validation, the capability gate,
the schema sanitiser, and the snapshot-on-publish audit trail.When the MCP Adapter is active, an MCP client connecting to the dino-discounts
server discovers these ten tools (the description text is what the agent uses to
pick the right one):
MCP server: dino-discounts dino-discounts/get-rule-schema Read the discount rule schema (read-only) dino-discounts/list-rules List discount rules (read-only) dino-discounts/get-rule Get a single discount rule by id (read-only) dino-discounts/preview-cart Dry-run a cart against rules (compute-only) dino-discounts/publish-rule Publish (create/update) a rule (mutating) dino-discounts/list-snapshots List rule history snapshots (read-only) dino-discounts/restore-snapshot Roll back rules to a snapshot (mutating) dino-discounts/read-discount-analytics Per-rule discount analytics (read-only) dino-discounts/read-coupon-analytics Coupon redemption analytics (read-only) dino-discounts/read-rule-performance Engine performance log (read-only)How an assistant typically maps a request to one or more abilities:
| You ask | Abilities the agent calls |
|---|---|
| "Which discounts are pulling their weight this month?" | read-discount-analytics |
| "Show me rule r-summer-10." | get-rule |
| "What would a £60 cart of two T-shirts get right now?" | list-rules → preview-cart |
| "Add 10% off orders over £50 and publish it." | get-rule-schema → publish-rule |
| "Undo the last rule change." | list-snapshots → restore-snapshot |
| "Why did checkout feel slow after that last change?" | read-rule-performance |
The publish-rule ability is the flagship — and it is deliberately not a
blind POST /rules (which would replace the entire rule set, so a careless
"add one rule" could wipe the rest). Instead it runs a safety chain, every step
of which is an existing REST route:
/rules/diff-summary. If it's
incomplete, the call aborts before any write and returns a recoverable
rule.schema_invalid error (code + message + HTTP 400) so the agent knows the
rule wasn't publishable and can fix it.id: a matching id
is replaced, a new id is appended. Omit id and one is generated and
returned, so the agent can re-publish the same rule idempotently later.preview_cart, the merged set is
dry-run priced against it as an extra check; a preview error aborts the
publish.rollback_snapshot_id, so undo is a single
restore-snapshot away.A publish-rule call (the rule object is in the shape get-rule-schema
returns — who / when / where / trigger / rewards / messaging):
{ "rule": { "name": "Spend £50, get 10% off", "is_active": true, "who": { "roles": null, "is_logged_in": null }, "when": { "schedule": null }, "where": { "scope": "entire-cart" }, "trigger": { "type": "cart-subtotal", "min_subtotal": 50 }, "rewards": { "type": "percentage", "amount": 10 }, "messaging": { "cart_label": "Spend & save: 10% off", "cart_label_visible": true } }, "preview_cart": [ { "isCustom": true, "name": "Sample item", "price": 60, "quantity": 1 } ], "snapshot_name": "Add spend-and-save 10%", "comment": "Created via AI assistant"}A successful response — note the rollback_snapshot_id for one-call undo:
{ "published": true, "rule_id": "f1c2…-uuid", "created": true, "updated": false, "rule_count": 7, "rollback_snapshot_id": 412, "archived_coupons": [], "preview": { "success": true, "cart": [ /* priced cart line items */ ], "discounts": [ /* the discounts that applied, per rule — the £6 off lives here */ ], "cart_total": 60.0, "cart_tax": 0, "effective_currency": "GBP" }}The preview body is exactly what the preview-cart ability returns — see its
shape in the REST API Reference.
If the rule were incomplete, step 1 would short-circuit instead — the agent gets back a recoverable error (and nothing is written):
{ "code": "rule.schema_invalid", "message": "Every discount has errors — fix at least one before publishing.", "data": { "status": 400 }}The ability layer normalises every failure to this envelope — a stable code, a
human-readable message, and the HTTP status — so the agent can branch on
why a call failed (forbidden, validation, conflict) and adjust.
To undo, the agent reads list-snapshots (or reuses the rollback_snapshot_id)
and calls restore-snapshot with that id — which itself snapshots the current
state first, so even the rollback is reversible.
The layer is intentionally thin. Three small classes under includes/Abilities/
do all of it:
AbilityCatalog — declares every ability and its two categories as pure
data: name, label, description (this drives the agent's tool selection),
capability, REST verb + route, and the input/output JSON Schemas. Routes are
built from AbstractController::REST_NAMESPACE (dino-discounts/v1), so a
catalogue target can't drift from the registered REST surface without a test
failing.RestGateway — the execution seam. It turns an ability call into a
WP_REST_Request, dispatches it with rest_do_request() (the same dispatcher
WordPress uses for an HTTP REST call, so the controller runs its full
validation + capability gate + handler), and normalises the result: a 2xx
response becomes the ability's data; a >= 400 response or WP_Error becomes
a stable, agent-recoverable error envelope (code + message + HTTP status)
the agent can read and adjust to.RuleComposer — the only place that does more than mirror one route. It
chains several routes through the gateway for the two abilities that need it:
get-rule (GET /rules then pick by id, since the store has no single-rule
read route) and the publish-rule safety chain above.AbilitiesModule wires the catalogue onto wp_abilities_api_init (registering
each category then each ability via wp_register_ability()) and
mcp_adapter_init (registering the dino-discounts MCP server with the same
tools). Both callbacks guard on the host being present, which is why the layer is
inert when it isn't.
Because every ability bottoms out in a dino-discounts/v1 route, the
REST API Reference is the authoritative
description of what each one ultimately does.