Skip to main content

What is MCP?

The Model Context Protocol (MCP) is Shopify’s official validation system that checks extensions and functions against their API schemas and best practices.

Why Validation Matters

Without proper validation:
  • ❌ Extensions fail at deploy time
  • ❌ Runtime errors in production
  • ❌ Poor customer experience
  • ❌ Wasted development time
With MCP validation:
  • ✅ Catch errors before deployment
  • ✅ Guarantee API compatibility
  • ✅ Follow Shopify best practices
  • ✅ Faster development cycles

Validation Process

1. Component Validation

Checks that all UI components exist and are used correctly:
// ✅ VALID - Correct import and usage
import { Banner, useCart } from '@shopify/ui-extensions-react/checkout';

export default function MyExtension() {
  const cart = useCart();
  return <Banner title="Hello">Message</Banner>;
}

// ❌ INVALID - Wrong import path
import { Banner } from '@shopify/ui-extensions-react';

// ❌ INVALID - Component doesn't exist
import { CustomBanner } from '@shopify/ui-extensions-react/checkout';

// ❌ INVALID - Wrong props
<Banner invalidProp="value">Content</Banner>
What MCP Checks:
  • Component exists in the specified package
  • Import path is correct (/checkout, /admin, etc.)
  • Props match the component’s TypeScript definition
  • Required props are provided
  • Prop types are correct

2. Hook Validation

Ensures React hooks follow all rules:
// ✅ VALID - Hook at top level
function Component() {
  const cart = useCart();
  const settings = useSettings();
  
  if (!cart) return null;
  return <Banner />;
}

// ❌ INVALID - Conditional hook
function Component() {
  const cart = useCart();
  
  if (cart.lines.length > 0) {
    const settings = useSettings(); // Error!
  }
}

// ❌ INVALID - Hook in callback
function Component() {
  const handleClick = () => {
    const cart = useCart(); // Error!
  };
}

// ❌ INVALID - Hook in loop
function Component() {
  return items.map(item => {
    const data = useItemData(item.id); // Error!
  });
}
What MCP Checks:
  • Hooks are called at top level only
  • Hooks are called in the same order every render
  • Hooks are not called conditionally
  • Hook names start with “use”
  • Custom hooks follow hook rules

3. GraphQL Validation

Validates queries against Shopify’s official schema:
# ✅ VALID - Correct schema
query {
  cart {
    id
    cost {
      totalAmount {
        amount
        currencyCode
      }
    }
    lines {
      id
      quantity
      merchandise {
        ... on ProductVariant {
          id
          title
          price {
            amount
          }
        }
      }
    }
  }
}

# ❌ INVALID - Field doesn't exist
query {
  cart {
    totalPrice  # Wrong field name
  }
}

# ❌ INVALID - Missing required selections
query {
  cart {
    cost {
      totalAmount  # Missing amount and currencyCode
    }
  }
}

# ❌ INVALID - Wrong type
query {
  cart {
    lines {
      merchandise {
        title  # Missing fragment for interface
      }
    }
  }
}
What MCP Checks:
  • All fields exist in the schema
  • Required field selections are present
  • Fragments are used correctly for interfaces/unions
  • Variables have correct types
  • Directives are valid

4. Extension Target Validation

Verifies extension targets are valid:
# ✅ VALID - Checkout targets
[[extensions.targeting]]
target = "purchase.checkout.block.render"
target = "purchase.checkout.actions.render-before"
target = "purchase.checkout.cart-line-item.render-after"
target = "purchase.checkout.shipping-option-list.render-after"

# ✅ VALID - Admin targets  
target = "admin.product-details.block.render"
target = "admin.product-details.action.render"
target = "admin.order-details.block.render"

# ✅ VALID - POS targets
target = "pos.purchase.post.block.render"

# ❌ INVALID - Target doesn't exist
target = "checkout.custom.block"
target = "admin.custom-page.render"
What MCP Checks:
  • Target exists in Shopify’s extension API
  • Target is appropriate for extension type
  • Module path matches target expectations
  • API version supports the target

5. Function Input Validation

For Shopify Functions, validates input queries:
# ✅ VALID - Discount function input
query {
  cart {
    lines {
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          id
          product {
            id
          }
        }
      }
    }
  }
  discountNode {
    metafield(namespace: "$app:function-config", key: "config") {
      value
    }
  }
}

# ❌ INVALID - Missing discountNode for discount function
query {
  cart {
    lines {
      quantity
    }
  }
}

# ❌ INVALID - Wrong metafield namespace
query {
  discountNode {
    metafield(namespace: "custom", key: "config") {
      value
    }
  }
}

Validation Scores

Synapse provides a score from 0-100:

Score Breakdown

ScoreRatingMeaningAction
95-100ExcellentProduction-ready, no issuesDeploy immediately
90-94Very GoodMinor style issuesDeploy, consider fixes
80-89GoodSome warningsReview warnings
70-79FairMultiple issuesFix before deploy
60-69PoorSignificant problemsRequires self-correction
0-59FailedCritical errorsMust fix

What Affects Your Score

Deductions:
  • Critical error (missing import, invalid GraphQL): -15 points
  • Major warning (unused variable, incorrect prop type): -5 points
  • Minor warning (missing comment, style issue): -2 points
  • Best practice violation: -3 points
Bonuses:
  • Comprehensive error handling: +5 points
  • TypeScript types used correctly: +3 points
  • Accessibility attributes present: +2 points
  • Performance optimizations: +2 points

Common Validation Errors

Error: Module '@shopify/ui-extensions-react' has no exported member 'Banner'Cause: Missing /checkout or /admin in import pathFix:
// Wrong
import { Banner } from '@shopify/ui-extensions-react';

// Right
import { Banner } from '@shopify/ui-extensions-react/checkout';
Error: Cannot query field 'totalPrice' on type 'Cart'Cause: Field name doesn’t exist in schemaFix:
# Wrong
query {
  cart {
    totalPrice
  }
}

# Right
query {
  cart {
    cost {
      totalAmount {
        amount
      }
    }
  }
}
Error: React Hook "useCart" is called conditionallyCause: Hook inside if statement or callbackFix:
// Wrong
function Component() {
  if (condition) {
    const cart = useCart();
  }
}

// Right
function Component() {
  const cart = useCart();
  
  if (!condition) return null;
  // use cart
}
Error: Extension target 'checkout.custom.block' is not validCause: Target doesn’t exist in Shopify’s APIFix:
# Wrong
target = "checkout.custom.block"

# Right
target = "purchase.checkout.block.render"

Validation Report Example

When you generate code, you’ll see a report like this:
{
  "score": 92,
  "status": "passed",
  "issues": [
    {
      "severity": "warning",
      "category": "best-practice",
      "message": "Consider adding error boundary",
      "line": 15,
      "suggestion": "Wrap component in ErrorBoundary for better error handling"
    }
  ],
  "checks": {
    "syntax": "passed",
    "components": "passed", 
    "hooks": "passed",
    "graphql": "passed",
    "target": "passed",
    "typescript": "passed"
  },
  "details": {
    "components_validated": 3,
    "hooks_validated": 2,
    "graphql_queries": 1,
    "execution_time_ms": 245
  }
}

How to Improve Your Score

1. Use TypeScript Types

// Good - explicit types
interface Props {
  title: string;
  amount: number;
}

export default function Component({ title, amount }: Props) {
  return <Banner title={title}>{amount}</Banner>;
}

// Better - use Shopify's types
import type { Cart } from '@shopify/ui-extensions-react/checkout';

function processCart(cart: Cart) {
  // Type-safe operations
}

2. Add Error Handling

// Good
function Component() {
  const cart = useCart();
  
  if (!cart) {
    return <Text>Loading...</Text>;
  }
  
  if (cart.lines.length === 0) {
    return <Text>Cart is empty</Text>;
  }
  
  return <Banner>Items: {cart.lines.length}</Banner>;
}

3. Follow React Best Practices

// Good - memoization for performance
const expensiveValue = useMemo(() => {
  return cart.lines.reduce((sum, line) => sum + line.quantity, 0);
}, [cart.lines]);

// Good - cleanup in useEffect
useEffect(() => {
  const timer = setInterval(() => {
    // do something
  }, 1000);
  
  return () => clearInterval(timer);
}, []);

4. Add Accessibility

<Button
  onPress={handleClick}
  accessibilityLabel="Add to cart"
  accessibilityRole="button"
>
  Add to Cart
</Button>

Advanced Validation

Custom Validation Rules

You can add project-specific rules:
// In your synapse config
{
  "validation": {
    "rules": {
      "require-error-boundaries": "warn",
      "max-component-lines": ["error", 200],
      "no-inline-styles": "warn"
    }
  }
}

Pre-deployment Checks

Before deploying, Synapse runs additional checks:
  • Bundle size analysis
  • Performance profiling
  • Security scanning
  • Dependency audit

Next Steps