A multi-protocol agentic-commerce validator for the AgentProbe fleet. It validates both commerce protocols side by side:
- UCP (Universal Commerce Protocol, Google + Shopify) — fetches the merchant-hosted
/.well-known/ucpmanifest and validates its static structure (version, services, capabilities, payment handlers, reverse-domain keys, HTTPS URLs), and detects + flags the MCP transport. - ACP (Agentic Commerce Protocol, OpenAI + Stripe) — validates a product-feed schema (when a feed sample is supplied) and detects the unreleased
/.well-known/acp.jsondiscovery document.
It emits structured findings { code, severity, location, message }, classifies capabilities into a boolean map, and builds a stable snapshot — the unified leaf contract the mcp-agent-assessment engine consumes. v1 is static-only; the live checkout phase (UCP checkout state machine + payment reachability; ACP's 5 checkout endpoints + delegated payment) is deferred.
git clone https://github.com/agentprobe/ucp-commerce-validator.git
cd ucp-commerce-validator
npm installimport { CommerceValidator } from 'ucp-commerce-validator'
// UCP (default protocol): fetches https://olaplex.com/.well-known/ucp
const ucp = await CommerceValidator.start( { endpoint: 'https://olaplex.com', protocol: 'ucp' } )
console.log( ucp.status, ucp.findings, ucp.categories, ucp.entries )
// ACP: validates a product feed sample + detects /.well-known/acp.json
const acp = await CommerceValidator.start( { endpoint: 'https://merchant.example.com', protocol: 'acp', feed: [ /* items */ ] } )- UCP
.well-known/ucpmanifest structure validation (wrapper,versionYYYY-MM-DD,supported_versions,services,capabilities.dev.ucp.shopping.checkout, non-emptypayment_handlers). - Reverse-domain registry-key + absolute-HTTPS-URL governance checks.
- MCP transport detection + flagging (declared? endpoint present?) — not deep MCP validation.
- ACP product-feed schema validation (required fields, availability enum, unique
item_id, eligibility cross-field, image HTTPS, max lengths). - ACP
/.well-known/acp.jsondiscovery detection (absence = valid answer, reported as info). - Structured findings with code bands 6xx (UCP) / 7xx (ACP) — disjoint from the rest of the fleet.
compare()snapshot diff withCMPintegrity findings.
flowchart LR
A[endpoint] --> S["CommerceValidator.start"]
S --> P{protocol dispatch}
P -->|ucp| U["protocols/ucp: GET /.well-known/ucp → UcpStructureValidator (VAL/CON/UCP-6xx)"]
P -->|acp| C["protocols/acp: feed schema + /.well-known/acp.json (VAL/CON-7xx, ACP)"]
U --> B["core/SnapshotBuilder → { status, findings, categories, entries }"]
C --> B
U -. deferred .-> L1[Live: UCP mcp.endpoint + payment + checkout]
C -. deferred .-> L2[Live: ACP 5 checkout endpoints + delegated payment]
Plugin architecture: shared src/core/ (Validation, HttpConnector, SnapshotBuilder, Finding) + src/protocols/{ucp,acp}/ (StructureValidator + CapabilityClassifier per protocol).
All methods are static and use object parameters / object returns.
| Key | Type | Description | Required |
|---|---|---|---|
endpoint |
string | Merchant base URL | yes |
timeout |
number | Fetch timeout in ms (default 10000) |
no |
protocol |
string | 'ucp' or 'acp' (default: probe → 'ucp') |
no |
feed |
array | ACP product-feed sample (ACP path only) | no |
Returns { status, findings, categories, entries }.
| Key | Type | Description | Required |
|---|---|---|---|
before |
object | Earlier snapshot ({ categories, entries }) |
yes |
after |
object | Later snapshot | yes |
Returns { status, findings, hasChanges, diff }.
Findings are structured objects { code, severity, location, message } conforming to the AgentProbe finding-object spec (severity is lowercase: error / warning / info). This validator owns the commerce bands — UCP: VAL-6xx / CON-6xx / UCP-0xx; ACP: VAL-7xx / CON-7xx / ACP-0xx. CMP-* codes are emitted by compare().
| Code | Severity | Location | Message |
|---|---|---|---|
| VAL-601 | error | endpoint | Missing value |
| VAL-602 | error | endpoint | Must be a string |
| VAL-603 | error | endpoint | Must not be empty |
| VAL-604 | error | endpoint | Must be a valid URL |
| VAL-605 | error | timeout | Must be a number |
| VAL-606 | error | timeout | Must be greater than 0 |
| VAL-607 | error | protocol | Must be a string |
| VAL-608 | error | protocol | Must be "ucp" or "acp" |
| VAL-609 | error | before | Missing value / not an object / missing categories or entries |
| VAL-610 | error | after | Missing value / not an object / missing categories or entries |
| VAL-701 | error | feed | Must be an array of product items |
| VAL-702 | error | feed | Must not be empty |
| Code | Severity | Location | Message |
|---|---|---|---|
| CON-610 / CON-710 | info | null | Server not reachable |
| CON-611 / CON-711 | info | null | Resource not found (HTTP 404) |
| CON-612 / CON-712 | info | null | HTTP error (status) |
| CON-613 / CON-713 | info | null | Response is not valid JSON |
| CON-614 / CON-714 | info | null | Request timeout exceeded |
| Code | Severity | Location | Message |
|---|---|---|---|
| UCP-001 | error | ucp | Manifest root is missing the "ucp" wrapper object |
| UCP-002 | error | ucp.version | Missing required field |
| UCP-003 | warning | ucp.version | Must be a YYYY-MM-DD date string |
| UCP-004 | error | ucp.services | Missing or empty services registry |
| UCP-005 | warning | ucp.services | No MCP transport with an endpoint declared |
| UCP-006 | error | ucp.capabilities…checkout | Missing checkout capability (minimum viable agentic commerce) |
| UCP-007 | error | ucp.payment_handlers | Missing or empty payment_handlers (checkout blocked) |
| UCP-008 | warning | ucp.{key} | Registry key is not a reverse-domain name |
| UCP-009 | warning | ucp.services[i] | Service entry missing version, spec or transport |
| UCP-010 | warning | ucp.capabilities[i] | Capability entry missing version, spec or schema |
| UCP-011 | warning | ucp.payment_handlers[i] | Payment-handler entry missing id, version, spec, schema or config |
| UCP-012 | warning | content-type | Content-Type is not application/json |
| UCP-013 | info | signing_keys | No signing_keys present (normal for Shopify-hosted manifests) |
| UCP-014 | warning | ucp | URL is not absolute HTTPS |
| Code | Severity | Location | Message |
|---|---|---|---|
| ACP-001 | warning | feed[i] | Missing required field(s) |
| ACP-002 | warning | feed[i].availability | Value is not in the allowed availability enum |
| ACP-003 | warning | feed | item_id values are not unique across the feed |
| ACP-004 | warning | feed[i] | is_eligible_checkout=true requires is_eligible_search=true |
| ACP-005 | warning | feed[i].image_url | image_url must be an absolute HTTPS URL |
| ACP-006 | warning | feed[i] | Field(s) exceed max length |
| ACP-010 | info | .well-known/acp.json | ACP discovery document present (Proposal-stage RFC) |
| ACP-011 | info | .well-known/acp.json | No ACP discovery document (HTTP 404 = does not support ACP; not a failure) |
| ACP-012 | warning | .well-known/acp.json | Discovery document missing protocol, api_base_url or transports |
| ACP-013 | warning | .well-known/acp.json | Discovery protocol.name is not "acp" |
| Code | Severity | Location | Message |
|---|---|---|---|
| CMP-001 | warning | compare | Snapshots are from different endpoints |
| CMP-002 | warning | compare | Before snapshot has no timestamp |
| CMP-003 | warning | compare | After snapshot is older than before |
MIT