Model Context Protocol (MCP) transport (Beta)

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/call endpoint. Coordinate early access with Google.

Request early access

To participate in UCP MCP Early Access (Beta), follow these steps:

  1. Publish "transport": "mcp" in your /.well-known/ucp profile with UCP version 2026-04-08 or a subsequent version.
  2. Implement a stateless tools/call MCP endpoint that meets the requirements in this guide.
  3. 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/ucp profile publishes UCP version 2026-04-08 (or a subsequent version) and a valid https:// MCP endpoint URL 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 HTTP POST request. Your server returns HTTP 200 OK (Content-Type: application/json) with result.structuredContent without an initialize handshake, Mcp-Session-Id header, or Accept header.
  • Authentication and signature verification: your endpoint validates configured credentials (OAuth 2.0, API key, or mTLS) and discovers platform keys from the UCP-Agent HTTP header. It also verifies RFC 9421 Signature and Content-Digest headers 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 HTTP Idempotency-Key header when meta["idempotency-key"] is absent.
  • Structured error responses: recoverable business errors (for example, address_undeliverable) return HTTP 200 OK with ucp.status set to "success", top-level status set to "incomplete" or "requires_escalation", and messages. Unrecoverable session-creation errors (for example, out_of_stock) return HTTP 200 OK with ucp.status set to "error" and messages inside result.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-08 or subsequent versions: publish UCP specification version 2026-04-08 or a subsequent version. The 2026-01-23 platform profile negotiates only rest and embedded transports.
  • Add an mcp service entry: under ucp.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 (mcp or rest) and skips non-RPC entries (for example, embedded). When you want Google to use MCP, put the mcp entry before any rest entry.
  • Meet endpoint URL rules: make sure your MCP endpoint URL 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/ucp profile URL or your registered Merchant Center domain.

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 POST request with Content-Type: application/json to the single MCP endpoint URL in your /.well-known/ucp profile.
  • Return HTTP 200 OK with Content-Type: application/json for 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 response id field.
  • 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), and cancel_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}), and cancel_cart (POST /carts/{id}/cancel).
  • params.arguments: an object that contains protocol metadata (meta), the target resource identifier (id, when applicable), and the domain payload (checkout or cart, 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 sends idempotency-key on state-changing calls (create_checkout, update_checkout, complete_checkout, cancel_checkout, create_cart, update_cart, and cancel_cart). Google omits meta["idempotency-key"] on read calls (get_checkout and get_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.arguments contains meta and checkout (or cart). It does not include a top-level id field.
  • Update and complete calls (update_checkout, complete_checkout, update_cart): params.arguments contains meta, a top-level id string, and the checkout (or cart) object. In UCP version 2026-04-08 and subsequent versions, the inner checkout object does not repeat the id field.
  • Cancel checkout (cancel_checkout): params.arguments contains meta, a top-level id string, and "checkout": {} (an empty object). The UCP MCP specification does not define a checkout property for cancel_checkout. Configure strict schema validators to accept cancel_checkout requests with or without "checkout": {}.
  • Read and cancel-cart calls (get_checkout, get_cart, cancel_cart): params.arguments contains meta and a top-level id string 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, and Content-Digest headers computed over the raw JSON-RPC HTTP request body. Google uses the signature label sig-ucp and sets tag="ucp-rest-request" in Signature-Input on all signed UCP requests, including MCP POST calls. Signatures cover @method, @path, @authority (and @query when present), content-type, authorization (when present), content-digest, and idempotency-key. Signatures exclude the ucp-agent header. Parse the signature label and covered components dynamically from Signature-Input per RFC 9421, not from a hardcoded label (for example, sig1) or fixed component list. Accept MCP requests with tag="ucp-rest-request".

Outbound HTTP headers

Google sends these HTTP headers on outbound MCP POST requests:

  • Content-Type: application/json
  • Request-Id: a unique UUID v4 for each HTTP request try
  • Idempotency-Key: a UUID v4 header sent on every HTTP POST request, including get_checkout and get_cart calls. Google generates the HTTP Idempotency-Key header independently from meta["idempotency-key"] in the JSON-RPC body. Treat the HTTP Idempotency-Key header and meta["idempotency-key"] as independent values, and accept Idempotency-Key headers on get_checkout and get_cart calls.
  • Payment-Environment: "live" for production traffic or "sandbox" for test traffic
  • UCP-Agent: the versioned Google platform profile URL (for example, profile="https://ucp.goog/profiles/2026-04-08/ucp.json")
  • User-Agent: one of Google-Shopping-Merchant-Integration, Google/UCP*, or Google-UCP-Prober/1.0
  • X-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 separate McpServer instance for each request, and close transport and server when the response closes. A shared McpServer instance throws Already connected to a transport when two requests overlap.
  • sessionIdGenerator: undefined: turns off Mcp-Session-Id session generation and skips session and initialization checks. This allows tools/call to succeed without a previous initialize handshake. Create a new stateless transport instance for each request.
  • enableJsonResponse: true: returns Content-Type: application/json directly in the HTTP POST response body without opening an SSE stream.
  • Accept header workaround: StreamableHTTPServerTransport has no option to turn off its Accept header check, and reads req.rawHeaders to build request headers. Set req.headers.accept and req.rawHeaders before calling transport.handleRequest (or clone the Web Request with an Accept header for WebStandardStreamableHTTPServerTransport):
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): set stateless_http=True, json_response=True, streamable_http_path="/ucp/mcp", and transport_security=TransportSecuritySettings(...) with your public domain in allowed_hosts. By default, FastMCP allows only localhost Host headers and rejects public domain requests with HTTP 421 Misdirected Request.
  • StreamableHTTPSessionManager (mcp.server.streamable_http_manager.StreamableHTTPSessionManager): set stateless=True and json_response=True. These settings create a fresh StreamableHTTPServerTransport(mcp_session_id=None, is_json_response_enabled=True) for each request and return Content-Type: application/json. They also start the server session in the initialized state so tools/call succeeds without initialize or Mcp-Session-Id.
  • Accept header workaround: when json_response=True is set, StreamableHTTPServerTransport still checks for application/json in the Accept header. It has no setting to allow a missing Accept header. Add an ASGI middleware or API gateway rule that sets a default Accept: application/json, text/event-stream header when accept is 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_undeliverable or quantity_adjusted), return HTTP 200 OK with result.structuredContent. Set ucp.status to "success", set top-level status to "incomplete" or "requires_escalation", and populate the messages array. See Error codes.
  • Unrecoverable session-creation errors: when a checkout session cannot be created (for example, out_of_stock or item_unavailable), return HTTP 200 OK with result.structuredContent. Set ucp.status to "error", and populate only the messages array (and optional continue_url) without top-level id or status fields. Top-level status accepts 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 error object only for protocol or tool failures, not for recoverable checkout states. If your server returns HTTP 200 OK with a top-level error object (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 HTTP 401 Unauthorized for 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/*, or prompts/*. Google calls tools/call directly with the fixed UCP tool names.
  • No stateful MCP sessions: Google does not send or track Mcp-Session-Id headers. Every tools/call HTTP POST request is independent and stateless. Accept tools/call requests directly without a previous initialize handshake.
  • 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), stdio transport, 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/ucp profile 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.