Skip to content

Conversation

@blurrah
Copy link
Collaborator

@blurrah blurrah commented Oct 12, 2025

Add Model Context Protocol (MCP) tool primitives to enable merchants to sell on ChatGPT Apps without waiting for ACP approval. Also complete the OpenAI product feed specification with all 70+ fields.

MCP Tools (acp-handler/mcp)

  • Export flat tool definitions with Zod schemas for 7 commerce operations
  • Provide handler factory that calls existing acp-handler endpoints
  • Tools: searchProducts, getProduct, createCheckout, updateCheckout, completeCheckout, cancelCheckout, getCheckout
  • Clean API: users compose tools with any MCP server framework
  • Fully customizable: override descriptions, add UI templates, custom handlers

Product Feed Enhancements (acp-handler/feed)

  • Add missing fields from OpenAI spec: physical properties, availability, merchant info, performance signals, compliance, reviews, geo-tagging
  • Total coverage: 70+ fields across all categories
  • Includes unit pricing, pickup methods, 3D models, region-specific pricing/availability
  • Format improvements for enums and nested objects

Benefits

  • Merchants can sell on ChatGPT TODAY via MCP apps
  • When ACP approved, add product feed for discovery
  • Both systems use same checkout logic (no duplication)
  • Low-level primitives allow maximum flexibility

🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

Add Model Context Protocol (MCP) tool primitives to enable merchants to sell on ChatGPT Apps without waiting for ACP approval. Also complete the OpenAI product feed specification with all 70+ fields.

## MCP Tools (acp-handler/mcp)
- Export flat tool definitions with Zod schemas for 7 commerce operations
- Provide handler factory that calls existing acp-handler endpoints
- Tools: searchProducts, getProduct, createCheckout, updateCheckout, completeCheckout, cancelCheckout, getCheckout
- Clean API: users compose tools with any MCP server framework
- Fully customizable: override descriptions, add UI templates, custom handlers

## Product Feed Enhancements (acp-handler/feed)
- Add missing fields from OpenAI spec: physical properties, availability, merchant info, performance signals, compliance, reviews, geo-tagging
- Total coverage: 70+ fields across all categories
- Includes unit pricing, pickup methods, 3D models, region-specific pricing/availability
- Format improvements for enums and nested objects

## Benefits
- Merchants can sell on ChatGPT TODAY via MCP apps
- When ACP approved, add product feed for discovery
- Both systems use same checkout logic (no duplication)
- Low-level primitives allow maximum flexibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@vercel
Copy link

vercel bot commented Oct 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
acp-handler-chat-sdk-example Ready Ready Preview Comment Oct 13, 2025 9:27am

updateCheckout: async (input: any): Promise<unknown> => {
const { session_id, ...body } = input;
return request(`/api/checkout/${session_id}`, {
method: "PATCH",
Copy link

@vercel vercel bot Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateCheckout handler uses HTTP method PATCH, but the ACP API expects POST for checkout updates. This will cause update operations to fail.

View Details
📝 Patch Details
diff --git a/packages/sdk/src/mcp/handlers.ts b/packages/sdk/src/mcp/handlers.ts
index f2b8b76..3e369e2 100644
--- a/packages/sdk/src/mcp/handlers.ts
+++ b/packages/sdk/src/mcp/handlers.ts
@@ -111,7 +111,7 @@ export function createHandlers(config: HandlerConfig) {
 		updateCheckout: async (input: any): Promise<unknown> => {
 			const { session_id, ...body } = input;
 			return request(`/api/checkout/${session_id}`, {
-				method: "PATCH",
+				method: "POST",
 				body: JSON.stringify(body),
 			});
 		},
diff --git a/packages/sdk/src/mcp/tools.ts b/packages/sdk/src/mcp/tools.ts
index 7b1086f..8abded1 100644
--- a/packages/sdk/src/mcp/tools.ts
+++ b/packages/sdk/src/mcp/tools.ts
@@ -97,7 +97,7 @@ export const createCheckout: MCPToolDefinition = {
 /**
  * Update an existing checkout session
  *
- * Maps to: PATCH /api/checkout/:id
+ * Maps to: POST /api/checkout/:id
  */
 export const updateCheckout: MCPToolDefinition = {
 	description:

Analysis

HTTP method mismatch in updateCheckout handler causes 405 Method Not Allowed errors

What fails: The updateCheckout handler in packages/sdk/src/mcp/handlers.ts line 114 uses method: "PATCH" to call /api/checkout/:id, but the API route only exports { GET, POST } methods.

How to reproduce:

  1. Call the MCP updateCheckout handler with a session ID
  2. Handler sends PATCH request to /api/checkout/{session_id}
  3. Next.js route handler (created by createNextCatchAll) only exports GET and POST

Result: Next.js returns 405 Method Not Allowed per Next.js route handler documentation, causing all checkout update operations to fail.

Expected: Should use POST method, matching the API implementation in packages/sdk/src/next/index.ts (lines 66-72) which routes POST /:id requests to the update handler, and consistent with the example implementation in examples/chat-sdk/lib/ai/tools/update-checkout.ts line 75 which explicitly uses POST.

Fixed:

  • Changed method: "PATCH" to method: "POST" in packages/sdk/src/mcp/handlers.ts line 114
  • Updated comment from PATCH /api/checkout/:id to POST /api/checkout/:id in packages/sdk/src/mcp/tools.ts line 100

Changed from PATCH to POST to match the actual ACP handler
implementation which uses POST for checkout updates.
Add smart payment handling in completeCheckout that returns a
checkout URL when no payment token is provided (MCP context),
while still supporting full payment processing when a delegated
token is available (ACP context).

Changes:
- Add checkoutUrlPattern and getCheckoutUrl to HandlerConfig
- Update completeCheckout handler to detect missing payment token
- Return checkout_url for MCP, process payment for ACP
- Update tool description and schema to document dual behavior
- Add comprehensive examples showing checkout URL configuration

This allows merchants to implement the ACP protocol once and use
it for both full ACP (with payments) and MCP (checkout redirect).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Comment on lines +123 to +138
createCheckout: async (input: any): Promise<unknown> => {
return request("/api/checkout", {
method: "POST",
body: JSON.stringify(input),
});
},

/**
* Update an existing checkout session
*/
updateCheckout: async (input: any): Promise<unknown> => {
const { session_id, ...body } = input;
return request(`/api/checkout/${session_id}`, {
method: "POST",
body: JSON.stringify(body),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MCP handlers send data directly to ACP endpoints without transforming the schema, causing validation failures. The MCP tool schemas use different field names than the ACP API expects (e.g., customer.email/name vs customer.billing_address/shipping_address, fulfillment.selected_option_id vs fulfillment.selected_id, payment.token vs payment.delegated_token).

View Details
📝 Patch Details
diff --git a/packages/sdk/src/mcp/handlers.ts b/packages/sdk/src/mcp/handlers.ts
index 7e77098..0f67057 100644
--- a/packages/sdk/src/mcp/handlers.ts
+++ b/packages/sdk/src/mcp/handlers.ts
@@ -40,6 +40,88 @@ export interface HandlerConfig {
 	getCheckoutUrl?: (sessionId: string) => string;
 }
 
+/**
+ * Transform MCP data to ACP schema
+ * MCP has customer: { email, name } and fulfillment: { address }
+ * ACP has customer: { shipping_address: Address }
+ * We need to merge the customer info with the fulfillment address
+ */
+function transformToACP(input: {
+	customer?: { email?: string; name?: string };
+	fulfillment?: {
+		selected_option_id?: string;
+		address?: {
+			line1: string;
+			line2?: string;
+			city: string;
+			state?: string;
+			postal_code: string;
+			country: string;
+		};
+	};
+}): {
+	customer?: {
+		shipping_address?: {
+			name?: string;
+			email?: string;
+			line1: string;
+			line2?: string;
+			city: string;
+			region?: string;
+			postal_code: string;
+			country: string;
+		};
+	};
+	fulfillment?: { selected_id?: string };
+} {
+	const result: any = {};
+
+	// Build shipping address from customer info + fulfillment address
+	if (input.fulfillment?.address) {
+		result.customer = {
+			shipping_address: {
+				line1: input.fulfillment.address.line1,
+				...(input.fulfillment.address.line2 && {
+					line2: input.fulfillment.address.line2,
+				}),
+				city: input.fulfillment.address.city,
+				...(input.fulfillment.address.state && {
+					region: input.fulfillment.address.state,
+				}),
+				postal_code: input.fulfillment.address.postal_code,
+				country: input.fulfillment.address.country,
+				...(input.customer?.name && { name: input.customer.name }),
+				...(input.customer?.email && { email: input.customer.email }),
+			},
+		};
+	}
+
+	// Transform fulfillment selected_option_id to selected_id
+	if (input.fulfillment?.selected_option_id) {
+		result.fulfillment = {
+			selected_id: input.fulfillment.selected_option_id,
+		};
+	}
+
+	return result;
+}
+
+/**
+ * Transform MCP payment data to ACP schema
+ * MCP: { method, token } → ACP: { method, delegated_token }
+ */
+function transformPayment(mcpPayment: {
+	method?: string;
+	token?: string;
+}): { method?: string; delegated_token?: string } | undefined {
+	if (!mcpPayment) return undefined;
+
+	return {
+		...(mcpPayment.method && { method: mcpPayment.method }),
+		...(mcpPayment.token && { delegated_token: mcpPayment.token }),
+	};
+}
+
 /**
  * Create handlers that call your acp-handler API endpoints
  *
@@ -121,9 +203,16 @@ export function createHandlers(config: HandlerConfig) {
 		 * Create a new checkout session
 		 */
 		createCheckout: async (input: any): Promise<unknown> => {
+			// Transform MCP schema to ACP schema
+			const transformed = transformToACP(input);
+			const acpBody: any = {
+				items: input.items,
+				...transformed,
+			};
+
 			return request("/api/checkout", {
 				method: "POST",
-				body: JSON.stringify(input),
+				body: JSON.stringify(acpBody),
 			});
 		},
 
@@ -131,10 +220,18 @@ export function createHandlers(config: HandlerConfig) {
 		 * Update an existing checkout session
 		 */
 		updateCheckout: async (input: any): Promise<unknown> => {
-			const { session_id, ...body } = input;
+			const { session_id, ...mcpBody } = input;
+
+			// Transform MCP schema to ACP schema
+			const transformed = transformToACP(mcpBody);
+			const acpBody: any = {
+				...(mcpBody.items && { items: mcpBody.items }),
+				...transformed,
+			};
+
 			return request(`/api/checkout/${session_id}`, {
 				method: "POST",
-				body: JSON.stringify(body),
+				body: JSON.stringify(acpBody),
 			});
 		},
 
@@ -173,9 +270,14 @@ export function createHandlers(config: HandlerConfig) {
 
 			// ACP context: payment token provided
 			// Process payment through ACP complete endpoint
+			// Transform MCP schema to ACP schema
+			const acpBody: any = {
+				...(payment && { payment: transformPayment(payment) }),
+			};
+
 			return request(`/api/checkout/${session_id}/complete`, {
 				method: "POST",
-				body: JSON.stringify({ customer, payment }),
+				body: JSON.stringify(acpBody),
 			});
 		},
 

Analysis

Schema mismatch between MCP handlers and ACP API prevents checkout completion

What fails: MCP handlers in packages/sdk/src/mcp/handlers.ts (createCheckout, updateCheckout, completeCheckout) send data in MCP tool schema format that gets silently stripped by ACP schema validation, preventing checkouts from becoming ready for payment.

How to reproduce:

  1. Use MCP createCheckout tool with customer and fulfillment data:
{
  customer: { email: "[email protected]", name: "John Doe" },
  fulfillment: { 
    selected_option_id: "express",
    address: { line1: "123 Main", city: "Seattle", postal_code: "98101", country: "US" }
  }
}
  1. MCP handler sends this directly to ACP API at /api/checkout
  2. ACP validates with CreateCheckoutSessionSchema which expects customer.shipping_address and fulfillment.selected_id
  3. Zod strips unknown fields (email, name, selected_option_id, address) and passes empty objects

Result: ACP handler receives customer: {} and fulfillment: {}. The products.price() function checks !!customer?.shipping_address and !!fulfillment?.selected_id (see examples/chat-sdk/lib/store/products.ts:105-108), both evaluate to false, so checkout status remains "not_ready_for_payment" forever. Same issue with completeCheckout: payment.token gets stripped instead of being mapped to payment.delegated_token.

Expected: MCP handlers should transform data to match ACP schema before sending requests, mapping customer.email/name + fulfillment.addresscustomer.shipping_address, fulfillment.selected_option_idfulfillment.selected_id, and payment.tokenpayment.delegated_token.

Fix: Added transformation functions transformToACP() and transformPayment() in handlers.ts that convert MCP schema to ACP schema before API requests.

Remove checkoutUrlPattern in favor of just getCheckoutUrl function.
Cleaner API with one clear way to customize checkout URLs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants