Browse Source

feat(mcp): add list_custom_contents tool to all MCP edge functions

Added new tool to list available custom content (PDFs and text entries)
for each platform:
- shopify_list_custom_contents
- woocommerce_list_custom_contents
- shoprenter_list_custom_contents

Returns title, content type, sync status, and metadata for all enabled
custom content items. This helps LLMs discover what custom documentation
is available before searching.

Also added listCustomContents helper function to mcp-qdrant-helpers.ts.

Deployed with --no-verify-jwt as MCP tools use their own JWT validation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh 4 months ago
parent
commit
2c52af18f8

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

@@ -386,3 +386,62 @@ function truncateText(text: string, maxLength: number): string {
   }
   }
   return text.substring(0, maxLength) + '...';
   return text.substring(0, maxLength) + '...';
 }
 }
+
+/**
+ * Custom content list item for LLM
+ */
+export interface LlmCustomContentListItem {
+  id: string;
+  title: string;
+  contentType: 'text_entry' | 'pdf_upload';
+  originalFilename?: string;
+  pageCount?: number;
+  chunkCount?: number;
+  syncStatus: string;
+  isEnabled: boolean;
+  createdAt: string;
+}
+
+/**
+ * List all custom content for a store from the database
+ *
+ * @param storeId - Store UUID
+ * @returns Array of custom content items with basic info
+ */
+export async function listCustomContents(
+  storeId: string
+): Promise<LlmCustomContentListItem[]> {
+  try {
+    console.log(`[MCP Qdrant] Listing custom content for store: ${storeId}`);
+
+    // Query custom_content table directly
+    const { data: contents, error } = await supabase
+      .from('custom_content')
+      .select('id, title, content_type, original_filename, page_count, chunk_count, sync_status, is_enabled, created_at')
+      .eq('store_id', storeId)
+      .order('created_at', { ascending: false });
+
+    if (error) {
+      console.error('[MCP Qdrant] Error listing custom content:', error);
+      throw error;
+    }
+
+    console.log(`[MCP Qdrant] Found ${contents?.length || 0} custom content items`);
+
+    // Format for LLM
+    return (contents || []).map((item) => ({
+      id: item.id,
+      title: item.title,
+      contentType: item.content_type as 'text_entry' | 'pdf_upload',
+      originalFilename: item.original_filename || undefined,
+      pageCount: item.page_count || undefined,
+      chunkCount: item.chunk_count || undefined,
+      syncStatus: item.sync_status || 'unknown',
+      isEnabled: item.is_enabled !== false, // Default to true if not set
+      createdAt: item.created_at,
+    }));
+  } catch (error) {
+    console.error('[MCP Qdrant] Error listing custom content:', error);
+    throw error;
+  }
+}

+ 63 - 1
supabase/functions/mcp-shopify/index.ts

@@ -40,7 +40,8 @@ import {
   queryQdrantProducts,
   queryQdrantProducts,
   queryQdrantOrders,
   queryQdrantOrders,
   queryQdrantCustomers,
   queryQdrantCustomers,
-  queryQdrantCustomContent
+  queryQdrantCustomContent,
+  listCustomContents
 } from '../_shared/mcp-qdrant-helpers.ts';
 } from '../_shared/mcp-qdrant-helpers.ts';
 import {
 import {
   getAccessPolicyConfig,
   getAccessPolicyConfig,
@@ -225,6 +226,20 @@ const TOOLS: McpTool[] = [
       },
       },
       required: ['shop_id', 'query']
       required: ['shop_id', 'query']
     }
     }
+  },
+  {
+    name: 'shopify_list_custom_contents',
+    description: 'List all available custom content (uploaded PDFs and text entries) for a store. Returns titles, types, and status of all custom knowledge base items. Use this to see what custom documentation is available before searching.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the Shopify store from the stores table'
+        }
+      },
+      required: ['shop_id']
+    }
   }
   }
 ];
 ];
 
 
@@ -794,6 +809,50 @@ async function handleSearchCustomContent(args: Record<string, any>): Promise<Too
   }
   }
 }
 }
 
 
+/**
+ * Handle list custom contents
+ */
+async function handleListCustomContents(args: Record<string, any>): Promise<ToolCallResult> {
+  const { shop_id } = args;
+
+  try {
+    // List custom content from database
+    const contents = await listCustomContents(shop_id);
+
+    // Filter to only enabled content for display
+    const enabledContents = contents.filter(c => c.isEnabled);
+
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          total: enabledContents.length,
+          contents: enabledContents.map(c => ({
+            id: c.id,
+            title: c.title,
+            content_type: c.contentType,
+            original_filename: c.originalFilename,
+            page_count: c.pageCount,
+            chunk_count: c.chunkCount,
+            sync_status: c.syncStatus,
+            created_at: c.createdAt
+          }))
+        })
+      }],
+      isError: false
+    };
+  } catch (error: any) {
+    console.error('[MCP Shopify] Error listing custom contents:', error);
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({ error: `Failed to list custom contents: ${error instanceof Error ? error.message : 'Unknown error'}` })
+      }],
+      isError: true
+    };
+  }
+}
+
 /**
 /**
  * Handle tool call
  * Handle tool call
  */
  */
@@ -855,6 +914,9 @@ async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
       }
       }
       return await handleSearchCustomContent(args);
       return await handleSearchCustomContent(args);
 
 
+    case 'shopify_list_custom_contents':
+      return await handleListCustomContents(args);
+
     // Legacy tool names for backward compatibility
     // Legacy tool names for backward compatibility
     case 'shopify_list_customers':
     case 'shopify_list_customers':
       return {
       return {

+ 63 - 1
supabase/functions/mcp-shoprenter/index.ts

@@ -42,7 +42,8 @@ import {
   queryQdrantProducts,
   queryQdrantProducts,
   queryQdrantOrders,
   queryQdrantOrders,
   queryQdrantCustomers,
   queryQdrantCustomers,
-  queryQdrantCustomContent
+  queryQdrantCustomContent,
+  listCustomContents
 } from '../_shared/mcp-qdrant-helpers.ts';
 } from '../_shared/mcp-qdrant-helpers.ts';
 import {
 import {
   getAccessPolicyConfig,
   getAccessPolicyConfig,
@@ -215,6 +216,20 @@ const TOOLS: McpTool[] = [
       },
       },
       required: ['shop_id', 'query']
       required: ['shop_id', 'query']
     }
     }
+  },
+  {
+    name: 'shoprenter_list_custom_contents',
+    description: 'List all available custom content (uploaded PDFs and text entries) for a store. Returns titles, types, and status of all custom knowledge base items. Use this to see what custom documentation is available before searching.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the ShopRenter store from the stores table'
+        }
+      },
+      required: ['shop_id']
+    }
   }
   }
 ];
 ];
 
 
@@ -1154,6 +1169,50 @@ async function handleSearchCustomContent(args: Record<string, any>): Promise<Too
   }
   }
 }
 }
 
 
+/**
+ * Handle list custom contents
+ */
+async function handleListCustomContents(args: Record<string, any>): Promise<ToolCallResult> {
+  const { shop_id } = args;
+
+  try {
+    // List custom content from database
+    const contents = await listCustomContents(shop_id);
+
+    // Filter to only enabled content for display
+    const enabledContents = contents.filter(c => c.isEnabled);
+
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          total: enabledContents.length,
+          contents: enabledContents.map(c => ({
+            id: c.id,
+            title: c.title,
+            content_type: c.contentType,
+            original_filename: c.originalFilename,
+            page_count: c.pageCount,
+            chunk_count: c.chunkCount,
+            sync_status: c.syncStatus,
+            created_at: c.createdAt
+          }))
+        })
+      }],
+      isError: false
+    };
+  } catch (error: any) {
+    console.error('[MCP ShopRenter] Error listing custom contents:', error);
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({ error: `Failed to list custom contents: ${error instanceof Error ? error.message : 'Unknown error'}` })
+      }],
+      isError: true
+    };
+  }
+}
+
 /**
 /**
  * Handle tool call
  * Handle tool call
  */
  */
@@ -1227,6 +1286,9 @@ async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
       }
       }
       return await handleSearchCustomContent(args);
       return await handleSearchCustomContent(args);
 
 
+    case 'shoprenter_list_custom_contents':
+      return await handleListCustomContents(args);
+
     // Legacy tool names for backward compatibility
     // Legacy tool names for backward compatibility
     case 'shoprenter_list_customers':
     case 'shoprenter_list_customers':
       return {
       return {

+ 63 - 1
supabase/functions/mcp-woocommerce/index.ts

@@ -42,7 +42,8 @@ import {
   queryQdrantProducts,
   queryQdrantProducts,
   queryQdrantOrders,
   queryQdrantOrders,
   queryQdrantCustomers,
   queryQdrantCustomers,
-  queryQdrantCustomContent
+  queryQdrantCustomContent,
+  listCustomContents
 } from '../_shared/mcp-qdrant-helpers.ts';
 } from '../_shared/mcp-qdrant-helpers.ts';
 import {
 import {
   getAccessPolicyConfig,
   getAccessPolicyConfig,
@@ -219,6 +220,20 @@ const TOOLS: McpTool[] = [
       },
       },
       required: ['shop_id', 'query']
       required: ['shop_id', 'query']
     }
     }
+  },
+  {
+    name: 'woocommerce_list_custom_contents',
+    description: 'List all available custom content (uploaded PDFs and text entries) for a store. Returns titles, types, and status of all custom knowledge base items. Use this to see what custom documentation is available before searching.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the WooCommerce store from the stores table'
+        }
+      },
+      required: ['shop_id']
+    }
   }
   }
 ];
 ];
 
 
@@ -874,6 +889,50 @@ async function handleSearchCustomContent(args: Record<string, any>): Promise<Too
   }
   }
 }
 }
 
 
+/**
+ * Handle list custom contents
+ */
+async function handleListCustomContents(args: Record<string, any>): Promise<ToolCallResult> {
+  const { shop_id } = args;
+
+  try {
+    // List custom content from database
+    const contents = await listCustomContents(shop_id);
+
+    // Filter to only enabled content for display
+    const enabledContents = contents.filter(c => c.isEnabled);
+
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          total: enabledContents.length,
+          contents: enabledContents.map(c => ({
+            id: c.id,
+            title: c.title,
+            content_type: c.contentType,
+            original_filename: c.originalFilename,
+            page_count: c.pageCount,
+            chunk_count: c.chunkCount,
+            sync_status: c.syncStatus,
+            created_at: c.createdAt
+          }))
+        })
+      }],
+      isError: false
+    };
+  } catch (error: any) {
+    console.error('[MCP WooCommerce] Error listing custom contents:', error);
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({ error: `Failed to list custom contents: ${error instanceof Error ? error.message : 'Unknown error'}` })
+      }],
+      isError: true
+    };
+  }
+}
+
 /**
 /**
  * Handle tool call
  * Handle tool call
  */
  */
@@ -947,6 +1006,9 @@ async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
       }
       }
       return await handleSearchCustomContent(args);
       return await handleSearchCustomContent(args);
 
 
+    case 'woocommerce_list_custom_contents':
+      return await handleListCustomContents(args);
+
     // Legacy tool names for backward compatibility
     // Legacy tool names for backward compatibility
     case 'woocommerce_list_customers':
     case 'woocommerce_list_customers':
       return {
       return {