Home / Guides

For developers

Implementing UCP on PrestaShop 8/9: the complete guide

AI agent commerce surfaces are coming. Google, Shopify and Walmart published UCP (Universal Commerce Protocol, Apache 2.0); Anthropic pushes MCP. For a PrestaShop store the question is no longer whether but how to become readable by these agents. Here's how we did it — decision by decision, with the real code from Fondouk.

The golden rule: implement the spec, not a blog post

UCP is a versioned spec (date YYYY-MM-DD) with JSON Schemas. We target the stable release 2026-04-08 and validate every response against the published schemas — not an internal copy. That's what guarantees a third-party agent understands us.

The discovery manifest lives at /.well-known/ucp. An agent reads it, finds the endpoint and the capabilities, then queries the store — nothing hard-coded:

{
  "ucp": {
    "version": "2026-04-08",
    "services": {
      "dev.ucp.shopping": [
        { "version": "2026-04-08", "transport": "rest",
          "endpoint": "https://demo.fondouk.dev/fondouk/ucp",
          "schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json" },
        { "version": "2026-04-08", "transport": "mcp",
          "endpoint": "https://demo.fondouk.dev/fondouk/ucp/mcp",
          "schema": "https://ucp.dev/2026-04-08/services/shopping/mcp.json" }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [{ "version": "2026-04-08",
        "schema": "https://ucp.dev/2026-04-08/capabilities/shopping/catalog/search.json" }],
      "dev.ucp.shopping.catalog.lookup": [{ "version": "2026-04-08",
        "schema": "https://ucp.dev/2026-04-08/capabilities/shopping/catalog/lookup.json" }]
    },
    "payment_handlers": {}
  }
}

Decision #1 — ORM/Presenter, not the webservice

PrestaShop's /api webservice returns raw data: base price, no computed tax-inclusive price, no discounts applied, stock in a separate call (N+1). Reproducing the storefront price would mean re-implementing the price engine — a source of divergence.

So we use the internal classes (Product::getPriceStatic, StockAvailable, SpecificPrice). The single most important detail is that the price is computed in an anonymous-visitor context — this is the security model:

// Does the shop display tax-included prices? (group/shop setting)
$displayTax = (Product::getTaxCalculationMethod() === PS_TAX_INC);

$price = Product::getPriceStatic(
    $idProduct,
    $displayTax,                 // tax-incl or tax-excl, per the shop's display setting
    $idProductAttribute,
    $decimals,
    null,
    false,
    /* usereduc */ true,
    1,
    false,
    /* id_customer */ 0,         // anonymous — no customer
    null,
    null,
    $specificPriceOutput,
    true,
    true,
    $context,
    /* use_customer_price */ false  // never a customer-specific price
);
// group = PS_UNIDENTIFIED_GROUP → public specific prices apply,
// B2B / group prices never do. What an anonymous visitor sees, nothing more.

That single call, with id_customer = 0 and use_customer_price = false, is why a B2B price can never leak through an agent request.

Decision #2 — /.well-known/ routing across PS8 and PS9

The front office is migrated to Symfony in neither PS8 nor PS9 (the FrontKernel is experimental). The portable mechanism is the legacy ModuleFrontController + moduleRoutes hook. Only the manifest needs the well-known path; the REST/MCP endpoints live under a module base:

public function hookModuleRoutes()
{
    $mod = ['fc' => 'module', 'module' => 'fondouk'];
    return [
        'module-fondouk-ucp' => [
            'rule' => '.well-known/ucp', 'keywords' => [],
            'controller' => 'ucp', 'params' => $mod,
        ],
        'module-fondouk-catalog-search' => [
            'rule' => 'fondouk/ucp/catalog/search', 'keywords' => [],
            'controller' => 'catalog', 'params' => $mod + ['action' => 'search'],
        ],
        // … lookup, product, mcp, llms.txt
    ];
}

Prerequisite: URL rewriting. Known trap: a physical .well-known/ directory (created by Let's Encrypt) short-circuits Apache via the .htaccess -d rule. We detect and surface it; a static-file fallback is opt-in.

The pivot: a decoupled core

The most important piece isn't the module — it's the Capability Graph: a neutral pivot format (versioned JSON Schema) with zero PrestaShop dependency. The module feeds it by introspection; adapters project it into UCP (REST, MCP). Here's the shape a variant takes in the graph — note prices is an array (multi-currency) and quantity is marked internal, never serialized to an agent:

"variant": {
  "required": ["id", "prices", "availability"],
  "properties": {
    "id":    { "type": "string" },
    "prices": { "type": "array", "items": { "required": ["price"],
      "properties": { "price": { "$ref": "#/$defs/money" }, "list_price": { "$ref": "#/$defs/money" } } } },
    "availability": { "required": ["available"], "properties": {
      "available": { "type": "boolean" }, "status": { "type": "string" },
      "quantity": { "type": "integer", "description": "Internal — NEVER exposed by adapters." } } }
  }
}

A platform that can produce this graph inherits every adapter for free. That's the whole point of the pivot.

What an agent actually gets

Discover, then query — the endpoint comes from the manifest:

curl -s -X POST https://demo.fondouk.dev/fondouk/ucp/catalog/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"t-shirt","pagination":{"limit":1}}'
{
  "ucp": { "version": "2026-04-08",
    "capabilities": { "dev.ucp.shopping.catalog.search": [{ "version": "2026-04-08" }] } },
  "products": [{
    "id": "gid://demo.fondouk.dev/1/product/1",
    "title": "Hummingbird printed t-shirt",
    "price_range": { "min": { "amount": 2294, "currency": "EUR" },
                     "max": { "amount": 2294, "currency": "EUR" } },
    "variants": [{
      "id": "gid://demo.fondouk.dev/1/variant/1-1", "title": "S / White",
      "price": { "amount": 2294, "currency": "EUR" },
      "availability": { "available": true, "status": "in_stock" }
    }]
  }],
  "pagination": { "has_next_page": true, "cursor": "eyJvIjoxfQ", "total_count": 6 }
}

amount is in minor units (2294 = €22.94), currency explicit — as the published UCP schema mandates. The same graph feeds the MCP server, so tools/call search_catalog returns the same catalog data in its structuredContent.

GET convenience for agentic browsers. POST is the canonical transport, but a browser-style agent that only issues GET requests reaches the same result by putting the query on the URL — non-normative, advertised as x-fondouk.get_convenience in the manifest:

curl -s 'https://demo.fondouk.dev/fondouk/ucp?capability=dev.ucp.shopping.catalog.search&query=t-shirt&limit=1'

A manifest is a promise

The detail that separates a usable server from a merely conformant one: only declare what actually responds. While an endpoint is a stub, its capability does not appear — services: {}, capabilities: {}. An empty manifest beats a lying one: on the day of a real agent test, a 501 on an advertised capability breaks trust.

Hardening, because "survive 50 agents" is tested, not declared

Token-bucket rate limiting (60/min, burst 120) with Retry-After, cache invalidated by product hooks, request-body and batch size caps (413/400). Proven under crawl on both PS8 and PS9: the store stays responsive.

In short

Read-only, zero configuration, public = public. Visibility is free forever; the rest — checkout (Fondouk Pro's paid layer), other platforms — is backlog. The code is open (MIT): github.com/fondouk-dev/fondouk.

Free and Pro coexist. Install Fondouk Pro alongside the free module and the free one politely stands by — it defers its routes so there is never a duplicate manifest — while Pro serves and adds the back-office dashboard. Uninstall Pro and the free module resumes serving on its own, no reinstall.

The Fondouk team — Synapsea

← All guides

Fondouk Pro

The module in this guide is free and stays free. Fondouk Pro adds an agent analytics dashboard on top — which AI agents visit your store, which products they view, per-agent control (allow / throttle / block) and GDPR tooling — on the official PrestaShop Addons marketplace.

Get Fondouk Pro on Addons   See the live dashboard →