Developer guide

Architecture overview

The shop is the server in UCP terminology. The platform is the

1. Architecture overview

1.1 System diagram

mermaid
flowchart LR
    subgraph Platform["Platform agent<br/>(ChatGPT / Perplexity / Gemini / custom)"]
        agent[Agent runtime]
        agentProfile[Platform profile<br/>JSON @ /platforms/...]
    end

    subgraph Shopware["Shopware shop<br/>(per Sales Channel Domain)"]
        wellKnown["/.well-known/ucp<br/>WellKnownUcpController"]
        ucpRoutes["/ucp/v1/* + /ucp/mcp<br/>UcpRouteScope"]
        capabilities["CapabilityRegistry<br/>(DI tag: ucp.capability)"]
        payments["PaymentHandlerRegistry<br/>(DI tag: ucp.payment_handler)"]
        storeApi["Store-API routes<br/>(existing)"]
        admin["Admin UI<br/>sw-settings-ucp"]
        db[(Sales Channel<br/>UCP config + keys)]
    end

    agent -->|GET /.well-known/ucp| wellKnown
    wellKnown -->|reads| db
    wellKnown -->|consults| capabilities
    wellKnown -->|consults| payments
    wellKnown -->|UCP profile JSON| agent

    agent -->|UCP-Agent header: profile URL| ucpRoutes
    ucpRoutes -->|fetch + validate| agentProfile
    ucpRoutes -->|delegate| storeApi
    ucpRoutes -->|response w/ envelope| agent

    admin -->|REST Admin-API| db

The shop is the server in UCP terminology. The platform is the client. Every interaction starts with the platform reading the shop's profile to learn what it supports, after which the platform makes calls through the negotiated transport against the negotiated capabilities.

For a step-by-step explanation of the runtime handshake, signatures, OAuth, AP2 and webhooks, see Runtime flows. For exact field-level data mapping between Shopware and UCP payloads, see Shopware field mappings.

1.2 The Sales-Channel boundary

Every UCP-related state belongs to a Sales Channel. Two channels on the same Shopware install:

  • have independent active toggles, capability sets, transports, signing keys
  • publish independent profiles at the public /.well-known/ucp of their domain
  • have independent OAuth Authorization Servers for Identity Linking

The full resolution chain on a UCP request:

mermaid
flowchart TD
    A[HTTP request comes in] --> B{Hostname<br/>matches a<br/>SalesChannelDomain?}
    B -- no --> Z[404]
    B -- yes --> C[Resolve SalesChannelContext]
    C --> D{UCP active<br/>for this channel?}
    D -- no --> Z
    D -- yes --> E[Load ucp_sales_channel_config]
    E --> F[Validate UCP-Agent header<br/>+ fetch platform profile]
    F --> G[Compute capability intersection]
    G --> H[Dispatch to capability controller]
    H --> I[Delegate to Store-API]
    I --> J[Wrap response in UCP envelope]
    J --> K[200 OK + ucp.capabilities]

1.3 Request lifecycle for one UCP call

mermaid
sequenceDiagram
    participant P as Platform
    participant SW as Shopware UCP
    participant Cap as CapabilityRegistry
    participant API as Store-API
    participant DB as Database

    P->>SW: POST /ucp/v1/checkout-sessions<br/>UCP-Agent: profile="https://platform/.../profile.json"<br/>{ cart_id, … }
    SW->>SW: UcpAgentRequestResolver<br/>extracts + caches platform profile
    SW->>Cap: pick controller for "create_checkout"
    Cap->>API: CheckoutController->create()<br/>(uses CartOrderRoute + OrderConverter)
    API->>DB: write cart + checkout state
    API-->>Cap: domain result
    Cap->>SW: wrap in UCP envelope<br/>+ active capabilities
    SW-->>P: 200 OK<br/>{ ucp: { version, capabilities, … }, id, status, … }

1.4 The three registries

All three Shopware UCP registries are populated at compile time via Symfony's container, not at runtime. This makes startup faster and gives you compile-time errors if your tag attributes are wrong.

RegistryCompiler passTagRequired attributes
CapabilityRegistryUcpCapabilityCompilerPassucp.capabilitycapability (the reverse-domain name)
UcpPaymentHandlerRegistryUcpPaymentHandlerCompilerPassucp.payment_handlerhandler_id (the reverse-domain name_id)
UcpMcpToolRegistryUcpMcpToolCompilerPassucp.mcp_tooltool_name, capability

1.5 The Negotiation algorithm

The exact algorithm runs in Negotiation/NegotiationOrchestrator.php. It mirrors what the spec calls the Intersection Algorithm:

mermaid
flowchart LR
    A[Business caps] --> M{intersect by<br/>name}
    P[Platform caps] --> M
    M --> V{pick highest<br/>mutual version}
    V -- empty --> X[exclude]
    V -- non-empty --> N[include]
    N --> O{has extends?}
    O -- no --> R[result]
    O -- yes --> O2{all parents<br/>in result?}
    O2 -- yes --> R
    O2 -- no --> X
    X --> Q[loop: re-prune<br/>until stable]
    Q --> R

For an extension with multiple parents (e.g. dev.ucp.shopping.discount extends both cart and checkout), at least one parent needs to be present for the extension to survive pruning — not all of them.

1.6 The envelope

Every UCP response contains a ucp envelope that lists the active capabilities relevant to this response. Per spec, only capabilities that match the operation type appear; the rest of the negotiated set is omitted from the response (it stays implicit).

Response typeIncludesDoes NOT include
Checkoutcheckout, discount, fulfillmentcart, order
Cartcart, discountcheckout, fulfillment, order
Orderordercheckout, cart, discount

The envelope-building logic lives in Capability/AbstractUcpCapability.php and is shared across every capability — you generally do not need to touch it.

→ Next: Adding a new UCP capability