ucp by vercel-labs/agentic-commerce-skills
npx skills add https://github.com/vercel-labs/agentic-commerce-skills --skill ucpucp.config.json 中按此顺序检查:
./ucp/ — 用户的本地副本(直接使用)./.ucp-spec/ — 之前克隆的规范(更新它)当需要克隆时:
git clone --depth 1 https://github.com/Universal-Commerce-Protocol/ucp.git .ucp-spec
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
如果 HTTPS 失败,尝试 SSH:
git clone --depth 1 git@github.com:Universal-Commerce-Protocol/ucp.git .ucp-spec
当 ./.ucp-spec/ 存在时:
cd .ucp-spec && git pull && cd ..
克隆后,确保 .gitignore 中包含 .ucp-spec/:
.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_version | string | UCP 规范版本(基于日期) |
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 授权签署 |
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 |
generated_files | string[] | 脚手架创建的文件(用于追踪) |
answers | object | 资格问题的原始答案 |
用户运行 /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/
用户运行 /ucp init
在项目中引导 UCP:克隆规范、创建配置、询问基本问题。
./ucp/ 是否存在
./.ucp-spec/ 是否存在
git pull 进行更新.ucp-spec/ 添加到 .gitignore./ucp.config.json 是否存在问题 1:您要实现哪个(些)角色?
如果用户选择多个角色,发出警告:
"实现多个角色是不常见的。这通常用于市场/聚合场景。您确定吗?"
问题 2:您将使用哪个运行时?
注意:如果用户提到 Edge,回复:
"UCP 实现不支持 Edge 运行时。请选择 Node.js 或 Bun。"
问题 3:您的业务域名是什么?
/.well-known/ucp 的域名shop.example.com问题 4:启动时需要哪些传输方式?
创建 ./ucp.config.json,包含:
ucp_version 设置为规范中的最新版本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 consult
完成所有 12 个资格问题,更新配置,生成实现路线图。
/ucp init)读取 ./ucp.config.json 并使用现有答案作为默认值。
询问每个问题。如果配置中已有答案,显示当前值并询问确认或更改。
问题 1:我们是要实现业务端、平台端,还是两者都实现?
roles问题 2:哪个 UCP 版本以及哪些能力/扩展在范围内?
dev.ucp.shopping.checkout(必需)dev.ucp.shopping.fulfillmentdev.ucp.shopping.discountdev.ucp.shopping.buyer_consentdev.ucp.shopping.ap2_mandatedev.ucp.shopping.orderdev.ucp.common.identity_linking问题 3:我们需要哪些支付处理器?
问题 4:我们需要 AP2 授权和签名密钥基础设施吗?
features.ap2_mandates: truefeatures.ap2_mandates: false问题 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:我们应该映射到 UCP 的现有结账和订单 API 是什么?
existing_apis 对象中/api/checkout, /api/cart, /api/orders问题 8:必需的策略 URL 是什么?
policy_urls 对象中问题 9:结账端点需要什么认证模型?
answers.authentication_model问题 10:谁将接收订单 Webhook,需要什么事件节奏?
order.created, order.updated, order.fulfilled, order.canceledanswers.webhook_config问题 11:我们需要在启动时支持 MCP、A2A 或嵌入式结账吗?
transports 数组transport_priority 顺序问题 12:将托管 /.well-known/ucp 的业务域名是什么?
domain 字段将所有答案写入 ./ucp.config.json
根据答案生成路线图:
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 plan
生成详细的实现计划,包含具体文件和操作顺序。
./ucp.config.json针对每个能力/传输方式,列出要创建/修改的文件。
示例输出:
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.
将计划存储在 answers.implementation_plan 中,供脚手架参考。
用户运行 /ucp gaps
针对 UCP 要求对现有代码库进行深度分析。使用 AST 解析和数据流追踪。
扫描:
app/api/**, pages/api/**)针对每个相关文件:
根据 UCP 要求进行分析:
| 要求 | 状态 | 发现 |
|---|---|---|
| 在 /.well-known/ucp 的发现配置文件 | 缺失 | 未找到路由 |
| 结账会话创建 | 部分 | 找到 /api/checkout 但缺少 UCP 字段 |
| 状态生命周期 | 缺失 | 没有状态状态机 |
| 能力协商 | 缺失 | 没有处理 UCP-Agent 头部 |
| 支付处理器支持 | 部分 | Stripe 存在但不兼容 UCP |
| 响应元数据(ucp 对象) | 缺失 | 响应不包含 ucp 字段 |
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 scaffold
基于配置和计划生成完整可用的 UCP 实现。
/ucp plan,否则脚手架将生成一个)询问用户:
"您想要什么级别的代码生成?"
- types : 仅 TypeScript 接口和 Zod 模式
- scaffolding : 包含业务逻辑 TODO 标记的结构
- full : 完整可用的实现(推荐)
将选择存储在 config.scaffold_depth 中
根据配置识别所需的包:
zod — 总是需要jose — 如果启用了 AP2 授权或 Webhook 签名uuid — 用于会话 ID 生成安装前询问:
"需要以下包:zod, jose, uuid" "现在安装吗?(npm install / bun add)"
如果是,运行相应的安装命令。
根据计划生成文件。针对每个文件:
config.generated_files 中追踪/**
* 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...
/**
* 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>;
/**
* 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',
},
});
}
/**
* 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 };
}
/**
* 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');
}
}
ucp.config.jsonCheck in this order:
./ucp/ — User's local copy (use as-is)./.ucp-spec/ — Previously cloned spec (update it)When cloning is needed:
git clone --depth 1 https://github.com/Universal-Commerce-Protocol/ucp.git .ucp-spec
If HTTPS fails, try SSH:
git clone --depth 1 git@github.com:Universal-Commerce-Protocol/ucp.git .ucp-spec
When ./.ucp-spec/ exists:
cd .ucp-spec && git pull && cd ..
After cloning, ensure .ucp-spec/ is in .gitignore:
.gitignore if it exists.ucp-spec/ or .ucp-spec is already listed.ucp-spec/ on a new linedocs/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 at project root
{
"$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 | Type | Description |
|---|---|---|
ucp_version | string | UCP spec version (date-based) |
roles | string[] | One or more of: business, platform, payment_provider, host_embedded |
runtime | string | (default) or |
User runs /ucp with no sub-command
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/
User runs /ucp init
Bootstrap UCP in a project: clone spec, create config, ask essential questions.
./ucp/ exists
./.ucp-spec/ exists
git pull to update.ucp-spec/ to .gitignore./ucp.config.json existsQ1: What role(s) are you implementing?
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?
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?
/.well-known/ucpshop.example.comQ4: Which transports do you need at launch?
Create ./ucp.config.json with:
ucp_version set to latest from specUCP 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
User runs /ucp consult
Walk through all 12 qualifying questions, update config, produce implementation roadmap.
/ucp init first)Read ./ucp.config.json and use existing answers as defaults.
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?
roles in configQ2: Which UCP version and which capabilities/extensions are in scope?
dev.ucp.shopping.checkout (required)dev.ucp.shopping.fulfillmentdev.ucp.shopping.discountdev.ucp.shopping.buyer_consentdev.ucp.shopping.ap2_mandatedev.ucp.shopping.orderdev.ucp.common.identity_linkingQ3: Which payment handlers do we need?
Q4: Do we need AP2 mandates and signing key infrastructure?
features.ap2_mandates: truefeatures.ap2_mandates: falseQ5: Do we need fulfillment options and multi-group/multi-destination support?
features.multi_destination_fulfillment: trueQ6: Do we need discounts, buyer consent capture, or identity linking?
dev.ucp.shopping.discount to extensionsdev.ucp.shopping.buyer_consent to extensionsdev.ucp.common.identity_linking, set features.identity_linking: trueQ7: What are the existing checkout and order APIs we should map to UCP?
existing_apis object/api/checkout, /api/cart, /api/ordersQ8: What are the required policy URLs?
policy_urls objectQ9: What authentication model is required for checkout endpoints?
answers.authentication_modelQ10: Who will receive order webhooks and what event cadence is required?
order.created, order.updated, order.fulfilled, order.canceledanswers.webhook_configQ11: Do we need to support MCP, A2A, or embedded checkout at launch?
transports arraytransport_priority orderQ12: What is the business domain that will host /.well-known/ucp?
domain fieldWrite all answers to ./ucp.config.json
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.
User runs /ucp plan
Generate detailed implementation plan with specific files and order of operations.
./ucp.config.jsonFor 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.
Store the plan in answers.implementation_plan for scaffold reference.
User runs /ucp gaps
Deep analysis of existing codebase against UCP requirements. Uses AST parsing and data flow tracing.
Scan for:
app/api/**, pages/api/**)For each relevant file:
Analyze against UCP requirements:
| Requirement | Status | Finding |
|---|---|---|
| Discovery profile at /.well-known/ucp | MISSING | No route found |
| Checkout session creation | PARTIAL | Found /api/checkout but missing UCP fields |
| Status lifecycle | MISSING | No status state machine |
| Capability negotiation | MISSING | No UCP-Agent header handling |
| Payment handler support | PARTIAL | Stripe exists but not UCP-compliant |
| Response metadata (ucp object) | MISSING | Responses don't include ucp field |
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
User runs /ucp scaffold
Generate full working UCP implementation based on config and plan.
/ucp plan first, or scaffold will generate one)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
Identify required packages based on config:
zod — Always neededjose — If AP2 mandates or webhook signing enableduuid — For session ID generationAsk before installing:
"The following packages are required: zod, jose, uuid" "Install now? (npm install / bun add)"
If yes, run appropriate install command.
Generate files according to plan. For each file:
config.generated_files/**
* 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...
/**
* 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>;
/**
* 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',
},
});
}
/**
* 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 };
}
/**
* 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');
}
}
/**
* 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
}
/**
* 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,
};
}
}
/**
* 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 }
);
}
When MCP transport is enabled in config, generate these additional files.
npm install mcp-handler @modelcontextprotocol/sdk@1.25.2 zod
IMPORTANT: Use @modelcontextprotocol/sdk@1.25.2 or later — earlier versions have security vulnerabilities.
/**
* 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 };
/**
* 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: {
Lark CLI IM 即时消息管理工具:机器人/用户身份操作聊天、消息、文件下载
34,600 周安装
nodejsbuncapabilities.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 | boolean | Enable AP2 mandate signing |
features.identity_linking | boolean | Enable OAuth identity linking |
features.multi_destination_fulfillment | boolean | Enable multi-destination shipping |
domain | string | Business domain for /.well-known/ucp |
existing_apis | object | Map of existing API endpoints to analyze |
policy_urls | object | URLs for privacy, terms, refunds, shipping policies |
scaffold_depth | string | types |
generated_files | string[] | Files created by scaffold (for tracking) |
answers | object | Raw answers to qualifying questions |