angeo / module-ucp

angeo/module-ucp

Spec-compliant Universal Commerce Protocol (UCP) profile generator for Magento 2. Generates /.well-known/ucp at protocol version 2026-04-08 with ECDSA P-256 signing keys, declared capabilities, and proper cache headers. v0.1.x is profile-only — catalog, cart, checkout endpoints land in later releases.

magento2-module 2.4.6-2.4.9 Compatible Based on composer requirements only QA: pending MIT
Viewing version 0.1.0-beta. Latest version is 0.1.1-beta. Switch to latest

Packagist Version
License
PHP
CI

angeo/module-ucp

Spec-compliant Universal Commerce Protocol (UCP) profile generator for Magento 2.

Generates /.well-known/ucp at protocol version 2026-04-08 with ECDSA P-256 signing keys, declared capabilities, and proper cache headers — the discovery layer AI agents fetch first when interacting with your store.

v0.1.0-beta is the profile-only release. It exposes a valid UCP profile to AI platforms. The actual REST endpoints for catalog, cart, checkout and order land in later versions. Enabling a capability here adds it to the advertised profile but does NOT implement the matching endpoint — leave capabilities disabled in production until the matching endpoint module is installed. The module is tagged 0.1.0-beta to signal pre-stable status under semver.


What it does

  • Serves a spec-compliant UCP profile at https://yourstore.com/.well-known/ucp
  • Declares the dev.ucp.shopping REST service binding
  • Lets you toggle which capabilities to advertise: catalog, cart, checkout, order, identity_linking
  • Generates ECDSA P-256 signing keys (JWK + PEM) via CLI
  • Returns correct Cache-Control: public, max-age=300 headers
  • Returns 404 ucp_not_advertised when the module is disabled — never a misleading empty profile

Requirements

Requirement Version
Magento 2.4.7+
PHP 8.2 or 8.3 or 8.4
OpenSSL extension enabled

Installation

composer require angeo/module-ucp
bin/magento module:enable Angeo_Ucp
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Quick start

1. Generate signing keys

bin/magento angeo:ucp:keys:generate

This generates an ECDSA P-256 keypair, saves the public JWK to config, and prints the private PEM to stdout exactly once. Copy the private PEM into app/etc/env.php:

'ucp' => [
    'signing_keys' => [
        'angeo-ucp-2026-abcd' => '<PEM contents>',
    ],
],

2. Enable the profile in admin

Stores → Configuration → Angeo → UCP → General → Advertise UCP Profile: Yes.

3. Verify

curl -s https://yourstore.com/.well-known/ucp | python3 -m json.tool

Expected output:

{
    "ucp": {
        "version": "2026-04-08",
        "services": {
            "dev.ucp.shopping": [
                {
                    "version": "2026-04-08",
                    "spec": "https://ucp.dev/2026-04-08/specification/overview",
                    "transport": "rest",
                    "endpoint": "https://yourstore.com/rest/V1/ucp",
                    "schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json"
                }
            ]
        },
        "capabilities": {}
    },
    "signing_keys": [
        {
            "kid": "angeo-ucp-2026-abcd",
            "kty": "EC",
            "crv": "P-256",
            "x": "...",
            "y": "...",
            "use": "sig",
            "alg": "ES256"
        }
    ]
}

4. Validate (optional)

bin/magento angeo:ucp:validate --json

Prints a green pass if the profile is well-formed; non-zero exit on validation failure (useful as a cron healthcheck).


Admin configuration

Path Field Purpose
General → Advertise UCP Profile Yes/No Master switch for /.well-known/ucp
Capabilities → Catalog Yes/No Advertise dev.ucp.shopping.catalog
Capabilities → Cart Yes/No Advertise dev.ucp.shopping.cart
Capabilities → Checkout Yes/No Advertise dev.ucp.shopping.checkout
Capabilities → Order Yes/No Advertise dev.ucp.shopping.order
Capabilities → Identity Linking Yes/No Advertise dev.ucp.common.identity_linking
Transport → REST Endpoint URL URL Override the default {baseUrl}/rest/V1/ucp
Keys → Public JWK textarea Populated by keys:generate

All capability and transport settings are scoped to the store view — multi-store deployments can advertise different profiles per storefront.

Multi-store on a single domain: /.well-known/ucp is a single path on the host, so Magento's StoreResolver picks one store view to render it. If multiple store views share a host, the bare host resolves to one of them (typically the default), and that store's profile is what AI agents see. To advertise different UCP profiles per store, give each store its own hostname.


CLI commands

Command Purpose
bin/magento angeo:ucp:keys:generate [--force] Generate a new P-256 keypair; print private PEM once, save public JWK to config
bin/magento angeo:ucp:validate [--json] Validate the generated profile structure; optionally print the JSON

Architecture

HTTP request: GET /.well-known/ucp
        │
        ▼
Angeo\Ucp\Controller\Router  (sortOrder=22, runs before CMS router)
        │
        ▼
Angeo\Ucp\Controller\WellKnown\Ucp  (HttpGetActionInterface)
        │
        ▼
Angeo\Ucp\Model\ProfileGenerator   ←  Angeo\Ucp\Model\Config
        │                                     │
        │                                     └── reads core_config_data
        ▼
JSON response with:
  - Content-Type: application/json
  - Cache-Control: public, max-age=300
  - X-UCP-Version: 2026-04-08

Security model

  • Private keys are never stored in the database. The keys:generate command prints them once to stdout. Operators are responsible for placing them in app/etc/env.php or a secrets manager.
  • Config::getPublicSigningKeys() strips private JWK fields (d, p, q, dp, dq, qi) as defence-in-depth before serialization, in case private material ever ends up in config by mistake.
  • HTTPS-only by design. The UCP spec requires HTTPS for /.well-known/ucp; this module's audit check refuses a non-HTTPS endpoint URL.
  • Cache headers comply with the spec (public, max-age >= 60). The module sends max-age=300.

Testing

composer install
vendor/bin/phpunit --testsuite=unit

Three test classes cover the critical paths:

  • ProfileGeneratorTest — protocol version, namespace correctness, JSON encoding, capability toggling
  • JwkFormatterTest — RFC 7518 base64url coordinate encoding, 32-byte P-256 width, private-field rejection
  • ConfigTest — store-base-url fallback, private-key sanitization

CI runs the matrix PHP 8.2 / 8.3 / 8.4 on every push.


Roadmap

Version Scope
0.1.0-beta (current) Profile generator, signing keys, admin UI, CLI, tests, CI
0.2.0 dev.ucp.shopping.catalog real endpoint (search + lookup) backed by Magento_Catalog
0.3.0 UCP-Agent header parsing, platform profile fetching, capability intersection
0.4.0 RFC 9421 HTTP Message Signatures for outgoing responses
0.5.0 Cart capability
1.0.0 Production-ready full UCP shopping vertical

Roadmap will shift as the UCP specification evolves.


License

MIT License — see LICENSE

Maintained by Ievgenii Gryshkun · [email protected]

Changelog

All notable changes to angeo/module-ucp are documented here.
Format follows Keep a Changelog.
Versioning follows Semantic Versioning.

[0.1.0-beta] - 2026-05-23

Fixed

  • Critical: composer.json was invalid JSON (trailing comma after
    support.source), preventing composer require from succeeding.
  • magento/module-store is now explicitly required in composer.json
    and listed in module.xml <sequence>. Previously the module relied
    on transitive loading via Magento_Backend, which works in practice
    but fails the Magento Extension Quality Program (MEQP) checks.
  • Removed dead etc/frontend/routes.xml. The frontend route never
    resolved /.well-known/ucp anyway (Magento frontNames cannot contain
    dots), and the custom Angeo\Ucp\Controller\Router dispatches the
    action directly. The leftover routes.xml was misleading.
  • Removed unused imports in Controller/WellKnown/Ucp.php
    (HttpResponse, ResponseInterface, ActionInterface).

Changed

  • Angeo\Ucp\Model\Config now takes a Psr\Log\LoggerInterface as a
    third constructor argument. Throwables from StoreManager::getStore()
    during endpoint resolution and JSON-decode failures on stored JWKs
    are now logged at error / warning instead of swallowed silently.
  • When Config::getPublicSigningKeys() strips private JWK fields
    (d, p, q, dp, dq, qi), a warning is now logged so the
    operator can see they pasted a private key by mistake.
  • Controller\WellKnown\Ucp now also catches non-JsonException
    throwables from the generator and returns 500 profile_generation_failed instead of bubbling.
  • Tagged 0.1.0-beta rather than 0.1.0 to signal pre-stable status
    explicitly via semver pre-release tags.

Added

  • .github/workflows/ci.yml — the matrix PHP 8.2/8.3/8.4 GitHub Actions
    workflow that the README badge points to. PHPStan job is included
    with continue-on-error until the full Magento source tree is
    available in CI.
  • SECURITY.md — vulnerability reporting policy and the private-key
    custodianship model promoted out of README prose into a discoverable
    file.
  • ext-openssl and ext-json now declared as composer requirements.
  • Two new ConfigTest cases covering the new logging behaviour
    (private-field warning, store-resolution-throw error).

Notes

  • v0.1.0-beta is profile-only. Enabling a capability adds it to
    the advertised profile but does NOT implement the corresponding REST
    endpoints. Real catalog/cart/checkout endpoints land in later releases.
  • Private signing keys are intentionally not stored in the database.
    The keys:generate command prints the private PEM to stdout once;
    operators are responsible for placing it in app/etc/env.php or a
    secrets manager.

[0.1.0] - 2026-05-21 (yanked — composer.json was invalid JSON)

Added

  • Spec-compliant /.well-known/ucp profile generator at UCP protocol
    version 2026-04-08.
  • Custom router (Angeo\Ucp\Controller\Router) that maps /.well-known/ucp
    to a Magento controller without abusing frontName (dots are not allowed
    in Magento frontNames).
  • ECDSA P-256 signing key generator with JWK output:
    bin/magento angeo:ucp:keys:generate.
  • Profile validator: bin/magento angeo:ucp:validate [--json].
  • Admin configuration under Stores → Configuration → Angeo → UCP with
    per-store-view scoping for capability toggles.
  • Declared capabilities: dev.ucp.shopping.{catalog,cart,checkout,order}
    and dev.ucp.common.identity_linking.
  • Cache headers per UCP spec: Cache-Control: public, max-age=300.
  • PHPUnit test suite (PHP 8.2, 8.3, 8.4).
  • GitHub Actions CI matrix.
Versions
Version Stability QA Status Released
0.1.1-beta beta Incomplete 2026-05-23 17:53:53
0.1.0-beta beta Not tested 2026-05-23 17:28:26

Requires 7

Package Constraint
ext-json *
ext-openssl *
magento/framework >=103.0.0
magento/module-backend >=102.0.0
magento/module-config >=101.0.0
magento/module-store >=101.0.0
php >=8.2

Requires-dev 3

Package Constraint
magento/magento-coding-standard *
phpstan/phpstan ^1.10
phpunit/phpunit ^10.0

No QA results yet

QA pipelines haven't run for this version. Status appears here once the vendor publishes a tagged release that gets ingested.

License
MIT
Authors
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.