ucp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UCP Skill — Universal Commerce Protocol Implementation

UCP Skill — 通用商务协议实现工具

Core Principles

核心原则

  1. Edge runtime is NOT USED — Only Node.js (default) or Bun (opt-in) runtimes
  2. Interactive error handling — When ambiguous, ask the user how to proceed
  3. Config-driven — All decisions persist in
    ucp.config.json
  4. Spec-grounded — All implementations reference the canonical UCP specification
  5. Next.js conventions — Follow App Router patterns for code organization
  6. Deep analysis — Use AST parsing and data flow tracing for gap detection

  1. 不使用Edge runtime — 仅支持Node.js(默认)或Bun(可选)运行时
  2. 交互式错误处理 — 遇到歧义场景时询问用户后续处理方式
  3. 配置驱动 — 所有决策都持久化存储在
    ucp.config.json
  4. 基于规范实现 — 所有实现都参考官方UCP规范
  5. 遵循Next.js约定 — 代码组织遵循App Router模式
  6. 深度分析 — 使用AST解析和数据流追踪检测实现缺口

Spec Repository Handling

规范仓库处理

Location Priority

查找优先级

Check in this order:
  1. ./ucp/
    — User's local copy (use as-is)
  2. ./.ucp-spec/
    — Previously cloned spec (update it)
  3. Neither exists — Clone fresh
按以下顺序检查:
  1. ./ucp/
    — 用户本地副本(直接使用)
  2. ./.ucp-spec/
    — 之前克隆的规范(更新即可)
  3. 两者都不存在 — 全新克隆

Clone Procedure

克隆流程

When cloning is needed:
bash
git clone --depth 1 https://github.com/Universal-Commerce-Protocol/ucp.git .ucp-spec
If HTTPS fails, try SSH:
bash
git clone --depth 1 git@github.com:Universal-Commerce-Protocol/ucp.git .ucp-spec
需要克隆时执行:
bash
git clone --depth 1 https://github.com/Universal-Commerce-Protocol/ucp.git .ucp-spec
如果HTTPS克隆失败,尝试SSH:
bash
git clone --depth 1 git@github.com:Universal-Commerce-Protocol/ucp.git .ucp-spec

Update Procedure

更新流程

When
./.ucp-spec/
exists:
bash
cd .ucp-spec && git pull && cd ..
./.ucp-spec/
存在时执行:
bash
cd .ucp-spec && git pull && cd ..

Gitignore Management

Gitignore管理

After cloning, ensure
.ucp-spec/
is in
.gitignore
:
  • Read
    .gitignore
    if it exists
  • Check if
    .ucp-spec/
    or
    .ucp-spec
    is already listed
  • If not, append
    .ucp-spec/
    on a new line
克隆完成后,确保
.ucp-spec/
已添加到
.gitignore
  • 如果
    .gitignore
    存在则读取内容
  • 检查
    .ucp-spec/
    .ucp-spec
    是否已经在列表中
  • 如果不存在,在新行追加
    .ucp-spec/

Spec File Locations (read on demand)

规范文件位置(按需读取)

docs/specification/overview.md
docs/specification/checkout.md
docs/specification/checkout-rest.md
docs/specification/checkout-mcp.md
docs/specification/checkout-a2a.md
docs/specification/embedded-checkout.md
docs/specification/order.md
docs/specification/fulfillment.md
docs/specification/discount.md
docs/specification/buyer-consent.md
docs/specification/identity-linking.md
docs/specification/ap2-mandates.md
docs/specification/payment-handler-guide.md
docs/specification/tokenization-guide.md
spec/services/shopping/rest.openapi.json
spec/services/shopping/mcp.openrpc.json
spec/services/shopping/embedded.openrpc.json
spec/handlers/tokenization/openapi.json
spec/schemas/shopping/*
spec/discovery/profile_schema.json

docs/specification/overview.md
docs/specification/checkout.md
docs/specification/checkout-rest.md
docs/specification/checkout-mcp.md
docs/specification/checkout-a2a.md
docs/specification/embedded-checkout.md
docs/specification/order.md
docs/specification/fulfillment.md
docs/specification/discount.md
docs/specification/buyer-consent.md
docs/specification/identity-linking.md
docs/specification/ap2-mandates.md
docs/specification/payment-handler-guide.md
docs/specification/tokenization-guide.md
spec/services/shopping/rest.openapi.json
spec/services/shopping/mcp.openrpc.json
spec/services/shopping/embedded.openrpc.json
spec/handlers/tokenization/openapi.json
spec/schemas/shopping/*
spec/discovery/profile_schema.json

Configuration File

配置文件

Location

位置

./ucp.config.json
at project root
项目根目录下的
./ucp.config.json

Schema

Schema

json
{
  "$schema": "./ucp.config.schema.json",
  "ucp_version": "2026-01-11",
  "roles": ["business"],
  "runtime": "nodejs",
  "capabilities": {
    "core": ["dev.ucp.shopping.checkout"],
    "extensions": []
  },
  "transports": ["rest"],
  "transport_priority": ["rest", "mcp", "a2a", "embedded"],
  "payment_handlers": [],
  "features": {
    "ap2_mandates": false,
    "identity_linking": false,
    "multi_destination_fulfillment": false
  },
  "domain": "",
  "existing_apis": {},
  "policy_urls": {
    "privacy": "",
    "terms": "",
    "refunds": "",
    "shipping": ""
  },
  "scaffold_depth": "full",
  "generated_files": [],
  "answers": {},
  "deployment": {
    "platform": "vercel",
    "region": "iad1",
    "mcp": {
      "enabled": false,
      "max_duration": 60
    }
  }
}
json
{
  "$schema": "./ucp.config.schema.json",
  "ucp_version": "2026-01-11",
  "roles": ["business"],
  "runtime": "nodejs",
  "capabilities": {
    "core": ["dev.ucp.shopping.checkout"],
    "extensions": []
  },
  "transports": ["rest"],
  "transport_priority": ["rest", "mcp", "a2a", "embedded"],
  "payment_handlers": [],
  "features": {
    "ap2_mandates": false,
    "identity_linking": false,
    "multi_destination_fulfillment": false
  },
  "domain": "",
  "existing_apis": {},
  "policy_urls": {
    "privacy": "",
    "terms": "",
    "refunds": "",
    "shipping": ""
  },
  "scaffold_depth": "full",
  "generated_files": [],
  "answers": {},
  "deployment": {
    "platform": "vercel",
    "region": "iad1",
    "mcp": {
      "enabled": false,
      "max_duration": 60
    }
  }
}

Field Descriptions

字段说明

FieldTypeDescription
ucp_version
stringUCP spec version (date-based)
roles
string[]One or more of:
business
,
platform
,
payment_provider
,
host_embedded
runtime
string
nodejs
(default) or
bun
capabilities.core
string[]Required capabilities to implement
capabilities.extensions
string[]Optional extensions to implement
transports
string[]Enabled transports:
rest
,
mcp
,
a2a
,
embedded
transport_priority
string[]Order to implement transports
payment_handlers
string[]Payment handler IDs to support
features.ap2_mandates
booleanEnable AP2 mandate signing
features.identity_linking
booleanEnable OAuth identity linking
features.multi_destination_fulfillment
booleanEnable multi-destination shipping
domain
stringBusiness domain for
/.well-known/ucp
existing_apis
objectMap of existing API endpoints to analyze
policy_urls
objectURLs for privacy, terms, refunds, shipping policies
scaffold_depth
string
types
|
scaffolding
|
full
generated_files
string[]Files created by scaffold (for tracking)
answers
objectRaw answers to qualifying questions

字段类型说明
ucp_version
stringUCP规范版本(基于日期命名)
roles
string[]可选项:
business
,
platform
,
payment_provider
,
host_embedded
,支持多选
runtime
string
nodejs
(默认)或
bun
capabilities.core
string[]需要实现的核心能力
capabilities.extensions
string[]需要实现的可选扩展能力
transports
string[]启用的传输协议:
rest
,
mcp
,
a2a
,
embedded
transport_priority
string[]传输协议的实现顺序
payment_handlers
string[]需要支持的支付处理程序ID
features.ap2_mandates
boolean启用AP2 mandate签名
features.identity_linking
boolean启用OAuth身份关联
features.multi_destination_fulfillment
boolean启用多地址配送
domain
string用于
/.well-known/ucp
的业务域名
existing_apis
object待分析的现有API端点映射
policy_urls
object隐私政策、服务条款、退款政策、配送政策的URL
scaffold_depth
string
types
|
scaffolding
|
full
generated_files
string[]脚手架生成的文件(用于追踪)
answers
object资格评估问题的原始回答

Sub-command: (no argument)

子命令:(无参数)

Trigger

触发方式

User runs
/ucp
with no sub-command
用户执行不带子命令的
/ucp

Behavior

执行逻辑

Display help listing all available sub-commands:
UCP Skill — Universal Commerce Protocol Implementation

Available commands:
  /ucp init      — Initialize UCP in this project (clone spec, create config)
  /ucp consult   — Full consultation: answer qualifying questions, build roadmap
  /ucp plan      — Generate detailed implementation plan
  /ucp gaps      — Analyze existing code against UCP requirements
  /ucp scaffold  — Generate full working UCP implementation
  /ucp validate  — Validate implementation against UCP schemas
  /ucp profile   — Generate /.well-known/ucp discovery profile
  /ucp test      — Generate unit tests for UCP handlers
  /ucp docs      — Generate internal documentation

Typical workflow:
  /ucp init → /ucp consult → /ucp plan → /ucp scaffold → /ucp profile → /ucp test → /ucp validate

Configuration: ./ucp.config.json
Spec location: ./ucp/ or ./.ucp-spec/

显示所有可用子命令的帮助列表:
UCP Skill — 通用商务协议实现工具

可用命令:
  /ucp init      — 在当前项目中初始化UCP(克隆规范、创建配置)
  /ucp consult   — 全流程咨询:回答资格评估问题,生成实现路线图
  /ucp plan      — 生成详细的实现计划
  /ucp gaps      — 对照UCP要求分析现有代码的缺口
  /ucp scaffold  — 生成完整可运行的UCP实现代码
  /ucp validate  — 对照UCP schema验证实现是否合规
  /ucp profile   — 生成`/.well-known/ucp`发现配置文件
  /ucp test      — 为UCP处理程序生成单元测试
  /ucp docs      — 生成内部文档

典型工作流:
  /ucp init → /ucp consult → /ucp plan → /ucp scaffold → /ucp profile → /ucp test → /ucp validate

配置文件:./ucp.config.json
规范位置:./ucp/ 或 ./.ucp-spec/

Sub-command: init

子命令:init

Trigger

触发方式

User runs
/ucp init
用户执行
/ucp init

Purpose

用途

Bootstrap UCP in a project: clone spec, create config, ask essential questions.
在项目中初始化UCP:克隆规范、创建配置、询问基础问题

Procedure

执行流程

Step 1: Check/Clone Spec Repository

步骤1:检查/克隆规范仓库

  1. Check if
    ./ucp/
    exists
    • If yes: "Found local UCP spec at ./ucp/"
  2. If not, check if
    ./.ucp-spec/
    exists
    • If yes: Run
      git pull
      to update
    • If no: Clone the repo (see Spec Repository Handling)
  3. After cloning, add
    .ucp-spec/
    to
    .gitignore
  1. 检查
    ./ucp/
    是否存在
    • 存在则提示:"Found local UCP spec at ./ucp/"
  2. 如果不存在,检查
    ./.ucp-spec/
    是否存在
    • 存在则执行
      git pull
      更新
    • 不存在则克隆仓库(参考规范仓库处理部分)
  3. 克隆完成后,将
    .ucp-spec/
    添加到
    .gitignore

Step 2: Check for Existing Config

步骤2:检查现有配置

  1. Check if
    ./ucp.config.json
    exists
  2. If yes, ask: "Config file exists. Overwrite, merge, or abort?"
    • Overwrite: Delete and create fresh
    • Merge: Keep existing values as defaults
    • Abort: Stop init
  1. 检查
    ./ucp.config.json
    是否存在
  2. 如果存在,询问:"配置文件已存在,要覆盖、合并还是终止操作?"
    • 覆盖:删除现有文件并创建全新配置
    • 合并:保留现有值作为默认值
    • 终止:停止初始化流程

Step 3: Ask Essential Questions (4 questions)

步骤3:询问基础问题(共4个)

Q1: What role(s) are you implementing?
  • Business (merchant of record)
  • Platform (consumer app or agent)
  • Payment credential provider
  • Host embedding checkout
  • Multiple (specify)
If user selects multiple roles, WARN:
"Implementing multiple roles is unusual. This is typically for marketplace/aggregator scenarios. Are you sure?"
Q2: What runtime will you use?
  • Node.js (recommended, stable)
  • Bun (opt-in, experimental)
NOTE: If user mentions Edge, respond:
"Edge runtime is not supported for UCP implementations. Please choose Node.js or Bun."
Q3: What is your business domain?
  • The domain that will host
    /.well-known/ucp
  • Example:
    shop.example.com
Q4: Which transports do you need at launch?
  • REST (recommended baseline)
  • MCP (Model Context Protocol)
  • A2A (Agent-to-Agent)
  • Embedded (iframe checkout)
问题1:你要实现的角色是什么?
  • 商家(记录交易主体)
  • 平台(消费端应用或Agent)
  • 支付凭证提供方
  • 宿主嵌入结账页
  • 多个角色(请说明)
如果用户选择多个角色,给出警告:
"实现多个角色属于非常规场景,通常仅适用于市场/聚合商场景,是否确认选择?"
问题2:你要使用什么运行时?
  • Node.js(推荐,稳定)
  • Bun(可选,实验性)
注意:如果用户提到Edge,回复:
"UCP实现不支持Edge runtime,请选择Node.js或Bun。"
问题3:你的业务域名是什么?
  • 用于托管
    /.well-known/ucp
    的域名
  • 示例:
    shop.example.com
问题4:上线时需要哪些传输协议?
  • REST(推荐的基础协议)
  • MCP(Model Context Protocol)
  • A2A(Agent-to-Agent)
  • Embedded(iframe结账)

Step 4: Create Config File

步骤4:创建配置文件

Create
./ucp.config.json
with:
  • Answers from essential questions
  • Sensible defaults for other fields
  • ucp_version
    set to latest from spec
创建
./ucp.config.json
,包含:
  • 基础问题的回答
  • 其他字段的合理默认值
  • ucp_version
    设置为规范中的最新版本

Step 5: Output Ready Message

步骤5:输出就绪提示

UCP initialized successfully!

Config: ./ucp.config.json
Spec:   ./.ucp-spec/ (or ./ucp/)
Role:   {role}
Domain: {domain}

Next steps:
  /ucp consult  — Complete full consultation (recommended)
  /ucp plan     — Skip to implementation planning
  /ucp gaps     — Analyze existing code first

UCP初始化成功!

配置文件:./ucp.config.json
规范位置:./.ucp-spec/(或./ucp/)
角色:{role}
域名:{domain}

下一步操作:
  /ucp consult  — 完成全流程咨询(推荐)
  /ucp plan     — 直接生成实现计划
  /ucp gaps     — 先分析现有代码缺口

Sub-command: consult

子命令:consult

Trigger

触发方式

User runs
/ucp consult
用户执行
/ucp consult

Purpose

用途

Walk through all 12 qualifying questions, update config, produce implementation roadmap.
引导用户回答全部12个资格评估问题,更新配置,生成实现路线图

Prerequisites

前置条件

  • Config file must exist (run
    /ucp init
    first)
  • Spec must be available
  • 配置文件必须存在(先执行
    /ucp init
  • 规范必须可用

Procedure

执行流程

Step 1: Load Existing Config

步骤1:加载现有配置

Read
./ucp.config.json
and use existing answers as defaults.
读取
./ucp.config.json
,使用现有回答作为默认值

Step 2: Walk Through 12 Qualifying Questions

步骤2:引导回答12个资格评估问题

Ask each question. If already answered in config, show current value and ask to confirm or change.
Q1: Are we implementing the business side, the platform side, or both?
  • Map to
    roles
    in config
  • If both/multiple, warn about unusual scenario
Q2: Which UCP version and which capabilities/extensions are in scope?
  • Read available versions from spec
  • Present capability options:
    • Core:
      dev.ucp.shopping.checkout
      (required)
    • Extensions:
      • dev.ucp.shopping.fulfillment
      • dev.ucp.shopping.discount
      • dev.ucp.shopping.buyer_consent
      • dev.ucp.shopping.ap2_mandate
      • dev.ucp.shopping.order
      • dev.ucp.common.identity_linking
Q3: Which payment handlers do we need?
  • Wallets (Apple Pay, Google Pay)
  • PSP tokenization (Stripe, Adyen, etc.)
  • Custom handler
  • None yet (decide later)
Q4: Do we need AP2 mandates and signing key infrastructure?
  • Yes → set
    features.ap2_mandates: true
  • No → set
    features.ap2_mandates: false
  • If yes, explain: "You'll need to provide JWS signing keys (ES256 recommended)"
Q5: Do we need fulfillment options and multi-group/multi-destination support?
  • No fulfillment needed
  • Single destination only
  • Multi-destination support → set
    features.multi_destination_fulfillment: true
Q6: Do we need discounts, buyer consent capture, or identity linking?
  • Discounts → add
    dev.ucp.shopping.discount
    to extensions
  • Buyer consent → add
    dev.ucp.shopping.buyer_consent
    to extensions
  • Identity linking → add
    dev.ucp.common.identity_linking
    , set
    features.identity_linking: true
Q7: What are the existing checkout and order APIs we should map to UCP?
  • Ask for existing endpoint paths
  • Store in
    existing_apis
    object
  • Examples:
    /api/checkout
    ,
    /api/cart
    ,
    /api/orders
Q8: What are the required policy URLs?
  • Privacy policy URL
  • Terms of service URL
  • Refund policy URL
  • Shipping policy URL
  • Store in
    policy_urls
    object
Q9: What authentication model is required for checkout endpoints?
  • None (anonymous checkout)
  • API key
  • OAuth 2.0
  • Session-based
  • Store in
    answers.authentication_model
Q10: Who will receive order webhooks and what event cadence is required?
  • Webhook URL for order events
  • Event types needed:
    order.created
    ,
    order.updated
    ,
    order.fulfilled
    ,
    order.canceled
  • Store in
    answers.webhook_config
Q11: Do we need to support MCP, A2A, or embedded checkout at launch?
  • Confirm/update
    transports
    array
  • Set
    transport_priority
    order
Q12: What is the business domain that will host /.well-known/ucp?
  • Confirm/update
    domain
    field
逐个询问问题,如果配置中已有答案,显示当前值并询问用户是否确认或修改。
问题1:我们要实现业务端、平台端,还是两者都实现?
  • 映射到配置中的
    roles
    字段
  • 如果选择两者/多个角色,警告非常规场景
问题2:要使用哪个UCP版本,哪些能力/扩展在实现范围内?
  • 从规范中读取可用版本
  • 展示能力选项:
    • 核心:
      dev.ucp.shopping.checkout
      (必填)
    • 扩展:
      • dev.ucp.shopping.fulfillment
      • dev.ucp.shopping.discount
      • dev.ucp.shopping.buyer_consent
      • dev.ucp.shopping.ap2_mandate
      • dev.ucp.shopping.order
      • dev.ucp.common.identity_linking
问题3:我们需要哪些支付处理程序?
  • 电子钱包(Apple Pay、Google Pay)
  • PSP令牌化(Stripe、Adyen等)
  • 自定义处理程序
  • 暂未确定(后续再选)
问题4:我们是否需要AP2 mandates和签名密钥基础设施?
  • 是 → 设置
    features.ap2_mandates: true
  • 否 → 设置
    features.ap2_mandates: false
  • 如果选择是,说明:"你需要提供JWS签名密钥(推荐ES256算法)"
问题5:我们是否需要配送选项和多分组/多地址支持?
  • 不需要配送
  • 仅支持单地址
  • 多地址支持 → 设置
    features.multi_destination_fulfillment: true
问题6:我们是否需要折扣、买家同意采集或身份关联?
  • 折扣 → 将
    dev.ucp.shopping.discount
    添加到扩展能力
  • 买家同意 → 将
    dev.ucp.shopping.buyer_consent
    添加到扩展能力
  • 身份关联 → 添加
    dev.ucp.common.identity_linking
    ,设置
    features.identity_linking: true
问题7:有哪些现有的结账和订单API需要映射到UCP?
  • 询问现有端点路径
  • 存储到
    existing_apis
    对象
  • 示例:
    /api/checkout
    /api/cart
    /api/orders
问题8:必填的政策URL有哪些?
  • 隐私政策URL
  • 服务条款URL
  • 退款政策URL
  • 配送政策URL
  • 存储到
    policy_urls
    对象
问题9:结账端点需要什么认证模式?
  • 无(匿名结账)
  • API密钥
  • OAuth 2.0
  • 基于会话
  • 存储到
    answers.authentication_model
问题10:谁会接收订单webhook,需要什么事件推送频率?
  • 订单事件的webhook URL
  • 需要的事件类型:
    order.created
    order.updated
    order.fulfilled
    order.canceled
  • 存储到
    answers.webhook_config
问题11:上线时是否需要支持MCP、A2A或嵌入式结账?
  • 确认/更新
    transports
    数组
  • 设置
    transport_priority
    顺序
问题12:托管
/.well-known/ucp
的业务域名是什么?
  • 确认/更新
    domain
    字段

Step 3: Update Config

步骤3:更新配置

Write all answers to
./ucp.config.json
将所有回答写入
./ucp.config.json

Step 4: Generate Implementation Roadmap

步骤4:生成实现路线图

Based on answers, produce a roadmap:
UCP Implementation Roadmap
==========================

Role: Business (merchant)
Version: 2026-01-11
Domain: shop.example.com

Capabilities to implement:
  ✓ dev.ucp.shopping.checkout (core)
  ✓ dev.ucp.shopping.fulfillment
  ✓ dev.ucp.shopping.discount
  ○ dev.ucp.shopping.order

Transports (in order):
  1. REST
  2. MCP

Payment handlers:
  - Stripe tokenization

Key implementation tasks:
  1. Create /.well-known/ucp discovery profile
  2. Implement checkout session endpoints (create, get, update, complete)
  3. Implement fulfillment options logic
  4. Implement discount code application
  5. Set up payment handler integration
  6. Implement order webhooks
  7. Add MCP transport layer

Estimated files to create/modify: ~15-20

Run /ucp plan for detailed file-by-file plan.

基于回答生成路线图:
UCP实现路线图
==========================

角色:商家(商户)
版本:2026-01-11
域名:shop.example.com

待实现能力:
  ✓ dev.ucp.shopping.checkout(核心)
  ✓ dev.ucp.shopping.fulfillment
  ✓ dev.ucp.shopping.discount
  ○ dev.ucp.shopping.order

传输协议(按优先级):
  1. REST
  2. MCP

支付处理程序:
  - Stripe令牌化

核心实现任务:
  1. 创建`/.well-known/ucp`发现配置
  2. 实现结账会话端点(创建、查询、更新、完成)
  3. 实现配送选项逻辑
  4. 实现折扣码应用逻辑
  5. 搭建支付处理程序集成
  6. 实现订单webhook
  7. 添加MCP传输层

预计创建/修改文件数:~15-20

执行/ucp plan获取逐文件的详细计划。

Sub-command: plan

子命令:plan

Trigger

触发方式

User runs
/ucp plan
用户执行
/ucp plan

Purpose

用途

Generate detailed implementation plan with specific files and order of operations.
生成详细的实现计划,包含具体文件和操作顺序

Prerequisites

前置条件

  • Config file must exist with completed consultation
  • Spec must be available
  • 配置文件必须存在且已完成咨询流程
  • 规范必须可用

Procedure

执行流程

Step 1: Load Config and Spec

步骤1:加载配置和规范

  • Read
    ./ucp.config.json
  • Read relevant spec files based on capabilities/transports
  • 读取
    ./ucp.config.json
  • 基于能力/传输协议读取相关规范文件

Step 2: Analyze Existing Codebase Structure

步骤2:分析现有代码库结构

  • Detect Next.js version (App Router vs Pages Router)
  • Find existing API routes
  • Find existing lib/utils structure
  • Find existing types/schemas
  • Identify package manager (npm, yarn, pnpm, bun)
  • 检测Next.js版本(App Router vs Pages Router)
  • 查找现有API路由
  • 查找现有lib/utils结构
  • 查找现有类型/schema
  • 识别包管理器(npm、yarn、pnpm、bun)

Step 3: Generate File Plan

步骤3:生成文件计划

For each capability/transport, list files to create/modify.
Example output:
UCP Implementation Plan
=======================

Phase 1: Core Types and Schemas
-------------------------------
CREATE  lib/ucp/types/checkout.ts
        - CheckoutSession interface
        - LineItem, Totals, Payment types
        - Status enum

CREATE  lib/ucp/types/index.ts
        - Re-export all types

CREATE  lib/ucp/schemas/checkout.ts
        - Zod schemas for validation

Phase 2: Discovery Profile
--------------------------
CREATE  app/.well-known/ucp/route.ts
        - GET handler returning profile JSON
        - Read capabilities from config

CREATE  lib/ucp/profile.ts
        - Profile generation logic

Phase 3: Checkout Endpoints (REST)
----------------------------------
CREATE  app/api/ucp/checkout/route.ts
        - POST: Create checkout session
        - Capability negotiation logic

CREATE  app/api/ucp/checkout/[id]/route.ts
        - GET: Retrieve checkout
        - PATCH: Update checkout
        - POST: Complete checkout (action=complete)

CREATE  lib/ucp/handlers/checkout.ts
        - Business logic for checkout operations
        - State machine implementation

Phase 4: Fulfillment Extension
------------------------------
CREATE  lib/ucp/handlers/fulfillment.ts
        - Fulfillment options calculation
        - Destination validation

MODIFY  lib/ucp/handlers/checkout.ts
        - Integrate fulfillment into checkout response

Phase 5: Discount Extension
---------------------------
CREATE  lib/ucp/handlers/discount.ts
        - Discount code validation
        - Applied discount calculation

MODIFY  lib/ucp/handlers/checkout.ts
        - Integrate discounts into checkout

Phase 6: Payment Integration
----------------------------
CREATE  lib/ucp/handlers/payment.ts
        - Payment handler registry
        - payment_data processing

CREATE  lib/ucp/handlers/stripe.ts
        - Stripe-specific tokenization

Phase 7: Order Webhooks
-----------------------
CREATE  lib/ucp/handlers/order.ts
        - Order event emission
        - Webhook signing (JWS)

CREATE  lib/ucp/webhooks/sender.ts
        - Webhook delivery with retries

Phase 8: MCP Transport (if enabled)
-----------------------------------
CREATE  lib/ucp/transports/mcp.ts
        - MCP tool definitions
        - JSON-RPC handlers

Dependencies to install:
------------------------
  zod          — Schema validation
  jose         — JWS signing (if AP2/webhooks enabled)

Run /ucp scaffold to generate these files.
为每个能力/传输协议列出需要创建/修改的文件。
示例输出:
UCP实现计划
=======================

阶段1:核心类型和Schema
-------------------------------
创建  lib/ucp/types/checkout.ts
        - CheckoutSession接口
        - LineItem、Totals、Payment类型
        - Status枚举

创建  lib/ucp/types/index.ts
        - 重新导出所有类型

创建  lib/ucp/schemas/checkout.ts
        - 用于验证的Zod schema

阶段2:发现配置
--------------------------
创建  app/.well-known/ucp/route.ts
        - 返回配置JSON的GET处理程序
        - 从配置读取能力

创建  lib/ucp/profile.ts
        - 配置生成逻辑

阶段3:结账端点(REST)
----------------------------------
创建  app/api/ucp/checkout/route.ts
        - POST:创建结账会话
        - 能力协商逻辑

创建  app/api/ucp/checkout/[id]/route.ts
        - GET:查询结账信息
        - PATCH:更新结账信息
        - POST:完成结账(action=complete)

创建  lib/ucp/handlers/checkout.ts
        - 结账操作的业务逻辑
        - 状态机实现

阶段4:配送扩展
------------------------------
创建  lib/ucp/handlers/fulfillment.ts
        - 配送选项计算
        - 地址验证

修改  lib/ucp/handlers/checkout.ts
        - 将配送逻辑集成到结账响应

阶段5:折扣扩展
---------------------------
创建  lib/ucp/handlers/discount.ts
        - 折扣码验证
        - 适用折扣计算

修改  lib/ucp/handlers/checkout.ts
        - 将折扣逻辑集成到结账流程

阶段6:支付集成
----------------------------
创建  lib/ucp/handlers/payment.ts
        - 支付处理程序注册表
        - payment_data处理逻辑

创建  lib/ucp/handlers/stripe.ts
        - Stripe专属令牌化逻辑

阶段7:订单Webhook
-----------------------
创建  lib/ucp/handlers/order.ts
        - 订单事件触发
        - Webhook签名(JWS)

创建  lib/ucp/webhooks/sender.ts
        - 带重试机制的Webhook投递

阶段8:MCP传输(如果启用)
-----------------------------------
创建  lib/ucp/transports/mcp.ts
        - MCP工具定义
        - JSON-RPC处理程序

待安装依赖:
------------------------
  zod          — Schema验证
  jose         — JWS签名(如果启用AP2/webhook)

执行/ucp scaffold生成这些文件。

Step 4: Save Plan to Config

步骤4:保存计划到配置

Store the plan in
answers.implementation_plan
for scaffold reference.

将计划存储到
answers.implementation_plan
供脚手架参考

Sub-command: gaps

子命令:gaps

Trigger

触发方式

User runs
/ucp gaps
用户执行
/ucp gaps

Purpose

用途

Deep analysis of existing codebase against UCP requirements. Uses AST parsing and data flow tracing.
对照UCP要求深度分析现有代码库,使用AST解析和数据流追踪能力

Prerequisites

前置条件

  • Config file should exist (for role/capability context)
  • Spec must be available
  • 配置文件应存在(提供角色/能力上下文)
  • 规范必须可用

Procedure

执行流程

Step 1: Load Context

步骤1:加载上下文

  • Read config for declared capabilities
  • Read relevant spec files
  • 读取配置中声明的能力
  • 读取相关规范文件

Step 2: Discover Existing Code

步骤2:发现现有代码

Scan for:
  • API routes (
    app/api/**
    ,
    pages/api/**
    )
  • Checkout-related files (search for "checkout", "cart", "order")
  • Payment handling code
  • Webhook implementations
扫描以下内容:
  • API路由(
    app/api/**
    pages/api/**
  • 结账相关文件(搜索"checkout"、"cart"、"order")
  • 支付处理代码
  • Webhook实现

Step 3: Deep Analysis (AST-based)

步骤3:深度分析(基于AST)

For each relevant file:
  • Parse AST
  • Trace data flow for checkout objects
  • Identify existing patterns
Analyze against UCP requirements:
RequirementStatusFinding
Discovery profile at /.well-known/ucpMISSINGNo route found
Checkout session creationPARTIALFound /api/checkout but missing UCP fields
Status lifecycleMISSINGNo status state machine
Capability negotiationMISSINGNo UCP-Agent header handling
Payment handler supportPARTIALStripe exists but not UCP-compliant
Response metadata (ucp object)MISSINGResponses don't include ucp field
对每个相关文件:
  • 解析AST
  • 追踪结账对象的数据流
  • 识别现有模式
对照UCP要求分析:
要求状态发现
/.well-known/ucp
位置的发现配置
缺失未找到对应路由
结账会话创建部分实现找到
/api/checkout
但缺失UCP字段
状态生命周期缺失无状态机实现
能力协商缺失无UCP-Agent header处理逻辑
支付处理程序支持部分实现已集成Stripe但不符合UCP规范
响应元数据(ucp对象)缺失响应未包含ucp字段

Step 4: Generate Gap Report

步骤4:生成缺口报告

UCP Gap Analysis Report
=======================

Existing codebase: Next.js 14 (App Router)
Target role: Business
Target capabilities: checkout, fulfillment, discount

CRITICAL GAPS (must fix)
------------------------
[GAP-001] Missing discovery profile
  - Required: /.well-known/ucp endpoint
  - Status: NOT FOUND
  - Fix: Create app/.well-known/ucp/route.ts

[GAP-002] Missing UCP response envelope
  - Required: All responses must include `ucp` object with version/capabilities
  - Found: app/api/checkout/route.ts returns raw checkout data
  - Fix: Wrap responses with UCP metadata

[GAP-003] Missing capability negotiation
  - Required: Read UCP-Agent header, compute intersection
  - Found: No header processing in checkout routes
  - Fix: Add middleware or handler logic

PARTIAL IMPLEMENTATIONS
-----------------------
[PARTIAL-001] Checkout session exists but non-compliant
  - File: app/api/checkout/route.ts
  - Missing: id, status, currency, totals.grand_total, links, payment fields
  - Has: line_items (needs schema adjustment)

[PARTIAL-002] Payment integration exists
  - File: lib/stripe.ts
  - Issue: Direct Stripe API, not UCP payment_data flow
  - Fix: Wrap with UCP payment handler abstraction

COMPLIANT AREAS
---------------
[OK] Policy URLs configured in existing checkout
[OK] HTTPS enforced
[OK] Idempotency key support in POST handlers

RECOMMENDATIONS
---------------
1. Start with /ucp scaffold to generate compliant structure
2. Migrate existing checkout logic into new handlers
3. Run /ucp validate after migration

Total: 3 critical gaps, 2 partial, 3 compliant

UCP缺口分析报告
=======================

现有代码库:Next.js 14(App Router)
目标角色:商家
目标能力:结账、配送、折扣

严重缺口(必须修复)
------------------------
[GAP-001] 缺失发现配置
  - 要求:`/.well-known/ucp`端点
  - 状态:未找到
  - 修复方案:创建app/.well-known/ucp/route.ts

[GAP-002] 缺失UCP响应封装
  - 要求:所有响应必须包含带版本/能力的`ucp`对象
  - 发现:app/api/checkout/route.ts返回原始结账数据
  - 修复方案:用UCP元数据封装响应

[GAP-003] 缺失能力协商
  - 要求:读取UCP-Agent header,计算能力交集
  - 发现:结账路由中无header处理逻辑
  - 修复方案:添加中间件或处理逻辑

部分实现
-----------------------
[PARTIAL-001] 结账会话已存在但不符合规范
  - 文件:app/api/checkout/route.ts
  - 缺失:id、status、currency、totals.grand_total、links、payment字段
  - 存在:line_items(需要调整schema)

[PARTIAL-002] 支付集成已存在
  - 文件:lib/stripe.ts
  - 问题:直接调用Stripe API,未遵循UCP payment_data流程
  - 修复方案:用UCP支付处理程序抽象层封装

合规区域
---------------
[OK] 现有结账中已配置政策URL
[OK] 已强制使用HTTPS
[OK] POST处理程序支持幂等键

建议
---------------
1. 先执行/ucp scaffold生成合规结构
2. 将现有结账逻辑迁移到新的处理程序中
3. 迁移完成后执行/ucp validate验证

总计:3个严重缺口,2个部分实现,3个合规区域

Sub-command: scaffold

子命令:scaffold

Trigger

触发方式

User runs
/ucp scaffold
用户执行
/ucp scaffold

Purpose

用途

Generate full working UCP implementation based on config and plan.
基于配置和计划生成完整可运行的UCP实现

Prerequisites

前置条件

  • Config file must exist
  • Plan should exist (run
    /ucp plan
    first, or scaffold will generate one)
  • 配置文件必须存在
  • 计划应存在(先执行
    /ucp plan
    ,否则脚手架会自动生成)

Procedure

执行流程

Step 1: Confirm Scaffold Depth

步骤1:确认脚手架生成级别

Ask user:
"What level of code generation do you want?"
  • types: TypeScript interfaces and Zod schemas only
  • scaffolding: Structure with TODO markers for business logic
  • full: Complete working implementation (recommended)
Store choice in
config.scaffold_depth
询问用户:
"你需要什么级别的代码生成?"
  • types:仅生成TypeScript接口和Zod schema
  • scaffolding:生成带TODO标记的业务逻辑结构
  • full:生成完整可运行的实现(推荐)
将选择存储到
config.scaffold_depth

Step 2: Check Dependencies

步骤2:检查依赖

Identify required packages based on config:
  • zod
    — Always needed
  • jose
    — If AP2 mandates or webhook signing enabled
  • uuid
    — For session ID generation
Ask before installing:
"The following packages are required: zod, jose, uuid" "Install now? (npm install / bun add)"
If yes, run appropriate install command.
基于配置识别所需包:
  • zod
    — 始终需要
  • jose
    — 如果启用AP2 mandates或webhook签名
  • uuid
    — 用于会话ID生成
安装前询问:
"需要安装以下依赖:zod、jose、uuid" "是否立即安装?(npm install / bun add)"
如果同意,执行对应的安装命令

Step 3: Generate Code

步骤3:生成代码

Generate files according to plan. For each file:
  1. Create parent directories if needed
  2. Write file content
  3. Track in
    config.generated_files
按照计划生成文件,对每个文件:
  1. 不存在父目录则创建
  2. 写入文件内容
  3. config.generated_files
    中追踪

Code Generation Templates

代码生成模板

lib/ucp/types/checkout.ts

lib/ucp/types/checkout.ts

typescript
/**
 * UCP Checkout Types
 * Generated by /ucp scaffold
 * Spec: {spec_version}
 */

export type CheckoutStatus =
  | 'incomplete'
  | 'requires_escalation'
  | 'ready_for_complete'
  | 'complete_in_progress'
  | 'completed'
  | 'canceled';

export type MessageSeverity =
  | 'recoverable'
  | 'requires_buyer_input'
  | 'requires_buyer_review';

export interface UCPMetadata {
  version: string;
  capabilities: string[];
}

export interface LineItem {
  id: string;
  name: string;
  quantity: number;
  unit_price: number;
  total_price: number;
  currency: string;
  // Extension fields added based on config
}

export interface Totals {
  subtotal: number;
  tax: number;
  shipping: number;
  discount: number;
  grand_total: number;
  currency: string;
}

export interface PaymentInfo {
  status: 'pending' | 'authorized' | 'captured' | 'failed';
  handlers: PaymentHandler[];
  amount_due: number;
  currency: string;
}

export interface PaymentHandler {
  id: string;
  type: string;
  config?: Record<string, unknown>;
}

export interface CheckoutMessage {
  code: string;
  severity: MessageSeverity;
  message: string;
  field?: string;
}

export interface CheckoutLinks {
  self: string;
  continue_url?: string;
  privacy_policy: string;
  terms_of_service: string;
  refund_policy?: string;
  shipping_policy?: string;
}

export interface CheckoutSession {
  ucp: UCPMetadata;
  id: string;
  status: CheckoutStatus;
  currency: string;
  line_items: LineItem[];
  totals: Totals;
  payment: PaymentInfo;
  links: CheckoutLinks;
  messages: CheckoutMessage[];
  expires_at: string;
  created_at: string;
  updated_at: string;
  // Extension fields populated based on negotiated capabilities
  buyer?: BuyerInfo;
  fulfillment?: FulfillmentInfo;
  discounts?: DiscountInfo;
}

export interface BuyerInfo {
  email?: string;
  phone?: string;
  name?: string;
  // consent fields if buyer_consent extension enabled
}

// Conditional types based on extensions...
typescript
/**
 * UCP Checkout Types
 * Generated by /ucp scaffold
 * Spec: {spec_version}
 */

export type CheckoutStatus =
  | 'incomplete'
  | 'requires_escalation'
  | 'ready_for_complete'
  | 'complete_in_progress'
  | 'completed'
  | 'canceled';

export type MessageSeverity =
  | 'recoverable'
  | 'requires_buyer_input'
  | 'requires_buyer_review';

export interface UCPMetadata {
  version: string;
  capabilities: string[];
}

export interface LineItem {
  id: string;
  name: string;
  quantity: number;
  unit_price: number;
  total_price: number;
  currency: string;
  // Extension fields added based on config
}

export interface Totals {
  subtotal: number;
  tax: number;
  shipping: number;
  discount: number;
  grand_total: number;
  currency: string;
}

export interface PaymentInfo {
  status: 'pending' | 'authorized' | 'captured' | 'failed';
  handlers: PaymentHandler[];
  amount_due: number;
  currency: string;
}

export interface PaymentHandler {
  id: string;
  type: string;
  config?: Record<string, unknown>;
}

export interface CheckoutMessage {
  code: string;
  severity: MessageSeverity;
  message: string;
  field?: string;
}

export interface CheckoutLinks {
  self: string;
  continue_url?: string;
  privacy_policy: string;
  terms_of_service: string;
  refund_policy?: string;
  shipping_policy?: string;
}

export interface CheckoutSession {
  ucp: UCPMetadata;
  id: string;
  status: CheckoutStatus;
  currency: string;
  line_items: LineItem[];
  totals: Totals;
  payment: PaymentInfo;
  links: CheckoutLinks;
  messages: CheckoutMessage[];
  expires_at: string;
  created_at: string;
  updated_at: string;
  // Extension fields populated based on negotiated capabilities
  buyer?: BuyerInfo;
  fulfillment?: FulfillmentInfo;
  discounts?: DiscountInfo;
}

export interface BuyerInfo {
  email?: string;
  phone?: string;
  name?: string;
  // consent fields if buyer_consent extension enabled
}

// Conditional types based on extensions...

lib/ucp/schemas/checkout.ts

lib/ucp/schemas/checkout.ts

typescript
/**
 * UCP Checkout Zod Schemas
 * Generated by /ucp scaffold
 */

import { z } from 'zod';

export const LineItemSchema = z.object({
  id: z.string(),
  name: z.string(),
  quantity: z.number().int().positive(),
  unit_price: z.number().int(), // minor units (cents)
  total_price: z.number().int(),
  currency: z.string().length(3),
});

export const TotalsSchema = z.object({
  subtotal: z.number().int(),
  tax: z.number().int(),
  shipping: z.number().int(),
  discount: z.number().int(),
  grand_total: z.number().int(),
  currency: z.string().length(3),
});

export const CreateCheckoutRequestSchema = z.object({
  line_items: z.array(LineItemSchema).min(1),
  currency: z.string().length(3),
  buyer: z.object({
    email: z.string().email().optional(),
    phone: z.string().optional(),
  }).optional(),
  // Extension fields...
});

export const UpdateCheckoutRequestSchema = z.object({
  line_items: z.array(LineItemSchema).optional(),
  buyer: z.object({
    email: z.string().email().optional(),
    phone: z.string().optional(),
  }).optional(),
  // Extension fields...
});

export const CompleteCheckoutRequestSchema = z.object({
  action: z.literal('complete'),
  payment_data: z.record(z.unknown()),
});

export type CreateCheckoutRequest = z.infer<typeof CreateCheckoutRequestSchema>;
export type UpdateCheckoutRequest = z.infer<typeof UpdateCheckoutRequestSchema>;
export type CompleteCheckoutRequest = z.infer<typeof CompleteCheckoutRequestSchema>;
typescript
/**
 * UCP Checkout Zod Schemas
 * Generated by /ucp scaffold
 */

import { z } from 'zod';

export const LineItemSchema = z.object({
  id: z.string(),
  name: z.string(),
  quantity: z.number().int().positive(),
  unit_price: z.number().int(), // minor units (cents)
  total_price: z.number().int(),
  currency: z.string().length(3),
});

export const TotalsSchema = z.object({
  subtotal: z.number().int(),
  tax: z.number().int(),
  shipping: z.number().int(),
  discount: z.number().int(),
  grand_total: z.number().int(),
  currency: z.string().length(3),
});

export const CreateCheckoutRequestSchema = z.object({
  line_items: z.array(LineItemSchema).min(1),
  currency: z.string().length(3),
  buyer: z.object({
    email: z.string().email().optional(),
    phone: z.string().optional(),
  }).optional(),
  // Extension fields...
});

export const UpdateCheckoutRequestSchema = z.object({
  line_items: z.array(LineItemSchema).optional(),
  buyer: z.object({
    email: z.string().email().optional(),
    phone: z.string().optional(),
  }).optional(),
  // Extension fields...
});

export const CompleteCheckoutRequestSchema = z.object({
  action: z.literal('complete'),
  payment_data: z.record(z.unknown()),
});

export type CreateCheckoutRequest = z.infer<typeof CreateCheckoutRequestSchema>;
export type UpdateCheckoutRequest = z.infer<typeof UpdateCheckoutRequestSchema>;
export type CompleteCheckoutRequest = z.infer<typeof CompleteCheckoutRequestSchema>;

app/.well-known/ucp/route.ts

app/.well-known/ucp/route.ts

typescript
/**
 * UCP Discovery Profile Endpoint
 * GET /.well-known/ucp
 * Generated by /ucp scaffold
 */

import { NextResponse } from 'next/server';
import { generateProfile } from '@/lib/ucp/profile';

export const runtime = 'nodejs'; // Edge runtime is not supported

export async function GET() {
  const profile = generateProfile();

  return NextResponse.json(profile, {
    headers: {
      'Cache-Control': 'public, max-age=3600',
      'Content-Type': 'application/json',
    },
  });
}
typescript
/**
 * UCP Discovery Profile Endpoint
 * GET /.well-known/ucp
 * Generated by /ucp scaffold
 */

import { NextResponse } from 'next/server';
import { generateProfile } from '@/lib/ucp/profile';

export const runtime = 'nodejs'; // Edge runtime is not supported

export async function GET() {
  const profile = generateProfile();

  return NextResponse.json(profile, {
    headers: {
      'Cache-Control': 'public, max-age=3600',
      'Content-Type': 'application/json',
    },
  });
}

lib/ucp/profile.ts

lib/ucp/profile.ts

typescript
/**
 * UCP Discovery Profile Generator
 * Generated by /ucp scaffold
 */

import config from '@/../ucp.config.json';

export interface UCPProfile {
  ucp: {
    version: string;
    services: Record<string, ServiceDefinition>;
    capabilities: CapabilityDefinition[];
  };
  payment?: {
    handlers: PaymentHandlerDefinition[];
  };
  signing_keys?: JsonWebKey[];
}

interface ServiceDefinition {
  version: string;
  spec: string;
  rest?: { schema: string; endpoint: string };
  mcp?: { schema: string; endpoint: string };
  a2a?: { endpoint: string };
  embedded?: { schema: string };
}

interface CapabilityDefinition {
  name: string;
  version: string;
  spec: string;
  schema: string;
  extends?: string;
  config?: Record<string, unknown>;
}

interface PaymentHandlerDefinition {
  id: string;
  type: string;
  spec: string;
  config_schema: string;
}

export function generateProfile(): UCPProfile {
  const baseUrl = `https://${config.domain}`;

  const profile: UCPProfile = {
    ucp: {
      version: config.ucp_version,
      services: {
        'dev.ucp.shopping': {
          version: config.ucp_version,
          spec: 'https://ucp.dev/spec/services/shopping',
          ...(config.transports.includes('rest') && {
            rest: {
              schema: 'https://ucp.dev/spec/services/shopping/rest.openapi.json',
              endpoint: `${baseUrl}/api/ucp`,
            },
          }),
          ...(config.transports.includes('mcp') && {
            mcp: {
              schema: 'https://ucp.dev/spec/services/shopping/mcp.openrpc.json',
              endpoint: `${baseUrl}/api/ucp/mcp`,
            },
          }),
          ...(config.transports.includes('a2a') && {
            a2a: {
              endpoint: `${baseUrl}/api/ucp/a2a`,
            },
          }),
          ...(config.transports.includes('embedded') && {
            embedded: {
              schema: 'https://ucp.dev/spec/services/shopping/embedded.openrpc.json',
            },
          }),
        },
      },
      capabilities: buildCapabilities(config),
    },
  };

  if (config.payment_handlers.length > 0) {
    profile.payment = {
      handlers: config.payment_handlers.map(buildHandlerDefinition),
    };
  }

  return profile;
}

function buildCapabilities(config: typeof import('@/../ucp.config.json')): CapabilityDefinition[] {
  const capabilities: CapabilityDefinition[] = [];

  // Core checkout capability (always present)
  capabilities.push({
    name: 'dev.ucp.shopping.checkout',
    version: config.ucp_version,
    spec: 'https://ucp.dev/spec/capabilities/checkout',
    schema: 'https://ucp.dev/spec/schemas/shopping/checkout.json',
  });

  // Add extensions based on config
  for (const ext of config.capabilities.extensions) {
    capabilities.push(buildExtensionCapability(ext, config));
  }

  return capabilities;
}

function buildExtensionCapability(
  extension: string,
  config: typeof import('@/../ucp.config.json')
): CapabilityDefinition {
  // Map extension names to spec URLs
  const extMap: Record<string, { spec: string; schema: string; extends?: string }> = {
    'dev.ucp.shopping.fulfillment': {
      spec: 'https://ucp.dev/spec/capabilities/fulfillment',
      schema: 'https://ucp.dev/spec/schemas/shopping/fulfillment.json',
      extends: 'dev.ucp.shopping.checkout',
    },
    'dev.ucp.shopping.discount': {
      spec: 'https://ucp.dev/spec/capabilities/discount',
      schema: 'https://ucp.dev/spec/schemas/shopping/discount.json',
      extends: 'dev.ucp.shopping.checkout',
    },
    'dev.ucp.shopping.buyer_consent': {
      spec: 'https://ucp.dev/spec/capabilities/buyer-consent',
      schema: 'https://ucp.dev/spec/schemas/shopping/buyer-consent.json',
      extends: 'dev.ucp.shopping.checkout',
    },
    'dev.ucp.shopping.order': {
      spec: 'https://ucp.dev/spec/capabilities/order',
      schema: 'https://ucp.dev/spec/schemas/shopping/order.json',
      config: {
        webhook_url: config.answers?.webhook_config?.url,
      },
    },
    'dev.ucp.common.identity_linking': {
      spec: 'https://ucp.dev/spec/capabilities/identity-linking',
      schema: 'https://ucp.dev/spec/schemas/common/identity-linking.json',
    },
  };

  const def = extMap[extension];
  return {
    name: extension,
    version: config.ucp_version,
    spec: def?.spec || '',
    schema: def?.schema || '',
    ...(def?.extends && { extends: def.extends }),
    ...(def?.config && { config: def.config }),
  };
}

function buildHandlerDefinition(handlerId: string): PaymentHandlerDefinition {
  // Map known handlers
  const handlerMap: Record<string, Omit<PaymentHandlerDefinition, 'id'>> = {
    stripe: {
      type: 'tokenization',
      spec: 'https://ucp.dev/spec/handlers/stripe',
      config_schema: 'https://ucp.dev/spec/handlers/stripe/config.json',
    },
    // Add more handlers as needed
  };

  const def = handlerMap[handlerId] || {
    type: 'custom',
    spec: '',
    config_schema: '',
  };

  return { id: handlerId, ...def };
}
typescript
/**
 * UCP Discovery Profile Generator
 * Generated by /ucp scaffold
 */

import config from '@/../ucp.config.json';

export interface UCPProfile {
  ucp: {
    version: string;
    services: Record<string, ServiceDefinition>;
    capabilities: CapabilityDefinition[];
  };
  payment?: {
    handlers: PaymentHandlerDefinition[];
  };
  signing_keys?: JsonWebKey[];
}

interface ServiceDefinition {
  version: string;
  spec: string;
  rest?: { schema: string; endpoint: string };
  mcp?: { schema: string; endpoint: string };
  a2a?: { endpoint: string };
  embedded?: { schema: string };
}

interface CapabilityDefinition {
  name: string;
  version: string;
  spec: string;
  schema: string;
  extends?: string;
  config?: Record<string, unknown>;
}

interface PaymentHandlerDefinition {
  id: string;
  type: string;
  spec: string;
  config_schema: string;
}

export function generateProfile(): UCPProfile {
  const baseUrl = `https://${config.domain}`;

  const profile: UCPProfile = {
    ucp: {
      version: config.ucp_version,
      services: {
        'dev.ucp.shopping': {
          version: config.ucp_version,
          spec: 'https://ucp.dev/spec/services/shopping',
          ...(config.transports.includes('rest') && {
            rest: {
              schema: 'https://ucp.dev/spec/services/shopping/rest.openapi.json',
              endpoint: `${baseUrl}/api/ucp`,
            },
          }),
          ...(config.transports.includes('mcp') && {
            mcp: {
              schema: 'https://ucp.dev/spec/services/shopping/mcp.openrpc.json',
              endpoint: `${baseUrl}/api/ucp/mcp`,
            },
          }),
          ...(config.transports.includes('a2a') && {
            a2a: {
              endpoint: `${baseUrl}/api/ucp/a2a`,
            },
          }),
          ...(config.transports.includes('embedded') && {
            embedded: {
              schema: 'https://ucp.dev/spec/services/shopping/embedded.openrpc.json',
            },
          }),
        },
      },
      capabilities: buildCapabilities(config),
    },
  };

  if (config.payment_handlers.length > 0) {
    profile.payment = {
      handlers: config.payment_handlers.map(buildHandlerDefinition),
    };
  }

  return profile;
}

function buildCapabilities(config: typeof import('@/../ucp.config.json')): CapabilityDefinition[] {
  const capabilities: CapabilityDefinition[] = [];

  // Core checkout capability (always present)
  capabilities.push({
    name: 'dev.ucp.shopping.checkout',
    version: config.ucp_version,
    spec: 'https://ucp.dev/spec/capabilities/checkout',
    schema: 'https://ucp.dev/spec/schemas/shopping/checkout.json',
  });

  // Add extensions based on config
  for (const ext of config.capabilities.extensions) {
    capabilities.push(buildExtensionCapability(ext, config));
  }

  return capabilities;
}

function buildExtensionCapability(
  extension: string,
  config: typeof import('@/../ucp.config.json')
): CapabilityDefinition {
  // Map extension names to spec URLs
  const extMap: Record<string, { spec: string; schema: string; extends?: string }> = {
    'dev.ucp.shopping.fulfillment': {
      spec: 'https://ucp.dev/spec/capabilities/fulfillment',
      schema: 'https://ucp.dev/spec/schemas/shopping/fulfillment.json',
      extends: 'dev.ucp.shopping.checkout',
    },
    'dev.ucp.shopping.discount': {
      spec: 'https://ucp.dev/spec/capabilities/discount',
      schema: 'https://ucp.dev/spec/schemas/shopping/discount.json',
      extends: 'dev.ucp.shopping.checkout',
    },
    'dev.ucp.shopping.buyer_consent': {
      spec: 'https://ucp.dev/spec/capabilities/buyer-consent',
      schema: 'https://ucp.dev/spec/schemas/shopping/buyer-consent.json',
      extends: 'dev.ucp.shopping.checkout',
    },
    'dev.ucp.shopping.order': {
      spec: 'https://ucp.dev/spec/capabilities/order',
      schema: 'https://ucp.dev/spec/schemas/shopping/order.json',
      config: {
        webhook_url: config.answers?.webhook_config?.url,
      },
    },
    'dev.ucp.common.identity_linking': {
      spec: 'https://ucp.dev/spec/capabilities/identity-linking',
      schema: 'https://ucp.dev/spec/schemas/common/identity-linking.json',
    },
  };

  const def = extMap[extension];
  return {
    name: extension,
    version: config.ucp_version,
    spec: def?.spec || '',
    schema: def?.schema || '',
    ...(def?.extends && { extends: def.extends }),
    ...(def?.config && { config: def.config }),
  };
}

function buildHandlerDefinition(handlerId: string): PaymentHandlerDefinition {
  // Map known handlers
  const handlerMap: Record<string, Omit<PaymentHandlerDefinition, 'id'>> = {
    stripe: {
      type: 'tokenization',
      spec: 'https://ucp.dev/spec/handlers/stripe',
      config_schema: 'https://ucp.dev/spec/handlers/stripe/config.json',
    },
    // Add more handlers as needed
  };

  const def = handlerMap[handlerId] || {
    type: 'custom',
    spec: '',
    config_schema: '',
  };

  return { id: handlerId, ...def };
}

app/api/ucp/checkout/route.ts

app/api/ucp/checkout/route.ts

typescript
/**
 * UCP Checkout Session Endpoint
 * POST /api/ucp/checkout - Create checkout session
 * Generated by /ucp scaffold
 */

import { NextRequest, NextResponse } from 'next/server';
import { createCheckout } from '@/lib/ucp/handlers/checkout';
import { CreateCheckoutRequestSchema } from '@/lib/ucp/schemas/checkout';
import { negotiateCapabilities, parseUCPAgent } from '@/lib/ucp/negotiation';
import { wrapResponse, errorResponse } from '@/lib/ucp/response';

export const runtime = 'nodejs'; // Edge runtime is not supported

export async function POST(request: NextRequest) {
  try {
    // Parse UCP-Agent header for capability negotiation
    const ucpAgent = parseUCPAgent(request.headers.get('UCP-Agent'));

    // Negotiate capabilities
    const negotiation = await negotiateCapabilities(ucpAgent?.profile);

    // Parse and validate request body
    const body = await request.json();
    const parsed = CreateCheckoutRequestSchema.safeParse(body);

    if (!parsed.success) {
      return errorResponse(400, 'invalid_request', parsed.error.message);
    }

    // Get idempotency key
    const idempotencyKey = request.headers.get('Idempotency-Key');

    // Create checkout session
    const checkout = await createCheckout(parsed.data, {
      capabilities: negotiation.capabilities,
      idempotencyKey,
    });

    return wrapResponse(checkout, negotiation, 201);
  } catch (error) {
    console.error('Checkout creation failed:', error);
    return errorResponse(500, 'internal_error', 'Failed to create checkout session');
  }
}
typescript
/**
 * UCP Checkout Session Endpoint
 * POST /api/ucp/checkout - Create checkout session
 * Generated by /ucp scaffold
 */

import { NextRequest, NextResponse } from 'next/server';
import { createCheckout } from '@/lib/ucp/handlers/checkout';
import { CreateCheckoutRequestSchema } from '@/lib/ucp/schemas/checkout';
import { negotiateCapabilities, parseUCPAgent } from '@/lib/ucp/negotiation';
import { wrapResponse, errorResponse } from '@/lib/ucp/response';

export const runtime = 'nodejs'; // Edge runtime is not supported

export async function POST(request: NextRequest) {
  try {
    // Parse UCP-Agent header for capability negotiation
    const ucpAgent = parseUCPAgent(request.headers.get('UCP-Agent'));

    // Negotiate capabilities
    const negotiation = await negotiateCapabilities(ucpAgent?.profile);

    // Parse and validate request body
    const body = await request.json();
    const parsed = CreateCheckoutRequestSchema.safeParse(body);

    if (!parsed.success) {
      return errorResponse(400, 'invalid_request', parsed.error.message);
    }

    // Get idempotency key
    const idempotencyKey = request.headers.get('Idempotency-Key');

    // Create checkout session
    const checkout = await createCheckout(parsed.data, {
      capabilities: negotiation.capabilities,
      idempotencyKey,
    });

    return wrapResponse(checkout, negotiation, 201);
  } catch (error) {
    console.error('Checkout creation failed:', error);
    return errorResponse(500, 'internal_error', 'Failed to create checkout session');
  }
}

lib/ucp/handlers/checkout.ts

lib/ucp/handlers/checkout.ts

typescript
/**
 * UCP Checkout Handler
 * Core business logic for checkout operations
 * Generated by /ucp scaffold
 */

import { randomUUID } from 'crypto';
import type {
  CheckoutSession,
  CheckoutStatus,
  CreateCheckoutRequest,
  UpdateCheckoutRequest,
} from '@/lib/ucp/types/checkout';
import config from '@/../ucp.config.json';

// In-memory store for demo - replace with your database
const checkoutStore = new Map<string, CheckoutSession>();

interface CreateCheckoutOptions {
  capabilities: string[];
  idempotencyKey?: string | null;
}

export async function createCheckout(
  request: CreateCheckoutRequest,
  options: CreateCheckoutOptions
): Promise<CheckoutSession> {
  const id = randomUUID();
  const now = new Date().toISOString();
  const expiresAt = new Date(Date.now() + 30 * 60 * 1000).toISOString(); // 30 min

  // Calculate totals
  const subtotal = request.line_items.reduce((sum, item) => sum + item.total_price, 0);
  const tax = calculateTax(subtotal); // Implement your tax logic
  const shipping = 0; // Set by fulfillment extension
  const discount = 0; // Set by discount extension

  const checkout: CheckoutSession = {
    ucp: {
      version: config.ucp_version,
      capabilities: options.capabilities,
    },
    id,
    status: 'incomplete',
    currency: request.currency,
    line_items: request.line_items.map((item, index) => ({
      ...item,
      id: item.id || `line_${index}`,
    })),
    totals: {
      subtotal,
      tax,
      shipping,
      discount,
      grand_total: subtotal + tax + shipping - discount,
      currency: request.currency,
    },
    payment: {
      status: 'pending',
      handlers: getPaymentHandlers(options.capabilities),
      amount_due: subtotal + tax + shipping - discount,
      currency: request.currency,
    },
    links: {
      self: `https://${config.domain}/api/ucp/checkout/${id}`,
      continue_url: `https://${config.domain}/checkout/${id}`,
      privacy_policy: config.policy_urls.privacy,
      terms_of_service: config.policy_urls.terms,
      ...(config.policy_urls.refunds && { refund_policy: config.policy_urls.refunds }),
      ...(config.policy_urls.shipping && { shipping_policy: config.policy_urls.shipping }),
    },
    messages: [],
    expires_at: expiresAt,
    created_at: now,
    updated_at: now,
  };

  // Add buyer info if provided
  if (request.buyer) {
    checkout.buyer = request.buyer;
  }

  // Validate checkout state and set appropriate status
  checkout.status = determineStatus(checkout);
  checkout.messages = generateMessages(checkout);

  // Store checkout
  checkoutStore.set(id, checkout);

  return checkout;
}

export async function getCheckout(id: string): Promise<CheckoutSession | null> {
  return checkoutStore.get(id) || null;
}

export async function updateCheckout(
  id: string,
  request: UpdateCheckoutRequest,
  capabilities: string[]
): Promise<CheckoutSession | null> {
  const checkout = checkoutStore.get(id);
  if (!checkout) return null;

  // Check if checkout can be modified
  if (['completed', 'canceled'].includes(checkout.status)) {
    throw new Error('Checkout cannot be modified in current state');
  }

  // Apply updates
  if (request.line_items) {
    checkout.line_items = request.line_items;
    recalculateTotals(checkout);
  }

  if (request.buyer) {
    checkout.buyer = { ...checkout.buyer, ...request.buyer };
  }

  // Update metadata
  checkout.updated_at = new Date().toISOString();
  checkout.ucp.capabilities = capabilities;
  checkout.status = determineStatus(checkout);
  checkout.messages = generateMessages(checkout);

  checkoutStore.set(id, checkout);
  return checkout;
}

export async function completeCheckout(
  id: string,
  paymentData: Record<string, unknown>,
  capabilities: string[]
): Promise<CheckoutSession | null> {
  const checkout = checkoutStore.get(id);
  if (!checkout) return null;

  if (checkout.status !== 'ready_for_complete') {
    throw new Error('Checkout is not ready for completion');
  }

  checkout.status = 'complete_in_progress';
  checkout.updated_at = new Date().toISOString();
  checkoutStore.set(id, checkout);

  try {
    // Process payment
    await processPayment(checkout, paymentData);

    checkout.status = 'completed';
    checkout.payment.status = 'captured';
    checkout.updated_at = new Date().toISOString();
    checkoutStore.set(id, checkout);

    // Emit order event if order capability enabled
    if (capabilities.includes('dev.ucp.shopping.order')) {
      await emitOrderCreated(checkout);
    }

    return checkout;
  } catch (error) {
    checkout.status = 'incomplete';
    checkout.payment.status = 'failed';
    checkout.messages.push({
      code: 'payment_failed',
      severity: 'recoverable',
      message: error instanceof Error ? error.message : 'Payment processing failed',
    });
    checkout.updated_at = new Date().toISOString();
    checkoutStore.set(id, checkout);
    return checkout;
  }
}

function determineStatus(checkout: CheckoutSession): CheckoutStatus {
  // Check for missing required fields
  const missingFields: string[] = [];

  if (!checkout.buyer?.email) {
    missingFields.push('buyer.email');
  }

  // Check fulfillment if extension enabled
  if (checkout.fulfillment && !checkout.fulfillment.selected_option) {
    missingFields.push('fulfillment.selected_option');
  }

  if (missingFields.length > 0) {
    return 'incomplete';
  }

  // Check if buyer input needed
  if (checkout.messages.some(m => m.severity === 'requires_buyer_input')) {
    return 'requires_escalation';
  }

  return 'ready_for_complete';
}

function generateMessages(checkout: CheckoutSession): CheckoutSession['messages'] {
  const messages: CheckoutSession['messages'] = [];

  if (!checkout.buyer?.email) {
    messages.push({
      code: 'missing_email',
      severity: 'recoverable',
      message: 'Buyer email is required',
      field: 'buyer.email',
    });
  }

  return messages;
}

function recalculateTotals(checkout: CheckoutSession): void {
  const subtotal = checkout.line_items.reduce((sum, item) => sum + item.total_price, 0);
  checkout.totals.subtotal = subtotal;
  checkout.totals.tax = calculateTax(subtotal);
  checkout.totals.grand_total =
    subtotal + checkout.totals.tax + checkout.totals.shipping - checkout.totals.discount;
  checkout.payment.amount_due = checkout.totals.grand_total;
}

function calculateTax(subtotal: number): number {
  // Implement your tax calculation logic
  return Math.round(subtotal * 0.08); // Example: 8% tax
}

function getPaymentHandlers(capabilities: string[]): CheckoutSession['payment']['handlers'] {
  // Return configured payment handlers
  return config.payment_handlers.map(id => ({
    id,
    type: 'tokenization',
  }));
}

async function processPayment(
  checkout: CheckoutSession,
  paymentData: Record<string, unknown>
): Promise<void> {
  // Validate handler_id against advertised handlers
  const handlerId = paymentData.handler_id as string;
  if (!config.payment_handlers.includes(handlerId)) {
    throw new Error(`Unknown payment handler: ${handlerId}`);
  }

  // Implement payment processing based on handler
  // This is where you integrate with Stripe, etc.
}

async function emitOrderCreated(checkout: CheckoutSession): Promise<void> {
  // Implement order webhook emission
  // See lib/ucp/webhooks/sender.ts
}
typescript
/**
 * UCP Checkout Handler
 * Core business logic for checkout operations
 * Generated by /ucp scaffold
 */

import { randomUUID } from 'crypto';
import type {
  CheckoutSession,
  CheckoutStatus,
  CreateCheckoutRequest,
  UpdateCheckoutRequest,
} from '@/lib/ucp/types/checkout';
import config from '@/../ucp.config.json';

// In-memory store for demo - replace with your database
const checkoutStore = new Map<string, CheckoutSession>();

interface CreateCheckoutOptions {
  capabilities: string[];
  idempotencyKey?: string | null;
}

export async function createCheckout(
  request: CreateCheckoutRequest,
  options: CreateCheckoutOptions
): Promise<CheckoutSession> {
  const id = randomUUID();
  const now = new Date().toISOString();
  const expiresAt = new Date(Date.now() + 30 * 60 * 1000).toISOString(); // 30 min

  // Calculate totals
  const subtotal = request.line_items.reduce((sum, item) => sum + item.total_price, 0);
  const tax = calculateTax(subtotal); // Implement your tax logic
  const shipping = 0; // Set by fulfillment extension
  const discount = 0; // Set by discount extension

  const checkout: CheckoutSession = {
    ucp: {
      version: config.ucp_version,
      capabilities: options.capabilities,
    },
    id,
    status: 'incomplete',
    currency: request.currency,
    line_items: request.line_items.map((item, index) => ({
      ...item,
      id: item.id || `line_${index}`,
    })),
    totals: {
      subtotal,
      tax,
      shipping,
      discount,
      grand_total: subtotal + tax + shipping - discount,
      currency: request.currency,
    },
    payment: {
      status: 'pending',
      handlers: getPaymentHandlers(options.capabilities),
      amount_due: subtotal + tax + shipping - discount,
      currency: request.currency,
    },
    links: {
      self: `https://${config.domain}/api/ucp/checkout/${id}`,
      continue_url: `https://${config.domain}/checkout/${id}`,
      privacy_policy: config.policy_urls.privacy,
      terms_of_service: config.policy_urls.terms,
      ...(config.policy_urls.refunds && { refund_policy: config.policy_urls.refunds }),
      ...(config.policy_urls.shipping && { shipping_policy: config.policy_urls.shipping }),
    },
    messages: [],
    expires_at: expiresAt,
    created_at: now,
    updated_at: now,
  };

  // Add buyer info if provided
  if (request.buyer) {
    checkout.buyer = request.buyer;
  }

  // Validate checkout state and set appropriate status
  checkout.status = determineStatus(checkout);
  checkout.messages = generateMessages(checkout);

  // Store checkout
  checkoutStore.set(id, checkout);

  return checkout;
}

export async function getCheckout(id: string): Promise<CheckoutSession | null> {
  return checkoutStore.get(id) || null;
}

export async function updateCheckout(
  id: string,
  request: UpdateCheckoutRequest,
  capabilities: string[]
): Promise<CheckoutSession | null> {
  const checkout = checkoutStore.get(id);
  if (!checkout) return null;

  // Check if checkout can be modified
  if (['completed', 'canceled'].includes(checkout.status)) {
    throw new Error('Checkout cannot be modified in current state');
  }

  // Apply updates
  if (request.line_items) {
    checkout.line_items = request.line_items;
    recalculateTotals(checkout);
  }

  if (request.buyer) {
    checkout.buyer = { ...checkout.buyer, ...request.buyer };
  }

  // Update metadata
  checkout.updated_at = new Date().toISOString();
  checkout.ucp.capabilities = capabilities;
  checkout.status = determineStatus(checkout);
  checkout.messages = generateMessages(checkout);

  checkoutStore.set(id, checkout);
  return checkout;
}

export async function completeCheckout(
  id: string,
  paymentData: Record<string, unknown>,
  capabilities: string[]
): Promise<CheckoutSession | null> {
  const checkout = checkoutStore.get(id);
  if (!checkout) return null;

  if (checkout.status !== 'ready_for_complete') {
    throw new Error('Checkout is not ready for completion');
  }

  checkout.status = 'complete_in_progress';
  checkout.updated_at = new Date().toISOString();
  checkoutStore.set(id, checkout);

  try {
    // Process payment
    await processPayment(checkout, paymentData);

    checkout.status = 'completed';
    checkout.payment.status = 'captured';
    checkout.updated_at = new Date().toISOString();
    checkoutStore.set(id, checkout);

    // Emit order event if order capability enabled
    if (capabilities.includes('dev.ucp.shopping.order')) {
      await emitOrderCreated(checkout);
    }

    return checkout;
  } catch (error) {
    checkout.status = 'incomplete';
    checkout.payment.status = 'failed';
    checkout.messages.push({
      code: 'payment_failed',
      severity: 'recoverable',
      message: error instanceof Error ? error.message : 'Payment processing failed',
    });
    checkout.updated_at = new Date().toISOString();
    checkoutStore.set(id, checkout);
    return checkout;
  }
}

function determineStatus(checkout: CheckoutSession): CheckoutStatus {
  // Check for missing required fields
  const missingFields: string[] = [];

  if (!checkout.buyer?.email) {
    missingFields.push('buyer.email');
  }

  // Check fulfillment if extension enabled
  if (checkout.fulfillment && !checkout.fulfillment.selected_option) {
    missingFields.push('fulfillment.selected_option');
  }

  if (missingFields.length > 0) {
    return 'incomplete';
  }

  // Check if buyer input needed
  if (checkout.messages.some(m => m.severity === 'requires_buyer_input')) {
    return 'requires_escalation';
  }

  return 'ready_for_complete';
}

function generateMessages(checkout: CheckoutSession): CheckoutSession['messages'] {
  const messages: CheckoutSession['messages'] = [];

  if (!checkout.buyer?.email) {
    messages.push({
      code: 'missing_email',
      severity: 'recoverable',
      message: 'Buyer email is required',
      field: 'buyer.email',
    });
  }

  return messages;
}

function recalculateTotals(checkout: CheckoutSession): void {
  const subtotal = checkout.line_items.reduce((sum, item) => sum + item.total_price, 0);
  checkout.totals.subtotal = subtotal;
  checkout.totals.tax = calculateTax(subtotal);
  checkout.totals.grand_total =
    subtotal + checkout.totals.tax + checkout.totals.shipping - checkout.totals.discount;
  checkout.payment.amount_due = checkout.totals.grand_total;
}

function calculateTax(subtotal: number): number {
  // Implement your tax calculation logic
  return Math.round(subtotal * 0.08); // Example: 8% tax
}

function getPaymentHandlers(capabilities: string[]): CheckoutSession['payment']['handlers'] {
  // Return configured payment handlers
  return config.payment_handlers.map(id => ({
    id,
    type: 'tokenization',
  }));
}

async function processPayment(
  checkout: CheckoutSession,
  paymentData: Record<string, unknown>
): Promise<void> {
  // Validate handler_id against advertised handlers
  const handlerId = paymentData.handler_id as string;
  if (!config.payment_handlers.includes(handlerId)) {
    throw new Error(`Unknown payment handler: ${handlerId}`);
  }

  // Implement payment processing based on handler
  // This is where you integrate with Stripe, etc.
}

async function emitOrderCreated(checkout: CheckoutSession): Promise<void> {
  // Implement order webhook emission
  // See lib/ucp/webhooks/sender.ts
}

lib/ucp/negotiation.ts

lib/ucp/negotiation.ts

typescript
/**
 * UCP Capability Negotiation
 * Generated by /ucp scaffold
 */

import config from '@/../ucp.config.json';

interface UCPAgentInfo {
  profile: string;
}

interface NegotiationResult {
  capabilities: string[];
  version: string;
}

/**
 * Parse UCP-Agent header (RFC 8941 dictionary syntax)
 * Example: profile="https://platform.example.com/.well-known/ucp"
 */
export function parseUCPAgent(header: string | null): UCPAgentInfo | null {
  if (!header) return null;

  const profileMatch = header.match(/profile="([^"]+)"/);
  if (!profileMatch) return null;

  return { profile: profileMatch[1] };
}

/**
 * Negotiate capabilities between business and platform
 */
export async function negotiateCapabilities(
  platformProfileUrl?: string
): Promise<NegotiationResult> {
  // Business capabilities
  const businessCapabilities = new Set([
    ...config.capabilities.core,
    ...config.capabilities.extensions,
  ]);

  if (!platformProfileUrl) {
    // No platform profile - return all business capabilities
    return {
      capabilities: Array.from(businessCapabilities),
      version: config.ucp_version,
    };
  }

  try {
    // Fetch platform profile
    const response = await fetch(platformProfileUrl, {
      headers: { Accept: 'application/json' },
    });

    if (!response.ok) {
      console.warn(`Failed to fetch platform profile: ${response.status}`);
      return {
        capabilities: Array.from(businessCapabilities),
        version: config.ucp_version,
      };
    }

    const platformProfile = await response.json();

    // Validate namespace authority
    const profileUrl = new URL(platformProfileUrl);
    // Platform controls its own domain - trust it

    // Compute intersection
    const platformCapabilities = new Set(
      platformProfile.ucp?.capabilities?.map((c: { name: string }) => c.name) || []
    );

    const intersection = [...businessCapabilities].filter(c =>
      platformCapabilities.has(c)
    );

    // Version negotiation - accept platform version <= business version
    const platformVersion = platformProfile.ucp?.version;
    if (platformVersion && platformVersion > config.ucp_version) {
      throw new Error('version_unsupported');
    }

    return {
      capabilities: intersection,
      version: config.ucp_version,
    };
  } catch (error) {
    console.error('Capability negotiation failed:', error);
    // Fall back to business capabilities
    return {
      capabilities: Array.from(businessCapabilities),
      version: config.ucp_version,
    };
  }
}
typescript
/**
 * UCP Capability Negotiation
 * Generated by /ucp scaffold
 */

import config from '@/../ucp.config.json';

interface UCPAgentInfo {
  profile: string;
}

interface NegotiationResult {
  capabilities: string[];
  version: string;
}

/**
 * Parse UCP-Agent header (RFC 8941 dictionary syntax)
 * Example: profile="https://platform.example.com/.well-known/ucp"
 */
export function parseUCPAgent(header: string | null): UCPAgentInfo | null {
  if (!header) return null;

  const profileMatch = header.match(/profile="([^"]+)"/);
  if (!profileMatch) return null;

  return { profile: profileMatch[1] };
}

/**
 * Negotiate capabilities between business and platform
 */
export async function negotiateCapabilities(
  platformProfileUrl?: string
): Promise<NegotiationResult> {
  // Business capabilities
  const businessCapabilities = new Set([
    ...config.capabilities.core,
    ...config.capabilities.extensions,
  ]);

  if (!platformProfileUrl) {
    // No platform profile - return all business capabilities
    return {
      capabilities: Array.from(businessCapabilities),
      version: config.ucp_version,
    };
  }

  try {
    // Fetch platform profile
    const response = await fetch(platformProfileUrl, {
      headers: { Accept: 'application/json' },
    });

    if (!response.ok) {
      console.warn(`Failed to fetch platform profile: ${response.status}`);
      return {
        capabilities: Array.from(businessCapabilities),
        version: config.ucp_version,
      };
    }

    const platformProfile = await response.json();

    // Validate namespace authority
    const profileUrl = new URL(platformProfileUrl);
    // Platform controls its own domain - trust it

    // Compute intersection
    const platformCapabilities = new Set(
      platformProfile.ucp?.capabilities?.map((c: { name: string }) => c.name) || []
    );

    const intersection = [...businessCapabilities].filter(c =>
      platformCapabilities.has(c)
    );

    // Version negotiation - accept platform version <= business version
    const platformVersion = platformProfile.ucp?.version;
    if (platformVersion && platformVersion > config.ucp_version) {
      throw new Error('version_unsupported');
    }

    return {
      capabilities: intersection,
      version: config.ucp_version,
    };
  } catch (error) {
    console.error('Capability negotiation failed:', error);
    // Fall back to business capabilities
    return {
      capabilities: Array.from(businessCapabilities),
      version: config.ucp_version,
    };
  }
}

lib/ucp/response.ts

lib/ucp/response.ts

typescript
/**
 * UCP Response Helpers
 * Generated by /ucp scaffold
 */

import { NextResponse } from 'next/server';
import type { NegotiationResult } from './negotiation';

/**
 * Wrap a response with UCP metadata
 */
export function wrapResponse<T extends { ucp?: unknown }>(
  data: T,
  negotiation: NegotiationResult,
  status: number = 200
): NextResponse {
  // Ensure ucp metadata is present
  const response = {
    ...data,
    ucp: {
      version: negotiation.version,
      capabilities: negotiation.capabilities,
    },
  };

  return NextResponse.json(response, { status });
}

/**
 * Create an error response
 */
export function errorResponse(
  status: number,
  code: string,
  message: string,
  details?: Record<string, unknown>
): NextResponse {
  return NextResponse.json(
    {
      error: {
        code,
        message,
        ...(details && { details }),
      },
    },
    { status }
  );
}
typescript
/**
 * UCP Response Helpers
 * Generated by /ucp scaffold
 */

import { NextResponse } from 'next/server';
import type { NegotiationResult } from './negotiation';

/**
 * Wrap a response with UCP metadata
 */
export function wrapResponse<T extends { ucp?: unknown }>(
  data: T,
  negotiation: NegotiationResult,
  status: number = 200
): NextResponse {
  // Ensure ucp metadata is present
  const response = {
    ...data,
    ucp: {
      version: negotiation.version,
      capabilities: negotiation.capabilities,
    },
  };

  return NextResponse.json(response, { status });
}

/**
 * Create an error response
 */
export function errorResponse(
  status: number,
  code: string,
  message: string,
  details?: Record<string, unknown>
): NextResponse {
  return NextResponse.json(
    {
      error: {
        code,
        message,
        ...(details && { details }),
      },
    },
    { status }
  );
}

MCP Transport Code Templates (using mcp-handler)

MCP传输代码模板(使用mcp-handler)

When MCP transport is enabled in config, generate these additional files.
当配置中启用MCP传输时,生成这些额外文件。

Dependencies for MCP Transport

MCP传输依赖

bash
npm install mcp-handler @modelcontextprotocol/sdk@1.25.2 zod
IMPORTANT: Use
@modelcontextprotocol/sdk@1.25.2
or later — earlier versions have security vulnerabilities.
bash
npm install mcp-handler @modelcontextprotocol/sdk@1.25.2 zod
重要提示: 请使用
@modelcontextprotocol/sdk@1.25.2
或更高版本 — 早期版本存在安全漏洞。

app/api/mcp/[transport]/route.ts

app/api/mcp/[transport]/route.ts

typescript
/**
 * UCP MCP Transport Endpoint
 * Model Context Protocol server for UCP checkout operations
 * Generated by /ucp scaffold
 *
 * Supports:
 * - Streamable HTTP transport (direct client connection)
 * - SSE transport (via mcp-remote bridge)
 *
 * @see https://github.com/vercel/mcp-handler
 */

import { createMcpHandler } from 'mcp-handler';
import { z } from 'zod';
import {
  createCheckout,
  getCheckout,
  updateCheckout,
  completeCheckout,
} from '@/lib/ucp/handlers/checkout';
import { negotiateCapabilities } from '@/lib/ucp/negotiation';
import { generateProfile } from '@/lib/ucp/profile';
import config from '@/../ucp.config.json';

export const runtime = 'nodejs'; // Edge runtime is not supported

const handler = createMcpHandler(
  (server) => {
    // =========================================================================
    // UCP Discovery
    // =========================================================================

    server.registerTool(
      'ucp_get_profile',
      {
        title: 'Get UCP Profile',
        description: 'Retrieve the UCP discovery profile for this business. Returns supported capabilities, transports, and payment handlers.',
        inputSchema: {},
      },
      async () => {
        const profile = generateProfile();
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(profile, null, 2),
            },
          ],
        };
      }
    );

    // =========================================================================
    // Checkout Session Management
    // =========================================================================

    server.registerTool(
      'ucp_create_checkout',
      {
        title: 'Create Checkout Session',
        description: 'Create a new UCP checkout session with line items. Returns a checkout session with id, status, totals, and available payment handlers.',
        inputSchema: {
          line_items: z.array(
            z.object({
              id: z.string().optional().describe('Unique identifier for the line item'),
              name: z.string().describe('Product name'),
              quantity: z.number().int().positive().describe('Quantity ordered'),
              unit_price: z.number().int().describe('Price per unit in minor units (cents)'),
              total_price: z.number().int().describe('Total price for this line (quantity * unit_price)'),
              currency: z.string().length(3).describe('ISO 4217 currency code'),
            })
          ).min(1).describe('Array of items in the checkout'),
          currency: z.string().length(3).describe('ISO 4217 currency code for the checkout'),
          buyer: z.object({
            email: z.string().email().optional().describe('Buyer email address'),
            phone: z.string().optional().describe('Buyer phone number'),
            name: z.string().optional().describe('Buyer full name'),
          }).optional().describe('Buyer information'),
          platform_profile_url: z.string().url().optional().describe('URL to the platform UCP profile for capability negotiation'),
        },
      },
      async ({ line_items, currency, buyer, platform_profile_url }) => {
        try {
          // Negotiate capabilities
          const negotiation = await negotiateCapabilities(platform_profile_url);

          const checkout = await createCheckout(
            { line_items, currency, buyer },
            { capabilities: negotiation.capabilities }
          );

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(checkout, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_creation_failed',
                    message: error instanceof Error ? error.message : 'Unknown error',
                  },
                }),
              },
            ],
            isError: true,
          };
        }
      }
    );

    server.registerTool(
      'ucp_get_checkout',
      {
        title: 'Get Checkout Session',
        description: 'Retrieve an existing checkout session by ID. Returns the current state including status, line items, totals, and messages.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
        },
      },
      async ({ checkout_id }) => {
        const checkout = await getCheckout(checkout_id);

        if (!checkout) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_not_found',
                    message: `Checkout session ${checkout_id} not found`,
                  },
                }),
              },
            ],
            isError: true,
          };
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(checkout, null, 2),
            },
          ],
        };
      }
    );

    server.registerTool(
      'ucp_update_checkout',
      {
        title: 'Update Checkout Session',
        description: 'Update an existing checkout session. Can modify line items, buyer info, fulfillment selection, or discount codes.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
          line_items: z.array(
            z.object({
              id: z.string(),
              name: z.string(),
              quantity: z.number().int().positive(),
              unit_price: z.number().int(),
              total_price: z.number().int(),
              currency: z.string().length(3),
            })
          ).optional().describe('Updated line items (replaces existing)'),
          buyer: z.object({
            email: z.string().email().optional(),
            phone: z.string().optional(),
            name: z.string().optional(),
          }).optional().describe('Updated buyer information (merged with existing)'),
          fulfillment: z.object({
            selected_option_id: z.string().optional().describe('ID of selected fulfillment option'),
            destination: z.object({
              address_line1: z.string(),
              address_line2: z.string().optional(),
              city: z.string(),
              state: z.string().optional(),
              postal_code: z.string(),
              country: z.string().length(2),
            }).optional().describe('Shipping destination address'),
          }).optional().describe('Fulfillment selection (if fulfillment extension enabled)'),
          discount_codes: z.array(z.string()).optional().describe('Discount codes to apply (if discount extension enabled)'),
          platform_profile_url: z.string().url().optional().describe('Platform profile URL for capability negotiation'),
        },
      },
      async ({ checkout_id, line_items, buyer, fulfillment, discount_codes, platform_profile_url }) => {
        try {
          const negotiation = await negotiateCapabilities(platform_profile_url);

          const checkout = await updateCheckout(
            checkout_id,
            { line_items, buyer },
            negotiation.capabilities
          );

          if (!checkout) {
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    error: {
                      code: 'checkout_not_found',
                      message: `Checkout session ${checkout_id} not found`,
                    },
                  }),
                },
              ],
              isError: true,
            };
          }

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(checkout, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_update_failed',
                    message: error instanceof Error ? error.message : 'Unknown error',
                  },
                }),
              },
            ],
            isError: true,
          };
        }
      }
    );

    server.registerTool(
      'ucp_complete_checkout',
      {
        title: 'Complete Checkout',
        description: 'Complete a checkout session with payment. Checkout must be in ready_for_complete status. Returns the completed checkout or error messages.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
          payment_data: z.object({
            handler_id: z.string().describe('Payment handler ID (must match one from checkout.payment.handlers)'),
            token: z.string().optional().describe('Payment token from tokenization handler'),
            instrument: z.record(z.unknown()).optional().describe('Payment instrument data'),
          }).describe('Payment data from the selected payment handler'),
          platform_profile_url: z.string().url().optional().describe('Platform profile URL'),
        },
      },
      async ({ checkout_id, payment_data, platform_profile_url }) => {
        try {
          const negotiation = await negotiateCapabilities(platform_profile_url);

          const checkout = await completeCheckout(
            checkout_id,
            payment_data,
            negotiation.capabilities
          );

          if (!checkout) {
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    error: {
                      code: 'checkout_not_found',
                      message: `Checkout session ${checkout_id} not found`,
                    },
                  }),
                },
              ],
              isError: true,
            };
          }

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(checkout, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_completion_failed',
                    message: error instanceof Error ? error.message : 'Unknown error',
                  },
                }),
              },
            ],
            isError: true,
          };
        }
      }
    );

    // =========================================================================
    // Fulfillment Extension Tools (if enabled)
    // =========================================================================

    if (config.capabilities.extensions.includes('dev.ucp.shopping.fulfillment')) {
      server.registerTool(
        'ucp_get_fulfillment_options',
        {
          title: 'Get Fulfillment Options',
          description: 'Get available fulfillment/shipping options for a checkout. Requires a destination address.',
          inputSchema: {
            checkout_id: z.string().describe('The checkout session ID'),
            destination: z.object({
              address_line1: z.string(),
              address_line2: z.string().optional(),
              city: z.string(),
              state: z.string().optional(),
              postal_code: z.string(),
              country: z.string().length(2).describe('ISO 3166-1 alpha-2 country code'),
            }).describe('Shipping destination'),
          },
        },
        async ({ checkout_id, destination }) => {
          // Implementation would call fulfillment handler
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  checkout_id,
                  destination,
                  options: [
                    {
                      id: 'standard',
                      name: 'Standard Shipping',
                      description: '5-7 business days',
                      price: 599,
                      currency: 'USD',
                    },
                    {
                      id: 'express',
                      name: 'Express Shipping',
                      description: '2-3 business days',
                      price: 1299,
                      currency: 'USD',
                    },
                  ],
                }, null, 2),
              },
            ],
          };
        }
      );
    }

    // =========================================================================
    // Discount Extension Tools (if enabled)
    // =========================================================================

    if (config.capabilities.extensions.includes('dev.ucp.shopping.discount')) {
      server.registerTool(
        'ucp_validate_discount',
        {
          title: 'Validate Discount Code',
          description: 'Validate a discount code before applying to checkout. Returns discount details or rejection reason.',
          inputSchema: {
            checkout_id: z.string().describe('The checkout session ID'),
            code: z.string().describe('The discount code to validate'),
          },
        },
        async ({ checkout_id, code }) => {
          // Implementation would call discount handler
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  valid: true,
                  code,
                  discount: {
                    type: 'percentage',
                    value: 10,
                    description: '10% off your order',
                  },
                }, null, 2),
              },
            ],
          };
        }
      );
    }

    // =========================================================================
    // Payment Handler Tools
    // =========================================================================

    server.registerTool(
      'ucp_get_payment_handlers',
      {
        title: 'Get Payment Handlers',
        description: 'Get available payment handlers for a checkout session. Use this to determine how to collect payment information.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
        },
      },
      async ({ checkout_id }) => {
        const checkout = await getCheckout(checkout_id);

        if (!checkout) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_not_found',
                    message: `Checkout session ${checkout_id} not found`,
                  },
                }),
              },
            ],
            isError: true,
          };
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                checkout_id,
                amount_due: checkout.payment.amount_due,
                currency: checkout.payment.currency,
                handlers: checkout.payment.handlers,
              }, null, 2),
            },
          ],
        };
      }
    );
  },
  {
    // Server metadata
    name: 'ucp-shopping',
    version: config.ucp_version,
  },
  {
    // Handler options
    basePath: '/api/mcp',
    maxDuration: 60,
    verboseLogs: process.env.NODE_ENV === 'development',
  }
);

export { handler as GET, handler as POST };
typescript
/**
 * UCP MCP Transport Endpoint
 * Model Context Protocol server for UCP checkout operations
 * Generated by /ucp scaffold
 *
 * Supports:
 * - Streamable HTTP transport (direct client connection)
 * - SSE transport (via mcp-remote bridge)
 *
 * @see https://github.com/vercel/mcp-handler
 */

import { createMcpHandler } from 'mcp-handler';
import { z } from 'zod';
import {
  createCheckout,
  getCheckout,
  updateCheckout,
  completeCheckout,
} from '@/lib/ucp/handlers/checkout';
import { negotiateCapabilities } from '@/lib/ucp/negotiation';
import { generateProfile } from '@/lib/ucp/profile';
import config from '@/../ucp.config.json';

export const runtime = 'nodejs'; // Edge runtime is not supported

const handler = createMcpHandler(
  (server) => {
    // =========================================================================
    // UCP Discovery
    // =========================================================================

    server.registerTool(
      'ucp_get_profile',
      {
        title: 'Get UCP Profile',
        description: 'Retrieve the UCP discovery profile for this business. Returns supported capabilities, transports, and payment handlers.',
        inputSchema: {},
      },
      async () => {
        const profile = generateProfile();
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(profile, null, 2),
            },
          ],
        };
      }
    );

    // =========================================================================
    // Checkout Session Management
    // =========================================================================

    server.registerTool(
      'ucp_create_checkout',
      {
        title: 'Create Checkout Session',
        description: 'Create a new UCP checkout session with line items. Returns a checkout session with id, status, totals, and available payment handlers.',
        inputSchema: {
          line_items: z.array(
            z.object({
              id: z.string().optional().describe('Unique identifier for the line item'),
              name: z.string().describe('Product name'),
              quantity: z.number().int().positive().describe('Quantity ordered'),
              unit_price: z.number().int().describe('Price per unit in minor units (cents)'),
              total_price: z.number().int().describe('Total price for this line (quantity * unit_price)'),
              currency: z.string().length(3).describe('ISO 4217 currency code'),
            })
          ).min(1).describe('Array of items in the checkout'),
          currency: z.string().length(3).describe('ISO 4217 currency code for the checkout'),
          buyer: z.object({
            email: z.string().email().optional().describe('Buyer email address'),
            phone: z.string().optional().describe('Buyer phone number'),
            name: z.string().optional().describe('Buyer full name'),
          }).optional().describe('Buyer information'),
          platform_profile_url: z.string().url().optional().describe('URL to the platform UCP profile for capability negotiation'),
        },
      },
      async ({ line_items, currency, buyer, platform_profile_url }) => {
        try {
          // Negotiate capabilities
          const negotiation = await negotiateCapabilities(platform_profile_url);

          const checkout = await createCheckout(
            { line_items, currency, buyer },
            { capabilities: negotiation.capabilities }
          );

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(checkout, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_creation_failed',
                    message: error instanceof Error ? error.message : 'Unknown error',
                  },
                }),
              },
            ],
            isError: true,
          };
        }
      }
    );

    server.registerTool(
      'ucp_get_checkout',
      {
        title: 'Get Checkout Session',
        description: 'Retrieve an existing checkout session by ID. Returns the current state including status, line items, totals, and messages.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
        },
      },
      async ({ checkout_id }) => {
        const checkout = await getCheckout(checkout_id);

        if (!checkout) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_not_found',
                    message: `Checkout session ${checkout_id} not found`,
                  },
                }),
              },
            ],
            isError: true,
          };
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(checkout, null, 2),
            },
          ],
        };
      }
    );

    server.registerTool(
      'ucp_update_checkout',
      {
        title: 'Update Checkout Session',
        description: 'Update an existing checkout session. Can modify line items, buyer info, fulfillment selection, or discount codes.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
          line_items: z.array(
            z.object({
              id: z.string(),
              name: z.string(),
              quantity: z.number().int().positive(),
              unit_price: z.number().int(),
              total_price: z.number().int(),
              currency: z.string().length(3),
            })
          ).optional().describe('Updated line items (replaces existing)'),
          buyer: z.object({
            email: z.string().email().optional(),
            phone: z.string().optional(),
            name: z.string().optional(),
          }).optional().describe('Updated buyer information (merged with existing)'),
          fulfillment: z.object({
            selected_option_id: z.string().optional().describe('ID of selected fulfillment option'),
            destination: z.object({
              address_line1: z.string(),
              address_line2: z.string().optional(),
              city: z.string(),
              state: z.string().optional(),
              postal_code: z.string(),
              country: z.string().length(2),
            }).optional().describe('Shipping destination address'),
          }).optional().describe('Fulfillment selection (if fulfillment extension enabled)'),
          discount_codes: z.array(z.string()).optional().describe('Discount codes to apply (if discount extension enabled)'),
          platform_profile_url: z.string().url().optional().describe('Platform profile URL for capability negotiation'),
        },
      },
      async ({ checkout_id, line_items, buyer, fulfillment, discount_codes, platform_profile_url }) => {
        try {
          const negotiation = await negotiateCapabilities(platform_profile_url);

          const checkout = await updateCheckout(
            checkout_id,
            { line_items, buyer },
            negotiation.capabilities
          );

          if (!checkout) {
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    error: {
                      code: 'checkout_not_found',
                      message: `Checkout session ${checkout_id} not found`,
                    },
                  }),
                },
              ],
              isError: true,
            };
          }

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(checkout, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_update_failed',
                    message: error instanceof Error ? error.message : 'Unknown error',
                  },
                }),
              },
            ],
            isError: true,
          };
        }
      }
    );

    server.registerTool(
      'ucp_complete_checkout',
      {
        title: 'Complete Checkout',
        description: 'Complete a checkout session with payment. Checkout must be in ready_for_complete status. Returns the completed checkout or error messages.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
          payment_data: z.object({
            handler_id: z.string().describe('Payment handler ID (must match one from checkout.payment.handlers)'),
            token: z.string().optional().describe('Payment token from tokenization handler'),
            instrument: z.record(z.unknown()).optional().describe('Payment instrument data'),
          }).describe('Payment data from the selected payment handler'),
          platform_profile_url: z.string().url().optional().describe('Platform profile URL'),
        },
      },
      async ({ checkout_id, payment_data, platform_profile_url }) => {
        try {
          const negotiation = await negotiateCapabilities(platform_profile_url);

          const checkout = await completeCheckout(
            checkout_id,
            payment_data,
            negotiation.capabilities
          );

          if (!checkout) {
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    error: {
                      code: 'checkout_not_found',
                      message: `Checkout session ${checkout_id} not found`,
                    },
                  }),
                },
              ],
              isError: true,
            };
          }

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(checkout, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_completion_failed',
                    message: error instanceof Error ? error.message : 'Unknown error',
                  },
                }),
              },
            ],
            isError: true,
          };
        }
      }
    );

    // =========================================================================
    // Fulfillment Extension Tools (if enabled)
    // =========================================================================

    if (config.capabilities.extensions.includes('dev.ucp.shopping.fulfillment')) {
      server.registerTool(
        'ucp_get_fulfillment_options',
        {
          title: 'Get Fulfillment Options',
          description: 'Get available fulfillment/shipping options for a checkout. Requires a destination address.',
          inputSchema: {
            checkout_id: z.string().describe('The checkout session ID'),
            destination: z.object({
              address_line1: z.string(),
              address_line2: z.string().optional(),
              city: z.string(),
              state: z.string().optional(),
              postal_code: z.string(),
              country: z.string().length(2).describe('ISO 3166-1 alpha-2 country code'),
            }).describe('Shipping destination'),
          },
        },
        async ({ checkout_id, destination }) => {
          // Implementation would call fulfillment handler
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  checkout_id,
                  destination,
                  options: [
                    {
                      id: 'standard',
                      name: 'Standard Shipping',
                      description: '5-7 business days',
                      price: 599,
                      currency: 'USD',
                    },
                    {
                      id: 'express',
                      name: 'Express Shipping',
                      description: '2-3 business days',
                      price: 1299,
                      currency: 'USD',
                    },
                  ],
                }, null, 2),
              },
            ],
          };
        }
      );
    }

    // =========================================================================
    // Discount Extension Tools (if enabled)
    // =========================================================================

    if (config.capabilities.extensions.includes('dev.ucp.shopping.discount')) {
      server.registerTool(
        'ucp_validate_discount',
        {
          title: 'Validate Discount Code',
          description: 'Validate a discount code before applying to checkout. Returns discount details or rejection reason.',
          inputSchema: {
            checkout_id: z.string().describe('The checkout session ID'),
            code: z.string().describe('The discount code to validate'),
          },
        },
        async ({ checkout_id, code }) => {
          // Implementation would call discount handler
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  valid: true,
                  code,
                  discount: {
                    type: 'percentage',
                    value: 10,
                    description: '10% off your order',
                  },
                }, null, 2),
              },
            ],
          };
        }
      );
    }

    // =========================================================================
    // Payment Handler Tools
    // =========================================================================

    server.registerTool(
      'ucp_get_payment_handlers',
      {
        title: 'Get Payment Handlers',
        description: 'Get available payment handlers for a checkout session. Use this to determine how to collect payment information.',
        inputSchema: {
          checkout_id: z.string().describe('The checkout session ID'),
        },
      },
      async ({ checkout_id }) => {
        const checkout = await getCheckout(checkout_id);

        if (!checkout) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: {
                    code: 'checkout_not_found',
                    message: `Checkout session ${checkout_id} not found`,
                  },
                }),
              },
            ],
            isError: true,
          };
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                checkout_id,
                amount_due: checkout.payment.amount_due,
                currency: checkout.payment.currency,
                handlers: checkout.payment.handlers,
              }, null, 2),
            },
          ],
        };
      }
    );
  },
  {
    // Server metadata
    name: 'ucp-shopping',
    version: config.ucp_version,
  },
  {
    // Handler options
    basePath: '/api/mcp',
    maxDuration: 60,
    verboseLogs: process.env.NODE_ENV === 'development',
  }
);

export { handler as GET, handler as POST };

lib/ucp/transports/mcp-tools.ts

lib/ucp/transports/mcp-tools.ts

typescript
/**
 * UCP MCP Tool Definitions
 * Reusable tool schemas for MCP transport
 * Generated by /ucp scaffold
 */

import { z } from 'zod';

// Common schemas
export const LineItemSchema = z.object({
  id: z.string().optional(),
  name: z.string(),
  quantity: z.number().int().positive(),
  unit_price: z.number().int(),
  total_price: z.number().int(),
  currency: z.string().length(3),
});

export const BuyerSchema = z.object({
  email: z.string().email().optional(),
  phone: z.string().optional(),
  name: z.string().optional(),
});

export const AddressSchema = z.object({
  address_line1: z.string(),
  address_line2: z.string().optional(),
  city: z.string(),
  state: z.string().optional(),
  postal_code: z.string(),
  country: z.string().length(2),
});

export const PaymentDataSchema = z.object({
  handler_id: z.string(),
  token: z.string().optional(),
  instrument: z.record(z.unknown()).optional(),
});

// Tool definitions for documentation
export const UCP_MCP_TOOLS = {
  ucp_get_profile: {
    description: 'Get UCP discovery profile',
    input: {},
  },
  ucp_create_checkout: {
    description: 'Create a new checkout session',
    input: {
      line_items: 'Array of line items (required)',
      currency: 'ISO 4217 currency code (required)',
      buyer: 'Buyer information (optional)',
      platform_profile_url: 'Platform UCP profile URL (optional)',
    },
  },
  ucp_get_checkout: {
    description: 'Get checkout session by ID',
    input: {
      checkout_id: 'Checkout session ID (required)',
    },
  },
  ucp_update_checkout: {
    description: 'Update checkout session',
    input: {
      checkout_id: 'Checkout session ID (required)',
      line_items: 'Updated line items (optional)',
      buyer: 'Updated buyer info (optional)',
      fulfillment: 'Fulfillment selection (optional)',
      discount_codes: 'Discount codes to apply (optional)',
    },
  },
  ucp_complete_checkout: {
    description: 'Complete checkout with payment',
    input: {
      checkout_id: 'Checkout session ID (required)',
      payment_data: 'Payment handler data (required)',
    },
  },
  ucp_get_fulfillment_options: {
    description: 'Get shipping options (fulfillment extension)',
    input: {
      checkout_id: 'Checkout session ID (required)',
      destination: 'Shipping address (required)',
    },
  },
  ucp_validate_discount: {
    description: 'Validate discount code (discount extension)',
    input: {
      checkout_id: 'Checkout session ID (required)',
      code: 'Discount code (required)',
    },
  },
  ucp_get_payment_handlers: {
    description: 'Get available payment handlers',
    input: {
      checkout_id: 'Checkout session ID (required)',
    },
  },
} as const;

typescript
/**
 * UCP MCP Tool Definitions
 * Reusable tool schemas for MCP transport
 * Generated by /ucp scaffold
 */

import { z } from 'zod';

// Common schemas
export const LineItemSchema = z.object({
  id: z.string().optional(),
  name: z.string(),
  quantity: z.number().int().positive(),
  unit_price: z.number().int(),
  total_price: z.number().int(),
  currency: z.string().length(3),
});

export const BuyerSchema = z.object({
  email: z.string().email().optional(),
  phone: z.string().optional(),
  name: z.string().optional(),
});

export const AddressSchema = z.object({
  address_line1: z.string(),
  address_line2: z.string().optional(),
  city: z.string(),
  state: z.string().optional(),
  postal_code: z.string(),
  country: z.string().length(2),
});

export const PaymentDataSchema = z.object({
  handler_id: z.string(),
  token: z.string().optional(),
  instrument: z.record(z.unknown()).optional(),
});

// Tool definitions for documentation
export const UCP_MCP_TOOLS = {
  ucp_get_profile: {
    description: 'Get UCP discovery profile',
    input: {},
  },
  ucp_create_checkout: {
    description: 'Create a new checkout session',
    input: {
      line_items: 'Array of line items (required)',
      currency: 'ISO 4217 currency code (required)',
      buyer: 'Buyer information (optional)',
      platform_profile_url: 'Platform UCP profile URL (optional)',
    },
  },
  ucp_get_checkout: {
    description: 'Get checkout session by ID',
    input: {
      checkout_id: 'Checkout session ID (required)',
    },
  },
  ucp_update_checkout: {
    description: 'Update checkout session',
    input: {
      checkout_id: 'Checkout session ID (required)',
      line_items: 'Updated line items (optional)',
      buyer: 'Updated buyer info (optional)',
      fulfillment: 'Fulfillment selection (optional)',
      discount_codes: 'Discount codes to apply (optional)',
    },
  },
  ucp_complete_checkout: {
    description: 'Complete checkout with payment',
    input: {
      checkout_id: 'Checkout session ID (required)',
      payment_data: 'Payment handler data (required)',
    },
  },
  ucp_get_fulfillment_options: {
    description: 'Get shipping options (fulfillment extension)',
    input: {
      checkout_id: 'Checkout session ID (required)',
      destination: 'Shipping address (required)',
    },
  },
  ucp_validate_discount: {
    description: 'Validate discount code (discount extension)',
    input: {
      checkout_id: 'Checkout session ID (required)',
      code: 'Discount code (required)',
    },
  },
  ucp_get_payment_handlers: {
    description: 'Get available payment handlers',
    input: {
      checkout_id: 'Checkout session ID (required)',
    },
  },
} as const;

Vercel Deployment

Vercel部署

Deployment Configuration

部署配置

vercel.json

vercel.json

json
{
  "framework": "nextjs",
  "regions": ["iad1"],
  "functions": {
    "app/api/mcp/[transport]/route.ts": {
      "maxDuration": 60
    },
    "app/api/ucp/**/*.ts": {
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/.well-known/ucp",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=3600"
        },
        {
          "key": "Content-Type",
          "value": "application/json"
        }
      ]
    }
  ]
}
json
{
  "framework": "nextjs",
  "regions": ["iad1"],
  "functions": {
    "app/api/mcp/[transport]/route.ts": {
      "maxDuration": 60
    },
    "app/api/ucp/**/*.ts": {
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/.well-known/ucp",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=3600"
        },
        {
          "key": "Content-Type",
          "value": "application/json"
        }
      ]
    }
  ]
}

Environment Variables

环境变量

Set these in Vercel Dashboard → Settings → Environment Variables:
VariableRequiredDescription
UCP_DOMAIN
YesProduction domain (e.g.,
shop.example.com
)
UCP_SIGNING_KEY
If AP2JWS signing key (PEM or JWK)
STRIPE_SECRET_KEY
If StripeStripe API secret key
在Vercel控制台 → 设置 → 环境变量中设置:
变量必填说明
UCP_DOMAIN
生产域名(例如:
shop.example.com
UCP_SIGNING_KEY
启用AP2时必填JWS签名密钥(PEM或JWK格式)
STRIPE_SECRET_KEY
集成Stripe时必填Stripe API密钥

next.config.js (MCP-optimized)

next.config.js(MCP优化版)

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Required for mcp-handler streaming
  experimental: {
    serverActions: {
      bodySizeLimit: '2mb',
    },
  },

  // Ensure proper headers for MCP
  async headers() {
    return [
      {
        source: '/api/mcp/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: '*' },
          { key: 'Access-Control-Allow-Methods', value: 'GET, POST, OPTIONS' },
          { key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
        ],
      },
    ];
  },

  // Required for MCP streaming responses
  async rewrites() {
    return [];
  },
};

module.exports = nextConfig;
javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Required for mcp-handler streaming
  experimental: {
    serverActions: {
      bodySizeLimit: '2mb',
    },
  },

  // Ensure proper headers for MCP
  async headers() {
    return [
      {
        source: '/api/mcp/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: '*' },
          { key: 'Access-Control-Allow-Methods', value: 'GET, POST, OPTIONS' },
          { key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
        ],
      },
    ];
  },

  // Required for MCP streaming responses
  async rewrites() {
    return [];
  },
};

module.exports = nextConfig;

MCP Client Configuration

MCP客户端配置

For Claude Desktop / Cursor / Windsurf

适用于Claude Desktop / Cursor / Windsurf

Option 1: Direct HTTP (if client supports streamable HTTP) Add to MCP client config:
json
{
  "mcpServers": {
    "ucp-shopping": {
      "url": "https://your-domain.vercel.app/api/mcp"
    }
  }
}
Option 2: Via mcp-remote bridge (for stdio-only clients)
json
{
  "mcpServers": {
    "ucp-shopping": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-domain.vercel.app/api/mcp"]
    }
  }
}
选项1:直接HTTP(如果客户端支持流式HTTP) 添加到MCP客户端配置:
json
{
  "mcpServers": {
    "ucp-shopping": {
      "url": "https://your-domain.vercel.app/api/mcp"
    }
  }
}
选项2:通过mcp-remote桥接(适用于仅支持stdio的客户端)
json
{
  "mcpServers": {
    "ucp-shopping": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-domain.vercel.app/api/mcp"]
    }
  }
}

Testing MCP Deployment

测试MCP部署

scripts/test-mcp.mjs

scripts/test-mcp.mjs

javascript
#!/usr/bin/env node
/**
 * MCP Server Test Script
 * Usage: node scripts/test-mcp.mjs [deployment-url]
 */

const deploymentUrl = process.argv[2] || 'http://localhost:3000';

async function testMcpServer() {
  console.log(`Testing MCP server at ${deploymentUrl}/api/mcp\n`);

  // Test 1: Get Profile
  console.log('1. Testing ucp_get_profile...');
  const profileResponse = await fetch(`${deploymentUrl}/api/mcp`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'tools/call',
      params: {
        name: 'ucp_get_profile',
        arguments: {},
      },
    }),
  });
  const profileResult = await profileResponse.json();
  console.log('   Profile:', profileResult.result ? 'OK' : 'FAILED');

  // Test 2: Create Checkout
  console.log('2. Testing ucp_create_checkout...');
  const createResponse = await fetch(`${deploymentUrl}/api/mcp`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 2,
      method: 'tools/call',
      params: {
        name: 'ucp_create_checkout',
        arguments: {
          line_items: [
            {
              name: 'Test Product',
              quantity: 1,
              unit_price: 1000,
              total_price: 1000,
              currency: 'USD',
            },
          ],
          currency: 'USD',
        },
      },
    }),
  });
  const createResult = await createResponse.json();
  console.log('   Create:', createResult.result ? 'OK' : 'FAILED');

  // Parse checkout ID for subsequent tests
  if (createResult.result?.content?.[0]?.text) {
    const checkout = JSON.parse(createResult.result.content[0].text);
    console.log(`   Checkout ID: ${checkout.id}`);
    console.log(`   Status: ${checkout.status}`);

    // Test 3: Get Checkout
    console.log('3. Testing ucp_get_checkout...');
    const getResponse = await fetch(`${deploymentUrl}/api/mcp`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 3,
        method: 'tools/call',
        params: {
          name: 'ucp_get_checkout',
          arguments: { checkout_id: checkout.id },
        },
      }),
    });
    const getResult = await getResponse.json();
    console.log('   Get:', getResult.result ? 'OK' : 'FAILED');
  }

  console.log('\nMCP server tests completed.');
}

testMcpServer().catch(console.error);
javascript
#!/usr/bin/env node
/**
 * MCP Server Test Script
 * Usage: node scripts/test-mcp.mjs [deployment-url]
 */

const deploymentUrl = process.argv[2] || 'http://localhost:3000';

async function testMcpServer() {
  console.log(`Testing MCP server at ${deploymentUrl}/api/mcp\n`);

  // Test 1: Get Profile
  console.log('1. Testing ucp_get_profile...');
  const profileResponse = await fetch(`${deploymentUrl}/api/mcp`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'tools/call',
      params: {
        name: 'ucp_get_profile',
        arguments: {},
      },
    }),
  });
  const profileResult = await profileResponse.json();
  console.log('   Profile:', profileResult.result ? 'OK' : 'FAILED');

  // Test 2: Create Checkout
  console.log('2. Testing ucp_create_checkout...');
  const createResponse = await fetch(`${deploymentUrl}/api/mcp`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 2,
      method: 'tools/call',
      params: {
        name: 'ucp_create_checkout',
        arguments: {
          line_items: [
            {
              name: 'Test Product',
              quantity: 1,
              unit_price: 1000,
              total_price: 1000,
              currency: 'USD',
            },
          ],
          currency: 'USD',
        },
      },
    }),
  });
  const createResult = await createResponse.json();
  console.log('   Create:', createResult.result ? 'OK' : 'FAILED');

  // Parse checkout ID for subsequent tests
  if (createResult.result?.content?.[0]?.text) {
    const checkout = JSON.parse(createResult.result.content[0].text);
    console.log(`   Checkout ID: ${checkout.id}`);
    console.log(`   Status: ${checkout.status}`);

    // Test 3: Get Checkout
    console.log('3. Testing ucp_get_checkout...');
    const getResponse = await fetch(`${deploymentUrl}/api/mcp`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 3,
        method: 'tools/call',
        params: {
          name: 'ucp_get_checkout',
          arguments: { checkout_id: checkout.id },
        },
      }),
    });
    const getResult = await getResponse.json();
    console.log('   Get:', getResult.result ? 'OK' : 'FAILED');
  }

  console.log('\nMCP server tests completed.');
}

testMcpServer().catch(console.error);

Vercel Deployment Checklist

Vercel部署检查清单

When running
/ucp scaffold
with Vercel deployment, verify:
  • vercel.json
    created with function timeouts
  • Environment variables documented
  • next.config.js
    updated for MCP streaming
  • MCP route at
    app/api/mcp/[transport]/route.ts
  • Test script at
    scripts/test-mcp.mjs
  • .env.example
    updated with required variables
使用Vercel部署执行
/ucp scaffold
时,验证以下内容:
  • 已创建带函数超时配置的
    vercel.json
  • 已记录环境变量
  • 已更新
    next.config.js
    支持MCP流式传输
  • MCP路由位于
    app/api/mcp/[transport]/route.ts
  • 测试脚本位于
    scripts/test-mcp.mjs
  • 已更新
    .env.example
    添加必填变量

Post-Deployment Verification

部署后验证

After deploying to Vercel:
  1. Check discovery profile:
    bash
    curl https://your-domain.vercel.app/.well-known/ucp | jq .
  2. Test MCP endpoint:
    bash
    node scripts/test-mcp.mjs https://your-domain.vercel.app
  3. Configure MCP client: Add the server to Claude Desktop, Cursor, or your preferred MCP client.
  4. Verify in client: Ask the AI to "list available UCP tools" — it should show the checkout tools.

部署到Vercel后:
  1. 检查发现配置:
    bash
    curl https://your-domain.vercel.app/.well-known/ucp | jq .
  2. 测试MCP端点:
    bash
    node scripts/test-mcp.mjs https://your-domain.vercel.app
  3. 配置MCP客户端: 将服务器添加到Claude Desktop、Cursor或你偏好的MCP客户端。
  4. 在客户端验证: 让AI"列出可用的UCP工具" — 应显示结账相关工具。

Post-Generation Steps

生成后步骤

Step 4: Update Config

步骤4:更新配置

After generation, update
ucp.config.json
:
  • Add all created files to
    generated_files
    array
  • Update
    scaffold_depth
    to reflect what was generated
生成完成后,更新
ucp.config.json
  • 将所有创建的文件添加到
    generated_files
    数组
  • 更新
    scaffold_depth
    反映生成的级别

Step 5: Output Summary

步骤5:输出摘要

UCP Scaffold Complete
=====================

Generated files:
  CREATE  lib/ucp/types/checkout.ts
  CREATE  lib/ucp/types/index.ts
  CREATE  lib/ucp/schemas/checkout.ts
  CREATE  lib/ucp/profile.ts
  CREATE  lib/ucp/negotiation.ts
  CREATE  lib/ucp/response.ts
  CREATE  lib/ucp/handlers/checkout.ts
  CREATE  app/.well-known/ucp/route.ts
  CREATE  app/api/ucp/checkout/route.ts
  CREATE  app/api/ucp/checkout/[id]/route.ts

MCP Transport (if enabled):
  CREATE  app/api/mcp/[transport]/route.ts
  CREATE  lib/ucp/transports/mcp-tools.ts

Vercel Deployment:
  CREATE  vercel.json
  CREATE  scripts/test-mcp.mjs
  MODIFY  next.config.js

Dependencies installed:
  zod, jose, uuid
  mcp-handler, @modelcontextprotocol/sdk (if MCP enabled)

Next steps:
  1. Review generated code and customize business logic
  2. Set environment variables in Vercel dashboard
  3. Deploy: vercel --prod
  4. Run /ucp profile to verify discovery profile
  5. Run /ucp test to generate unit tests
  6. Run /ucp validate to check compliance
  7. Configure MCP client with deployment URL

UCP脚手架生成完成
=====================

生成的文件:
  创建  lib/ucp/types/checkout.ts
  创建  lib/ucp/types/index.ts
  创建  lib/ucp/schemas/checkout.ts
  创建  lib/ucp/profile.ts
  创建  lib/ucp/negotiation.ts
  创建  lib/ucp/response.ts
  创建  lib/ucp/handlers/checkout.ts
  创建  app/.well-known/ucp/route.ts
  创建  app/api/ucp/checkout/route.ts
  创建  app/api/ucp/checkout/[id]/route.ts

MCP传输(如果启用):
  创建  app/api/mcp/[transport]/route.ts
  创建  lib/ucp/transports/mcp-tools.ts

Vercel部署:
  创建  vercel.json
  创建  scripts/test-mcp.mjs
  修改  next.config.js

已安装依赖:
  zod, jose, uuid
  mcp-handler, @modelcontextprotocol/sdk(如果启用MCP)

下一步操作:
  1. 审查生成的代码,自定义业务逻辑
  2. 在Vercel控制台设置环境变量
  3. 部署:vercel --prod
  4. 执行/ucp profile验证发现配置
  5. 执行/ucp test生成单元测试
  6. 执行/ucp validate检查合规性
  7. 用部署URL配置MCP客户端

Sub-command: validate

子命令:validate

Trigger

触发方式

User runs
/ucp validate
用户执行
/ucp validate

Purpose

用途

Validate the implementation against UCP JSON schemas.
对照UCP JSON schema验证实现是否合规

Prerequisites

前置条件

  • Implementation must exist (run
    /ucp scaffold
    first)
  • Spec must be available
  • 实现必须存在(先执行
    /ucp scaffold
  • 规范必须可用

Procedure

执行流程

Step 1: Load Schemas

步骤1:加载Schema

Read JSON schemas from spec:
  • spec/schemas/shopping/checkout.json
  • spec/schemas/shopping/fulfillment.json
  • spec/schemas/shopping/discount.json
  • spec/discovery/profile_schema.json
从规范中读取JSON schema:
  • spec/schemas/shopping/checkout.json
  • spec/schemas/shopping/fulfillment.json
  • spec/schemas/shopping/discount.json
  • spec/discovery/profile_schema.json

Step 2: Validate Discovery Profile

步骤2:验证发现配置

  • Make request to
    /.well-known/ucp
    route (or read file directly)
  • Validate against
    profile_schema.json
  • 请求
    /.well-known/ucp
    路由(或直接读取文件)
  • 对照
    profile_schema.json
    验证

Step 3: Validate Response Shapes

步骤3:验证响应结构

For each implemented endpoint:
  • Generate sample request
  • Execute handler (mock mode)
  • Validate response against schema
对每个实现的端点:
  • 生成示例请求
  • 执行处理程序(模拟模式)
  • 对照schema验证响应

Step 4: Check Protocol Requirements

步骤4:检查协议要求

Verify:
  • All responses include
    ucp
    object
  • Status values are valid enum values
  • Amounts are integers (minor units)
  • Dates are RFC 3339 format
  • Links are absolute HTTPS URLs
  • Capability names follow reverse-DNS format
验证以下内容:
  • 所有响应都包含
    ucp
    对象
  • 状态值属于有效枚举值
  • 金额为整数(最小单位)
  • 日期为RFC 3339格式
  • 链接为绝对HTTPS URL
  • 能力名称遵循反向DNS格式

Step 5: Output Report

步骤5:输出报告

UCP Validation Report
=====================

Discovery Profile: PASS
  ✓ Schema valid
  ✓ Required fields present
  ✓ Service definitions valid
  ✓ Capability specs accessible

Checkout Endpoints:
  POST /api/ucp/checkout
    ✓ Response schema valid
    ✓ UCP metadata present
    ✓ Status enum valid

  GET /api/ucp/checkout/[id]
    ✓ Response schema valid
    ✓ Idempotent

  PATCH /api/ucp/checkout/[id]
    ✓ Response schema valid
    ✓ Partial update works

  POST /api/ucp/checkout/[id] (complete)
    ✓ Response schema valid
    ✓ State transition correct

Protocol Requirements:
  ✓ Amounts in minor units
  ✓ Dates in RFC 3339
  ✓ HTTPS links
  ✓ Reverse-DNS capability names

Overall: PASS (24/24 checks)

UCP验证报告
=====================

发现配置:通过
  ✓ Schema有效
  ✓ 存在必填字段
  ✓ 服务定义有效
  ✓ 能力规范可访问

结账端点:
  POST /api/ucp/checkout
    ✓ 响应Schema有效
    ✓ 存在UCP元数据
    ✓ 状态枚举有效

  GET /api/ucp/checkout/[id]
    ✓ 响应Schema有效
    ✓ 幂等

  PATCH /api/ucp/checkout/[id]
    ✓ 响应Schema有效
    ✓ 部分更新正常

  POST /api/ucp/checkout/[id](完成)
    ✓ 响应Schema有效
    ✓ 状态转换正确

协议要求:
  ✓ 金额使用最小单位
  ✓ 日期为RFC 3339格式
  ✓ 使用HTTPS链接
  ✓ 能力名称为反向DNS格式

整体:通过(24/24检查项)

Sub-command: profile

子命令:profile

Trigger

触发方式

User runs
/ucp profile
用户执行
/ucp profile

Purpose

用途

Generate and display the
/.well-known/ucp
discovery profile JSON.
生成并展示
/.well-known/ucp
发现配置JSON

Prerequisites

前置条件

  • Config must exist
  • 配置必须存在

Procedure

执行流程

Step 1: Generate Profile

步骤1:生成配置

Use the
generateProfile()
function from
lib/ucp/profile.ts
(or generate inline if not scaffolded yet).
使用
lib/ucp/profile.ts
中的
generateProfile()
函数(如果尚未生成脚手架则内联生成)

Step 2: Display Profile

步骤2:展示配置

Output the formatted JSON:
json
{
  "ucp": {
    "version": "2026-01-11",
    "services": {
      "dev.ucp.shopping": {
        "version": "2026-01-11",
        "spec": "https://ucp.dev/spec/services/shopping",
        "rest": {
          "schema": "https://ucp.dev/spec/services/shopping/rest.openapi.json",
          "endpoint": "https://shop.example.com/api/ucp"
        }
      }
    },
    "capabilities": [
      {
        "name": "dev.ucp.shopping.checkout",
        "version": "2026-01-11",
        "spec": "https://ucp.dev/spec/capabilities/checkout",
        "schema": "https://ucp.dev/spec/schemas/shopping/checkout.json"
      }
    ]
  }
}
输出格式化的JSON:
json
{
  "ucp": {
    "version": "2026-01-11",
    "services": {
      "dev.ucp.shopping": {
        "version": "2026-01-11",
        "spec": "https://ucp.dev/spec/services/shopping",
        "rest": {
          "schema": "https://ucp.dev/spec/services/shopping/rest.openapi.json",
          "endpoint": "https://shop.example.com/api/ucp"
        }
      }
    },
    "capabilities": [
      {
        "name": "dev.ucp.shopping.checkout",
        "version": "2026-01-11",
        "spec": "https://ucp.dev/spec/capabilities/checkout",
        "schema": "https://ucp.dev/spec/schemas/shopping/checkout.json"
      }
    ]
  }
}

Step 3: Offer to Write File

步骤3:提供写入文件选项

Ask: "Write this to
public/.well-known/ucp
for static serving, or keep as dynamic route?"
If static:
  • Create
    public/.well-known/ucp
    (no extension, JSON content)
  • Note: May need Next.js config for extensionless files

询问:"是否将此内容写入
public/.well-known/ucp
用于静态托管,还是保留为动态路由?"
如果选择静态:
  • 创建
    public/.well-known/ucp
    (无后缀,JSON内容)
  • 注意:可能需要修改Next.js配置支持无后缀文件

Sub-command: test

子命令:test

Trigger

触发方式

User runs
/ucp test
用户执行
/ucp test

Purpose

用途

Generate unit tests for UCP handlers.
为UCP处理程序生成单元测试

Prerequisites

前置条件

  • Implementation must exist
  • Test framework detected (Jest, Vitest, etc.)
  • 实现必须存在
  • 已检测到测试框架(Jest、Vitest等)

Procedure

执行流程

Step 1: Detect Test Framework

步骤1:检测测试框架

Look for:
  • jest.config.js
    /
    jest.config.ts
    → Jest
  • vitest.config.js
    /
    vitest.config.ts
    → Vitest
  • package.json
    test script hints
查找以下内容:
  • jest.config.js
    /
    jest.config.ts
    → Jest
  • vitest.config.js
    /
    vitest.config.ts
    → Vitest
  • package.json
    中的test脚本提示

Step 2: Generate Test Files

步骤2:生成测试文件

For each handler, generate corresponding test file.
为每个处理程序生成对应的测试文件

Example: lib/ucp/handlers/tests/checkout.test.ts

示例:lib/ucp/handlers/tests/checkout.test.ts

typescript
/**
 * UCP Checkout Handler Tests
 * Generated by /ucp test
 */

import { describe, it, expect, beforeEach } from 'vitest'; // or jest
import {
  createCheckout,
  getCheckout,
  updateCheckout,
  completeCheckout,
} from '../checkout';

describe('UCP Checkout Handler', () => {
  const validRequest = {
    line_items: [
      {
        id: 'item_1',
        name: 'Test Product',
        quantity: 1,
        unit_price: 1000,
        total_price: 1000,
        currency: 'USD',
      },
    ],
    currency: 'USD',
  };

  describe('createCheckout', () => {
    it('creates a checkout with valid request', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.id).toBeDefined();
      expect(checkout.status).toBe('incomplete');
      expect(checkout.currency).toBe('USD');
      expect(checkout.line_items).toHaveLength(1);
      expect(checkout.ucp.version).toBeDefined();
      expect(checkout.ucp.capabilities).toContain('dev.ucp.shopping.checkout');
    });

    it('calculates totals correctly', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.totals.subtotal).toBe(1000);
      expect(checkout.totals.grand_total).toBeGreaterThanOrEqual(checkout.totals.subtotal);
    });

    it('sets expiration time', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.expires_at).toBeDefined();
      const expiresAt = new Date(checkout.expires_at);
      expect(expiresAt.getTime()).toBeGreaterThan(Date.now());
    });

    it('includes required links', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.links.self).toContain(checkout.id);
      expect(checkout.links.privacy_policy).toBeDefined();
      expect(checkout.links.terms_of_service).toBeDefined();
    });
  });

  describe('getCheckout', () => {
    it('retrieves existing checkout', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const retrieved = await getCheckout(created.id);
      expect(retrieved).toEqual(created);
    });

    it('returns null for non-existent checkout', async () => {
      const retrieved = await getCheckout('non-existent-id');
      expect(retrieved).toBeNull();
    });
  });

  describe('updateCheckout', () => {
    it('updates line items', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const updated = await updateCheckout(
        created.id,
        {
          line_items: [
            { ...validRequest.line_items[0], quantity: 2, total_price: 2000 },
          ],
        },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.totals.subtotal).toBe(2000);
    });

    it('updates buyer info', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const updated = await updateCheckout(
        created.id,
        { buyer: { email: 'test@example.com' } },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.buyer?.email).toBe('test@example.com');
    });

    it('transitions to ready_for_complete when requirements met', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const updated = await updateCheckout(
        created.id,
        { buyer: { email: 'test@example.com' } },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.status).toBe('ready_for_complete');
    });
  });

  describe('status lifecycle', () => {
    it('follows correct state transitions', async () => {
      // incomplete -> ready_for_complete -> complete_in_progress -> completed
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });
      expect(checkout.status).toBe('incomplete');

      const updated = await updateCheckout(
        checkout.id,
        { buyer: { email: 'test@example.com' } },
        ['dev.ucp.shopping.checkout']
      );
      expect(updated?.status).toBe('ready_for_complete');
    });
  });
});
typescript
/**
 * UCP Checkout Handler Tests
 * Generated by /ucp test
 */

import { describe, it, expect, beforeEach } from 'vitest'; // or jest
import {
  createCheckout,
  getCheckout,
  updateCheckout,
  completeCheckout,
} from '../checkout';

describe('UCP Checkout Handler', () => {
  const validRequest = {
    line_items: [
      {
        id: 'item_1',
        name: 'Test Product',
        quantity: 1,
        unit_price: 1000,
        total_price: 1000,
        currency: 'USD',
      },
    ],
    currency: 'USD',
  };

  describe('createCheckout', () => {
    it('creates a checkout with valid request', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.id).toBeDefined();
      expect(checkout.status).toBe('incomplete');
      expect(checkout.currency).toBe('USD');
      expect(checkout.line_items).toHaveLength(1);
      expect(checkout.ucp.version).toBeDefined();
      expect(checkout.ucp.capabilities).toContain('dev.ucp.shopping.checkout');
    });

    it('calculates totals correctly', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.totals.subtotal).toBe(1000);
      expect(checkout.totals.grand_total).toBeGreaterThanOrEqual(checkout.totals.subtotal);
    });

    it('sets expiration time', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.expires_at).toBeDefined();
      const expiresAt = new Date(checkout.expires_at);
      expect(expiresAt.getTime()).toBeGreaterThan(Date.now());
    });

    it('includes required links', async () => {
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      expect(checkout.links.self).toContain(checkout.id);
      expect(checkout.links.privacy_policy).toBeDefined();
      expect(checkout.links.terms_of_service).toBeDefined();
    });
  });

  describe('getCheckout', () => {
    it('retrieves existing checkout', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const retrieved = await getCheckout(created.id);
      expect(retrieved).toEqual(created);
    });

    it('returns null for non-existent checkout', async () => {
      const retrieved = await getCheckout('non-existent-id');
      expect(retrieved).toBeNull();
    });
  });

  describe('updateCheckout', () => {
    it('updates line items', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const updated = await updateCheckout(
        created.id,
        {
          line_items: [
            { ...validRequest.line_items[0], quantity: 2, total_price: 2000 },
          ],
        },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.totals.subtotal).toBe(2000);
    });

    it('updates buyer info', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const updated = await updateCheckout(
        created.id,
        { buyer: { email: 'test@example.com' } },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.buyer?.email).toBe('test@example.com');
    });

    it('transitions to ready_for_complete when requirements met', async () => {
      const created = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });

      const updated = await updateCheckout(
        created.id,
        { buyer: { email: 'test@example.com' } },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.status).toBe('ready_for_complete');
    });
  });

  describe('status lifecycle', () => {
    it('follows correct state transitions', async () => {
      // incomplete -> ready_for_complete -> complete_in_progress -> completed
      const checkout = await createCheckout(validRequest, {
        capabilities: ['dev.ucp.shopping.checkout'],
      });
      expect(checkout.status).toBe('incomplete');

      const updated = await updateCheckout(
        checkout.id,
        { buyer: { email: 'test@example.com' } },
        ['dev.ucp.shopping.checkout']
      );
      expect(updated?.status).toBe('ready_for_complete');
    });
  });
});

Step 3: Output Summary

步骤3:输出摘要

Generated test files:
  CREATE  lib/ucp/handlers/__tests__/checkout.test.ts
  CREATE  lib/ucp/handlers/__tests__/fulfillment.test.ts
  CREATE  lib/ucp/__tests__/negotiation.test.ts
  CREATE  lib/ucp/__tests__/profile.test.ts
  CREATE  app/api/ucp/__tests__/checkout.route.test.ts

Run tests with: npm test (or bun test)

生成的测试文件:
  创建  lib/ucp/handlers/__tests__/checkout.test.ts
  创建  lib/ucp/handlers/__tests__/fulfillment.test.ts
  创建  lib/ucp/__tests__/negotiation.test.ts
  创建  lib/ucp/__tests__/profile.test.ts
  创建  app/api/ucp/__tests__/checkout.route.test.ts

执行测试命令:npm test(或bun test)

Sub-command: docs

子命令:docs

Trigger

触发方式

User runs
/ucp docs
用户执行
/ucp docs

Purpose

用途

Generate internal documentation for the UCP integration.
生成UCP集成的内部文档

Prerequisites

前置条件

  • Config must exist
  • Implementation ideally exists
  • 配置必须存在
  • 最好已有实现

Procedure

执行流程

Step 1: Gather Information

步骤1:收集信息

  • Read config for capabilities, transports, handlers
  • Scan generated files
  • Read spec files for accurate descriptions
  • 读取配置中的能力、传输协议、处理程序
  • 扫描生成的文件
  • 读取规范文件获取准确描述

Step 2: Generate Documentation

步骤2:生成文档

Create
docs/ucp-integration.md
:
markdown
undefined
创建
docs/ucp-integration.md
markdown
undefined

UCP Integration Documentation

UCP集成文档

Overview

概述

This codebase implements the Universal Commerce Protocol (UCP) version {version}.
Role: {role} Domain: {domain} Transports: {transports}
本代码库实现了{version}版本的通用商务协议(UCP)。
角色: {role} 域名: {domain} 传输协议: {transports}

Capabilities

能力

Core

核心

  • dev.ucp.shopping.checkout
    - Checkout session management
  • dev.ucp.shopping.checkout
    - 结账会话管理

Extensions

扩展

{list extensions with descriptions}
{列出扩展能力及描述}

API Endpoints

API端点

Discovery

发现

  • GET /.well-known/ucp
    - UCP discovery profile
  • GET /.well-known/ucp
    - UCP发现配置

Checkout (REST)

结账(REST)

  • POST /api/ucp/checkout
    - Create checkout session
  • GET /api/ucp/checkout/:id
    - Get checkout session
  • PATCH /api/ucp/checkout/:id
    - Update checkout session
  • POST /api/ucp/checkout/:id
    (action=complete) - Complete checkout
  • POST /api/ucp/checkout
    - 创建结账会话
  • GET /api/ucp/checkout/:id
    - 查询结账会话
  • PATCH /api/ucp/checkout/:id
    - 更新结账会话
  • POST /api/ucp/checkout/:id
    (action=complete)- 完成结账

Checkout Status Lifecycle

结账状态生命周期

incomplete → requires_escalation → ready_for_complete → complete_in_progress → completed
                                                                            ↘ canceled
incomplete → requires_escalation → ready_for_complete → complete_in_progress → completed
                                                                            ↘ canceled

Payment Handlers

支付处理程序

{list configured handlers with integration notes}
{列出已配置的处理程序及集成说明}

Capability Negotiation

能力协商

The platform sends their profile URL via
UCP-Agent
header:
UCP-Agent: profile="https://platform.example.com/.well-known/ucp"
The business fetches the platform profile, computes the capability intersection, and includes the negotiated capabilities in every response.
平台通过
UCP-Agent
header发送其配置URL:
UCP-Agent: profile="https://platform.example.com/.well-known/ucp"
商家侧获取平台配置,计算能力交集,并在每个响应中包含协商后的能力。

Configuration

配置

Configuration is stored in
ucp.config.json
at the project root.
配置存储在项目根目录的
ucp.config.json
中。

Files

文件列表

{list generated files with descriptions}
{列出生成的文件及描述}

Testing

测试

Run unit tests:
bash
npm test
运行单元测试:
bash
npm test

Validation

验证

Validate implementation against UCP schemas:
bash
undefined
对照UCP schema验证实现合规性:
bash
undefined

Using the skill

使用skill

/ucp validate
undefined
/ucp validate
undefined

Step 3: Output

步骤3:输出

Generated documentation:
  CREATE  docs/ucp-integration.md

Documentation includes:
  - Capability overview
  - API endpoint reference
  - Status lifecycle diagram
  - Configuration guide
  - File manifest

已生成文档:
  创建  docs/ucp-integration.md

文档包含:
  - 能力概览
  - API端点参考
  - 状态生命周期图
  - 配置指南
  - 文件清单

Error Handling

错误处理

Interactive Error Resolution

交互式错误解决

When encountering ambiguous situations, always ask the user:
  1. Missing config: "Config file not found. Run /ucp init first, or create manually?"
  2. Spec fetch failed: "Could not clone UCP spec. Check network/auth. Retry, use local path, or abort?"
  3. Conflicting files: "File {path} already exists. Overwrite, merge, skip, or abort?"
  4. Unknown role: "Role '{role}' not recognized. Did you mean: business, platform, payment_provider, or host_embedded?"
  5. Validation failure: "Schema validation failed for {file}. Show details, attempt fix, or skip?"
遇到歧义场景时,始终询问用户:
  1. 配置缺失: "未找到配置文件,先执行/ucp init还是手动创建?"
  2. 规范拉取失败: "无法克隆UCP规范,请检查网络/权限。要重试、使用本地路径还是终止?"
  3. 文件冲突: "文件{path}已存在,要覆盖、合并、跳过还是终止?"
  4. 未知角色: "角色'{role}'未识别,是否是:business、platform、payment_provider或host_embedded?"
  5. 验证失败: "{file}的schema验证失败,要显示详情、尝试修复还是跳过?"

Error Codes

错误码

CodeMeaningResolution
CONFIG_NOT_FOUND
ucp.config.json missingRun /ucp init
SPEC_NOT_FOUND
Spec repo not availableCheck network, clone manually
INVALID_ROLE
Unknown role in configFix config
SCHEMA_INVALID
Response doesn't match schemaReview generated code
EDGE_RUNTIME_DETECTED
Edge runtime usedChange to nodejs/bun

码值含义解决方案
CONFIG_NOT_FOUND
缺失ucp.config.json执行/ucp init
SPEC_NOT_FOUND
规范仓库不可用检查网络,手动克隆
INVALID_ROLE
配置中存在未知角色修复配置
SCHEMA_INVALID
响应不符合schema审查生成的代码
EDGE_RUNTIME_DETECTED
使用了Edge runtime切换为nodejs/bun

Next.js Conventions

Next.js约定

File Structure

文件结构

project/
├── app/
│   ├── .well-known/
│   │   └── ucp/
│   │       └── route.ts          # Discovery profile
│   └── api/
│       └── ucp/
│           ├── checkout/
│           │   ├── route.ts      # POST create
│           │   └── [id]/
│           │       └── route.ts  # GET, PATCH, POST complete
│           ├── mcp/
│           │   └── route.ts      # MCP transport (if enabled)
│           └── a2a/
│               └── route.ts      # A2A transport (if enabled)
├── lib/
│   └── ucp/
│       ├── types/
│       │   ├── checkout.ts
│       │   └── index.ts
│       ├── schemas/
│       │   └── checkout.ts
│       ├── handlers/
│       │   ├── checkout.ts
│       │   ├── fulfillment.ts
│       │   ├── discount.ts
│       │   └── payment.ts
│       ├── transports/
│       │   └── mcp.ts
│       ├── profile.ts
│       ├── negotiation.ts
│       └── response.ts
├── docs/
│   └── ucp-integration.md
├── ucp.config.json
└── .ucp-spec/                    # Cloned spec (gitignored)
project/
├── app/
│   ├── .well-known/
│   │   └── ucp/
│   │       └── route.ts          # 发现配置
│   └── api/
│       └── ucp/
│           ├── checkout/
│           │   ├── route.ts      # POST 创建
│           │   └── [id]/
│           │       └── route.ts  # GET, PATCH, POST 完成
│           ├── mcp/
│           │   └── route.ts      # MCP传输(如果启用)
│           └── a2a/
│               └── route.ts      # A2A传输(如果启用)
├── lib/
│   └── ucp/
│       ├── types/
│       │   ├── checkout.ts
│       │   └── index.ts
│       ├── schemas/
│       │   └── checkout.ts
│       ├── handlers/
│       │   ├── checkout.ts
│       │   ├── fulfillment.ts
│       │   ├── discount.ts
│       │   └── payment.ts
│       ├── transports/
│       │   └── mcp.ts
│       ├── profile.ts
│       ├── negotiation.ts
│       └── response.ts
├── docs/
│   └── ucp-integration.md
├── ucp.config.json
└── .ucp-spec/                    # 克隆的规范(已gitignore)

Runtime Declaration

运行时声明

Every route file must include:
typescript
export const runtime = 'nodejs'; // Edge runtime is not supported
Or for Bun:
typescript
export const runtime = 'nodejs'; // Bun-compatible Node.js runtime
每个路由文件必须包含:
typescript
export const runtime = 'nodejs'; // Edge runtime is not supported
如果使用Bun:
typescript
export const runtime = 'nodejs'; // Bun-compatible Node.js runtime

App Router Patterns

App Router模式

  • Use Route Handlers (
    route.ts
    ) not API Routes (
    pages/api
    )
  • Use
    NextRequest
    and
    NextResponse
    from
    next/server
  • Await
    request.json()
    for body parsing
  • Use
    request.headers.get()
    for header access

  • 使用Route Handlers(
    route.ts
    )而非API Routes(
    pages/api
  • 使用
    next/server
    导出的
    NextRequest
    NextResponse
  • 使用
    await request.json()
    解析请求体
  • 使用
    request.headers.get()
    读取header

Credential Handling

凭证处理

Keys Required For

需要密钥的场景

  • AP2 Mandates: JWS signing key (ES256)
  • Webhook Signing: JWS signing key (ES256)
  • Identity Linking: OAuth client credentials
  • AP2 Mandates: JWS签名密钥(ES256)
  • Webhook签名: JWS签名密钥(ES256)
  • 身份关联: OAuth客户端凭证

Prompting for Credentials

凭证提示

When a feature requires credentials, ask:
"AP2 mandates require a JWS signing key (ES256 recommended). Do you have an existing key pair, or should I explain how to generate one?"
If user needs generation instructions:
bash
undefined
当某个功能需要凭证时,询问用户:
"AP2 mandates需要JWS签名密钥(推荐ES256算法)。 你已有现成的密钥对,还是需要我说明生成方式?"
如果用户需要生成说明:
bash
undefined

Generate ES256 key pair

生成ES256密钥对

openssl ecparam -genkey -name prime256v1 -noout -out private.pem openssl ec -in private.pem -pubout -out public.pem
openssl ecparam -genkey -name prime256v1 -noout -out private.pem openssl ec -in private.pem -pubout -out public.pem

Convert to JWK format (use jose library or online tool)

转换为JWK格式(使用jose库或在线工具)

undefined
undefined

Storage

存储

Credentials should be stored in environment variables, not in config:
  • UCP_SIGNING_KEY
    - Private key (PEM or JWK)
  • UCP_OAUTH_CLIENT_ID
    - OAuth client ID
  • UCP_OAUTH_CLIENT_SECRET
    - OAuth client secret

凭证应存储在环境变量中,不要放在配置里:
  • UCP_SIGNING_KEY
    - 私钥(PEM或JWK格式)
  • UCP_OAUTH_CLIENT_ID
    - OAuth客户端ID
  • UCP_OAUTH_CLIENT_SECRET
    - OAuth客户端密钥

Version History

版本历史

  • 2026-01-11: Initial UCP spec version
  • Skill v1.0: Initial skill release

  • 2026-01-11:UCP初始规范版本
  • Skill v1.0:Skill初始版本

References

参考