Переглянути джерело

fix: MCP ShopRenter respects data access policy #105

- Check access policy FIRST before attempting Qdrant access
- For api_only mode: Skip Qdrant, use direct API calls
- For sync mode: Use Qdrant if enabled, otherwise fallback to API
- For not_allowed: Return access denied error
- Fixed handleListOrders and handleGetOrder functions
- Products and customers handlers already had correct logic
Claude 4 місяців тому
батько
коміт
8a544da896
1 змінених файлів з 244 додано та 128 видалено
  1. 244 128
      supabase/functions/mcp-shoprenter/index.ts

+ 244 - 128
supabase/functions/mcp-shoprenter/index.ts

@@ -579,56 +579,11 @@ async function handleGetOrder(args: Record<string, any>): Promise<ToolCallResult
   const policy = getDataTypePolicy(accessConfig!, 'orders');
 
   try {
-    // 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
-        };
-      }
-
-      // Clean response data
-      const cleanedOrder = cleanResponseData(order);
-
-      return {
-        content: [{
-          type: 'text',
-          text: JSON.stringify({
-            source: isApiOnlyMode(policy) ? 'api_direct' : 'qdrant',
-            access_mode: policy,
-            notice: isApiOnlyMode(policy) ? createApiOnlyNotice('orders') : undefined,
-            order: cleanedOrder
-          })
-        }]
-      };
-    } else {
-      console.log('[MCP ShopRenter] Qdrant not enabled, using ShopRenter API');
+    // Handle api_only mode OR sync mode without Qdrant: Use direct API
+    if (isApiOnlyMode(policy) || !isSyncAllowed(policy)) {
+      console.log('[MCP ShopRenter] Using direct API access');
 
       // 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 });
 
@@ -669,6 +624,105 @@ async function handleGetOrder(args: Record<string, any>): Promise<ToolCallResult
         }]
       };
     }
+
+    // Handle sync mode: Use Qdrant if enabled
+    if (isSyncAllowed(policy)) {
+      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
+          };
+        }
+
+        // Clean response data
+        const cleanedOrder = cleanResponseData(order);
+
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              source: 'qdrant',
+              access_mode: 'sync',
+              order: cleanedOrder
+            })
+          }]
+        };
+      } else {
+        console.log('[MCP ShopRenter] Qdrant not enabled, using ShopRenter API');
+
+        // Search for order by innerId (customer-visible order number)
+        console.log('[MCP ShopRenter] Fetching order with innerId:', order_id);
+        const response = await fetchOrders(shop_id, 0, 1, { innerId: order_id });
+
+        const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
+
+        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',
+              access_mode: 'sync',
+              order: cleanedOrder
+            })
+          }]
+        };
+      }
+    }
+
+    // Fallback (should not reach here)
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({ error: 'Invalid access policy configuration' })
+      }],
+      isError: true
+    };
   } catch (error) {
     console.error('[MCP ShopRenter] Error fetching order:', error);
     return {
@@ -727,83 +781,9 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
   const policy = getDataTypePolicy(accessConfig!, 'orders');
 
   try {
-    // 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 orders');
-
-      // Query from Qdrant with filters
-      const qdrantOrders = await queryQdrantOrders(
-        shop_id,
-        qdrantConfig.shopname,
-        { email, status },
-        actualLimit
-      );
-
-      // Apply date filters client-side if provided
-      let filteredOrders = qdrantOrders;
-
-      if (createdAtMin) {
-        const minDate = new Date(createdAtMin);
-        filteredOrders = filteredOrders.filter((o: any) => {
-          const created = new Date(o.dateCreated || o.createdAt || 0);
-          return created >= minDate;
-        });
-      }
-
-      if (createdAtMax) {
-        const maxDate = new Date(createdAtMax);
-        filteredOrders = filteredOrders.filter((o: any) => {
-          const created = new Date(o.dateCreated || o.createdAt || 0);
-          return created <= maxDate;
-        });
-      }
-
-      if (updatedAtMin) {
-        const minDate = new Date(updatedAtMin);
-        filteredOrders = filteredOrders.filter((o: any) => {
-          const updated = new Date(o.dateUpdated || o.updatedAt || 0);
-          return updated >= minDate;
-        });
-      }
-
-      if (updatedAtMax) {
-        const maxDate = new Date(updatedAtMax);
-        filteredOrders = filteredOrders.filter((o: any) => {
-          const updated = new Date(o.dateUpdated || o.updatedAt || 0);
-          return updated <= maxDate;
-        });
-      }
-
-      // Limit after filtering
-      filteredOrders = filteredOrders.slice(0, actualLimit);
-
-      // Clean response data
-      const cleanedOrders = cleanResponseData(filteredOrders);
-
-      return {
-        content: [{
-          type: 'text',
-          text: JSON.stringify({
-            count: filteredOrders.length,
-            limit: actualLimit,
-            source: 'qdrant',
-            access_mode: 'sync',
-            filters_applied: {
-              createdAtMin,
-              createdAtMax,
-              updatedAtMin,
-              updatedAtMax,
-              email,
-              status
-            },
-            orders: cleanedOrders
-          })
-        }]
-      };
-    } else {
-      console.log('[MCP ShopRenter] Qdrant not enabled, using ShopRenter API');
+    // Handle api_only mode: Direct API call
+    if (isApiOnlyMode(policy)) {
+      console.log('[MCP ShopRenter] Using direct API access (api_only mode)');
 
       // Build filters using correct parameter names
       const filters: ShopRenterOrderFilters = {};
@@ -835,9 +815,9 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
           text: JSON.stringify({
             count: formattedOrders.length,
             limit: actualLimit,
-            source: isApiOnlyMode(policy) ? 'api_direct' : 'api',
-            access_mode: policy,
-            notice: isApiOnlyMode(policy) ? createApiOnlyNotice('orders') : undefined,
+            source: 'api_direct',
+            access_mode: 'api_only',
+            notice: createApiOnlyNotice('orders'),
             filters_applied: {
               createdAtMin,
               createdAtMax,
@@ -851,6 +831,142 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
         }]
       };
     }
+
+    // Handle sync mode: Use Qdrant or SQL cache
+    if (isSyncAllowed(policy)) {
+      // 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 orders');
+
+        // Query from Qdrant with filters
+        const qdrantOrders = await queryQdrantOrders(
+          shop_id,
+          qdrantConfig.shopname,
+          { email, status },
+          actualLimit
+        );
+
+        // Apply date filters client-side if provided
+        let filteredOrders = qdrantOrders;
+
+        if (createdAtMin) {
+          const minDate = new Date(createdAtMin);
+          filteredOrders = filteredOrders.filter((o: any) => {
+            const created = new Date(o.dateCreated || o.createdAt || 0);
+            return created >= minDate;
+          });
+        }
+
+        if (createdAtMax) {
+          const maxDate = new Date(createdAtMax);
+          filteredOrders = filteredOrders.filter((o: any) => {
+            const created = new Date(o.dateCreated || o.createdAt || 0);
+            return created <= maxDate;
+          });
+        }
+
+        if (updatedAtMin) {
+          const minDate = new Date(updatedAtMin);
+          filteredOrders = filteredOrders.filter((o: any) => {
+            const updated = new Date(o.dateUpdated || o.updatedAt || 0);
+            return updated >= minDate;
+          });
+        }
+
+        if (updatedAtMax) {
+          const maxDate = new Date(updatedAtMax);
+          filteredOrders = filteredOrders.filter((o: any) => {
+            const updated = new Date(o.dateUpdated || o.updatedAt || 0);
+            return updated <= maxDate;
+          });
+        }
+
+        // Limit after filtering
+        filteredOrders = filteredOrders.slice(0, actualLimit);
+
+        // Clean response data
+        const cleanedOrders = cleanResponseData(filteredOrders);
+
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              count: filteredOrders.length,
+              limit: actualLimit,
+              source: 'qdrant',
+              access_mode: 'sync',
+              filters_applied: {
+                createdAtMin,
+                createdAtMax,
+                updatedAtMin,
+                updatedAtMax,
+                email,
+                status
+              },
+              orders: cleanedOrders
+            })
+          }]
+        };
+      } else {
+        console.log('[MCP ShopRenter] Qdrant not enabled, using ShopRenter API');
+
+        // Build filters using correct parameter names
+        const filters: ShopRenterOrderFilters = {};
+        if (status) filters.status = status;
+        if (email) filters.email = email;
+        if (createdAtMin) filters.createdAtMin = createdAtMin;
+        if (createdAtMax) filters.createdAtMax = createdAtMax;
+        if (updatedAtMin) filters.updatedAtMin = updatedAtMin;
+        if (updatedAtMax) filters.updatedAtMax = updatedAtMax;
+
+        // Fetch orders from ShopRenter API
+        const response = await fetchOrders(shop_id, 0, actualLimit, filters);
+        const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
+
+        console.log('[MCP ShopRenter] API response:', { ordersCount: orders.length });
+
+        // Apply limit
+        const limitedOrders = orders.slice(0, actualLimit);
+
+        // Format for LLM (now preserves all ShopRenter fields)
+        const formattedOrders = limitedOrders.map(formatOrderForLlm);
+
+        // Clean response data (removes URLs, empty values)
+        const cleanedOrders = cleanResponseData(formattedOrders);
+
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              count: formattedOrders.length,
+              limit: actualLimit,
+              source: 'api',
+              access_mode: 'sync',
+              filters_applied: {
+                createdAtMin,
+                createdAtMax,
+                updatedAtMin,
+                updatedAtMax,
+                email,
+                status
+              },
+              orders: cleanedOrders
+            })
+          }]
+        };
+      }
+    }
+
+    // Fallback (should not reach here)
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({ error: 'Invalid access policy configuration' })
+      }],
+      isError: true
+    };
   } catch (error) {
     console.error('[MCP ShopRenter] Error fetching orders:', error);
     return {