Skip to main content
Glama

Seed Demo Store

store_demo_seed

Generate a demo store with 20 products, 40 customers, and 150+ orders for testing ShopOps tools without real credentials. Returns a store ID for use with inventory, customer segmentation, and order analysis tools.

Instructions

Create a realistic demo store populated with 20 products, 40 customers across 6 archetype buckets (champions, loyal, new, at-risk, hibernating, one-off), and 150+ orders spanning the last 6 months. Use this to explore ShopOps without real Shopify or WooCommerce credentials — every tool (inventory_status, customers_segment, order_anomalies, report_weekly, etc.) will return meaningful output on the returned store_id. Safe to call multiple times; each call creates a new demo store with a unique ID. Returns the store_id plus product/customer/order counts.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • src/index.ts:108-125 (registration)
    Tool registration for 'store_demo_seed' using server.registerTool. Defines metadata (title, description, empty inputSchema, annotations) and an async handler that calls seedDemoStore() and returns the result as JSON.
    // ── Tool: store_demo_seed ─────────────────────────────────────────
    server.registerTool(
      'store_demo_seed',
      {
        title: 'Seed Demo Store',
        description: 'Create a realistic demo store populated with 20 products, 40 customers across 6 archetype buckets (champions, loyal, new, at-risk, hibernating, one-off), and 150+ orders spanning the last 6 months. Use this to explore ShopOps without real Shopify or WooCommerce credentials — every tool (inventory_status, customers_segment, order_anomalies, report_weekly, etc.) will return meaningful output on the returned store_id. Safe to call multiple times; each call creates a new demo store with a unique ID. Returns the store_id plus product/customer/order counts.',
        inputSchema: z.object({}),
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      },
      async () => {
        try {
          const result = await seedDemoStore();
          return {
            content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
          };
        } catch (e) { return handleToolError(e); }
      }
    );
  • Inline async handler for the store_demo_seed tool. Calls seedDemoStore() imported from './services/demo-seed.js', wraps the result in a 'text' content response, and catches errors via handleToolError.
    async () => {
      try {
        const result = await seedDemoStore();
        return {
          content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
        };
      } catch (e) { return handleToolError(e); }
    }
  • Input/output schema for store_demo_seed. Uses z.object({}) (empty object, no inputs). The output type is inferred from the DemoSeedResult interface returned by seedDemoStore.
    title: 'Seed Demo Store',
    description: 'Create a realistic demo store populated with 20 products, 40 customers across 6 archetype buckets (champions, loyal, new, at-risk, hibernating, one-off), and 150+ orders spanning the last 6 months. Use this to explore ShopOps without real Shopify or WooCommerce credentials — every tool (inventory_status, customers_segment, order_anomalies, report_weekly, etc.) will return meaningful output on the returned store_id. Safe to call multiple times; each call creates a new demo store with a unique ID. Returns the store_id plus product/customer/order counts.',
    inputSchema: z.object({}),
  • DemoSeedResult interface defining the return type: store_id, store_name, products, customers, orders, and message.
    export interface DemoSeedResult {
      store_id: string;
      store_name: string;
      products: number;
      customers: number;
      orders: number;
      message: string;
    }
  • seedDemoStore() — the core implementation that creates a demo store with 20 products (from PRODUCT_TEMPLATES), 40 customers across 6 RFM archetypes, and 150+ orders spanning the last 6 months. Generates deterministic-ish random data, persists everything via the Storage interface, and returns a DemoSeedResult.
    export async function seedDemoStore(store?: Storage): Promise<DemoSeedResult> {
      const s = store ?? defaultStorage;
      const now = new Date();
      const storeId = uuidv4();
    
      // 1. Create the store record
      const storeRecord: StoreConfig = {
        id: storeId,
        name: 'Acme Demo Store',
        platform: 'shopify',
        url: 'https://acme-demo.myshopify.com',
        api_key: 'demo_token_not_real',
        api_secret: undefined,
        connected_at: now.toISOString(),
        last_sync_at: now.toISOString(),
        product_count: PRODUCT_TEMPLATES.length,
        order_count: 0,      // filled in below
        customer_count: 0,   // filled in below
      };
    
      // 2. Create products
      const products: Product[] = PRODUCT_TEMPLATES.map((t, i) => ({
        id: `${storeId}_p_${i + 1}`,
        store_id: storeId,
        external_id: `demo-sku-${i + 1}`,
        title: t.title,
        sku: `ACME-${String(i + 1).padStart(4, '0')}`,
        price: t.price,
        compare_at_price: t.price > 80 ? Math.round(t.price * 1.25 * 100) / 100 : null,
        cost_price: t.cost,
        currency: 'USD',
        inventory_quantity: t.stock,
        category: t.category,
        status: 'active',
        created_at: new Date(now.getTime() - 180 * 86_400_000).toISOString(),
        updated_at: now.toISOString(),
      }));
    
      // 3. Create customers + orders by archetype
      const customers: Customer[] = [];
      const orders: Order[] = [];
      let customerIdx = 0;
      let orderCounter = 1000;
    
      for (const { archetype, count } of ARCHETYPE_MIX) {
        const params = archetypeParams(archetype);
    
        for (let k = 0; k < count; k++) {
          const seedBase = customerIdx * 101 + k * 17;
          const name = pick(CUSTOMER_NAMES, customerIdx);
          const email = name.toLowerCase().replace(/\W+/g, '.') + '@example.test';
          const country = pick(COUNTRIES, customerIdx);
          const city = pick(CITIES[country], customerIdx + 1);
    
          const orderCount = randInt(params.orderCount[0], params.orderCount[1], seedBase + 1);
          const avgSpend = rand(params.avgSpend[0], params.avgSpend[1], seedBase + 2);
          const firstDaysAgo = randInt(params.daysAgo[0], params.daysAgo[1] + 30, seedBase + 3);
          const lastDaysAgo = randInt(params.daysAgo[0], params.daysAgo[1], seedBase + 4);
    
          const customer: Customer = {
            id: `${storeId}_c_${customerIdx + 1}`,
            store_id: storeId,
            external_id: `demo-cust-${customerIdx + 1}`,
            email,
            name,
            total_orders: orderCount,
            total_spent: 0, // filled in below
            first_order_at: new Date(now.getTime() - firstDaysAgo * 86_400_000).toISOString(),
            last_order_at: new Date(now.getTime() - lastDaysAgo * 86_400_000).toISOString(),
            avg_order_value: 0, // filled in below
            country,
            city,
            tags: archetype === 'champion' ? ['vip'] : [],
            created_at: new Date(now.getTime() - (firstDaysAgo + 15) * 86_400_000).toISOString(),
          };
          customers.push(customer);
    
          // Generate this customer's orders
          let customerTotal = 0;
          for (let o = 0; o < orderCount; o++) {
            const product = pickWeighted(PRODUCT_TEMPLATES.map((p, idx) => ({ ...p, idx })), seedBase + o * 3 + 5);
            const quantity = randInt(1, 3, seedBase + o * 3 + 6);
            const unitPrice = product.price;
            const lineTotal = unitPrice * quantity;
            const tax = Math.round(lineTotal * 0.08 * 100) / 100;
            const shipping = lineTotal > 100 ? 0 : 8.95;
            const total = Math.round((lineTotal + tax + shipping) * 100) / 100;
            customerTotal += total;
    
            // Spread orders from first to last
            let daysAgo: number;
            if (orderCount === 1) {
              daysAgo = lastDaysAgo;
            } else {
              const ratio = o / (orderCount - 1);
              daysAgo = Math.round(firstDaysAgo - (firstDaysAgo - lastDaysAgo) * ratio);
            }
    
            const orderTs = new Date(now.getTime() - daysAgo * 86_400_000);
            orderCounter++;
    
            const item: OrderItem = {
              product_id: `${storeId}_p_${product.idx + 1}`,
              title: product.title,
              sku: `ACME-${String(product.idx + 1).padStart(4, '0')}`,
              quantity,
              unit_price: unitPrice,
              total: Math.round(lineTotal * 100) / 100,
            };
    
            orders.push({
              id: `${storeId}_o_${orderCounter}`,
              store_id: storeId,
              external_id: `demo-order-${orderCounter}`,
              order_number: String(orderCounter),
              customer_id: customer.id,
              customer_email: customer.email,
              status: daysAgo < 3 ? 'processing' : daysAgo < 14 ? 'shipped' : 'delivered',
              subtotal: Math.round(lineTotal * 100) / 100,
              tax_total: tax,
              shipping_total: shipping,
              discount_total: 0,
              total,
              currency: 'USD',
              items: [item],
              shipping_country: country,
              shipping_city: city,
              payment_method: 'card',
              ip_address: null,
              created_at: orderTs.toISOString(),
              updated_at: orderTs.toISOString(),
            });
          }
    
          customer.total_spent = Math.round(customerTotal * 100) / 100;
          customer.avg_order_value = orderCount > 0 ? Math.round((customerTotal / orderCount) * 100) / 100 : 0;
          customerIdx++;
        }
      }
    
      // 4. Persist everything
      storeRecord.order_count = orders.length;
      storeRecord.customer_count = customers.length;
      await s.addStore(storeRecord);
      await s.upsertProducts(products);
      await s.upsertOrders(orders);
      await s.upsertCustomers(customers);
    
      return {
        store_id: storeId,
        store_name: storeRecord.name,
        products: products.length,
        customers: customers.length,
        orders: orders.length,
        message: `Demo store "${storeRecord.name}" seeded with ${products.length} products, ${customers.length} customers, and ${orders.length} orders. Use this store_id with any ShopOps tool to explore the product without real Shopify or WooCommerce credentials.`,
      };
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate non-read-only, non-destructive, non-idempotent behavior. The description adds that each call creates a new demo store with a unique ID and is safe to call multiple times, providing full transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Concise single paragraph with front-loaded purpose, specific details, and zero wasted words. Every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Comprehensive for a zero-parameter tool with no output schema. Describes what is created, what is returned, and how it integrates with siblings.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

No parameters exist, so schema coverage is 100%. The description adds no parameter info, which is acceptable. Per guidelines, baseline of 4 for zero parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states the tool creates a demo store with specific counts of products, customers, and orders, distinguishing it from sibling tools by noting it works without real credentials.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells the agent to use this for exploration without real credentials and that other tools will work on the returned store_id. Also notes it is safe to call multiple times.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/enzoemir1/shopops-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server