A profile at a well-known URL.
Every business publishes a profile at /.well-known/ucp declaring its protocol version, services, capabilities, and endpoints.
The Universal Commerce Protocol gives agents one typed contract — discovery, negotiation, REST and MCP — for transacting with any business. But most suppliers don't expose a UCP API; they expose a website. A3 closes that gap: it learns each supplier's site into deterministic stages, then serves those flows behind a real UCP server.
UCP is an open specification for how platforms (agents) and businesses transact. It standardises capability discovery, version negotiation, transport bindings, and payments — so an agent can deal with any compliant business without a bespoke integration. These are the moving parts A3 has to satisfy.
Every business publishes a profile at /.well-known/ucp declaring its protocol version, services, capabilities, and endpoints.
The platform advertises its profile per-request; the business intersects both capability sets and picks mutually-supported versions.
UCP-AgentFeatures are named {domain}.{service}.{capability} — e.g. dev.ucp.hotels.deals — encoding governance without a central registry.
The same operations are exposed over REST (OpenAPI) and MCP (JSON-RPC), so both classic clients and tool-calling agents can transact.
rest · mcpucp.Responses include the negotiated version and active capabilities, plus structured messages and a continue_url fallback.
UCP separates payment instruments from handlers so credentials flow platform → business only, keeping intermediaries out of PCI scope.
handlersUCP defines the contract an agent wants to call. The reality is that the vast majority of hotels, retailers, and travel suppliers will never ship a native UCP server. A3 stands in for them: it learns the live consumer site once, captures it as typed stages and a playbook, then replays those flows deterministically behind a UCP server — REST, MCP, discovery, and negotiation included.
# A typed UCP operation over REST or MCP POST /expedia-com/deals UCP-Agent: profile="https://agent.example/profile.json" { "hotelId": "31558266", "checkIn": "2026-09-14", "checkOut": "2026-09-17", "rooms": [{ "adults": 2 }] }
# No supplier API. A3 drives the real site: 1. negotiate capabilities from the agent profile 2. main stage builds the results URL + navigates 3. overlay-cookie dismisses the consent banner 4. room-results scrapes rooms, rates + checkoutIds 5. validate against the UCP schema, wrap in the ucp envelope # deterministic — no LLM in the loop at runtime
deals, checkout, book).
Inside each, stages are named after what the page is (room-results, select-deal,
guest-form), so site changes stay local and the UCP contract stays stable.
Operations are declared once as the single source of truth shared by both transports. An operation
binds a UCP capability to a workflow path, a Zod schema, its REST route, and
its MCP tool. Browser-backed operations run a workflow; lightweight ones
(book/status, booking/details) read from an in-memory store with no browser.
dev.ucp.hotels.deals.{
kind: 'workflow',
capability: 'dev.ucp.hotels.deals',
workflow: 'src/workflows/deals/HotelDeals.workflow.ts',
schema: GetHotelDealsRequestSchema,
rest: { method: 'POST', path: '/deals' },
tool: {
name: 'get_deals',
description: 'Get available room deals and rates for a hotel and stay.',
},
toPayload: out => ({
rooms: out.rooms ?? [],
hotel: out.hotel,
support: out.support,
}),
}
A UCP operation maps to a defineWorkflow with a typed input/output schema and a
directory of per-supplier stages. The workflow itself carries almost no logic — it points at the
schema and the sites directory. The real automation is learned into stages named after page states.
ctx.inputs and navigates directly.checkoutId per rate.export default defineWorkflow({ title: 'UCP Hotels — Deals', description: 'dev.ucp.hotels.deals — scrape rooms and rates', inputsSchema: GetHotelDealsRequestSchema.shape, outputsSchema: GetHotelDealsResponseSchema.shape, rootDir: 'src', sitesDir: workflowSitesDir('deals'), projectLocations: ucpOperationProjectLocations(WORKFLOW_ROOT), });
export default defineStage({ title: 'Room Results', description: 'Scrape room and rate deals from the hotel page', match: async (ctx) => { const url = new URL(ctx.page.url()); if (!url.hostname.includes('expedia.com')) return false; return await ctx.page.evaluate(() => !!document.querySelector('[data-stid^="property-offer-"]')); }, run: async (ctx) => { const rooms = await ctx.page.evaluate(scrapeOffers); ctx.outputs.rooms = normalize(rooms); // each rate has a checkoutId ctx.success(); }, });
/.well-known/ucp profile.
The server builds a business profile per supplier site, advertising the hotels service over both
REST and MCP and listing the capabilities it implements. Every path is prefixed with the supplier
slug (e.g. expedia-com), so one server can front many suppliers — each fully discoverable.
dev.ucp.hotels bound to REST and MCP endpoints.deals, checkout, book with spec + schema URLs.ucp.dev, matching the namespace authority.{
"ucp": {
"version": "2026-04-08",
"services": {
"dev.ucp.hotels": [
{ "transport": "rest", "endpoint": "…/expedia-com" },
{ "transport": "mcp", "endpoint": "…/expedia-com/ucp/mcp" }
]
},
"capabilities": {
"dev.ucp.hotels.deals": [{ "version": "2026-04-08", "spec": "https://ucp.dev/…" }],
"dev.ucp.hotels.checkout": [{ "version": "2026-04-08", "spec": "https://ucp.dev/…" }],
"dev.ucp.hotels.book": [{ "version": "2026-04-08", "spec": "https://ucp.dev/…" }]
}
}
}
Both transports delegate to the same UcpService, which validates input, negotiates
capabilities, runs the workflow, and returns a UCP envelope. REST routes by method and path; MCP
exposes each operation as a tool, with input nested under the capability's short domain key.
POST /<site>/deals with the UCP-Agent header.tools/call with get_deals and a nested deals argument.curl -X POST http://localhost:3004/expedia-com/deals \ -H 'Content-Type: application/json' \ -H 'UCP-Agent: profile="https://agent.example/profile.json"' \ -d '{ "hotelId": "31558266", "checkIn": "2026-09-14", "checkOut": "2026-09-17", "rooms": [{ "adults": 2 }] }'
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_deals",
"arguments": {
"meta": { "ucp-agent": { "profile": "https://…" } },
"deals": { "hotelId": "31558266", "rooms": [{ "adults": 2 }] }
}
},
"id": 1
}
The three browser workflows compose into a full booking. A rate's checkoutId flows from
deals into checkout and book; book returns a
bookingId that the server-only status and details operations read from an in-memory store.
Only semantic payload crosses the boundary — never internal browser session tokens.
deals (per rate); consumed by checkout + book.book; consumed by status + details (no browser).UCP-Agent; the server fetches and caches it, checks
the protocol version, and intersects capabilities before any browser session starts —
returning capabilities_incompatible or version_unsupported when they don't match.
Read the UCP specification for the protocol details, then explore A3's stage-based model to see how learned, deterministic flows stand in for missing supplier APIs.