Files
handbook/docker/packages/shopify/shopify/test_client.py
T
2026-06-24 14:16:58 -07:00

298 lines
12 KiB
Python

#!/usr/bin/env python3
"""
Simple test client for the Shopify MCP server.
Usage:
uv run python packages/shopify/shopify/test_client.py
"""
import asyncio
import json
from pathlib import Path
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# Sample test data to load before testing
SAMPLE_DATA = {
"products": {
"gid://shopify/Product/1001": {
"id": "gid://shopify/Product/1001",
"title": "Hydrating Face Moisturizer",
"description": "A lightweight, hydrating moisturizer for all skin types.",
"descriptionHtml": "<p>A lightweight, hydrating moisturizer for all skin types.</p>",
"handle": "hydrating-face-moisturizer",
"productType": "Skincare",
"vendor": "RhodeSkin",
"tags": ["moisturizer", "hydrating", "face", "skincare"],
"availableForSale": True,
"priceRange": {
"minVariantPrice": {"amount": "29.00", "currencyCode": "USD"},
"maxVariantPrice": {"amount": "29.00", "currencyCode": "USD"},
},
"featuredImage": {
"id": "gid://shopify/Image/1",
"url": "https://example.com/moisturizer.jpg",
"altText": "Hydrating Face Moisturizer",
},
"images": [
{
"id": "gid://shopify/Image/1",
"url": "https://example.com/moisturizer.jpg",
"altText": "Hydrating Face Moisturizer",
}
],
"options": [{"id": "opt1", "name": "Size", "values": ["30ml", "50ml"]}],
"variants": [
{
"id": "gid://shopify/ProductVariant/1001-30",
"title": "30ml",
"price": {"amount": "29.00", "currencyCode": "USD"},
"availableForSale": True,
"sku": "MOIST-30",
"selectedOptions": [{"name": "Size", "value": "30ml"}],
},
{
"id": "gid://shopify/ProductVariant/1001-50",
"title": "50ml",
"price": {"amount": "45.00", "currencyCode": "USD"},
"availableForSale": True,
"sku": "MOIST-50",
"selectedOptions": [{"name": "Size", "value": "50ml"}],
},
],
},
"gid://shopify/Product/1002": {
"id": "gid://shopify/Product/1002",
"title": "Lip Treatment Oil",
"description": "Nourishing lip oil for soft, glossy lips.",
"descriptionHtml": "<p>Nourishing lip oil for soft, glossy lips.</p>",
"handle": "lip-treatment-oil",
"productType": "Lip Care",
"vendor": "RhodeSkin",
"tags": ["lip", "oil", "treatment", "gloss"],
"availableForSale": True,
"priceRange": {
"minVariantPrice": {"amount": "18.00", "currencyCode": "USD"},
"maxVariantPrice": {"amount": "18.00", "currencyCode": "USD"},
},
"featuredImage": {
"id": "gid://shopify/Image/2",
"url": "https://example.com/lipoil.jpg",
"altText": "Lip Treatment Oil",
},
"images": [
{"id": "gid://shopify/Image/2", "url": "https://example.com/lipoil.jpg", "altText": "Lip Treatment Oil"}
],
"options": [{"id": "opt2", "name": "Shade", "values": ["Clear", "Rose", "Berry"]}],
"variants": [
{
"id": "gid://shopify/ProductVariant/1002-clear",
"title": "Clear",
"price": {"amount": "18.00", "currencyCode": "USD"},
"availableForSale": True,
"sku": "LIP-CLEAR",
"selectedOptions": [{"name": "Shade", "value": "Clear"}],
},
{
"id": "gid://shopify/ProductVariant/1002-rose",
"title": "Rose",
"price": {"amount": "18.00", "currencyCode": "USD"},
"availableForSale": True,
"sku": "LIP-ROSE",
"selectedOptions": [{"name": "Shade", "value": "Rose"}],
},
{
"id": "gid://shopify/ProductVariant/1002-berry",
"title": "Berry",
"price": {"amount": "18.00", "currencyCode": "USD"},
"availableForSale": False,
"sku": "LIP-BERRY",
"selectedOptions": [{"name": "Shade", "value": "Berry"}],
},
],
},
},
"carts": {},
"policies": [
{
"id": "gid://shopify/ShopPolicy/1",
"title": "Return Policy",
"body": "We offer a 30-day return policy for all unused products in original packaging. Returns are free for orders within the US.",
"url": "https://shop.example.com/policies/return",
},
{
"id": "gid://shopify/ShopPolicy/2",
"title": "Shipping Policy",
"body": "Free shipping on orders over $50. Standard shipping takes 5-7 business days. Express shipping available for $12.99.",
"url": "https://shop.example.com/policies/shipping",
},
],
"counters": {"cart_id": 1000, "line_id": 1000},
}
def setup_test_data():
"""Write sample data to the state file."""
state_file = Path(__file__).resolve().parents[1] / "shopify_data.json"
with open(state_file, "w") as f:
json.dump(SAMPLE_DATA, f, indent=2)
print(f"Wrote test data to {state_file}")
async def main():
# Setup test data first
setup_test_data()
# Server command - use uv run to execute the server module
server_params = StdioServerParameters(
command="uv",
args=["run", "python", "-m", "shopify.server"],
env=None,
)
print("\nStarting Shopify MCP server via stdio...")
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the session
await session.initialize()
print("Session initialized!\n")
# List available tools
print("=" * 60)
print("Available Tools:")
print("=" * 60)
tools = await session.list_tools()
for tool in tools.tools:
print(f" - {tool.name}")
print()
# Test 1: search_shop_catalog
print("=" * 60)
print("Test 1: search_shop_catalog - Search for 'moisturizer'")
print("=" * 60)
result = await session.call_tool(
"search_shop_catalog",
arguments={
"query": "moisturizer",
"context": "Looking for face products",
"limit": 5,
},
)
data = json.loads(result.content[0].text) # type: ignore
print(f"Found {data['totalCount']} products")
for product in data.get("nodes", []):
print(f" - {product['title']} (${product['priceRange']['minVariantPrice']['amount']})")
print(f"Available filters: {len(data.get('productFilters', []))}")
print()
# Test 2: get_product_details
print("=" * 60)
print("Test 2: get_product_details - Get moisturizer details")
print("=" * 60)
result = await session.call_tool(
"get_product_details",
arguments={
"product_id": "gid://shopify/Product/1001",
"options": {"Size": "50ml"},
},
)
data = json.loads(result.content[0].text) # type: ignore
if data.get("product"):
print(f"Product: {data['product']['title']}")
print(f"Variants: {len(data['product'].get('variants', []))}")
if data.get("selectedVariant"):
print(
f"Selected variant: {data['selectedVariant']['title']} - ${data['selectedVariant']['price']['amount']}"
)
print()
# Test 3: update_cart - Create new cart and add items
print("=" * 60)
print("Test 3: update_cart - Create cart and add moisturizer")
print("=" * 60)
result = await session.call_tool(
"update_cart",
arguments={
"add_items": [
{"merchandiseId": "gid://shopify/ProductVariant/1001-50", "quantity": 1},
{"merchandiseId": "gid://shopify/ProductVariant/1002-rose", "quantity": 2},
],
"note": "Test order",
},
)
data = json.loads(result.content[0].text) # type: ignore
cart_id = data.get("id")
print(f"Cart created: {cart_id}")
print(f"Lines: {len(data.get('lines', []))}")
print(f"Total: ${data.get('cost', {}).get('totalAmount', {}).get('amount', '0')}")
for line in data.get("lines", []):
print(
f" - {line['merchandise']['title']} x{line['quantity']} = ${line['cost']['totalAmount']['amount']}"
)
print()
# Test 4: get_cart
print("=" * 60)
print("Test 4: get_cart - Retrieve the cart we just created")
print("=" * 60)
result = await session.call_tool(
"get_cart",
arguments={
"cart_id": cart_id,
},
)
data = json.loads(result.content[0].text) # type: ignore
if data.get("id"):
print(f"Cart ID: {data['id']}")
print(f"Checkout URL: {data['checkoutUrl']}")
print(f"Total Quantity: {data['totalQuantity']}")
print(f"Total: ${data['cost']['totalAmount']['amount']}")
else:
print(f"Error: {data}")
print()
# Test 5: update_cart - Update quantity
print("=" * 60)
print("Test 5: update_cart - Update quantity of first item")
print("=" * 60)
first_line_id = data.get("lines", [{}])[0].get("id") if data.get("lines") else None
if first_line_id:
result = await session.call_tool(
"update_cart",
arguments={
"cart_id": cart_id,
"update_items": [{"id": first_line_id, "quantity": 3}],
},
)
data = json.loads(result.content[0].text) # type: ignore
print(f"Updated cart total: ${data.get('cost', {}).get('totalAmount', {}).get('amount', '0')}")
for line in data.get("lines", []):
print(f" - {line['merchandise']['title']} x{line['quantity']}")
print()
# Test 6: search_shop_policies_and_faqs
print("=" * 60)
print("Test 6: search_shop_policies_and_faqs - Return policy")
print("=" * 60)
result = await session.call_tool(
"search_shop_policies_and_faqs",
arguments={
"query": "return policy",
},
)
data = json.loads(result.content[0].text) # type: ignore
print(f"Found {len(data.get('results', []))} policies")
if data.get("answer"):
print(f"Answer: {data['answer'][:100]}...")
print()
print("=" * 60)
print("All tests completed!")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())