Declarative vs. Imperative WebMCP: A Storefront Build Guide
Is a storefront action an HTML attribute or a JS function? With WebMCP, that is not a style question, it is an architecture decision every frontend team should make before the first sprint. This guide answers it practically: two code examples, one decision matrix, no new debate about boundaries or GEO. We already covered those in the posts linked below.
Two ways to make a storefront agent-actionable
WebMCP makes storefront actions executable for AI agents. How an action gets exposed to the agent comes down to two patterns:
- Declarative annotation: the action lives as an attribute directly in the HTML markup. A WebMCP runtime script reads the attribute and exposes the action, no extra JS code per action required.
- Imperative tool actuation: the action is a registered JS function with its own name, parameters, and return value. The agent calls the function like an API.
Both patterns produce the same outcome, the agent performs an action in the storefront, but they differ in effort, control, and failure surface.
The declarative path: HTML annotation
Declarative annotation fits actions that map 1:1 to a visible UI element, are short-lived, and do not need a multi-step state transition. A cart button is the standard example:
<button
data-webmcp-action="cart.add"
data-webmcp-target="product"
data-webmcp-params='{"variantId": "{{variant.id}}", "quantity": 1}'
data-webmcp-result="cart.summary"
>
Add to cart
</button>The annotation fully describes what happens: action name, target entity, parameters, expected result. No separate tool setup, no import, no build step. The runtime script scans the DOM, registers the action, done. The downside: any logic that cannot be expressed in an attribute value, validation, multi-step conditions, asynchronous intermediate steps, does not fit here.
The imperative path: JS tool actuation
Imperative tool actuation fits once an action needs business logic, multiple backend calls, or error handling:
webmcp.registerTool({
name: "cart.add",
description: "Adds a product variant to the active cart.",
parameters: {
variantId: { type: "string", required: true },
quantity: { type: "number", default: 1 }
},
async execute({ variantId, quantity }) {
const stock = await storefront.inventory.check(variantId);
if (stock.available < quantity) {
throw new Error("insufficient_stock");
}
const cart = await storefront.cart.addLine(variantId, quantity);
return { cartId: cart.id, itemCount: cart.itemCount, subtotal: cart.subtotal };
}
});The function encapsulates the check, the backend call, and the response shape. The agent only sees the name, the parameter schema, and the return value, not the implementation. That gives full control over error cases and state transitions, but costs setup effort per action: registration, typing, testing.
Decision matrix: which one, when?
| Criterion | Declarative annotation | Imperative tool actuation |
|---|---|---|
| Action complexity | Low, maps 1:1 to a UI element | High, multi-step logic |
| Backend calls | None or a single call | Several, possibly sequential |
| Error handling | Barely expressible | Fully in code |
| Setup effort per action | Minimal, set an attribute | Medium to high, registration, typing, tests |
| Maintenance on UI changes | Attribute travels with the element | Function is independent of markup |
| Typical example | Cart button, filter, sort | Checkout flow, discount calculation, stock check |
| Auditability | Visible directly in the HTML | Needs a tool-registry log |
Rule of thumb for the team: if the action fits in one sentence with no condition, annotate declaratively. Once an "if X then Y, else Z" enters the picture, register it imperatively.
Common mistakes when annotating
- Annotation overload: too many
data-webmcp-*attributes on one element with no clear naming convention, the agent can no longer tell priorities apart. - Tool without an error path: registered tools that return no structured response on failure, the agent then reads a crash as a success.
- Duplicate source of truth: the same action annotated declaratively and registered imperatively at the same time, the runtime script does not know which pattern applies.
- Missing result schema: the agent gets no structured success or failure back and has to guess from the UI text.
Combining both patterns in the same storefront
In practice, production storefronts mix both patterns. PDP actions like add to cart, save to wishlist, or switch variant run declaratively, because they are simple and tied to the UI. Checkout, discount, and stock logic runs imperatively, because it checks backend state and has to handle errors. That split is exactly why Laioutr, as a Frontend Management Platform, offers annotation and a tool registry in the same component layer. Component authors decide per action which pattern fits, without maintaining two separate systems. The frontend stays a Composable Headless Frontend, not two parallel stacks.
If you think of WebMCP as an operating model rather than a single feature, the broader framing lives under Frontend as a Service: annotation and tool actuation are two building blocks of the agent layer that is designed in from the start.
Build checklist
- List every storefront action agents should be allowed to perform: cart, wishlist, checkout steps, discount code.
- Sort each action using the matrix above: declarative or imperative.
- Annotate declarative actions directly in the component template, not in separate config files.
- Register imperative tools with a full parameter schema, not just a name.
- Test both paths with real agent traffic, not only with manual clicks.
- Document return values per action so agents can reliably interpret results.
If you want the fundamentals of frontend agent actuation, read WebMCP: When Frontends Become Agent Actions. For the GEO angle, how agent-ready frontends get cited in AI overviews, see Agent-Ready Frontend: GEO Meets WebMCP. The boundary between WebMCP and MCP in a commerce context, browser vs. server actuation, is covered in WebMCP vs. MCP Commerce: Browser vs. Server. And how frontends let agents write safely, governance and guardrails, is the topic of MCP Commerce Frontends: Letting Agents Write Safely.
This guide is deliberately implementation-focused: the annotation pattern, the tool-actuation pattern, the decision matrix. If you want the boundary and governance questions answered, the four posts above cover them. If you want to build today, the matrix and the two code examples are the direct starting point. For the platform behind all of it, visit the Laioutr homepage.