Skip to content

Conversation

@vibegui
Copy link

@vibegui vibegui commented Nov 29, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated VTEX commerce platform for product search, suggestions, and detailed product information.
    • Added shopping cart functionality: retrieve cart, add items, update quantities.
    • Support for multiple locale and currency configurations.
  • Documentation

    • Added integration guide covering configuration, development setup, and complete API reference.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

Walkthrough

This PR adds a comprehensive VTEX e-commerce integration package to the monorepo. It includes a typed VTEX API client, data transformation utilities, tool factories for product search and cart management, Zod-based schemas for validation, configuration files, tests, and documentation.

Changes

Cohort / File(s) Summary
Root Workspace Configuration
package.json
Added "vtex" workspace entry to monorepo configuration; adjusted JSON syntax with trailing comma on "whisper" entry.
Package Manifest & Build Configuration
vtex/package.json, vtex/tsconfig.json, vtex/vite.config.ts, vtex/wrangler.toml
Introduced package manifest with dependencies (zod, @decocms/runtime), scripts, and exports; added TypeScript compiler options with path aliases (shared/, server/); configured Vite with Cloudflare Workers plugin and Deco integration; defined Wrangler configuration for Deco VTEX project.
Documentation
vtex/README.md
Added comprehensive README documenting VTEX MCP features (Product Tools, Cart Tools), configuration requirements, development guide, API reference with TypeScript interfaces, and license.
Core Library – API Client & Data Transformation
vtex/server/lib/vtex-client.ts, vtex/server/lib/transform.ts
Implemented typed VTEX API client wrapper (createClient) with methods for product search, product lookup, suggestions, and cart operations; added transformation utilities (pickSku, toProduct, toCart) to map VTEX data to standard commerce types with image normalization, offer mapping, and cart aggregation.
Tool Factories & Aggregation
vtex/server/tools/products.ts, vtex/server/tools/cart.ts, vtex/server/tools/index.ts
Created tool factories for product operations (search, get by slug, suggestions) and cart operations (get, add, update); exported productTools and cartTools arrays; aggregated all tools in index.ts for unified exports.
Validation Schemas
vtex/server/tools/schemas.ts
Defined Zod schemas for Product, Cart, and CartItem data structures; introduced input/output schemas for all product and cart tool operations with validation rules, defaults, and metadata.
Server Entry Point & State Management
vtex/server/main.ts, vtex/shared/deco.gen.ts
Implemented MCP server entry point with runtime configuration, tool integration, and fetch fallback; generated Deco state schema (StateSchema) with VTEX configuration fields (account, environment, salesChannel, locale, currency) and TypeScript types.
Tests
vtex/server/lib/client.test.ts
Added comprehensive test suite for VTEX client methods (searchProducts, getProductBySlug, getSuggestions) and transformation utilities (pickSku, toProduct) with mock data and validation assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • VTEX API client: Error handling, edge cases in product/suggestion/cart operations, and URL construction logic require careful review
  • Data transformation logic: Product and cart mapping across multiple fields (images, offers, variants, pricing) involves dense conversion code
  • Tool factories and schemas: Consistency of input/output validation across product and cart tools; integration with Deco/MCP patterns
  • State and configuration: Proper integration with Deco's state management and OAuth configuration
  • Test coverage: Validates client and transforms but may not cover all error paths and edge cases

Possibly related PRs

  • Feat/add sora #6: Adds "sora" workspace entry to monorepo package.json, extending the workspace pattern similarly to this PR's "vtex" addition.

Suggested reviewers

  • viktormarinho

Poem

🐰✨ A VTEX warren, now complete with care,
Products hop and carts dance through the air!
Schemas bundle, transforms align,
Deco tools now beautifully shine,
Commerce integration, furry-blessed and fair! 🛒

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding the initial implementation for a VTEX MCP (Model Context Protocol) application, which aligns with the extensive set of new files introduced in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gui/mcp-vtex

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vibegui vibegui requested a review from guitavano November 29, 2025 10:24
@github-actions
Copy link

🚀 Preview Deployments Ready!

⚠️ No preview URLs were generated. Check the workflow logs for details.


Deployed from commit: f0359bc

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (1)
vtex/server/tools/cart.ts (1)

24-30: Duplicate of getState from products.ts.

As noted in the products.ts review, this should be extracted to a shared module.

🧹 Nitpick comments (10)
vtex/tsconfig.json (2)

13-13: Consider enabling verbatimModuleSyntax for better ESM compliance.

Setting verbatimModuleSyntax: false can lead to issues with import/export handling. The recommended practice is to set this to true for modern ESM projects to ensure type-only imports are properly distinguished from value imports.

Apply this diff if there are no specific reasons to disable it:

-    "verbatimModuleSyntax": false,
+    "verbatimModuleSyntax": true,

16-16: Verify if allowJs is needed.

The allowJs option is enabled, but the codebase appears to use TypeScript exclusively. Unless there are JavaScript files that need to be included, this option can be removed for stricter type safety.

vtex/wrangler.toml (1)

15-15: Consider hosting the icon asset.

The icon URL points to Wikimedia Commons (https://upload.wikimedia.org/wikipedia/commons/a/a9/VTEX_Logo.svg), which could change or become unavailable. Consider hosting the icon in a more reliable location (e.g., in your own assets or CDN) for better long-term stability.

vtex/server/tools/products.ts (2)

24-32: Duplicate getState function - extract to shared utility.

This getState function is duplicated in cart.ts (lines 24-30). Extract it to a shared module (e.g., ../lib/utils.ts or ./helpers.ts) to follow DRY principles.

// In a new file: ../lib/state.ts
import type { Env } from "../main.ts";

export function getState(env: Env) {
  const state = env.DECO_REQUEST_CONTEXT?.state;
  if (!state?.account) {
    throw new Error("VTEX account not configured. Please configure the MCP with your VTEX account.");
  }
  return state;
}

43-70: Consider adding error handling for API failures.

The execute function doesn't wrap the client calls in try-catch. Network failures or VTEX API errors will propagate as unhandled exceptions. Depending on how createPrivateTool handles errors, you may want to add explicit error handling with user-friendly messages.

vtex/server/lib/vtex-client.ts (3)

1-116: Client configuration: unused salesChannel and formatting check

ClientConfig.salesChannel is never used inside createClient, which is mildly confusing given sales channel affects catalog/checkout behavior. Either remove it from the config/state mapping or thread it through to relevant calls (typically as an sc query param on cart mutations and possibly search) so the configured trade policy actually takes effect.(community.vtex.com)

Also, CI is failing with oxfmt --check on this file; please run oxfmt to reformat it before merging.


160-170: getProductBySlug should distinguish 404 from other HTTP errors

Right now any non‑OK status returns null, so upstream code cannot tell “product not found” apart from server errors, auth issues, or misconfiguration:

if (!response.ok) {
  return null;
}

Consider only returning null for 404 and throwing for other error codes to avoid silently hiding infrastructure/API problems:

-    const response = await fetch(url, { headers });
-    if (!response.ok) {
-      return null;
-    }
+    const response = await fetch(url, { headers });
+    if (response.status === 404) {
+      return null;
+    }
+    if (!response.ok) {
+      throw new Error(`VTEX getProductBySlug failed: ${response.status}`);
+    }

215-260: Cart mutation endpoints align with Checkout, consider optional query params and call serialization

addToCart and updateCartItems correctly POST to:

  • .../orderForm/{orderFormId}/items with an orderItems array containing id, seller, quantity.(developers.vtex.com)
  • .../orderForm/{orderFormId}/items/update with an orderItems array containing index, quantity.(developers.vtex.com)

This matches the documented body shapes and is a good minimal implementation.

Two optional improvements you may want later:

  • Thread salesChannel into these URLs as ?sc=... for multi‑trade‑policy setups, instead of assuming the default sales channel.(community.vtex.com)
  • Expose allowedOutdatedData as a configurable query param to let callers opt in to performance vs. freshness trade‑offs when updating/adding items.(developers.vtex.com)

Also note that VTEX recommends not performing data‑modifying Checkout calls in parallel; callers of this client should serialize cart mutations to avoid race conditions.(plataformas-digitais.apidog.io)

vtex/server/tools/schemas.ts (2)

1-72: Common product/cart schemas are sensible; minor tightening possible

productSchema, cartItemSchema, and cartSchema look coherent with the “standard format” you’re targeting and provide good structure for validation and tool metadata.

If you later need stricter typing around variants, you could replace hasVariant: z.array(z.any()) with a dedicated variant schema, but it’s fine to keep it loose for now. Also, remember to run oxfmt here as well to fix the formatting check failure.


77-110: Use productSchema instead of z.any in product tool outputs

searchProductsOutputSchema.products and getProductOutputSchema.product currently allow any shape, even though you already have a detailed productSchema. Tightening these will give you better runtime guarantees and clearer tool documentation:

 export const searchProductsOutputSchema = z.object({
-  products: z.array(z.any()).describe("Array of products in standard format"),
+  products: z.array(productSchema).describe("Array of products in standard format"),
   total: z.number().describe("Total number of products matching the query"),
   page: z.number().describe("Current page number"),
   pageSize: z.number().describe("Number of products per page"),
 });

 export const getProductOutputSchema = z.object({
-  product: z.any().nullable().describe("Product in standard format, or null if not found"),
+  product: productSchema.nullable().describe("Product in standard format, or null if not found"),
 });

This should be a drop‑in change as long as your toProduct transform already outputs the same structure.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce9b954 and 76cd9a7.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • package.json (1 hunks)
  • vtex/README.md (1 hunks)
  • vtex/package.json (1 hunks)
  • vtex/server/lib/client.test.ts (1 hunks)
  • vtex/server/lib/transform.ts (1 hunks)
  • vtex/server/lib/vtex-client.ts (1 hunks)
  • vtex/server/main.ts (1 hunks)
  • vtex/server/tools/cart.ts (1 hunks)
  • vtex/server/tools/index.ts (1 hunks)
  • vtex/server/tools/products.ts (1 hunks)
  • vtex/server/tools/schemas.ts (1 hunks)
  • vtex/shared/deco.gen.ts (1 hunks)
  • vtex/tsconfig.json (1 hunks)
  • vtex/vite.config.ts (1 hunks)
  • vtex/wrangler.toml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
vtex/server/tools/index.ts (2)
vtex/server/tools/products.ts (1)
  • productTools (123-127)
vtex/server/tools/cart.ts (1)
  • cartTools (99-103)
vtex/vite.config.ts (1)
shared/deco-vite-plugin.ts (1)
  • deco (89-146)
vtex/server/tools/cart.ts (4)
vtex/server/main.ts (1)
  • Env (29-34)
vtex/shared/deco.gen.ts (1)
  • Env (37-42)
vtex/server/tools/schemas.ts (4)
  • getCartInputSchema (130-132)
  • cartOutputSchema (126-128)
  • addToCartInputSchema (134-141)
  • updateCartInputSchema (143-149)
vtex/server/lib/vtex-client.ts (1)
  • getClientFromState (267-274)
vtex/shared/deco.gen.ts (1)
vtex/server/main.ts (1)
  • Env (29-34)
vtex/server/lib/vtex-client.ts (1)
vtex/shared/deco.gen.ts (1)
  • State (35-35)
🪛 GitHub Actions: Checks
vtex/server/lib/client.test.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/main.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/tools/index.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/vite.config.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/tools/products.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/lib/transform.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/tools/cart.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/shared/deco.gen.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/lib/vtex-client.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

vtex/server/tools/schemas.ts

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

🔇 Additional comments (26)
package.json (1)

38-39: LGTM! Workspace addition looks correct.

The addition of the "vtex" workspace to the monorepo is properly formatted and follows the existing pattern.

vtex/vite.config.ts (1)

8-29: Configuration looks good for Cloudflare Workers deployment.

The Vite configuration is properly set up with:

  • Cloudflare plugin for Workers support
  • Deco plugin integration
  • Proper environment variable definitions for Workers context
  • Appropriate caching strategy
vtex/shared/deco.gen.ts (1)

8-33: Schema structure is well-designed.

The StateSchema provides appropriate VTEX configuration with:

  • Required account field (correctly not having a default)
  • Sensible defaults for environment, salesChannel, locale, and currency
  • Clear descriptions for each field
  • Proper Zod validation
vtex/wrangler.toml (2)

4-4: Verify the compatibility date.

The compatibility date is set to 2025-06-17. Please confirm this is the intended date for Cloudflare Workers compatibility flags.


1-19: Configuration structure looks good.

The Wrangler configuration is properly set up with:

  • Correct main entry point
  • Node.js compatibility enabled
  • Deco workspace integration configured
  • Clear integration metadata
  • Observability enabled
vtex/server/lib/client.test.ts (3)

9-11: Verify the use of a real VTEX account in tests.

The tests use a real VTEX account (brmotorolanew) for integration testing. Please confirm:

  1. This is intentional and the account is suitable for testing
  2. The account will remain accessible for CI/CD environments
  3. Consider documenting the test account setup in the README or test documentation

13-77: VTEX client tests are well-structured.

The test suite comprehensively covers:

  • Product search with and without query
  • Product retrieval by slug
  • Handling of invalid slugs
  • Suggestions API

The tests validate both structure and behavior appropriately.


79-197: Transform utility tests are comprehensive.

The tests effectively validate:

  • SKU selection logic (preferring available SKUs)
  • Complete product transformation to schema.org format
  • All key fields including pricing, images, variants, and metadata

The test data is realistic and covers important edge cases.

vtex/server/main.ts (1)

36-63: Server configuration is well-structured.

The runtime configuration is properly set up with:

  • Combined tool sets (user tools + VTEX tools)
  • Comprehensive Env typing with proper extensions
  • StateSchema for installation-time configuration
  • Appropriate fetch fallback for asset serving

The OAuth scopes array is empty (line 42), which appears intentional since VTEX APIs are accessed via account credentials in the state rather than OAuth tokens.

vtex/README.md (1)

1-121: Excellent documentation.

The README is comprehensive and well-structured, covering:

  • Clear feature descriptions for all tools
  • Configuration requirements with helpful defaults table
  • Complete development workflow
  • API reference with TypeScript interfaces for key data structures

This provides users with everything needed to understand, configure, and use the VTEX MCP integration.

vtex/server/tools/index.ts (1)

1-24: Clean module aggregation pattern.

The file provides a well-organized central export point that maintains domain separation while enabling easy imports. The structure is clean and follows good practices.

Minor note: The docstring (line 11) mentions userTools from @decocms/mcps-shared, but this isn't actually used in the file. Consider removing or updating this reference to avoid confusion.

vtex/package.json (2)

22-25: Dependencies look appropriate for the VTEX integration.

The runtime dependency and zod for validation are well-chosen. The use of caret versioning for zod allows patch/minor updates while @decocms/runtime is pinned for stability.


34-34: Review comment is incorrect — Vite 7.2.0 is a valid, current stable release.

As of November 2025, Vite v7.2.4 is the latest stable release, making v7.2.0 a standard, in-range version. The concern that 7.2.0 is "ahead of typical stable releases (5.x-6.x range)" reflects outdated information; Vite 7.x is the current stable major version. Pinning an exact version (7.2.0 without caret) is a valid practice for reproducibility and does not indicate an error.

vtex/server/lib/transform.ts (3)

118-126: Handle edge case when no sellers exist.

If sku.sellers is empty, both find and [0] access will return undefined, causing optional chaining to work but potentially yielding unexpected defaults (price=0, seller="1"). Consider adding explicit validation or logging for this edge case.


176-194: Offer structure only includes a single seller.

The offers array is hardcoded to contain only one offer (the selected seller). If multiple sellers exist with different prices/availability, this information is lost. This may be intentional for simplicity, but worth confirming.


209-228: toCart transformation looks correct.

The cart transformation properly converts cents to major currency units, handles optional imageUrl with fallback, and extracts subtotal from totalizers. The empty coupons array is appropriate for initial implementation.

vtex/server/tools/products.ts (1)

122-127: Tool exports follow a clean factory pattern.

The productTools array export is well-organized and consistent with the cart tools pattern.

vtex/server/tools/cart.ts (4)

35-50: Get cart tool implementation is correct.

The tool properly handles optional orderFormId - creating a new cart when not provided or fetching existing one. The transformation via toCart ensures consistent output format.


55-73: Add-to-cart implementation looks good.

The tool correctly passes items array to the client and transforms the result. The schema (from snippets) properly validates the required fields.


78-96: Update cart tool correctly handles item removal.

The description clearly documents that setting quantity to 0 removes an item. The implementation delegates to the client's updateCartItems method appropriately.


98-103: Consistent tool export pattern.

The cartTools array follows the same factory pattern as productTools, ensuring consistency across the codebase.

vtex/server/lib/vtex-client.ts (3)

121-155: searchProducts implementation LGTM

Query parameter handling, pagination defaults, and facets path construction look reasonable and align with how the Intelligent Search product search endpoint is typically used. I don’t see functional issues here.


175-189: getSuggestions implementation looks correct

The endpoint, query construction, and error handling are consistent with VTEX Intelligent Search’s suggestions API (locale + query, throwing on non‑OK). No changes needed here.


267-273: getClientFromState wiring is straightforward

The mapping from State to ClientConfig is direct and consistent with the client’s expectations. Assuming StateSchema defines account, environment, salesChannel, and locale, this helper is good to go. Just remember to update it if you later remove or repurpose salesChannel from ClientConfig per the earlier comment.

vtex/server/tools/schemas.ts (2)

111-121: Suggestion schemas match VTEX suggestions; ensure transform maps searchessuggestions

The output schema:

suggestions: z.array(z.object({ term: z.string(), count: z.number() }))

matches the shape returned by VTEX Intelligent Search’s “Get list of suggested terms” endpoint (searches: [{ term, count }, ...]) once you rename the top‑level property.(developers.vtex.com)

Just double‑check that your tool layer converts the VTEX response { searches: [...] } into { suggestions: [...] } before validation so it aligns with this schema.


126-149: Cart tool schemas align with Checkout API and client expectations

  • getCartInputSchema.orderFormId being optional matches getOrderForm(orderFormId?: string) in the client.
  • addToCartInputSchema.items matches the Checkout “Add cart items” request body (orderItems with id, seller, quantity), and the quantity >= 1 constraint is consistent with VTEX docs.(developers.vtex.com)
  • updateCartInputSchema.items (index >= 0, quantity >= 0) lines up with the “Update cart items” endpoint, where quantity = 0 removes an item.(developers.vtex.com)

These schemas look well aligned with the VTEX client and Checkout behavior.

Comment on lines +1 to +198
/**
* VTEX Client Tests
*/

import { describe, test, expect, beforeAll } from "bun:test";
import { createClient, type VTEXProduct, type ProductSearchResult } from "./vtex-client.ts";
import { toProduct, pickSku, type Product } from "./transform.ts";

const TEST_ACCOUNT = "brmotorolanew";
const TEST_ENVIRONMENT = "vtexcommercestable";
const CURRENCY = "BRL";

describe("VTEX Client", () => {
const client = createClient({
account: TEST_ACCOUNT,
environment: TEST_ENVIRONMENT,
salesChannel: "1",
locale: "pt-BR",
});

const baseUrl = `https://${TEST_ACCOUNT}.${TEST_ENVIRONMENT}.com.br`;

test("searchProducts returns products", async () => {
const result = await client.searchProducts({
query: "",
count: 5,
});

expect(result).toBeDefined();
expect(result.products).toBeArray();
expect(result.products.length).toBeGreaterThan(0);
expect(result.recordsFiltered).toBeGreaterThan(0);

// Check product structure
const product = result.products[0];
expect(product.productId).toBeDefined();
expect(product.productName).toBeDefined();
expect(product.linkText).toBeDefined();
expect(product.items).toBeArray();
});

test("searchProducts with query returns results", async () => {
const result = await client.searchProducts({
query: "motorola",
count: 5,
});

// Query might return 0 results depending on API state, just check structure
expect(result.products).toBeArray();
expect(typeof result.recordsFiltered).toBe("number");
});

test("getProductBySlug returns a product", async () => {
// First get a product from search to get a valid slug
const searchResult = await client.searchProducts({ count: 1 });
const slug = searchResult.products[0].linkText;

const product = await client.getProductBySlug(slug);

expect(product).toBeDefined();
expect(product?.productId).toBeDefined();
expect(product?.productName).toBeDefined();
expect(product?.items).toBeArray();
});

test("getProductBySlug returns null for invalid slug", async () => {
const product = await client.getProductBySlug("this-product-does-not-exist-12345");
expect(product).toBeNull();
});

test("getSuggestions returns suggestions", async () => {
const result = await client.getSuggestions("smart");

expect(result).toBeDefined();
expect(result.searches).toBeArray();
});
});

describe("VTEX Transform", () => {
const baseUrl = `https://${TEST_ACCOUNT}.${TEST_ENVIRONMENT}.com.br`;

test("pickSku returns available SKU", () => {
const items = [
{
itemId: "123",
name: "SKU 1",
nameComplete: "SKU 1 Complete",
images: [],
sellers: [
{
sellerId: "1",
sellerName: "Main Seller",
commertialOffer: {
Price: 10000,
ListPrice: 12000,
AvailableQuantity: 0,
PriceWithoutDiscount: 12000,
Installments: [],
},
},
],
variations: [],
},
{
itemId: "456",
name: "SKU 2",
nameComplete: "SKU 2 Complete",
images: [],
sellers: [
{
sellerId: "1",
sellerName: "Main Seller",
commertialOffer: {
Price: 10000,
ListPrice: 12000,
AvailableQuantity: 5,
PriceWithoutDiscount: 12000,
Installments: [],
},
},
],
variations: [],
},
];

const sku = pickSku(items);
expect(sku.itemId).toBe("456"); // Should pick the available one
});

test("toProduct transforms VTEX product to schema.org format", () => {
const vtexProduct: VTEXProduct = {
productId: "123",
productName: "Test Product",
brand: "Test Brand",
brandId: 1,
description: "Test description",
linkText: "test-product",
link: "/test-product/p",
categories: ["/Category/"],
categoryId: "1",
items: [
{
itemId: "456",
name: "Test SKU",
nameComplete: "Test SKU Complete",
images: [
{
imageUrl: "https://example.com/image.jpg",
imageText: "Image",
imageLabel: "main",
},
],
sellers: [
{
sellerId: "1",
sellerName: "Main Seller",
commertialOffer: {
Price: 10000,
ListPrice: 12000,
AvailableQuantity: 5,
PriceWithoutDiscount: 12000,
Installments: [
{
Value: 10000,
NumberOfInstallments: 1,
PaymentSystemName: "Visa",
},
],
},
},
],
variations: [
{ name: "Color", values: ["Red"] },
],
},
],
origin: "intelligent-search",
};

const sku = pickSku(vtexProduct.items);
const product = toProduct(vtexProduct, sku, baseUrl, CURRENCY);

expect(product["@type"]).toBe("Product");
expect(product.productID).toBe("123");
expect(product.name).toBe("Test Product");
expect(product.description).toBe("Test description");
expect(product.url).toContain("/test-product/p");
expect(product.brand?.name).toBe("Test Brand");
expect(product.sku).toBe("456");
expect(product.offers.lowPrice).toBe(100); // 10000/100
expect(product.offers.highPrice).toBe(120); // 12000/100
expect(product.offers.priceCurrency).toBe(CURRENCY);
expect(product.offers.offers[0].availability).toBe("https://schema.org/InStock");
expect(product.image.length).toBeGreaterThan(0);
expect(product.additionalProperty?.length).toBeGreaterThan(0);
expect(product.isVariantOf?.hasVariant.length).toBeGreaterThan(0);
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting issues.

The pipeline indicates formatting issues. Please run oxfmt to fix the formatting.

Run this command to fix formatting:

#!/bin/bash
# Format the test file
oxfmt vtex/server/lib/client.test.ts
🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

🤖 Prompt for AI Agents
In vtex/server/lib/client.test.ts lines 1-198 the file has formatting issues
reported by the pipeline; run the project formatter (oxfmt) against this file to
apply the standard code style and commit the changes (e.g., run `oxfmt
vtex/server/lib/client.test.ts` in the repo root), then re-run tests/CI and
include the formatted file in your PR.

* Simplified from apps/vtex/utils/transform.ts
*/

import type { VTEXProduct, VTEXItem, OrderForm } from "./client.ts";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# List all files in the vtex/server/lib directory to verify the actual filename
ls -la vtex/server/lib/ | grep -i client

Repository: decocms/mcps

Length of output: 183


Correct the import path from "./client.ts" to "./vtex-client.ts".

The import on line 7 references "./client.ts", but the actual file in vtex/server/lib/ is named vtex-client.ts. This import path will cause a module resolution failure.

🤖 Prompt for AI Agents
In vtex/server/lib/transform.ts around line 7 the import currently references
"./client.ts" but the actual module file is named "vtex-client.ts"; update the
import path to "./vtex-client.ts" so the correct module is resolved and exported
types VTEXProduct, VTEXItem, OrderForm are imported from the existing file.

Comment on lines +102 to +107
export function pickSku(items: VTEXItem[]): VTEXItem {
const available = items.find(
(item) => item.sellers.some((s) => s.commertialOffer.AvailableQuantity > 0)
);
return available || items[0];
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add guard for empty items array.

pickSku will return undefined if the items array is empty, which could cause downstream errors in toProduct. Consider adding a guard or throwing a descriptive error.

 export function pickSku(items: VTEXItem[]): VTEXItem {
+  if (!items.length) {
+    throw new Error("Product has no SKU items");
+  }
   const available = items.find(
     (item) => item.sellers.some((s) => s.commertialOffer.AvailableQuantity > 0)
   );
   return available || items[0];
 }
🤖 Prompt for AI Agents
In vtex/server/lib/transform.ts around lines 102 to 107, pickSku currently
assumes items has at least one element and may return undefined for an empty
array; add an explicit guard at the top of the function to handle an empty items
array (either throw a descriptive Error like "pickSku: items array is empty" or
return a well-defined fallback) and ensure the function's return type and
callers (e.g., toProduct) are updated/checked to handle the thrown error or
fallback value accordingly.

Comment on lines +194 to +210
async getOrderForm(orderFormId?: string): Promise<OrderForm> {
const url = orderFormId
? `${baseUrl}/api/checkout/pub/orderForm/${orderFormId}`
: `${baseUrl}/api/checkout/pub/orderForm`;

const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify({}),
});

if (!response.ok) {
throw new Error(`VTEX cart failed: ${response.status}`);
}

return response.json();
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's find and examine the vtex-client.ts file
find . -name "vtex-client.ts" -type f

Repository: decocms/mcps

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Search for any usage of getOrderForm in the codebase
rg "getOrderForm" -n -C 3

Repository: decocms/mcps

Length of output: 998


🏁 Script executed:

#!/bin/bash
# Also check for any comments or documentation about the HTTP method used
rg "orderForm" -n -C 2 --type=ts --type=js

Repository: decocms/mcps

Length of output: 5328


🌐 Web query:

VTEX Checkout API GET orderForm endpoint documentation

💡 Result:

Key VTEX Checkout GET orderForm endpoints and docs:

  • Get current (or create) cart — GET /api/checkout/pub/orderForm

    • Create new cart: add ?forceNewCart=true
    • Returns full orderForm (includes orderFormId). See docs. [1]
  • Get cart by ID — GET /api/checkout/pub/orderForm/{orderFormId}

    • Optional query: refreshOutdatedData
    • Use when you already have an orderFormId. [2]
  • OrderForm fields / schema (items, clientProfileData, shippingData, totalizers, etc.) — overview of orderForm structure and field meanings. [3]

Notes:

  • Personal data may be masked unless the correct CheckoutOrderFormOwnership cookie / auth is present; some endpoints require authentication depending on customer context. [1][2]

Sources:
[1] VTEX — Get current or create a new cart (Checkout API).
[2] VTEX — Get cart information by ID (Checkout API).
[3] VTEX — orderForm fields / orderForm overview.


🌐 Web query:

VTEX API POST vs GET /checkout/pub/orderForm method specification

💡 Result:

Short answer — use GET to read/create the cart; there is no generic POST that replaces it.

Key points

  • GET /api/checkout/pub/orderForm
    • Returns the current orderForm (cart) for the session or, with ?forceNewCart=true, creates a new empty cart and returns its orderFormId. [1][2]
  • There is no documented single POST /api/checkout/pub/orderForm to replace or create/modify the full orderForm. Instead you modify the cart with the specific Checkout POST/PUT endpoints (add/change items, attachments like shippingData, simulation, place order, etc.). Data-changing endpoints are POST/PATCH/PUT/DELETE on resources such as /items, /attachments/shippingData, /orderForms/simulation, /orderForms/place (see Checkout API overview and guides). Also note Checkout mutating requests must not be performed in parallel (enqueue them). [2][3][4]

Sources

  • VTEX OrderForm overview (fields & semantics). [1]
  • VTEX Checkout API — Get current or create cart; overview of checkout endpoints and recommendations about mutating requests. [2][3]
  • Examples / Postman collection for cart item and simulation endpoints (shows POST/PUT usage for specific cart operations). [4]

getOrderForm incorrectly uses POST instead of GET—VTEX Checkout API requires GET

The VTEX Checkout API documentation confirms that cart retrieval and creation use GET:

  • GET /api/checkout/pub/orderForm — returns current cart or creates a new one (optionally ?forceNewCart=true)
  • GET /api/checkout/pub/orderForm/{orderFormId} — retrieves cart by ID (optionally ?refreshOutdatedData=true)

There is no POST endpoint on these paths. Sending POST with an empty body will fail with 404/405 or undefined behavior.

Switch to GET and optionally include refreshOutdatedData=true when retrieving an existing cart:

     /**
      * Get or create order form (cart)
      */
     async getOrderForm(orderFormId?: string): Promise<OrderForm> {
       const url = orderFormId
-        ? `${baseUrl}/api/checkout/pub/orderForm/${orderFormId}`
-        : `${baseUrl}/api/checkout/pub/orderForm`;
-      
-      const response = await fetch(url, {
-        method: "POST",
-        headers,
-        body: JSON.stringify({}),
-      });
-      
+        ? `${baseUrl}/api/checkout/pub/orderForm/${orderFormId}?refreshOutdatedData=true`
+        : `${baseUrl}/api/checkout/pub/orderForm`;
+
+      const response = await fetch(url, {
+        headers,
+      });
+
       if (!response.ok) {
         throw new Error(`VTEX cart failed: ${response.status}`);
       }
-      
+
       return response.json();
     },
🤖 Prompt for AI Agents
In vtex/server/lib/vtex-client.ts around lines 194 to 210, the getOrderForm
implementation incorrectly uses POST; change it to use GET, remove the empty
request body, and include the proper query parameters: when orderFormId is
provided append ?refreshOutdatedData=true (or allow passing that flag) and when
no orderFormId consider supporting ?forceNewCart=true; keep the existing
headers, issue a GET fetch to the constructed URL, check response.ok and throw
as before, and return response.json().

Comment on lines +1 to +65
/**
* VTEX MCP Server
*
* This is the main entry point for the VTEX MCP server.
* It provides tools for interacting with VTEX commerce APIs including:
* - Product search and retrieval
* - Shopping cart management
* - Search suggestions
*/
import { DefaultEnv, withRuntime } from "@decocms/runtime";
import {
type Env as DecoEnv,
StateSchema,
} from "../shared/deco.gen.ts";

import { vtexTools } from "./tools/index.ts";
import { userTools } from "@decocms/mcps-shared/tools/user";

// Combine VTEX tools with shared user tools
const tools = [...userTools, ...vtexTools];

/**
* This Env type is the main context object that is passed to
* all of your Application.
*
* It includes all of the generated types from your
* Deco bindings, along with the default ones.
*/
export type Env = DefaultEnv &
DecoEnv & {
ASSETS: {
fetch: (request: Request, init?: RequestInit) => Promise<Response>;
};
};

const runtime = withRuntime<Env, typeof StateSchema>({
oauth: {
/**
* These scopes define the asking permissions of your
* app when a user is installing it.
*/
scopes: [],
/**
* The state schema of your Application defines what
* your installed App state will look like. When a user
* is installing your App, they will have to fill in
* a form with the fields defined in the state schema.
*
* For VTEX, users need to provide:
* - account: VTEX account name
* - environment: VTEX environment (default: vtexcommercestable)
* - salesChannel: Sales channel ID (default: 1)
* - locale: Default locale (default: pt-BR)
* - currency: Currency code (default: BRL)
*/
state: StateSchema,
},
tools,
/**
* Fallback directly to assets for all requests that do not match a tool or auth.
*/
fetch: (req, env) => env.ASSETS.fetch(req),
});

export default runtime;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting issues.

The pipeline indicates formatting issues. Please run oxfmt to fix the formatting.

Run this command to fix formatting:

#!/bin/bash
# Format the main server file
oxfmt vtex/server/main.ts
🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

🤖 Prompt for AI Agents
vtex/server/main.ts lines 1-65: the file has formatting issues flagged by CI;
run the project formatter (oxfmt) on this file or apply the formatter's changes
to fix whitespace/indentation/comment spacing and ensure imports/exports are
properly aligned; run `oxfmt vtex/server/main.ts` (or your repo's format script)
and commit the resulting formatted file.

Comment on lines +56 to +57
const baseUrl = `https://${state.account}.vtexcommercestable.com.br`;
const currency = state.currency || "BRL";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Step 1: Check the environment usage in vtex-client.ts
rg -n 'environment' vtex/server/lib/vtex-client.ts -A2 -B2

Repository: decocms/mcps

Length of output: 665


🏁 Script executed:

#!/bin/bash
# Step 2: Read products.ts around lines 56-57 for context
cat -n vtex/server/tools/products.ts | head -70 | tail -40

Repository: decocms/mcps

Length of output: 1609


🏁 Script executed:

#!/bin/bash
# Step 3: Check for other hardcoded vtexcommercestable patterns in products.ts
rg -n 'vtexcommercestable' vtex/server/tools/products.ts

Repository: decocms/mcps

Length of output: 213


🏁 Script executed:

#!/bin/bash
# Step 4: Verify state.environment is properly available in the State type
rg -n 'environment' vtex/server/tools/products.ts -A1 -B1

Repository: decocms/mcps

Length of output: 127


🏁 Script executed:

#!/bin/bash
# Step 5: Find and read the State interface definition
rg -n 'interface State|type State' vtex/server/tools/products.ts -A10

Repository: decocms/mcps

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Step 6: Read context around line 92
cat -n vtex/server/tools/products.ts | sed -n '80,105p'

Repository: decocms/mcps

Length of output: 985


🏁 Script executed:

#!/bin/bash
# Step 7: Check imports and State definition in products.ts
head -20 vtex/server/tools/products.ts

Repository: decocms/mcps

Length of output: 667


🏁 Script executed:

#!/bin/bash
# Step 8: Verify State type has environment field by checking vtex-client.ts
rg -n 'export.*State|interface.*State|type.*State' vtex/server/lib/vtex-client.ts -A5

Repository: decocms/mcps

Length of output: 569


Perfect! I now have all the information needed to verify the review comment. Let me confirm the findings:

Verification Results:

  1. State.environment EXISTS and is AVAILABLE - Imported from "../../shared/deco.gen.ts" and used by getClientFromState()
  2. Pattern is DEMONSTRATED in vtex-client.ts - Line 110 shows: https://${config.account}.${config.environment}.com.br
  3. The issue is CONFIRMED - Line 56 hardcodes .vtexcommercestable.com.br instead of using state.environment
  4. Issue occurs in MULTIPLE PLACES - Found at both line 56 AND line 92 in products.ts
  5. Inconsistency is REAL - Both functions use getClientFromState(state) which internally uses state.environment, but then construct baseUrl with hardcoded domain

The review comment is accurate and identifies a real bug. The hardcoded domain pattern would produce incorrect URLs if the environment differs from "vtexcommercestable".


Hardcoded domain pattern should use state.environment instead of "vtexcommercestable".

Both createSearchProductsTool (line 56) and createGetSuggestionsTool (line 92) hardcode .vtexcommercestable.com.br, while the client obtained from getClientFromState(state) dynamically uses state.environment. This inconsistency will produce incorrect product URLs when the environment differs. Follow the pattern in vtex-client.ts line 110: use https://${state.account}.${state.environment}.com.br.

🤖 Prompt for AI Agents
vtex/server/tools/products.ts lines 56 and 92: both functions build baseUrl with
a hardcoded ".vtexcommercestable.com.br" which contradicts
getClientFromState(state) that relies on state.environment; change the baseUrl
construction to use the state's environment (e.g.,
`https://${state.account}.${state.environment}.com.br`) in both
createSearchProductsTool and createGetSuggestionsTool so URLs follow the same
pattern as vtex-client.ts line 110.

Comment on lines +1 to +42
/**
* Auto-generated file. Do not edit manually.
* Run `deco gen` to regenerate.
*/

import { z } from "zod";

export const StateSchema = z.object({
/**
* @description VTEX Account Name (e.g., "mystore")
*/
account: z.string().describe("VTEX Account Name"),

/**
* @description VTEX Environment (e.g., "vtexcommercestable")
*/
environment: z.string().default("vtexcommercestable").describe("VTEX Environment"),

/**
* @description Sales Channel ID
*/
salesChannel: z.string().default("1").describe("Sales Channel ID"),

/**
* @description Default locale (e.g., "pt-BR")
*/
locale: z.string().default("pt-BR").describe("Default Locale"),

/**
* @description Default currency code (e.g., "BRL")
*/
currency: z.string().default("BRL").describe("Currency Code"),
});

export type State = z.infer<typeof StateSchema>;

export interface Env {
SELF: unknown;
DECO_REQUEST_CONTEXT?: {
state?: State;
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting issues in auto-generated file.

The pipeline indicates formatting issues. Since this is an auto-generated file (lines 1-4), consider updating the generation tool to produce properly formatted output, or add a post-generation formatting step.

Run this command to fix formatting:

#!/bin/bash
# Format the auto-generated file
oxfmt vtex/shared/deco.gen.ts
🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

🤖 Prompt for AI Agents
In vtex/shared/deco.gen.ts around lines 1 to 42 the auto-generated file has
formatting issues; update the generator to emit properly formatted output or add
a post-generation formatting step that runs the project formatter (e.g., run
oxfmt on vtex/shared/deco.gen.ts as part of the generation script or CI) and
commit the reformatted file so the pipeline passes.

Comment on lines +1 to +29
import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import deco from "@decocms/mcps-shared/vite-plugin";

const VITE_SERVER_ENVIRONMENT_NAME = "server";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
cloudflare({
configPath: "wrangler.toml",
viteEnvironment: {
name: VITE_SERVER_ENVIRONMENT_NAME,
},
}),
deco(),
],

define: {
// Ensure proper module definitions for Cloudflare Workers context
"process.env.NODE_ENV": JSON.stringify(
process.env.NODE_ENV || "development",
),
global: "globalThis",
},

// Clear cache more aggressively
cacheDir: "node_modules/.vite",
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting issues.

The pipeline indicates formatting issues detected by oxfmt. Please run oxfmt to fix the formatting.

Run this command to fix formatting:

#!/bin/bash
# Format the file with oxfmt
oxfmt vtex/vite.config.ts
🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues found by oxfmt --check. Run 'oxfmt' to fix.

🤖 Prompt for AI Agents
In vtex/vite.config.ts around lines 1 to 29, the file fails the project's oxfmt
formatting checks; run the formatter and save the file to correct whitespace and
style. Execute the project's formatter (oxfmt) on this file (e.g., run `oxfmt
vtex/vite.config.ts`) and commit the resulting changes so the file matches the
repository's formatting conventions.

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