Преглед изворни кода

feat: add Qdrant integration and data cleaning to MCP tools (WIP) #88

- Add cleanResponseData() helper to remove URLs/hrefs and empty values
- Add mcp-qdrant-helpers.ts for Qdrant integration in MCP tools
- Add LlmProduct type to mcp-types.ts
- Update mcp-shoprenter: add products tool, Qdrant support for products and orders
- Add data cleaning to all responses
- Still need to finish updating list_orders, get_customer handlers and other MCP tools
Claude пре 5 месеци
родитељ
комит
14d8383472

+ 50 - 0
supabase/functions/_shared/mcp-helpers.ts

@@ -75,3 +75,53 @@ export function validateParams(
 
 
   return { valid: true };
   return { valid: true };
 }
 }
+
+/**
+ * Clean response data by removing URL/href fields and empty values
+ * Recursively processes objects and arrays
+ */
+export function cleanResponseData(data: any): any {
+  if (data === null || data === undefined) {
+    return undefined;
+  }
+
+  // Handle arrays
+  if (Array.isArray(data)) {
+    return data.map(item => cleanResponseData(item)).filter(item => item !== undefined);
+  }
+
+  // Handle objects
+  if (typeof data === 'object') {
+    const cleaned: Record<string, any> = {};
+
+    for (const [key, value] of Object.entries(data)) {
+      // Skip keys containing 'url' or 'href' (case-insensitive)
+      if (key.toLowerCase().includes('url') || key.toLowerCase().includes('href')) {
+        continue;
+      }
+
+      // Clean the value recursively
+      const cleanedValue = cleanResponseData(value);
+
+      // Skip empty values (null, undefined, empty string, empty array, empty object)
+      if (cleanedValue === null || cleanedValue === undefined || cleanedValue === '') {
+        continue;
+      }
+
+      if (Array.isArray(cleanedValue) && cleanedValue.length === 0) {
+        continue;
+      }
+
+      if (typeof cleanedValue === 'object' && !Array.isArray(cleanedValue) && Object.keys(cleanedValue).length === 0) {
+        continue;
+      }
+
+      cleaned[key] = cleanedValue;
+    }
+
+    return Object.keys(cleaned).length > 0 ? cleaned : undefined;
+  }
+
+  // Return primitive values as-is
+  return data;
+}

+ 294 - 0
supabase/functions/_shared/mcp-qdrant-helpers.ts

@@ -0,0 +1,294 @@
+/**
+ * Qdrant MCP Helpers
+ *
+ * Shared utilities for querying Qdrant from MCP tools
+ */
+
+import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
+import { scrollPoints, searchPoints, getCollectionName } from './qdrant-client.ts';
+import { LlmOrder, LlmCustomer, LlmProduct } from './mcp-types.ts';
+
+// Initialize Supabase client
+const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
+const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
+const supabase = createClient(supabaseUrl, supabaseServiceKey);
+
+/**
+ * Product type for LLM output
+ */
+export interface LlmProduct {
+  id: string;
+  name: string;
+  sku?: string;
+  price?: string;
+  currency?: string;
+  status?: string;
+  description?: string;
+  tags?: string[];
+}
+
+/**
+ * Check if store has Qdrant sync enabled and get sync configuration
+ */
+export async function getStoreQdrantConfig(storeId: string): Promise<{
+  enabled: boolean;
+  syncProducts: boolean;
+  syncOrders: boolean;
+  syncCustomers: boolean;
+  shopname: string;
+  platform: string;
+} | null> {
+  // Get store info
+  const { data: store, error: storeError } = await supabase
+    .from('stores')
+    .select('store_name, platform_name, qdrant_sync_enabled')
+    .eq('id', storeId)
+    .single();
+
+  if (storeError || !store) {
+    console.error('[MCP Qdrant] Store not found:', storeId);
+    return null;
+  }
+
+  // Get sync config
+  const { data: syncConfig, error: syncError } = await supabase
+    .from('store_sync_config')
+    .select('sync_products, sync_orders, sync_customers')
+    .eq('store_id', storeId)
+    .single();
+
+  // Default to all enabled if no config exists
+  const config = syncConfig || {
+    sync_products: true,
+    sync_orders: true,
+    sync_customers: true
+  };
+
+  return {
+    enabled: store.qdrant_sync_enabled !== false, // Default to true
+    syncProducts: config.sync_products !== false, // Products always synced, but check anyway
+    syncOrders: config.sync_orders !== false,
+    syncCustomers: config.sync_customers !== false,
+    shopname: store.store_name,
+    platform: store.platform_name
+  };
+}
+
+/**
+ * Query products from Qdrant
+ */
+export async function queryQdrantProducts(
+  storeId: string,
+  shopname: string,
+  filters?: {
+    sku?: string;
+    name?: string;
+    status?: string;
+    minPrice?: number;
+    maxPrice?: number;
+  },
+  limit: number = 20
+): Promise<LlmProduct[]> {
+  const collectionName = getCollectionName(shopname, 'products');
+
+  try {
+    // Build Qdrant filter
+    const qdrantFilter: any = {
+      must: [
+        { key: 'store_id', match: { value: storeId } }
+      ]
+    };
+
+    if (filters?.sku) {
+      qdrantFilter.must.push({ key: 'sku', match: { value: filters.sku } });
+    }
+
+    if (filters?.status) {
+      qdrantFilter.must.push({ key: 'status', match: { value: filters.status } });
+    }
+
+    if (filters?.minPrice !== undefined || filters?.maxPrice !== undefined) {
+      const priceFilter: any = { key: 'price' };
+      if (filters.minPrice !== undefined && filters.maxPrice !== undefined) {
+        priceFilter.range = { gte: filters.minPrice, lte: filters.maxPrice };
+      } else if (filters.minPrice !== undefined) {
+        priceFilter.range = { gte: filters.minPrice };
+      } else if (filters.maxPrice !== undefined) {
+        priceFilter.range = { lte: filters.maxPrice };
+      }
+      qdrantFilter.must.push(priceFilter);
+    }
+
+    // Scroll through points (no vector search needed for filtering)
+    const result = await scrollPoints(collectionName, qdrantFilter, limit);
+
+    // If name filter is provided, we need to filter locally (not indexed)
+    let products = result.points.map((point: any) => point.payload);
+
+    if (filters?.name) {
+      const nameLower = filters.name.toLowerCase();
+      products = products.filter((p: any) =>
+        (p.name || p.title || '').toLowerCase().includes(nameLower)
+      );
+    }
+
+    // Limit results
+    products = products.slice(0, limit);
+
+    // Format for LLM
+    return products.map((p: any) => formatProductForLlm(p));
+  } catch (error) {
+    console.error('[MCP Qdrant] Error querying products:', error);
+    throw error;
+  }
+}
+
+/**
+ * Query orders from Qdrant
+ */
+export async function queryQdrantOrders(
+  storeId: string,
+  shopname: string,
+  filters?: {
+    email?: string;
+    status?: string;
+    minTotal?: number;
+    maxTotal?: number;
+  },
+  limit: number = 20
+): Promise<LlmOrder[]> {
+  const collectionName = getCollectionName(shopname, 'orders');
+
+  try {
+    // Build Qdrant filter
+    const qdrantFilter: any = {
+      must: [
+        { key: 'store_id', match: { value: storeId } }
+      ]
+    };
+
+    if (filters?.email) {
+      qdrantFilter.must.push({ key: 'customer_email', match: { value: filters.email } });
+    }
+
+    if (filters?.status) {
+      qdrantFilter.must.push({ key: 'status', match: { value: filters.status } });
+    }
+
+    if (filters?.minTotal !== undefined || filters?.maxTotal !== undefined) {
+      const totalFilter: any = { key: 'total_price' };
+      if (filters.minTotal !== undefined && filters.maxTotal !== undefined) {
+        totalFilter.range = { gte: filters.minTotal, lte: filters.maxTotal };
+      } else if (filters.minTotal !== undefined) {
+        totalFilter.range = { gte: filters.minTotal };
+      } else if (filters.maxTotal !== undefined) {
+        totalFilter.range = { lte: filters.maxTotal };
+      }
+      qdrantFilter.must.push(totalFilter);
+    }
+
+    // Scroll through points
+    const result = await scrollPoints(collectionName, qdrantFilter, limit);
+
+    const orders = result.points.map((point: any) => point.payload).slice(0, limit);
+
+    // Format for LLM
+    return orders.map((o: any) => formatOrderForLlm(o));
+  } catch (error) {
+    console.error('[MCP Qdrant] Error querying orders:', error);
+    throw error;
+  }
+}
+
+/**
+ * Query customers from Qdrant
+ */
+export async function queryQdrantCustomers(
+  storeId: string,
+  shopname: string,
+  filters?: {
+    email?: string;
+    phone?: string;
+  },
+  limit: number = 20
+): Promise<LlmCustomer[]> {
+  const collectionName = getCollectionName(shopname, 'customers');
+
+  try {
+    // Build Qdrant filter
+    const qdrantFilter: any = {
+      must: [
+        { key: 'store_id', match: { value: storeId } }
+      ]
+    };
+
+    if (filters?.email) {
+      qdrantFilter.must.push({ key: 'email', match: { value: filters.email } });
+    }
+
+    if (filters?.phone) {
+      qdrantFilter.must.push({ key: 'phone', match: { value: filters.phone } });
+    }
+
+    // Scroll through points
+    const result = await scrollPoints(collectionName, qdrantFilter, limit);
+
+    const customers = result.points.map((point: any) => point.payload).slice(0, limit);
+
+    // Format for LLM
+    return customers.map((c: any) => formatCustomerForLlm(c));
+  } catch (error) {
+    console.error('[MCP Qdrant] Error querying customers:', error);
+    throw error;
+  }
+}
+
+/**
+ * Format product from Qdrant payload to LLM-friendly format
+ */
+function formatProductForLlm(product: any): LlmProduct {
+  return {
+    id: product.product_id || product.id,
+    name: product.name || product.title,
+    sku: product.sku || undefined,
+    price: product.price ? product.price.toString() : undefined,
+    currency: product.currency || undefined,
+    status: product.status || undefined,
+    description: product.description || undefined,
+    tags: product.tags || undefined
+  };
+}
+
+/**
+ * Format order from Qdrant payload to LLM-friendly format
+ */
+function formatOrderForLlm(order: any): LlmOrder {
+  return {
+    id: order.order_id || order.id,
+    orderNumber: order.order_number || order.name,
+    customer: {
+      name: order.customer_name || 'Unknown',
+      email: order.customer_email || order.email || '',
+      phone: order.customer_phone || order.phone || undefined
+    },
+    status: order.status || order.financial_status || 'unknown',
+    total: order.total || order.total_price || '0',
+    currency: order.currency || undefined,
+    items: order.line_items || order.items || [],
+    createdAt: order.created_at || order.order_created_at || new Date().toISOString()
+  };
+}
+
+/**
+ * Format customer from Qdrant payload to LLM-friendly format
+ */
+function formatCustomerForLlm(customer: any): LlmCustomer {
+  return {
+    id: customer.customer_id || customer.id,
+    name: `${customer.first_name || ''} ${customer.last_name || ''}`.trim() || 'Unknown',
+    email: customer.email || '',
+    phone: customer.phone || undefined,
+    ordersCount: customer.orders_count || undefined,
+    totalSpent: customer.total_spent ? customer.total_spent.toString() : undefined
+  };
+}

+ 14 - 0
supabase/functions/_shared/mcp-types.ts

@@ -75,3 +75,17 @@ export interface LlmOrder {
   }>;
   }>;
   createdAt: string;
   createdAt: string;
 }
 }
+
+/**
+ * Compact LLM-friendly product representation
+ */
+export interface LlmProduct {
+  id: string;
+  name: string;
+  sku?: string;
+  price?: string;
+  currency?: string;
+  status?: string;
+  description?: string;
+  tags?: string[];
+}

+ 297 - 49
supabase/functions/mcp-shoprenter/index.ts

@@ -27,13 +27,21 @@ import {
 import {
 import {
   McpTool,
   McpTool,
   LlmCustomer,
   LlmCustomer,
-  LlmOrder
+  LlmOrder,
+  LlmProduct
 } from '../_shared/mcp-types.ts';
 } from '../_shared/mcp-types.ts';
 import {
 import {
   createMcpErrorResponse,
   createMcpErrorResponse,
   createMcpSuccessResponse,
   createMcpSuccessResponse,
-  validateParams
+  validateParams,
+  cleanResponseData
 } from '../_shared/mcp-helpers.ts';
 } from '../_shared/mcp-helpers.ts';
+import {
+  getStoreQdrantConfig,
+  queryQdrantProducts,
+  queryQdrantOrders,
+  queryQdrantCustomers
+} from '../_shared/mcp-qdrant-helpers.ts';
 import {
 import {
   JsonRpcRequest,
   JsonRpcRequest,
   createSseResponse,
   createSseResponse,
@@ -56,10 +64,48 @@ const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
 const supabase = createClient(supabaseUrl, supabaseServiceKey);
 const supabase = createClient(supabaseUrl, supabaseServiceKey);
 
 
 const SERVER_NAME = 'mcp-shoprenter';
 const SERVER_NAME = 'mcp-shoprenter';
-const SERVER_VERSION = '2.0.0';
+const SERVER_VERSION = '3.0.0';
 
 
 // MCP Tool Definitions
 // MCP Tool Definitions
 const TOOLS: McpTool[] = [
 const TOOLS: McpTool[] = [
+  {
+    name: 'shoprenter_get_products',
+    description: 'Get products from a ShopRenter store with filtering. Returns product catalog with details like name, SKU, price, and status. Use this to search for products or browse the catalog. Limited to 20 results maximum.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the ShopRenter store from the stores table'
+        },
+        sku: {
+          type: 'string',
+          description: 'Filter by product SKU (exact match)'
+        },
+        name: {
+          type: 'string',
+          description: 'Filter by product name (partial match, case-insensitive)'
+        },
+        status: {
+          type: 'string',
+          description: 'Filter by product status (e.g., active, draft)'
+        },
+        min_price: {
+          type: 'number',
+          description: 'Minimum price filter'
+        },
+        max_price: {
+          type: 'number',
+          description: 'Maximum price filter'
+        },
+        limit: {
+          type: 'number',
+          description: 'Number of products to return (default: 10, max: 20)'
+        }
+      },
+      required: ['shop_id']
+    }
+  },
   {
   {
     name: 'shoprenter_get_order',
     name: 'shoprenter_get_order',
     description: 'Get a specific order from a ShopRenter store by customer-visible order number (innerId). Returns complete order details including customer info, items, status, and totals. Use this when a customer provides their order number.',
     description: 'Get a specific order from a ShopRenter store by customer-visible order number (innerId). Returns complete order details including customer info, items, status, and totals. Use this when a customer provides their order number.',
@@ -183,6 +229,175 @@ function formatOrderForLlm(order: any): LlmOrder {
   };
   };
 }
 }
 
 
+/**
+ * Handle shoprenter_get_products tool
+ */
+async function handleGetProducts(args: Record<string, any>): Promise<ToolCallResult> {
+  const { shop_id, sku, name, status, min_price, max_price, limit = 10 } = args;
+
+  console.log('[MCP ShopRenter] handleGetProducts called with:', { shop_id, sku, name, status, min_price, max_price, limit });
+
+  // Enforce limit constraints
+  const actualLimit = Math.min(Math.max(1, limit), 20);
+
+  // Validate shop exists and is ShopRenter
+  const { data: store, error: storeError } = await supabase
+    .from('stores')
+    .select('id, platform_name, store_name, data_access_permissions')
+    .eq('id', shop_id)
+    .eq('platform_name', 'shoprenter')
+    .single();
+
+  if (storeError || !store) {
+    console.error('[MCP ShopRenter] Store not found:', { shop_id, error: storeError });
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'ShopRenter store not found' }) }],
+      isError: true
+    };
+  }
+
+  console.log('[MCP ShopRenter] Store found:', { id: store.id, store_name: store.store_name });
+
+  // Check permissions
+  const permissions = store.data_access_permissions as any;
+  if (permissions && !permissions.allow_product_access) {
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Product access not allowed for this store' }) }],
+      isError: true
+    };
+  }
+
+  try {
+    // Check if Qdrant is enabled for this store
+    const qdrantConfig = await getStoreQdrantConfig(shop_id);
+
+    if (qdrantConfig && qdrantConfig.enabled && qdrantConfig.syncProducts) {
+      console.log('[MCP ShopRenter] Using Qdrant for products');
+
+      // Query from Qdrant
+      const products = await queryQdrantProducts(
+        shop_id,
+        qdrantConfig.shopname,
+        { sku, name, status, minPrice: min_price, maxPrice: max_price },
+        actualLimit
+      );
+
+      // Clean response data
+      const cleanedProducts = cleanResponseData(products);
+
+      return {
+        content: [{
+          type: 'text',
+          text: JSON.stringify({
+            count: products.length,
+            limit: actualLimit,
+            source: 'qdrant',
+            products: cleanedProducts
+          })
+        }]
+      };
+    } else {
+      console.log('[MCP ShopRenter] Qdrant not enabled, using SQL cache');
+
+      // Fallback: Query from SQL cache (shoprenter_products_cache)
+      let query = supabase
+        .from('shoprenter_products_cache')
+        .select('product_data')
+        .eq('store_id', shop_id);
+
+      // Apply filters (limited by JSONB querying)
+      if (sku) {
+        query = query.eq('product_data->>sku', sku);
+      }
+
+      if (status) {
+        query = query.eq('product_data->>status', status);
+      }
+
+      query = query.limit(actualLimit);
+
+      const { data: cachedProducts, error: cacheError } = await query;
+
+      if (cacheError) {
+        console.error('[MCP ShopRenter] Error querying SQL cache:', cacheError);
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              error: `Failed to fetch products from cache: ${cacheError.message}`
+            })
+          }],
+          isError: true
+        };
+      }
+
+      // Extract product data
+      let products = (cachedProducts || []).map((p: any) => p.product_data);
+
+      // Apply client-side filters
+      if (name) {
+        const nameLower = name.toLowerCase();
+        products = products.filter((p: any) =>
+          (p.name || p.title || '').toLowerCase().includes(nameLower)
+        );
+      }
+
+      if (min_price !== undefined) {
+        products = products.filter((p: any) =>
+          (p.price || 0) >= min_price
+        );
+      }
+
+      if (max_price !== undefined) {
+        products = products.filter((p: any) =>
+          (p.price || 0) <= max_price
+        );
+      }
+
+      // Limit after filtering
+      products = products.slice(0, actualLimit);
+
+      // Format for LLM
+      const formattedProducts: LlmProduct[] = products.map((p: any) => ({
+        id: p.id || p.product_id,
+        name: p.name || p.title || 'Unknown',
+        sku: p.sku || undefined,
+        price: p.price ? p.price.toString() : undefined,
+        currency: p.currency || undefined,
+        status: p.status || undefined,
+        description: p.description || undefined,
+        tags: p.tags || undefined
+      }));
+
+      // Clean response data
+      const cleanedProducts = cleanResponseData(formattedProducts);
+
+      return {
+        content: [{
+          type: 'text',
+          text: JSON.stringify({
+            count: formattedProducts.length,
+            limit: actualLimit,
+            source: 'sql_cache',
+            products: cleanedProducts
+          })
+        }]
+      };
+    }
+  } catch (error) {
+    console.error('[MCP ShopRenter] Error fetching products:', error);
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch products: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
+  }
+}
+
 /**
 /**
  * Fetch all orders with pagination (for filtering by customer name)
  * Fetch all orders with pagination (for filtering by customer name)
  */
  */
@@ -260,69 +475,99 @@ async function handleGetOrder(args: Record<string, any>): Promise<ToolCallResult
   }
   }
 
 
   try {
   try {
-    // Search for order by innerId (customer-visible order number)
-    // The order_id parameter is actually the innerId from the customer's perspective
-    console.log('[MCP ShopRenter] Fetching order with innerId:', order_id);
-    const response = await fetchOrders(shop_id, 0, 1, { innerId: order_id });
-
-    console.log('[MCP ShopRenter] Raw response type:', typeof response, Array.isArray(response) ? 'array' : '');
-    console.log('[MCP ShopRenter] Response structure:', {
-      isArray: Array.isArray(response),
-      hasData: !!response?.data,
-      hasOrders: !!response?.orders,
-      responseKeys: response && typeof response === 'object' ? Object.keys(response) : []
-    });
-
-    const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
+    // Check if Qdrant is enabled for this store
+    const qdrantConfig = await getStoreQdrantConfig(shop_id);
+
+    if (qdrantConfig && qdrantConfig.enabled && qdrantConfig.syncOrders) {
+      console.log('[MCP ShopRenter] Using Qdrant for order lookup');
+
+      // Query from Qdrant - scroll through orders to find matching order_id
+      const orders = await queryQdrantOrders(
+        shop_id,
+        qdrantConfig.shopname,
+        {},
+        100  // Get more to search through
+      );
+
+      // Find order by innerId (order number)
+      const order = orders.find((o: any) =>
+        o.orderNumber === order_id || o.id === order_id
+      );
+
+      if (!order) {
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              error: `No order found with order number: ${order_id}`
+            })
+          }],
+          isError: true
+        };
+      }
 
 
-    console.log('[MCP ShopRenter] API response:', { ordersCount: orders.length });
+      // Clean response data
+      const cleanedOrder = cleanResponseData(order);
 
 
-    if (orders.length === 0) {
-      console.warn('[MCP ShopRenter] No order found with innerId:', order_id);
-      console.log('[MCP ShopRenter] Full response for debugging:', JSON.stringify(response).substring(0, 500));
       return {
       return {
         content: [{
         content: [{
           type: 'text',
           type: 'text',
           text: JSON.stringify({
           text: JSON.stringify({
-            error: `No order found with order number: ${order_id}`,
-            debug: {
-              shop_id,
-              store_name: store.store_name,
-              searched_innerId: order_id,
-              responseType: typeof response,
-              responseKeys: response && typeof response === 'object' ? Object.keys(response) : []
-            }
+            source: 'qdrant',
+            order: cleanedOrder
           })
           })
-        }],
-        isError: true
+        }]
       };
       };
-    }
+    } else {
+      console.log('[MCP ShopRenter] Qdrant not enabled, using ShopRenter API');
 
 
-    console.log('[MCP ShopRenter] Order found:', { innerId: orders[0].innerId || orders[0].id });
+      // Search for order by innerId (customer-visible order number)
+      // The order_id parameter is actually the innerId from the customer's perspective
+      console.log('[MCP ShopRenter] Fetching order with innerId:', order_id);
+      const response = await fetchOrders(shop_id, 0, 1, { innerId: order_id });
 
 
-    // Format for LLM
-    const formattedOrder = formatOrderForLlm(orders[0]);
+      const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
 
 
-    return {
-      content: [{
-        type: 'text',
-        text: JSON.stringify({
-          order: formattedOrder
-        })
-      }]
-    };
+      console.log('[MCP ShopRenter] API response:', { ordersCount: orders.length });
+
+      if (orders.length === 0) {
+        console.warn('[MCP ShopRenter] No order found with innerId:', order_id);
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              error: `No order found with order number: ${order_id}`
+            })
+          }],
+          isError: true
+        };
+      }
+
+      console.log('[MCP ShopRenter] Order found:', { innerId: orders[0].innerId || orders[0].id });
+
+      // Format for LLM
+      const formattedOrder = formatOrderForLlm(orders[0]);
+
+      // Clean response data
+      const cleanedOrder = cleanResponseData(formattedOrder);
+
+      return {
+        content: [{
+          type: 'text',
+          text: JSON.stringify({
+            source: 'api',
+            order: cleanedOrder
+          })
+        }]
+      };
+    }
   } catch (error) {
   } catch (error) {
     console.error('[MCP ShopRenter] Error fetching order:', error);
     console.error('[MCP ShopRenter] Error fetching order:', error);
     return {
     return {
       content: [{
       content: [{
         type: 'text',
         type: 'text',
         text: JSON.stringify({
         text: JSON.stringify({
-          error: `Failed to fetch order: ${error instanceof Error ? error.message : 'Unknown error'}`,
-          debug: {
-            shop_id,
-            order_id,
-            errorDetails: error instanceof Error ? error.stack : String(error)
-          }
+          error: `Failed to fetch order: ${error instanceof Error ? error.message : 'Unknown error'}`
         })
         })
       }],
       }],
       isError: true
       isError: true
@@ -545,6 +790,9 @@ async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
 
 
   // Route to appropriate handler
   // Route to appropriate handler
   switch (name) {
   switch (name) {
+    case 'shoprenter_get_products':
+      return await handleGetProducts(args);
+
     case 'shoprenter_get_order':
     case 'shoprenter_get_order':
       const orderValidation = validateParams(args, ['shop_id', 'order_id']);
       const orderValidation = validateParams(args, ['shop_id', 'order_id']);
       if (!orderValidation.valid) {
       if (!orderValidation.valid) {