How MCP, A2A, UCP, AP2, A2UI, and AG-UI each solve a different problem in a single real shopping flow — and why you need all of them.
Who this is for: engineers building multi-agent applications who have already skimmed the individual protocol docs and want to see how they wire together in a real codebase.
The AI agent space has exploded with protocol acronyms. MCP, A2A, AG-UI, and others are being announced, discussed, and debated everywhere. But most examples show each protocol in isolation. A standalone MCP server. A single A2A agent. A demo with AG-UI streaming a response.
What happens when all six protocols run together in one application?
We built ShopAgent to find out. It is a reference demo — an AI-powered e-commerce assistant where a user can search for products, add items to a cart, and complete a purchase entirely through conversation. The payment processor, shipping provider, and mandate signer are all mock implementations; the architecture and protocol wiring are real. Every feature in the flow uses a different protocol — not arbitrarily, but because each protocol genuinely solves the problem it is paired with.
This post explains what each protocol does, why it is the right tool for its role, and what would break if you removed it.
THE SHOPPING FLOW
When a user types "show me running shoes under $100", this is what happens in order:
- The orchestrator receives the message
- MCP searches the product catalogue
- A2A passes results to a recommender agent, which scores them against user preferences
- A2A passes product IDs to an inventory agent, which checks stock and delivery estimates
- A2UI bundles the enriched products into a structured UI payload
- AG-UI streams the complete state — products, cart, errors — to the React frontend in real time
- The user clicks "Add to Cart"
- The orchestrator updates cart state
- The user says "I'm ready to checkout"
- UCP creates a checkout session with line items, tax, and currency
- AP2 signs a payment mandate linking the user's intent to the specific cart and authorised total
- The order is confirmed
Each numbered protocol step is handled by a different system. None of them knows about the others.
MCP — MODEL CONTEXT PROTOCOL
The role: Connecting the AI agent to structured data sources.
MCP is the protocol that lets an AI model call tools in a standardised way. In ShopAgent, the MCP server exposes the product catalogue. The orchestrator calls it to search, filter, and retrieve products — not by querying a database directly, but by asking a protocol-compliant tool server.
raw_products = await mcp_search_products(
query=prefs.query,
category=prefs.category,
max_price=prefs.max_price,
)
The value of MCP here is not that it makes the query faster. It is that the agent does not need to know anything about the underlying database. If you replaced SQLite with Postgres or Elasticsearch, the orchestrator code would not change. The MCP server is the boundary.
What you lose without it: The agent would need a direct database connection and hardcoded query logic. You lose the ability to swap the data source, add another one (a second product source, a third-party catalogue), or enforce access control at the tool boundary.
A2A — AGENT-TO-AGENT
The role: Letting one AI agent delegate specialised work to another.
After MCP returns the raw product list, two separate A2A agents enrich it. The recommender agent scores each product against the user's stated preferences and returns a ranked list with explanation strings. The inventory agent queries stock levels and calculates delivery estimates.
recommendations = await a2a_get_recommendations(
products=raw_products,
preferences={"query": prefs.query, "max_price": prefs.max_price},
)
inventory = await a2a_get_inventory(product_ids=product_ids)
Both calls are JSON-RPC over HTTP to independent containers. The orchestrator does not know how the recommender scores products or how the inventory agent connects to its database. It just sends a request and reads the response.
Note on terminology: "A2A" as a label is still fluid in the ecosystem. What we mean specifically is the combination of JSON-RPC messaging with an Agent Card schema — a machine-readable capability declaration at /.well-known/agent-card.json that describes what the agent does, what formats it accepts, and what skills it exposes. This is how our implementation enables service discovery — an orchestrator that has never been explicitly configured for a specific agent can read the card, understand what the agent does, and decide whether to call it.
What you lose without it: You either bake the recommendation and inventory logic into the orchestrator (making it a monolith) or you call the services directly as HTTP APIs (losing the service discovery contract and the ability to swap implementations).
UCP — UNIVERSAL COMMERCE PROTOCOL
The role: Standardising the checkout flow so the agent and the merchant speak the same language.
Note: UCP is a proposed protocol that we are implementing in ShopAgent, not a universally adopted industry standard. The value demonstrated here is the design pattern — a shared checkout session contract that decouples the agent from any specific merchant API.
When the user says "I'm ready to checkout", the orchestrator calls the UCP merchant service to create a session. UCP defines what a checkout session looks like — line items, currency, tax, session ID, status lifecycle. The orchestrator does not hardcode any of these concepts.
session_data = await ucp_create_checkout(cart=cart)
session = CheckoutSession(
session_id=session_data["id"],
status=session_data["status"],
subtotal=session_data.get("subtotal_cents", 0) / 100,
tax=session_data.get("tax_cents", 0) / 100,
total=session_data.get("total_cents", 0) / 100,
)
UCP stores all monetary amounts as integer cents. The orchestrator converts to display units at the boundary. This is deliberate — integer arithmetic does not have the rounding errors that floating-point arithmetic does. A tax calculation on $99.99 at 8.875% should produce the same number every time.
What you lose without it: The agent has to understand your specific merchant API's field names, session lifecycle, and money representation. Every merchant is different. UCP defines a shared contract so an agent-powered checkout can work with any compliant merchant without code changes.
AP2 — AGENT PAYMENT PROTOCOL
The role: Ensuring an AI agent cannot authorise a payment the user did not agree to.
Note: Like UCP, AP2 is a proposed protocol implemented in ShopAgent. The mandate-signing pattern described here is the core design principle, applicable whether you call it AP2 or something else.
AP2 is the least visible protocol in the UI, but it is the most important safety mechanism. Before the payment is processed, AP2 creates a signed mandate that chains together three things: the user's stated intent, the exact cart contents at that moment, and the total amount being charged.
The mandate is signed with Ed25519. The signature is stored in the audit trail. If anyone — including the agent itself — tries to charge more than the user agreed to, or to charge for items that were not in the cart when the user said "confirm", the mandate verification fails and the payment is rejected.
# The mandate links intent → cart → payment
mandate_payload = {
"intent": "purchase",
"session_id": session.session_id,
"cart_items": [item.model_dump() for item in cart],
"total_cents": int(session.total * 100),
"currency": session.currency,
"user_id": user_id,
}
result = mandate_signer.sign(mandate_payload)
What you lose without it: An LLM-driven checkout is inherently non-deterministic. The agent could, under adversarial conditions or edge cases, attempt to complete a payment for different items or a different amount than what the user confirmed. AP2 is the cryptographic guard that makes this detectable and rejectable — assuming the signing keys and verification implementation are correct.
A2UI — AGENT-TO-UI
The role: Letting the agent describe what the UI should render, not how.
When the orchestrator has the enriched product list, it does not generate HTML or React components. Instead it builds a structured payload — a flat list of typed components with an adjacency-list layout tree.
def build_product_grid(candidates):
components = []
for p in candidates:
components += [
{"id": f"card-{p.product_id}", "type": "card", "children": [...]},
{"id": f"card-{p.product_id}-name", "type": "text",
"props": {"content": p.name, "variant": "heading"}},
{"id": f"card-{p.product_id}-btn", "type": "button",
"props": {"label": "Add to Cart", "action": "add_to_cart",
"value": p.product_id}},
]
return {"type": "product_grid", "components": components}
The type field is the discriminator. When the user asks to compare products instead of searching, the same agent returns a payload with type: "comparison_table". The frontend switches rendering mode entirely based on that one field — no conditional rendering logic in the orchestrator, no layout decisions in the Python code.
What you lose without it: The agent produces either plain text (no interactivity) or you hardcode every UI state in the frontend with if-else branches checking what the agent returned. A2UI gives you a clean boundary: the agent owns the data structure, the frontend owns the visual rendering.
AG-UI — AGENT-GENERATED UI
The role: Streaming agent state to the frontend in real time via Server-Sent Events.
AG-UI is how the entire agent state — cart contents, product list, checkout session, errors — reaches the React frontend without polling or page reloads. Every time a node in the LangGraph graph updates state, that update is streamed as a state_snapshot event.
The frontend consumes this with a single hook:
const { agent } = useAgent({ agentId: "shopAgent" });
agent.state updates reactively. When search_products finishes, the product cards appear. When add_to_cart updates the cart, the cart panel updates. When complete_checkout clears the cart and sets the order ID, the confirmation screen renders.
The user sees the UI change as the agent works — not after it finishes.
What you lose without it: You need a polling loop, a WebSocket implementation, or a custom SSE layer. You also lose the agent state projection — you would have to rebuild the frontend state management from scratch.
WHY YOU NEED ALL SIX
Each protocol solves a problem the others cannot.
MCP handles tool access — how the agent talks to external data.
A2A handles specialised delegation — how agents offload work to other agents.
UCP handles commerce semantics — what a cart and a checkout session look like.
AP2 handles payment authorisation — making AI-driven payments safe by design.
A2UI handles UI description — letting the agent specify what to render without knowing how.
AG-UI handles real-time state sync — bridging a Python backend to a React frontend.
You could build a shopping assistant without any of them — just a single LLM that outputs text and calls internal functions directly. It would work at demo scale. It would not be maintainable, extensible, or safe at production scale.
The protocols are the architecture.
THE TAKEAWAY
Building ShopAgent taught us that these protocols are not marketing abstractions — they solve real engineering problems at the boundaries of an AI system. The value is not in any single protocol. It is in what becomes possible when they work together: an agent that can search, recommend, check stock, generate UI, stream state, manage a cart, create a checkout session, and sign a payment mandate — all through a single conversation, with each concern handled by the right layer.
The ShopAgent demo is live at https://shop-agent.agilecreativeminds.nl. See the demo showcase or follow the demo walkthrough. Built by Agile Creative Minds.