mcpField
U
ucp

ucp

github.com/vercel-labs开发15911 次安装38 周前更新
代码仓库

关于 ucp

ucp 是面向开发者的 Universal Commerce Protocol 实现技能,帮助项目按规范建立 UCP 支持,涵盖初始化、规划、代码缺口分析与脚手架生成。

主要功能

  1. 01初始化 UCP 配置与规范
  2. 02提供咨询与实施路线图
  3. 03生成文件级实施计划
  4. 04基于 AST 分析代码缺口
  5. 05生成 UCP 实现脚手架

适用场景

  1. 01在项目中启动 UCP 支持
  2. 02将现有结账 API 映射到 UCP
  3. 03识别缺失的 UCP 要求
  4. 04规划实施文件与阶段

MCP 相关

该技能可将 MCP 配置为 UCP 传输方式,并协助评估是否支持它。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/agentic-commerce-skills --skill ucp

安装后在 Claude Code 中输入/ucp确认已加载。

详细介绍

UCP 技能 — Universal Commerce Protocol 实现

核心原则

  1. 不使用 Edge 运行时 — 仅使用 Node.js(默认)或 Bun(选择启用)运行时
  2. 交互式错误处理 — 遇到模糊情况时,询问用户如何继续
  3. 由配置驱动 — 所有决策都保存在 ucp.config.json 中
  4. 以规范为依据 — 所有实现都参考 UCP 官方规范
  5. 遵循 Next.js 约定 — 按照 App Router 模式组织代码
  6. 深入分析 — 使用 AST 解析和数据流追踪来检测差距

规范仓库处理

位置优先级

按以下顺序检查:

  1. ./ucp/ — 用户的本地副本(按原样使用)
  2. ./.ucp-spec/ — 之前克隆的规范(更新)
  3. 两者均不存在 — 全新克隆
克隆流程

需要克隆时:

git clone --depth 1 https://github.com/Universal-Commerce-Protocol/ucp.git .ucp-spec

如果 HTTPS 失败,尝试 SSH:

git clone --depth 1 [email protected]:Universal-Commerce-Protocol/ucp.git .ucp-spec
更新流程

当 ./.ucp-spec/ 存在时:

cd .ucp-spec && git pull && cd ..
Gitignore 管理

克隆后,确保 .ucp-spec/ 已加入 .gitignore:

  • 如果 .gitignore 存在,则读取它
  • 检查是否已列出 .ucp-spec/ 或 .ucp-spec
  • 如果尚未列出,则在新的一行追加 .ucp-spec/
规范文件位置(按需读取)
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

配置文件

位置

项目根目录下的 ./ucp.config.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
    }
  }
}
字段说明
字段类型说明
ucp_versionstringUCP 规范版本(基于日期)
rolesstring[]以下一个或多个角色:business、platform、payment_provider、host_embedded
runtimestringnodejs(默认)或 bun
capabilities.corestring[]要实现的必需能力
capabilities.extensionsstring[]要实现的可选扩展
transportsstring[]已启用的传输方式:rest、mcp、a2a、embedded
transport_prioritystring[]实现传输方式的顺序
payment_handlersstring[]要支持的支付处理程序 ID
features.ap2_mandatesboolean启用 AP2 委托签名
features.identity_linkingboolean启用 OAuth 身份关联
features.multi_destination_fulfillmentboolean启用多目的地配送
domainstring/.well-known/ucp 的业务域名
existing_apisobject要分析的现有 API 端点映射
policy_urlsobject隐私、条款、退款和配送政策的 URL
scaffold_depthstringtypes | scaffolding | full
generated_filesstring[]脚手架创建的文件(用于追踪)
answersobject对资格确认问题的原始回答

子命令:(无参数)

触发条件

用户运行不带子命令的 /ucp

行为

显示列出所有可用子命令的帮助信息:

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/

子命令:init

触发条件

用户运行 /ucp init

用途

在项目中引导初始化 UCP:克隆规范、创建配置并询问必要问题。

流程
步骤 1:检查/克隆规范仓库
  1. 检查 ./ucp/ 是否存在
    • 如果存在:输出“找到本地 UCP 规范,位置:./ucp/”
  2. 如果不存在,检查 ./.ucp-spec/ 是否存在
    • 如果存在:运行 git pull 进行更新
    • 如果不存在:克隆仓库(参见“规范仓库处理”)
  3. 克隆后,将 .ucp-spec/ 添加到 .gitignore
步骤 2:检查现有配置
  1. 检查 ./ucp.config.json 是否存在
  2. 如果存在,询问:“配置文件已存在。要覆盖、合并还是中止?”
    • 覆盖:删除并重新创建
    • 合并:保留现有值作为默认值
    • 中止:停止初始化
步骤 3:询问关键问题(4 个问题)

Q1:你要实现哪些角色?

  • 商家(记录商家)
  • 平台(消费者应用或 Agent)
  • 支付凭证提供方
  • 嵌入结账功能的宿主
  • 多个(请注明)

如果用户选择多个角色,请警告:

“实现多个角色并不常见。通常适用于市场平台/聚合器场景。你确定吗?”

Q2:你将使用哪种运行时?

  • Node.js(推荐,稳定)
  • Bun(选择启用,实验性)

注意:如果用户提到 Edge,请回复:

“UCP 实现不支持 Edge 运行时。请选择 Node.js 或 Bun。”

Q3:你的业务域名是什么?

  • 将托管 /.well-known/ucp 的域名
  • 示例:shop.example.com

Q4:你计划在发布时使用哪些传输方式?

  • REST(推荐的基线方案)
  • MCP(模型上下文协议)
  • A2A(Agent 到 Agent)
  • 嵌入式(iframe 结账)
步骤 4:创建配置文件

创建 ./ucp.config.json,其中包含:

  • 关键问题的答案
  • 其他字段的合理默认值
  • 将 ucp_version 设置为规范中的最新版本
步骤 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

子命令:consult

触发条件

用户运行 /ucp consult

用途

引导用户完成全部 12 个资格问题,更新配置,并生成实施路线图。

前置条件
  • 配置文件必须存在(请先运行 /ucp init)
  • 规范必须可用
流程
步骤 1:加载现有配置

读取 ./ucp.config.json,并将已有答案用作默认值。

步骤 2:逐一确认 12 个问题

逐一提问。如果配置中已有答案,则显示当前值,并询问是否确认或修改。

Q1:我们要实现业务端、平台端,还是两者都实现?

  • 映射到配置中的 roles
  • 如果选择两者/多个角色,提示这属于不常见场景

Q2:要采用哪个 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

Q3:需要哪些支付处理器?

  • 钱包(Apple Pay、Google Pay)
  • PSP 令牌化(Stripe、Adyen 等)
  • 自定义处理器
  • 暂无(稍后决定)

Q4:是否需要 AP2 授权书和签名密钥基础设施?

  • 是 → 设置 features.ap2_mandates: true
  • 否 → 设置 features.ap2_mandates: false
  • 如果需要,请说明:“你需要提供 JWS 签名密钥(建议使用 ES256)”

Q5:是否需要配送选项以及多组/多目的地支持?

  • 无需配送
  • 仅支持单一目的地
  • 支持多目的地 → 设置 features.multi_destination_fulfillment: true

Q6:是否需要折扣、买家同意信息收集或身份关联?

  • 折扣 → 将 dev.ucp.shopping.discount 添加到扩展中
  • 买家同意 → 将 dev.ucp.shopping.buyer_consent 添加到扩展中
  • 身份关联 → 添加 dev.ucp.common.identity_linking,并设置 features.identity_linking: true

Q7:需要映射到 UCP 的现有结账和订单 API 是哪些?

  • 询问现有端点路径
  • 存储在 existing_apis 对象中
  • 示例:/api/checkout、/api/cart、/api/orders

Q8:需要哪些政策 URL?

  • 隐私政策 URL
  • 服务条款 URL
  • 退款政策 URL
  • 配送政策 URL
  • 存储在 policy_urls 对象中

Q9:结账端点需要哪种身份验证模式?

  • 无(匿名结账)
  • API 密钥
  • OAuth 2.0
  • 基于会话
  • 存储在 answers.authentication_model 中

Q10:谁会接收订单 Webhook?需要怎样的事件频率?

  • 订单事件的 Webhook URL
  • 所需事件类型:order.created、order.updated、order.fulfilled、order.canceled
  • 存储在 answers.webhook_config 中

Q11:上线时是否需要支持 MCP、A2A 或嵌入式结账?

  • 确认/更新 transports 数组
  • 设置 transport_priority 顺序

Q12:哪个业务域名将提供 /.well-known/ucp?

  • 确认/更新 domain 字段
步骤 3:更新配置

将所有答案写入 ./ucp.config.json

步骤 4:生成实施路线图

根据答案生成路线图:

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.

子命令:plan

触发条件

用户运行 /ucp plan

用途

生成详细的实施计划,列出具体文件和操作顺序。

前置条件
  • 配置文件必须存在,且咨询流程已完成
  • 规范必须可用
流程
步骤 1:加载配置和规范
  • 读取 ./ucp.config.json
  • 根据能力/传输方式读取相关规范文件
步骤 2:分析现有代码库结构
  • 检测 Next.js 版本(App Router 还是 Pages Router)
  • 查找现有 API 路由
  • 查找现有 lib/utils 结构
  • 查找现有类型/架构
  • 识别包管理器(npm、yarn、pnpm、bun)
步骤 3:生成文件计划

针对每项能力/传输方式,列出需要创建/修改的文件。

输出示例:

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.
步骤 4:将计划保存到配置

将计划存储在 answers.implementation_plan 中,供脚手架参考。


子命令:gaps

触发条件

用户运行 /ucp gaps

用途

根据 UCP 要求深入分析现有代码库。使用 AST 解析和数据流追踪。

前置条件
  • 配置文件应存在(以提供角色/能力上下文)
  • 规范必须可用
流程
步骤 1:加载上下文
  • 读取配置,了解已声明的功能
  • 读取相关规范文件
步骤 2:发现现有代码

扫描以下内容:

  • API 路由(app/api/**、pages/api/**)
  • 与结账相关的文件(搜索 "checkout"、"cart"、"order")
  • 支付处理代码
  • Webhook 实现
步骤 3:深入分析(基于 AST)

对每个相关文件:

  • 解析 AST
  • 跟踪结账对象的数据流
  • 找出现有模式

对照 UCP 要求进行分析:

要求状态发现
位于 /.well-known/ucp 的发现配置文件缺失未找到路由
创建结账会话部分满足找到 /api/checkout,但缺少 UCP 字段
状态生命周期缺失没有状态机
功能协商缺失没有处理 UCP-Agent 标头
支付处理程序支持部分满足存在 Stripe,但不符合 UCP 规范
响应元数据(ucp 对象)缺失响应中不包含 ucp 字段
步骤 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

子命令:scaffold

触发条件

用户运行 /ucp scaffold

目的

根据配置和计划生成完整可用的 UCP 实现。

前提条件
  • 必须存在配置文件
  • 应存在计划(先运行 /ucp plan,否则 scaffold 会生成计划)
流程
步骤 1:确认脚手架生成深度

询问用户:

"你希望生成到什么程度的代码?"

  • types:仅生成 TypeScript 接口和 Zod 架构
  • scaffolding:生成带有业务逻辑 TODO 标记的结构
  • full:生成完整可用的实现(推荐)

将选择存储在 config.scaffold_depth

步骤 2:检查依赖项

根据配置确定所需的软件包:

  • zod — 始终需要
  • jose — 如果 AP2 有要求或启用了 webhook 签名
  • uuid — 用于生成会话 ID

安装前询问:

"需要以下软件包:zod、jose、uuid" "现在安装?(npm install / bun add)"

如果用户同意,运行相应的安装命令。

步骤 3:生成代码

根据计划生成文件。对于每个文件:

  1. 如有需要,创建父目录
  2. 写入文件内容
  3. 在 config.generated_files 中记录
代码生成模板
lib/ucp/types/checkout.ts
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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 传输代码模板(使用 mcp-handler)

启用配置中的 MCP 传输后,生成以下附加文件。

MCP 传输所需依赖项
npm install mcp-handler @modelcontextprotocol/[email protected] zod

**重要:**使用 @modelcontextprotocol/[email protected] 或更高版本——较早版本存在安全漏洞。

app/api/mcp/[transport]/route.ts
/**
 * 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
/**
 * 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 部署

部署配置
vercel.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"
        }
      ]
    }
  ]
}
环境变量

在 Vercel 控制面板 → Settings → Environment Variables 中设置以下变量:

变量必需描述
UCP_DOMAIN是生产域名(例如,shop.example.com)
UCP_SIGNING_KEY如果使用 AP2JWS 签名密钥(PEM 或 JWK)
STRIPE_SECRET_KEY如果使用 StripeStripe API 密钥
next.config.js(针对 MCP 优化)
/** @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 客户端配置
适用于 Claude Desktop / Cursor / Windsurf

选项 1:直接使用 HTTP(如果客户端支持可流式传输的 HTTP) 添加到 MCP 客户端配置:

{
  "mcpServers": {
    "ucp-shopping": {
      "url": "https://your-domain.vercel.app/api/mcp"
    }
  }
}

选项 2:通过 mcp-remote 桥接(适用于仅支持 stdio 的客户端)

{
  "mcpServers": {
    "ucp-shopping": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-domain.vercel.app/api/mcp"]
    }
  }
}
测试 MCP 部署
scripts/test-mcp.mjs
#!/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 部署检查清单

使用 Vercel 部署运行 /ucp scaffold 时,请验证:

  • 已创建带有函数超时设置的 vercel.json
  • 已记录环境变量
  • 已更新 next.config.js 以支持 MCP 流式传输
  • MCP 路由位于 app/api/mcp/[transport]/route.ts
  • 测试脚本位于 scripts/test-mcp.mjs
  • 已使用必需变量更新 .env.example
部署后验证

部署到 Vercel 后:

  1. 检查发现配置文件:

    curl https://your-domain.vercel.app/.well-known/ucp | jq .
    
  2. 测试 MCP 端点:

    node scripts/test-mcp.mjs https://your-domain.vercel.app
    
  3. 配置 MCP 客户端: 将服务添加到 Claude Desktop、Cursor 或你偏好的 MCP 客户端。

  4. 在客户端中验证: 请 AI“列出可用的 UCP 工具”——它应显示结账工具。


生成后步骤
步骤 4:更新配置

生成后,更新 ucp.config.json:

  • 将所有已创建的文件添加到 generated_files 数组
  • 更新 scaffold_depth,以反映已生成的内容
步骤 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

子命令:validate

触发条件

用户运行 /ucp validate

目的

根据 UCP JSON 架构验证实现。

前提条件
  • 实现必须已存在(先运行 /ucp scaffold)
  • 规范必须可用
流程
步骤 1:加载架构

从规范中读取 JSON 架构:

  • spec/schemas/shopping/checkout.json
  • spec/schemas/shopping/fulfillment.json
  • spec/schemas/shopping/discount.json
  • spec/discovery/profile_schema.json
步骤 2:验证发现配置文件
  • 向 /.well-known/ucp 路由发起请求(或直接读取文件)
  • 根据 profile_schema.json 进行验证
步骤 3:验证响应结构

针对每个已实现的端点:

  • 生成示例请求
  • 执行处理程序(模拟模式)
  • 根据架构验证响应
步骤 4:检查协议要求

验证:

  • 所有响应都包含 ucp 对象
  • 状态值均为有效的枚举值
  • 金额均为整数(最小货币单位)
  • 日期采用 RFC 3339 格式
  • 链接均为绝对 HTTPS URL
  • 功能名称遵循反向 DNS 格式
步骤 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)

子命令:profile

触发条件

用户运行 /ucp profile

目的

生成并显示 /.well-known/ucp 发现配置文件 JSON。

前提条件
  • 配置必须已存在
流程
步骤 1:生成配置文件

使用 lib/ucp/profile.ts 中的 generateProfile() 函数(如果尚未生成框架,也可以直接内联生成)。

步骤 2:显示配置文件

输出格式化后的 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"
      }
    ]
  }
}
步骤 3:询问是否写入文件

询问:“将其写入 public/.well-known/ucp 以供静态服务,还是保留为动态路由?”

如果选择静态:

  • 创建 public/.well-known/ucp(无扩展名,内容为 JSON)
  • 注意:无扩展名文件可能需要 Next.js 配置

子命令:test

触发条件

用户运行 /ucp test

目的

为 UCP 处理程序生成单元测试。

前提条件
  • 实现必须已存在
  • 已检测到测试框架(Jest、Vitest 等)
流程
步骤 1:检测测试框架

查找:

  • jest.config.js / jest.config.ts → Jest
  • vitest.config.js / vitest.config.ts → Vitest
  • package.json 中测试脚本的相关提示
步骤 2:生成测试文件

为每个处理程序生成对应的测试文件。

示例:lib/ucp/handlers/tests/checkout.test.ts
/**
 * 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: '[email protected]' } },
        ['dev.ucp.shopping.checkout']
      );

      expect(updated?.buyer?.email).toBe('[email protected]');
    });

    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: '[email protected]' } },
        ['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: '[email protected]' } },
        ['dev.ucp.shopping.checkout']
      );
      expect(updated?.status).toBe('ready_for_complete');
    });
  });
});
步骤 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)

子命令:docs

触发条件

用户运行 /ucp docs

目的

为 UCP 集成生成内部文档。

前提条件
  • 配置必须已存在
  • 最好已存在实现
流程
步骤 1:收集信息
  • 读取配置中的功能、传输方式和处理程序
  • 扫描已生成的文件
  • 读取规范文件以获取准确描述
步骤 2:生成文档

创建 docs/ucp-integration.md:

# UCP Integration Documentation

## Overview

This codebase implements the Universal Commerce Protocol (UCP) version {version}.

**Role:** {role}
**Domain:** {domain}
**Transports:** {transports}

## Capabilities

### Core
- `dev.ucp.shopping.checkout` - Checkout session management

### Extensions
{list extensions with descriptions}

## API Endpoints

### Discovery
- `GET /.well-known/ucp` - UCP discovery profile

### Checkout (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

## Checkout Status Lifecycle

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.

## Configuration

Configuration is stored in `ucp.config.json` at the project root.

## Files

{list generated files with descriptions}

## Testing

Run unit tests:
```bash
npm test

验证

根据 UCP 架构验证实现:

# Using the skill
/ucp validate

#### Step 3: Output

生成的文档: CREATE 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?"

### Error Codes
| Code | Meaning | Resolution |
|------|---------|------------|
| `CONFIG_NOT_FOUND` | ucp.config.json missing | Run /ucp init |
| `SPEC_NOT_FOUND` | Spec repo not available | Check network, clone manually |
| `INVALID_ROLE` | Unknown role in config | Fix config |
| `SCHEMA_INVALID` | Response doesn't match schema | Review generated code |
| `EDGE_RUNTIME_DETECTED` | Edge runtime used | Change to nodejs/bun |

---

## Next.js Conventions

### File Structure

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

或者使用 Bun:

export const runtime = 'nodejs'; // Bun-compatible Node.js runtime
App Router 模式
  • 使用 Route Handlers(route.ts),不要使用 API Routes(pages/api)
  • 使用 next/server 中的 NextRequest 和 NextResponse
  • 使用 await request.json() 解析请求正文
  • 使用 request.headers.get() 访问请求头

凭据处理

所需密钥
  • **AP2 Mandates:**JWS 签名密钥(ES256)
  • **Webhook 签名:**JWS 签名密钥(ES256)
  • **身份关联:**OAuth 客户端凭据
询问凭据

当某项功能需要凭据时,请询问:

“AP2 mandates 需要 JWS 签名密钥(推荐使用 ES256)。 你是否已有密钥对,还是需要我说明如何生成?”

如果用户需要生成说明:

# Generate ES256 key pair
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)
存储

凭据应存储在环境变量中,而非配置文件中:

  • UCP_SIGNING_KEY - 私钥(PEM 或 JWK)
  • UCP_OAUTH_CLIENT_ID - OAuth 客户端 ID
  • UCP_OAUTH_CLIENT_SECRET - OAuth 客户端密钥

版本历史

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

参考资料

评论

支持 Markdown