This guide explains how to implement UCP Native Checkout and Cart over a
stateless Model Context Protocol (MCP) endpoint. It also shows how to declare
MCP in your /.well-known/ucp profile and lists the
current limitations.
Choose between REST and MCP
Google supports two RPC transport bindings for UCP Native Checkout and Cart:
- REST (recommended default): use REST for standard onboarding, self-serve validation in Merchant Center, and standard HTTP resource and status code semantics. See the Native checkout overview.
- MCP Early Access (Beta): use MCP only if your backend already exposes
UCP capabilities through a stateless
JSON-RPC 2.0
tools/callendpoint. Coordinate early access with Google.
Request early access
To participate in UCP MCP Early Access (Beta), follow these steps:
- Publish
"transport": "mcp"in your/.well-known/ucpprofile with UCP version2026-04-08or a subsequent version. - Implement a stateless
tools/callMCP endpoint that meets the requirements in this guide. - Submit the UCP integration interest form or contact your Google representative to request early access and integration testing.
Integration test checklist
Because Merchant Center self-serve checkout validation supports only REST, Google validates your MCP endpoint against these criteria during early access integration testing:
- UCP profile discovery: your
/.well-known/ucpprofile publishes UCP version2026-04-08(or a subsequent version) and a validhttps://MCPendpointURL on your registered business domain. - Stateless JSON-RPC 2.0 execution: each checkout tool (
create_checkout,get_checkout,update_checkout,complete_checkout,cancel_checkout, and cart tools when enabled) succeeds on a single HTTPPOSTrequest. Your server returns HTTP200 OK(Content-Type: application/json) withresult.structuredContentwithout aninitializehandshake,Mcp-Session-Idheader, orAcceptheader. - Authentication and signature verification: your endpoint validates
configured credentials (OAuth 2.0, API key, or mTLS) and discovers platform
keys from the
UCP-AgentHTTP header. It also verifies RFC 9421SignatureandContent-Digestheaders over the raw JSON-RPC request body. - Idempotency and retry handling: state-changing calls enforce retry
safety with
params.arguments.meta["idempotency-key"], and read calls (get_checkout,get_cart) accept an HTTPIdempotency-Keyheader whenmeta["idempotency-key"]is absent. - Structured error responses: recoverable business errors (for example,
address_undeliverable) return HTTP200 OKwithucp.statusset to"success", top-levelstatusset to"incomplete"or"requires_escalation", andmessages. Unrecoverable session-creation errors (for example,out_of_stock) return HTTP200 OKwithucp.statusset to"error"andmessagesinsideresult.structuredContent.
Declare MCP in your UCP profile
To advertise an MCP endpoint in your /.well-known/ucp profile, follow these
rules:
- Use UCP version
2026-04-08or subsequent versions: publish UCP specification version2026-04-08or a subsequent version. The2026-01-23platform profile negotiates onlyrestandembeddedtransports. - Add an
mcpservice entry: underucp.services["dev.ucp.shopping"], add a service object with"transport": "mcp"and your MCP"endpoint"URL. See Prepare your UCP profile. - Order transport entries intentionally: when MCP profile selection is
enabled for your account, Google reads the
ucp.services["dev.ucp.shopping"]array in order. Google selects the first RPC transport entry (mcporrest) and skips non-RPC entries (for example,embedded). When you want Google to use MCP, put themcpentry before anyrestentry. - Meet endpoint URL rules: make sure your MCP
endpointURL meets these validation criteria:- Use the
https://scheme and a non-empty hostname. - Exclude userinfo (
user:pass@), query strings (?...), URL fragments (#...), backslashes (\`), and dot path segments (.or..`). - Use the same business domain (effective TLD+1) as your
/.well-known/ucpprofile URL or your registered Merchant Center domain.
- Use the
Example /.well-known/ucp service declaration:
{
"ucp": {
"version": "2026-04-08",
"services": {
"dev.ucp.shopping": [
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/overview",
"transport": "mcp",
"endpoint": "https://merchant.example.com/ucp/mcp",
"schema": "https://ucp.dev/2026-04-08/services/shopping/mcp.openrpc.json"
},
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/overview",
"transport": "rest",
"endpoint": "https://merchant.example.com/ucp/v1",
"schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json"
}
]
}
}
}
Supported MCP profile
Google implements a stateless subset of the Model Context Protocol over HTTP
POST.
- Google sends every tool call as an HTTP
POSTrequest withContent-Type: application/jsonto the single MCPendpointURL in your/.well-known/ucpprofile. - Return HTTP
200 OKwithContent-Type: application/jsonfor every JSON-RPC 2.0 response.
Request envelope
Every request uses a JSON-RPC 2.0 tools/call envelope:
jsonrpc: always"2.0".id: a unique request identifier string (UUID v4). Echo this value in the JSON-RPC responseidfield.method: always"tools/call".params.name: the UCP tool name.- Checkout tools:
create_checkout(POST /checkout-sessions),get_checkout(GET /checkout-sessions/{id}),update_checkout(PUT /checkout-sessions/{id}),complete_checkout(POST /checkout-sessions/{id}/complete), andcancel_checkout(POST /checkout-sessions/{id}/cancel). - Cart tools (supported when Cart and MCP are enabled for your
account):
create_cart(POST /carts),get_cart(GET /carts/{id}),update_cart(PUT /carts/{id}), andcancel_cart(POST /carts/{id}/cancel).
- Checkout tools:
params.arguments: an object that contains protocol metadata (meta), the target resource identifier (id, when applicable), and the domain payload (checkoutorcart, when applicable).
Request metadata
Every tool call includes params.arguments.meta:
meta["ucp-agent"]["profile"]: a string that identifies the calling platform. Google sets this field to"https://www.google.com"or a versioned"https://ucp.goog/..."profile URI. In your endpoint, accept"https://www.google.com"and"https://ucp.goog/..."URIs.meta["idempotency-key"]: a UUID v4 string for retry safety. Google sendsidempotency-keyon state-changing calls (create_checkout,update_checkout,complete_checkout,cancel_checkout,create_cart,update_cart, andcancel_cart). Google omitsmeta["idempotency-key"]on read calls (get_checkoutandget_cart).
Identifier and payload pattern
MCP tools separate the target resource identifier from the resource payload in
params.arguments:
- Create calls (
create_checkout,create_cart):params.argumentscontainsmetaandcheckout(orcart). It does not include a top-levelidfield. - Update and complete calls (
update_checkout,complete_checkout,update_cart):params.argumentscontainsmeta, a top-levelidstring, and thecheckout(orcart) object. In UCP version2026-04-08and subsequent versions, the innercheckoutobject does not repeat theidfield. - Cancel checkout (
cancel_checkout):params.argumentscontainsmeta, a top-levelidstring, and"checkout": {}(an empty object). The UCP MCP specification does not define acheckoutproperty forcancel_checkout. Configure strict schema validators to acceptcancel_checkoutrequests with or without"checkout": {}. - Read and cancel-cart calls (
get_checkout,get_cart,cancel_cart):params.argumentscontainsmetaand a top-levelidstring only.
Example create_checkout request:
{
"jsonrpc": "2.0",
"id": "b3b8c9d0-1234-4ef5-89ab-cdef01234567",
"method": "tools/call",
"params": {
"name": "create_checkout",
"arguments": {
"meta": {
"ucp-agent": {
"profile": "https://www.google.com"
},
"idempotency-key": "550e8400-e29b-41d4-a716-446655440000"
},
"checkout": {
"line_items": [
{
"item": {
"id": "sku_123"
},
"quantity": 1
}
],
"context": {
"currency": "USD"
}
}
}
}
}
Example update_checkout request:
{
"jsonrpc": "2.0",
"id": "c4d5e6f7-2345-4fa6-90bc-def012345678",
"method": "tools/call",
"params": {
"name": "update_checkout",
"arguments": {
"meta": {
"ucp-agent": {
"profile": "https://www.google.com"
},
"idempotency-key": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
},
"id": "checkout_abc123",
"checkout": {
"line_items": [
{
"id": "li_1",
"item": {
"id": "sku_123"
},
"quantity": 2
}
]
}
}
}
}
Example get_checkout request:
{
"jsonrpc": "2.0",
"id": "d5e6f7a8-3456-4ab7-81cd-ef0123456789",
"method": "tools/call",
"params": {
"name": "get_checkout",
"arguments": {
"meta": {
"ucp-agent": {
"profile": "https://www.google.com"
}
},
"id": "checkout_abc123"
}
}
}
Response envelope
Return HTTP 200 OK with Content-Type: application/json and a JSON-RPC 2.0
response object. Put the full UCP Checkout (or Cart) object inside
result.structuredContent. The schema inside result.structuredContent is the
same as the UCP REST response schema. See the
Native Checkout API
and Cart API guides.
Example create_checkout response:
{
"jsonrpc": "2.0",
"id": "b3b8c9d0-1234-4ef5-89ab-cdef01234567",
"result": {
"structuredContent": {
"ucp": {
"version": "2026-04-08",
"status": "success",
"capabilities": {
"dev.ucp.shopping.checkout": [
{
"version": "2026-04-08"
}
]
}
},
"id": "checkout_abc123",
"status": "incomplete",
"currency": "USD",
"line_items": [
{
"id": "li_1",
"item": {
"id": "sku_123",
"title": "Blue Jeans",
"price": 5000
},
"quantity": 1,
"totals": [
{
"type": "subtotal",
"amount": 5000
},
{
"type": "total",
"amount": 5000
}
]
}
],
"totals": [
{
"type": "subtotal",
"amount": 5000
},
{
"type": "total",
"amount": 5000
}
],
"links": [
{
"type": "privacy_policy",
"url": "https://merchant.example.com/privacy"
},
{
"type": "terms_of_service",
"url": "https://merchant.example.com/terms"
}
],
"continue_url": "https://merchant.example.com/checkout/checkout_abc123"
}
}
}
Authentication, security, and HTTP headers
This section describes authentication, message signatures, and outbound HTTP headers for UCP MCP requests.
Authentication and message signing
MCP over HTTP supports the same authentication and security options as REST. See Authentication for setup details:
- OAuth 2.0 bearer tokens:
Authorization: Bearer <access_token> - API keys:
X-API-Key: <api_key>(or your configured custom header name) - Mutual TLS (mTLS): client certificates issued by the Google UCP Root CAs
- RFC 9421 HTTP
message signatures:
Signature,Signature-Input, andContent-Digestheaders computed over the raw JSON-RPC HTTP request body. Google uses the signature labelsig-ucpand setstag="ucp-rest-request"inSignature-Inputon all signed UCP requests, including MCPPOSTcalls. Signatures cover@method,@path,@authority(and@querywhen present),content-type,authorization(when present),content-digest, andidempotency-key. Signatures exclude theucp-agentheader. Parse the signature label and covered components dynamically fromSignature-Inputper RFC 9421, not from a hardcoded label (for example,sig1) or fixed component list. Accept MCP requests withtag="ucp-rest-request".
Outbound HTTP headers
Google sends these HTTP headers on outbound MCP POST requests:
Content-Type:application/jsonRequest-Id: a unique UUID v4 for each HTTP request tryIdempotency-Key: a UUID v4 header sent on every HTTPPOSTrequest, includingget_checkoutandget_cartcalls. Google generates the HTTPIdempotency-Keyheader independently frommeta["idempotency-key"]in the JSON-RPC body. Treat the HTTPIdempotency-Keyheader andmeta["idempotency-key"]as independent values, and acceptIdempotency-Keyheaders onget_checkoutandget_cartcalls.Payment-Environment:"live"for production traffic or"sandbox"for test trafficUCP-Agent: the versioned Google platform profile URL (for example,profile="https://ucp.goog/profiles/2026-04-08/ucp.json")User-Agent: one ofGoogle-Shopping-Merchant-Integration,Google/UCP*, orGoogle-UCP-Prober/1.0X-Forwarded-For: the shopper client IP address, when IP forwarding is enabled for the request
Configure official MCP SDKs for stateless JSON responses
The MCP Streamable HTTP specification
(revision 2025-03-26) allows an MCP server to
return a single Content-Type: application/json response without stateful
sessions (Mcp-Session-Id) or SSE streams.
While the specification expects an Accept: application/json, text/event-stream
header on POST requests, Google sends Content-Type: application/json without
an Accept header.
Official TypeScript SDK (@modelcontextprotocol/sdk)
In the official MCP TypeScript SDK, create a new
McpServer instance and a new StreamableHTTPServerTransport (or
WebStandardStreamableHTTPServerTransport) instance for each HTTP POST
request:
McpServer: create a separateMcpServerinstance for each request, and closetransportandserverwhen the response closes. A sharedMcpServerinstance throwsAlready connected to a transportwhen two requests overlap.sessionIdGenerator: undefined: turns offMcp-Session-Idsession generation and skips session and initialization checks. This allowstools/callto succeed without a previousinitializehandshake. Create a new stateless transport instance for each request.enableJsonResponse: true: returnsContent-Type: application/jsondirectly in the HTTPPOSTresponse body without opening an SSE stream.Acceptheader workaround:StreamableHTTPServerTransporthas no option to turn off itsAcceptheader check, and readsreq.rawHeadersto build request headers. Setreq.headers.acceptandreq.rawHeadersbefore callingtransport.handleRequest(or clone the WebRequestwith anAcceptheader forWebStandardStreamableHTTPServerTransport):
import {
StreamableHTTPServerTransport,
} from '@modelcontextprotocol/sdk/server/streamableHttp.js';
app.post('/ucp/mcp', async (req, res) => {
if (!req.headers.accept) {
req.headers.accept = 'application/json, text/event-stream';
req.rawHeaders.push('Accept', 'application/json, text/event-stream');
}
const server = createUcpMcpServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on('close', () => {
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
Official Python SDK (mcp)
In the official MCP Python SDK, turn on stateless mode and direct JSON responses on your server or session manager:
FastMCP(mcp.server.fastmcp.FastMCP): setstateless_http=True,json_response=True,streamable_http_path="/ucp/mcp", andtransport_security=TransportSecuritySettings(...)with your public domain inallowed_hosts. By default,FastMCPallows onlylocalhostHostheaders and rejects public domain requests with HTTP421 Misdirected Request.StreamableHTTPSessionManager(mcp.server.streamable_http_manager.StreamableHTTPSessionManager): setstateless=Trueandjson_response=True. These settings create a freshStreamableHTTPServerTransport(mcp_session_id=None, is_json_response_enabled=True)for each request and returnContent-Type: application/json. They also start the server session in the initialized state sotools/callsucceeds withoutinitializeorMcp-Session-Id.Acceptheader workaround: whenjson_response=Trueis set,StreamableHTTPServerTransportstill checks forapplication/jsonin theAcceptheader. It has no setting to allow a missingAcceptheader. Add an ASGI middleware or API gateway rule that sets a defaultAccept: application/json, text/event-streamheader whenacceptis absent:
from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
from starlette.types import ASGIApp, Receive, Scope, Send
mcp = FastMCP(
"ucp-server",
stateless_http=True,
json_response=True,
streamable_http_path="/ucp/mcp",
transport_security=TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=["merchant.example.com"],
),
)
class DefaultAcceptHeaderMiddleware:
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(
self, scope: Scope, receive: Receive, send: Send
) -> None:
if scope["type"] == "http":
headers = list(scope.get("headers", []))
if not any(name.lower() == b"accept" for name, _ in headers):
headers.append(
(b"accept", b"application/json, text/event-stream")
)
scope = {**scope, "headers": headers}
await self.app(scope, receive, send)
app = DefaultAcceptHeaderMiddleware(mcp.streamable_http_app())
Handle errors
UCP separates business outcomes from protocol and transport failures:
- Recoverable business errors and warnings: when an active checkout
session has missing or invalid data (for example,
address_undeliverableorquantity_adjusted), return HTTP200 OKwithresult.structuredContent. Setucp.statusto"success", set top-levelstatusto"incomplete"or"requires_escalation", and populate themessagesarray. See Error codes. - Unrecoverable session-creation errors: when a checkout session cannot be
created (for example,
out_of_stockoritem_unavailable), return HTTP200 OKwithresult.structuredContent. Setucp.statusto"error", and populate only themessagesarray (and optionalcontinue_url) without top-levelidorstatusfields. Top-levelstatusaccepts only checkout lifecycle values (incomplete,requires_escalation,ready_for_complete,complete_in_progress,completed,canceled), not"error". - JSON-RPC protocol and tool errors: use a top-level JSON-RPC
errorobject only for protocol or tool failures, not for recoverable checkout states. If your server returns HTTP200 OKwith a top-levelerrorobject (for example,{"jsonrpc": "2.0", "id": "...", "error": {"code": -32602, "message": "Invalid params"}}), Google treats the response as a protocol or tool failure. - HTTP transport errors: non-200 HTTP status codes (
401,403,429,500,503) fail the RPC call. On HTTP401 Unauthorizedfor an OAuth endpoint, Google evicts the cached OAuth token, fetches a new token, and retries once.
Example recoverable session error inside result.structuredContent:
{
"jsonrpc": "2.0",
"id": "c4d5e6f7-2345-4fa6-90bc-def012345678",
"result": {
"structuredContent": {
"ucp": {
"version": "2026-04-08",
"status": "success",
"capabilities": {
"dev.ucp.shopping.checkout": [
{
"version": "2026-04-08"
}
]
}
},
"id": "checkout_abc123",
"status": "incomplete",
"currency": "USD",
"line_items": [
{
"id": "li_1",
"item": {
"id": "sku_123",
"title": "Blue Jeans",
"price": 5000
},
"quantity": 1,
"totals": [
{
"type": "subtotal",
"amount": 5000
},
{
"type": "total",
"amount": 5000
}
]
}
],
"totals": [
{
"type": "subtotal",
"amount": 5000
},
{
"type": "total",
"amount": 5000
}
],
"messages": [
{
"type": "error",
"code": "address_undeliverable",
"content": "We cannot ship to this address.",
"severity": "recoverable",
"path": "$.fulfillment.methods[0].destinations[0]"
}
],
"continue_url": "https://merchant.example.com/checkout/checkout_abc123"
}
}
}
Example unrecoverable session-creation error inside result.structuredContent
(no top-level id or status):
{
"jsonrpc": "2.0",
"id": "b3b8c9d0-1234-4ef5-89ab-cdef01234567",
"result": {
"structuredContent": {
"ucp": {
"version": "2026-04-08",
"status": "error"
},
"messages": [
{
"type": "error",
"code": "out_of_stock",
"content": "All requested items are out of stock.",
"severity": "unrecoverable"
}
],
"continue_url": "https://merchant.example.com/cart"
}
}
}
Current limitations
The following limitations apply to UCP over MCP:
- No MCP lifecycle or discovery calls: Google does not call
initialize,notifications/initialized,tools/list,resources/*, orprompts/*. Google callstools/calldirectly with the fixed UCP tool names. - No stateful MCP sessions: Google does not send or track
Mcp-Session-Idheaders. Everytools/callHTTPPOSTrequest is independent and stateless. Accepttools/callrequests directly without a previousinitializehandshake. - No SSE, streaming, stdio, or JSON-RPC batching: Google supports only
single JSON-RPC 2.0 request and response objects over synchronous HTTP
POST(Content-Type: application/json). Server-Sent Events (text/event-stream),stdiotransport, and JSON-RPC batch arrays are not supported. - No self-serve validation in Merchant Center: the Merchant Center self-serve checkout validation tool tests only REST endpoints. It does not support MCP endpoints. See the Integration test checklist for the test cases that Google runs during early access onboarding.
- Per-account enablement during Early Access (Beta): publishing
"transport": "mcp"in your/.well-known/ucpprofile does not activate MCP in production on its own. Google enables MCP transport selection for your partner account after integration testing. - Scope: this guide covers only UCP Native Checkout and Cart
(
dev.ucp.shopping). Other Google agentic integrations that connect to a stateful MCP gateway are documented separately.