Przeglądaj źródła

feat: update mcp-shopify and mcp-shoprenter to use MCP SSE protocol #76

- Convert both servers from REST to JSON-RPC 2.0 over SSE
- Support initialize, tools/list, and tools/call MCP methods
- Compatible with n8n HTTP Streamable configuration
- Maintain all existing tools and authentication logic
Claude 5 miesięcy temu
rodzic
commit
7a16e0516c

+ 245 - 106
supabase/functions/mcp-shopify/index.ts

@@ -1,11 +1,12 @@
 /**
 /**
- * MCP HTTP Server for Shopify
+ * MCP SSE Server for Shopify
  *
  *
  * Provides MCP tools for accessing Shopify orders and customers data
  * Provides MCP tools for accessing Shopify orders and customers data
- * without storing personal information locally (GDPR compliance).
+ * using Server-Sent Events (SSE) protocol for n8n integration.
  *
  *
  * Authentication: Internal API keys from internal_api_keys table
  * Authentication: Internal API keys from internal_api_keys table
  * Required parameter: shop_id (store ID from stores table)
  * Required parameter: shop_id (store ID from stores table)
+ * Protocol: MCP over HTTP with SSE (2024-11-05)
  */
  */
 
 
 import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
 import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
@@ -21,8 +22,7 @@ import {
   ShopifyCustomer
   ShopifyCustomer
 } from '../_shared/shopify-client.ts';
 } from '../_shared/shopify-client.ts';
 import {
 import {
-  McpToolsListResponse,
-  McpToolCallRequest,
+  McpTool,
   LlmCustomer,
   LlmCustomer,
   LlmOrder
   LlmOrder
 } from '../_shared/mcp-types.ts';
 } from '../_shared/mcp-types.ts';
@@ -31,15 +31,32 @@ import {
   createMcpSuccessResponse,
   createMcpSuccessResponse,
   validateParams
   validateParams
 } from '../_shared/mcp-helpers.ts';
 } from '../_shared/mcp-helpers.ts';
+import {
+  JsonRpcRequest,
+  createSseResponse,
+  sendSseMessage,
+  closeSseStream,
+  parseJsonRpcRequest,
+  createJsonRpcSuccess,
+  createJsonRpcError,
+  createInitializeResult,
+  validateInitializeParams,
+  validateToolCallParams,
+  ToolCallParams,
+  ToolCallResult,
+  JsonRpcErrorCode
+} from '../_shared/mcp-sse.ts';
 
 
 // Initialize Supabase client
 // Initialize Supabase client
 const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
 const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
 const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
 const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
 const supabase = createClient(supabaseUrl, supabaseServiceKey);
 const supabase = createClient(supabaseUrl, supabaseServiceKey);
 
 
+const SERVER_NAME = 'mcp-shopify';
+const SERVER_VERSION = '1.0.0';
+
 // MCP Tool Definitions
 // MCP Tool Definitions
-const TOOLS: McpToolsListResponse = {
-  tools: [
+const TOOLS: McpTool[] = [
     {
     {
       name: 'shopify_list_orders',
       name: 'shopify_list_orders',
       description: 'List orders from a Shopify store. Returns order details including customer info, items, status, and totals.',
       description: 'List orders from a Shopify store. Returns order details including customer info, items, status, and totals.',
@@ -99,8 +116,7 @@ const TOOLS: McpToolsListResponse = {
         required: ['shop_id', 'customer_id']
         required: ['shop_id', 'customer_id']
       }
       }
     }
     }
-  ]
-};
+  ];
 
 
 /**
 /**
  * Convert Shopify customer to LLM-friendly format
  * Convert Shopify customer to LLM-friendly format
@@ -143,7 +159,7 @@ function formatOrderForLlm(order: ShopifyOrder): LlmOrder {
 /**
 /**
  * Handle shopify_list_orders tool
  * Handle shopify_list_orders tool
  */
  */
-async function handleListOrders(args: Record<string, any>): Promise<Response> {
+async function handleListOrders(args: Record<string, any>): Promise<ToolCallResult> {
   const { shop_id, status = 'any', limit = 50 } = args;
   const { shop_id, status = 'any', limit = 50 } = args;
 
 
   // Validate shop exists and is Shopify
   // Validate shop exists and is Shopify
@@ -155,13 +171,19 @@ async function handleListOrders(args: Record<string, any>): Promise<Response> {
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'Shopify store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Shopify store not found' }) }],
+      isError: true
+    };
   }
   }
 
 
   // Check permissions
   // Check permissions
   const permissions = store.data_access_permissions as any;
   const permissions = store.data_access_permissions as any;
   if (permissions && !permissions.allow_order_access) {
   if (permissions && !permissions.allow_order_access) {
-    return createMcpErrorResponse('PERMISSION_DENIED', 'Order access not allowed for this store', 403);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Order access not allowed for this store' }) }],
+      isError: true
+    };
   }
   }
 
 
   try {
   try {
@@ -174,25 +196,34 @@ async function handleListOrders(args: Record<string, any>): Promise<Response> {
     // Format for LLM
     // Format for LLM
     const formattedOrders = orders.map(formatOrderForLlm);
     const formattedOrders = orders.map(formatOrderForLlm);
 
 
-    return createMcpSuccessResponse({
-      count: formattedOrders.length,
-      total: allOrders.length,
-      orders: formattedOrders
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          count: formattedOrders.length,
+          total: allOrders.length,
+          orders: formattedOrders
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP Shopify] Error fetching orders:', error);
     console.error('[MCP Shopify] Error fetching orders:', error);
-    return createMcpErrorResponse(
-      'FETCH_ERROR',
-      `Failed to fetch orders: ${error instanceof Error ? error.message : 'Unknown error'}`,
-      500
-    );
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch orders: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
   }
   }
 }
 }
 
 
 /**
 /**
  * Handle shopify_list_customers tool
  * Handle shopify_list_customers tool
  */
  */
-async function handleListCustomers(args: Record<string, any>): Promise<Response> {
+async function handleListCustomers(args: Record<string, any>): Promise<ToolCallResult> {
   const { shop_id, limit = 50 } = args;
   const { shop_id, limit = 50 } = args;
 
 
   // Validate shop exists and is Shopify
   // Validate shop exists and is Shopify
@@ -204,13 +235,19 @@ async function handleListCustomers(args: Record<string, any>): Promise<Response>
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'Shopify store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Shopify store not found' }) }],
+      isError: true
+    };
   }
   }
 
 
   // Check permissions
   // Check permissions
   const permissions = store.data_access_permissions as any;
   const permissions = store.data_access_permissions as any;
   if (permissions && !permissions.allow_customer_access) {
   if (permissions && !permissions.allow_customer_access) {
-    return createMcpErrorResponse('PERMISSION_DENIED', 'Customer access not allowed for this store', 403);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Customer access not allowed for this store' }) }],
+      isError: true
+    };
   }
   }
 
 
   try {
   try {
@@ -223,25 +260,34 @@ async function handleListCustomers(args: Record<string, any>): Promise<Response>
     // Format for LLM
     // Format for LLM
     const formattedCustomers = customers.map(formatCustomerForLlm);
     const formattedCustomers = customers.map(formatCustomerForLlm);
 
 
-    return createMcpSuccessResponse({
-      count: formattedCustomers.length,
-      total: allCustomers.length,
-      customers: formattedCustomers
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          count: formattedCustomers.length,
+          total: allCustomers.length,
+          customers: formattedCustomers
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP Shopify] Error fetching customers:', error);
     console.error('[MCP Shopify] Error fetching customers:', error);
-    return createMcpErrorResponse(
-      'FETCH_ERROR',
-      `Failed to fetch customers: ${error instanceof Error ? error.message : 'Unknown error'}`,
-      500
-    );
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch customers: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
   }
   }
 }
 }
 
 
 /**
 /**
  * Handle shopify_get_customer_orders tool
  * Handle shopify_get_customer_orders tool
  */
  */
-async function handleGetCustomerOrders(args: Record<string, any>): Promise<Response> {
+async function handleGetCustomerOrders(args: Record<string, any>): Promise<ToolCallResult> {
   const { shop_id, customer_id } = args;
   const { shop_id, customer_id } = args;
 
 
   // Validate shop exists and is Shopify
   // Validate shop exists and is Shopify
@@ -253,13 +299,19 @@ async function handleGetCustomerOrders(args: Record<string, any>): Promise<Respo
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'Shopify store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Shopify store not found' }) }],
+      isError: true
+    };
   }
   }
 
 
   // Check permissions
   // Check permissions
   const permissions = store.data_access_permissions as any;
   const permissions = store.data_access_permissions as any;
   if (permissions && (!permissions.allow_order_access || !permissions.allow_customer_access)) {
   if (permissions && (!permissions.allow_order_access || !permissions.allow_customer_access)) {
-    return createMcpErrorResponse('PERMISSION_DENIED', 'Order and customer access required', 403);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Order and customer access required' }) }],
+      isError: true
+    };
   }
   }
 
 
   try {
   try {
@@ -270,101 +322,188 @@ async function handleGetCustomerOrders(args: Record<string, any>): Promise<Respo
     // Format for LLM
     // Format for LLM
     const formattedOrders = customerOrders.map(formatOrderForLlm);
     const formattedOrders = customerOrders.map(formatOrderForLlm);
 
 
-    return createMcpSuccessResponse({
-      customerId: customer_id,
-      count: formattedOrders.length,
-      orders: formattedOrders
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          customerId: customer_id,
+          count: formattedOrders.length,
+          orders: formattedOrders
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP Shopify] Error fetching customer orders:', error);
     console.error('[MCP Shopify] Error fetching customer orders:', error);
-    return createMcpErrorResponse(
-      'FETCH_ERROR',
-      `Failed to fetch customer orders: ${error instanceof Error ? error.message : 'Unknown error'}`,
-      500
-    );
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch customer orders: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
   }
   }
 }
 }
 
 
 /**
 /**
- * Main request handler
+ * Handle tool call
+ */
+async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
+  const { name, arguments: args = {} } = params;
+
+  // Validate shop_id is provided
+  const validation = validateParams(args, ['shop_id']);
+  if (!validation.valid) {
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Missing required parameters: ${validation.missing?.join(', ')}`
+        })
+      }],
+      isError: true
+    };
+  }
+
+  // Route to appropriate handler
+  switch (name) {
+    case 'shopify_list_orders':
+      return await handleListOrders(args);
+
+    case 'shopify_list_customers':
+      return await handleListCustomers(args);
+
+    case 'shopify_get_customer_orders':
+      const customerValidation = validateParams(args, ['shop_id', 'customer_id']);
+      if (!customerValidation.valid) {
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              error: `Missing required parameters: ${customerValidation.missing?.join(', ')}`
+            })
+          }],
+          isError: true
+        };
+      }
+      return await handleGetCustomerOrders(args);
+
+    default:
+      return {
+        content: [{
+          type: 'text',
+          text: JSON.stringify({ error: `Unknown tool: ${name}` })
+        }],
+        isError: true
+      };
+  }
+}
+
+/**
+ * Main request handler with MCP SSE protocol
  */
  */
 serve(async (req: Request) => {
 serve(async (req: Request) => {
-  // CORS headers
+  // CORS preflight
   if (req.method === 'OPTIONS') {
   if (req.method === 'OPTIONS') {
     return new Response(null, {
     return new Response(null, {
       headers: {
       headers: {
         'Access-Control-Allow-Origin': '*',
         'Access-Control-Allow-Origin': '*',
-        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+        'Access-Control-Allow-Methods': 'POST, OPTIONS',
         'Access-Control-Allow-Headers': 'Authorization, Content-Type'
         'Access-Control-Allow-Headers': 'Authorization, Content-Type'
       }
       }
     });
     });
   }
   }
 
 
-  const url = new URL(req.url);
-
-  // Handle /tools endpoint - list available tools
-  if (url.pathname.endsWith('/tools') && req.method === 'GET') {
-    return new Response(JSON.stringify(TOOLS), {
-      headers: {
-        'Content-Type': 'application/json',
-        'Access-Control-Allow-Origin': '*'
-      }
+  // MCP SSE endpoint - only POST requests
+  if (req.method !== 'POST') {
+    return new Response(JSON.stringify({ error: 'Method not allowed. Use POST.' }), {
+      status: 405,
+      headers: { 'Content-Type': 'application/json' }
     });
     });
   }
   }
 
 
-  // Handle /call endpoint - execute tool
-  if (url.pathname.endsWith('/call') && req.method === 'POST') {
-    // Authenticate with internal API key
-    const authResult = await requireInternalApiKey(req, supabase, 'read_orders');
+  // Authenticate with internal API key
+  const authResult = await requireInternalApiKey(req, supabase, 'read_orders');
 
 
-    if (!authResult.valid) {
-      return createInternalApiKeyErrorResponse(authResult);
-    }
+  if (!authResult.valid) {
+    return createInternalApiKeyErrorResponse(authResult);
+  }
 
 
-    try {
-      const body: McpToolCallRequest = await req.json();
-      const { name, arguments: args } = body;
+  // Create SSE response
+  const { response, controller } = createSseResponse();
 
 
-      // Validate shop_id is provided
-      const validation = validateParams(args, ['shop_id']);
-      if (!validation.valid) {
-        return createMcpErrorResponse(
-          'MISSING_PARAMS',
-          `Missing required parameters: ${validation.missing?.join(', ')}`
-        );
-      }
+  // Parse JSON-RPC request
+  let rpcRequest: JsonRpcRequest;
+  try {
+    rpcRequest = await parseJsonRpcRequest(req);
+  } catch (error) {
+    sendSseMessage(
+      controller,
+      createJsonRpcError(
+        null,
+        JsonRpcErrorCode.PARSE_ERROR,
+        error instanceof Error ? error.message : 'Parse error'
+      )
+    );
+    closeSseStream(controller);
+    return response;
+  }
 
 
-      // Route to appropriate handler
-      switch (name) {
-        case 'shopify_list_orders':
-          return await handleListOrders(args);
-
-        case 'shopify_list_customers':
-          return await handleListCustomers(args);
-
-        case 'shopify_get_customer_orders':
-          const customerValidation = validateParams(args, ['shop_id', 'customer_id']);
-          if (!customerValidation.valid) {
-            return createMcpErrorResponse(
-              'MISSING_PARAMS',
-              `Missing required parameters: ${customerValidation.missing?.join(', ')}`
-            );
-          }
-          return await handleGetCustomerOrders(args);
+  const { id, method, params } = rpcRequest;
 
 
-        default:
-          return createMcpErrorResponse('UNKNOWN_TOOL', `Unknown tool: ${name}`, 404);
-      }
-    } catch (error) {
-      console.error('[MCP Shopify] Error handling request:', error);
-      return createMcpErrorResponse(
-        'INTERNAL_ERROR',
-        error instanceof Error ? error.message : 'Internal server error',
-        500
-      );
+  try {
+    // Handle MCP methods
+    switch (method) {
+      case 'initialize':
+        if (!validateInitializeParams(params)) {
+          sendSseMessage(
+            controller,
+            createJsonRpcError(id!, JsonRpcErrorCode.INVALID_PARAMS, 'Invalid initialize parameters')
+          );
+          break;
+        }
+
+        const initResult = createInitializeResult(SERVER_NAME, SERVER_VERSION);
+        sendSseMessage(controller, createJsonRpcSuccess(id!, initResult));
+        break;
+
+      case 'tools/list':
+        sendSseMessage(controller, createJsonRpcSuccess(id!, { tools: TOOLS }));
+        break;
+
+      case 'tools/call':
+        if (!validateToolCallParams(params)) {
+          sendSseMessage(
+            controller,
+            createJsonRpcError(id!, JsonRpcErrorCode.INVALID_PARAMS, 'Invalid tool call parameters')
+          );
+          break;
+        }
+
+        const toolResult = await handleToolCall(params);
+        sendSseMessage(controller, createJsonRpcSuccess(id!, toolResult));
+        break;
+
+      default:
+        sendSseMessage(
+          controller,
+          createJsonRpcError(id!, JsonRpcErrorCode.METHOD_NOT_FOUND, `Method not found: ${method}`)
+        );
     }
     }
+  } catch (error) {
+    console.error('[MCP Shopify] Error handling request:', error);
+    sendSseMessage(
+      controller,
+      createJsonRpcError(
+        id!,
+        JsonRpcErrorCode.INTERNAL_ERROR,
+        error instanceof Error ? error.message : 'Internal error'
+      )
+    );
   }
   }
 
 
-  // 404 for unknown endpoints
-  return createMcpErrorResponse('NOT_FOUND', 'Endpoint not found. Use /tools or /call', 404);
+  // Close stream
+  closeSseStream(controller);
+  return response;
 });
 });

+ 307 - 170
supabase/functions/mcp-shoprenter/index.ts

@@ -1,11 +1,12 @@
 /**
 /**
- * MCP HTTP Server for ShopRenter
+ * MCP SSE Server for ShopRenter
  *
  *
  * Provides MCP tools for accessing ShopRenter orders and customers data
  * Provides MCP tools for accessing ShopRenter orders and customers data
- * without storing personal information locally (GDPR compliance).
+ * using Server-Sent Events (SSE) protocol for n8n integration.
  *
  *
  * Authentication: Internal API keys from internal_api_keys table
  * Authentication: Internal API keys from internal_api_keys table
  * Required parameter: shop_id (store ID from stores table)
  * Required parameter: shop_id (store ID from stores table)
+ * Protocol: MCP over HTTP with SSE (2024-11-05)
  */
  */
 
 
 import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
 import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
@@ -21,89 +22,102 @@ import {
   ShopRenterCustomer
   ShopRenterCustomer
 } from '../_shared/shoprenter-client.ts';
 } from '../_shared/shoprenter-client.ts';
 import {
 import {
-  McpToolsListResponse,
-  McpToolCallRequest,
+  McpTool,
   LlmCustomer,
   LlmCustomer,
   LlmOrder
   LlmOrder
 } from '../_shared/mcp-types.ts';
 } from '../_shared/mcp-types.ts';
 import {
 import {
-  createMcpErrorResponse,
-  createMcpSuccessResponse,
   validateParams
   validateParams
 } from '../_shared/mcp-helpers.ts';
 } from '../_shared/mcp-helpers.ts';
+import {
+  JsonRpcRequest,
+  createSseResponse,
+  sendSseMessage,
+  closeSseStream,
+  parseJsonRpcRequest,
+  createJsonRpcSuccess,
+  createJsonRpcError,
+  createInitializeResult,
+  validateInitializeParams,
+  validateToolCallParams,
+  ToolCallParams,
+  ToolCallResult,
+  JsonRpcErrorCode
+} from '../_shared/mcp-sse.ts';
 
 
 // Initialize Supabase client
 // Initialize Supabase client
 const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
 const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
 const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
 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_VERSION = '1.0.0';
+
 // MCP Tool Definitions
 // MCP Tool Definitions
-const TOOLS: McpToolsListResponse = {
-  tools: [
-    {
-      name: 'shoprenter_list_orders',
-      description: 'List orders from a ShopRenter store. Returns order details including customer info, items, status, and totals.',
-      inputSchema: {
-        type: 'object',
-        properties: {
-          shop_id: {
-            type: 'string',
-            description: 'The UUID of the ShopRenter store from the stores table'
-          },
-          page: {
-            type: 'number',
-            description: 'Page number for pagination (default: 1)'
-          },
-          limit: {
-            type: 'number',
-            description: 'Number of orders per page (default: 25, max: 100)'
-          }
+const TOOLS: McpTool[] = [
+  {
+    name: 'shoprenter_list_orders',
+    description: 'List orders from a ShopRenter store. Returns order details including customer info, items, status, and totals.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the ShopRenter store from the stores table'
         },
         },
-        required: ['shop_id']
-      }
-    },
-    {
-      name: 'shoprenter_list_customers',
-      description: 'List customers from a ShopRenter store. Returns customer details including contact info and order history.',
-      inputSchema: {
-        type: 'object',
-        properties: {
-          shop_id: {
-            type: 'string',
-            description: 'The UUID of the ShopRenter store from the stores table'
-          },
-          page: {
-            type: 'number',
-            description: 'Page number for pagination (default: 1)'
-          },
-          limit: {
-            type: 'number',
-            description: 'Number of customers per page (default: 25, max: 100)'
-          }
+        page: {
+          type: 'number',
+          description: 'Page number for pagination (default: 1)'
         },
         },
-        required: ['shop_id']
-      }
-    },
-    {
-      name: 'shoprenter_get_customer_orders',
-      description: 'Get all orders for a specific customer by customer email. Returns detailed order history.',
-      inputSchema: {
-        type: 'object',
-        properties: {
-          shop_id: {
-            type: 'string',
-            description: 'The UUID of the ShopRenter store from the stores table'
-          },
-          customer_email: {
-            type: 'string',
-            description: 'The customer email address'
-          }
+        limit: {
+          type: 'number',
+          description: 'Number of orders per page (default: 25, max: 100)'
+        }
+      },
+      required: ['shop_id']
+    }
+  },
+  {
+    name: 'shoprenter_list_customers',
+    description: 'List customers from a ShopRenter store. Returns customer details including contact info and order history.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the ShopRenter store from the stores table'
         },
         },
-        required: ['shop_id', 'customer_email']
-      }
+        page: {
+          type: 'number',
+          description: 'Page number for pagination (default: 1)'
+        },
+        limit: {
+          type: 'number',
+          description: 'Number of customers per page (default: 25, max: 100)'
+        }
+      },
+      required: ['shop_id']
     }
     }
-  ]
-};
+  },
+  {
+    name: 'shoprenter_get_customer_orders',
+    description: 'Get all orders for a specific customer by customer email. Returns detailed order history.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the ShopRenter store from the stores table'
+        },
+        customer_email: {
+          type: 'string',
+          description: 'The customer email address'
+        }
+      },
+      required: ['shop_id', 'customer_email']
+    }
+  }
+];
 
 
 /**
 /**
  * Extract phone number from various ShopRenter structures
  * Extract phone number from various ShopRenter structures
@@ -195,7 +209,7 @@ async function fetchAllOrdersPages(
 /**
 /**
  * Handle shoprenter_list_orders tool
  * Handle shoprenter_list_orders tool
  */
  */
-async function handleListOrders(args: Record<string, any>): Promise<Response> {
+async function handleListOrders(args: Record<string, any>): Promise<ToolCallResult> {
   const { shop_id, page = 1, limit = 25 } = args;
   const { shop_id, page = 1, limit = 25 } = args;
 
 
   // Validate shop exists and is ShopRenter
   // Validate shop exists and is ShopRenter
@@ -207,13 +221,19 @@ async function handleListOrders(args: Record<string, any>): Promise<Response> {
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'ShopRenter store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'ShopRenter store not found' }) }],
+      isError: true
+    };
   }
   }
 
 
   // Check permissions
   // Check permissions
   const permissions = store.data_access_permissions as any;
   const permissions = store.data_access_permissions as any;
   if (permissions && !permissions.allow_order_access) {
   if (permissions && !permissions.allow_order_access) {
-    return createMcpErrorResponse('PERMISSION_DENIED', 'Order access not allowed for this store', 403);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Order access not allowed for this store' }) }],
+      isError: true
+    };
   }
   }
 
 
   try {
   try {
@@ -224,26 +244,35 @@ async function handleListOrders(args: Record<string, any>): Promise<Response> {
     // Format for LLM
     // Format for LLM
     const formattedOrders = orders.map(formatOrderForLlm);
     const formattedOrders = orders.map(formatOrderForLlm);
 
 
-    return createMcpSuccessResponse({
-      count: formattedOrders.length,
-      page,
-      limit,
-      orders: formattedOrders
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          count: formattedOrders.length,
+          page,
+          limit,
+          orders: formattedOrders
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP ShopRenter] Error fetching orders:', error);
     console.error('[MCP ShopRenter] Error fetching orders:', error);
-    return createMcpErrorResponse(
-      'FETCH_ERROR',
-      `Failed to fetch orders: ${error instanceof Error ? error.message : 'Unknown error'}`,
-      500
-    );
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch orders: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
   }
   }
 }
 }
 
 
 /**
 /**
  * Handle shoprenter_list_customers tool
  * Handle shoprenter_list_customers tool
  */
  */
-async function handleListCustomers(args: Record<string, any>): Promise<Response> {
+async function handleListCustomers(args: Record<string, any>): Promise<ToolCallResult> {
   const { shop_id, page = 1, limit = 25 } = args;
   const { shop_id, page = 1, limit = 25 } = args;
 
 
   // Validate shop exists and is ShopRenter
   // Validate shop exists and is ShopRenter
@@ -255,13 +284,19 @@ async function handleListCustomers(args: Record<string, any>): Promise<Response>
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'ShopRenter store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'ShopRenter store not found' }) }],
+      isError: true
+    };
   }
   }
 
 
   // Check permissions
   // Check permissions
   const permissions = store.data_access_permissions as any;
   const permissions = store.data_access_permissions as any;
   if (permissions && !permissions.allow_customer_access) {
   if (permissions && !permissions.allow_customer_access) {
-    return createMcpErrorResponse('PERMISSION_DENIED', 'Customer access not allowed for this store', 403);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Customer access not allowed for this store' }) }],
+      isError: true
+    };
   }
   }
 
 
   try {
   try {
@@ -272,26 +307,35 @@ async function handleListCustomers(args: Record<string, any>): Promise<Response>
     // Format for LLM
     // Format for LLM
     const formattedCustomers = customers.map(formatCustomerForLlm);
     const formattedCustomers = customers.map(formatCustomerForLlm);
 
 
-    return createMcpSuccessResponse({
-      count: formattedCustomers.length,
-      page,
-      limit,
-      customers: formattedCustomers
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          count: formattedCustomers.length,
+          page,
+          limit,
+          customers: formattedCustomers
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP ShopRenter] Error fetching customers:', error);
     console.error('[MCP ShopRenter] Error fetching customers:', error);
-    return createMcpErrorResponse(
-      'FETCH_ERROR',
-      `Failed to fetch customers: ${error instanceof Error ? error.message : 'Unknown error'}`,
-      500
-    );
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch customers: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
   }
   }
 }
 }
 
 
 /**
 /**
  * Handle shoprenter_get_customer_orders tool
  * Handle shoprenter_get_customer_orders tool
  */
  */
-async function handleGetCustomerOrders(args: Record<string, any>): Promise<Response> {
+async function handleGetCustomerOrders(args: Record<string, any>): Promise<ToolCallResult> {
   const { shop_id, customer_email } = args;
   const { shop_id, customer_email } = args;
 
 
   // Validate shop exists and is ShopRenter
   // Validate shop exists and is ShopRenter
@@ -303,13 +347,19 @@ async function handleGetCustomerOrders(args: Record<string, any>): Promise<Respo
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'ShopRenter store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'ShopRenter store not found' }) }],
+      isError: true
+    };
   }
   }
 
 
   // Check permissions
   // Check permissions
   const permissions = store.data_access_permissions as any;
   const permissions = store.data_access_permissions as any;
   if (permissions && (!permissions.allow_order_access || !permissions.allow_customer_access)) {
   if (permissions && (!permissions.allow_order_access || !permissions.allow_customer_access)) {
-    return createMcpErrorResponse('PERMISSION_DENIED', 'Order and customer access required', 403);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'Order and customer access required' }) }],
+      isError: true
+    };
   }
   }
 
 
   try {
   try {
@@ -323,101 +373,188 @@ async function handleGetCustomerOrders(args: Record<string, any>): Promise<Respo
     // Format for LLM
     // Format for LLM
     const formattedOrders = customerOrders.map(formatOrderForLlm);
     const formattedOrders = customerOrders.map(formatOrderForLlm);
 
 
-    return createMcpSuccessResponse({
-      customerEmail: customer_email,
-      count: formattedOrders.length,
-      orders: formattedOrders
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          customerEmail: customer_email,
+          count: formattedOrders.length,
+          orders: formattedOrders
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP ShopRenter] Error fetching customer orders:', error);
     console.error('[MCP ShopRenter] Error fetching customer orders:', error);
-    return createMcpErrorResponse(
-      'FETCH_ERROR',
-      `Failed to fetch customer orders: ${error instanceof Error ? error.message : 'Unknown error'}`,
-      500
-    );
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Failed to fetch customer orders: ${error instanceof Error ? error.message : 'Unknown error'}`
+        })
+      }],
+      isError: true
+    };
+  }
+}
+
+/**
+ * Handle tool call
+ */
+async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
+  const { name, arguments: args = {} } = params;
+
+  // Validate shop_id is provided
+  const validation = validateParams(args, ['shop_id']);
+  if (!validation.valid) {
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          error: `Missing required parameters: ${validation.missing?.join(', ')}`
+        })
+      }],
+      isError: true
+    };
+  }
+
+  // Route to appropriate handler
+  switch (name) {
+    case 'shoprenter_list_orders':
+      return await handleListOrders(args);
+
+    case 'shoprenter_list_customers':
+      return await handleListCustomers(args);
+
+    case 'shoprenter_get_customer_orders':
+      const customerValidation = validateParams(args, ['shop_id', 'customer_email']);
+      if (!customerValidation.valid) {
+        return {
+          content: [{
+            type: 'text',
+            text: JSON.stringify({
+              error: `Missing required parameters: ${customerValidation.missing?.join(', ')}`
+            })
+          }],
+          isError: true
+        };
+      }
+      return await handleGetCustomerOrders(args);
+
+    default:
+      return {
+        content: [{
+          type: 'text',
+          text: JSON.stringify({ error: `Unknown tool: ${name}` })
+        }],
+        isError: true
+      };
   }
   }
 }
 }
 
 
 /**
 /**
- * Main request handler
+ * Main request handler with MCP SSE protocol
  */
  */
 serve(async (req: Request) => {
 serve(async (req: Request) => {
-  // CORS headers
+  // CORS preflight
   if (req.method === 'OPTIONS') {
   if (req.method === 'OPTIONS') {
     return new Response(null, {
     return new Response(null, {
       headers: {
       headers: {
         'Access-Control-Allow-Origin': '*',
         'Access-Control-Allow-Origin': '*',
-        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+        'Access-Control-Allow-Methods': 'POST, OPTIONS',
         'Access-Control-Allow-Headers': 'Authorization, Content-Type'
         'Access-Control-Allow-Headers': 'Authorization, Content-Type'
       }
       }
     });
     });
   }
   }
 
 
-  const url = new URL(req.url);
-
-  // Handle /tools endpoint - list available tools
-  if (url.pathname.endsWith('/tools') && req.method === 'GET') {
-    return new Response(JSON.stringify(TOOLS), {
-      headers: {
-        'Content-Type': 'application/json',
-        'Access-Control-Allow-Origin': '*'
-      }
+  // MCP SSE endpoint - only POST requests
+  if (req.method !== 'POST') {
+    return new Response(JSON.stringify({ error: 'Method not allowed. Use POST.' }), {
+      status: 405,
+      headers: { 'Content-Type': 'application/json' }
     });
     });
   }
   }
 
 
-  // Handle /call endpoint - execute tool
-  if (url.pathname.endsWith('/call') && req.method === 'POST') {
-    // Authenticate with internal API key
-    const authResult = await requireInternalApiKey(req, supabase, 'read_orders');
+  // Authenticate with internal API key
+  const authResult = await requireInternalApiKey(req, supabase, 'read_orders');
 
 
-    if (!authResult.valid) {
-      return createInternalApiKeyErrorResponse(authResult);
-    }
+  if (!authResult.valid) {
+    return createInternalApiKeyErrorResponse(authResult);
+  }
 
 
-    try {
-      const body: McpToolCallRequest = await req.json();
-      const { name, arguments: args } = body;
-
-      // Validate shop_id is provided
-      const validation = validateParams(args, ['shop_id']);
-      if (!validation.valid) {
-        return createMcpErrorResponse(
-          'MISSING_PARAMS',
-          `Missing required parameters: ${validation.missing?.join(', ')}`
-        );
-      }
+  // Create SSE response
+  const { response, controller } = createSseResponse();
 
 
-      // Route to appropriate handler
-      switch (name) {
-        case 'shoprenter_list_orders':
-          return await handleListOrders(args);
-
-        case 'shoprenter_list_customers':
-          return await handleListCustomers(args);
-
-        case 'shoprenter_get_customer_orders':
-          const customerValidation = validateParams(args, ['shop_id', 'customer_email']);
-          if (!customerValidation.valid) {
-            return createMcpErrorResponse(
-              'MISSING_PARAMS',
-              `Missing required parameters: ${customerValidation.missing?.join(', ')}`
-            );
-          }
-          return await handleGetCustomerOrders(args);
-
-        default:
-          return createMcpErrorResponse('UNKNOWN_TOOL', `Unknown tool: ${name}`, 404);
-      }
-    } catch (error) {
-      console.error('[MCP ShopRenter] Error handling request:', error);
-      return createMcpErrorResponse(
-        'INTERNAL_ERROR',
-        error instanceof Error ? error.message : 'Internal server error',
-        500
-      );
+  // Parse JSON-RPC request
+  let rpcRequest: JsonRpcRequest;
+  try {
+    rpcRequest = await parseJsonRpcRequest(req);
+  } catch (error) {
+    sendSseMessage(
+      controller,
+      createJsonRpcError(
+        null,
+        JsonRpcErrorCode.PARSE_ERROR,
+        error instanceof Error ? error.message : 'Parse error'
+      )
+    );
+    closeSseStream(controller);
+    return response;
+  }
+
+  const { id, method, params } = rpcRequest;
+
+  try {
+    // Handle MCP methods
+    switch (method) {
+      case 'initialize':
+        if (!validateInitializeParams(params)) {
+          sendSseMessage(
+            controller,
+            createJsonRpcError(id!, JsonRpcErrorCode.INVALID_PARAMS, 'Invalid initialize parameters')
+          );
+          break;
+        }
+
+        const initResult = createInitializeResult(SERVER_NAME, SERVER_VERSION);
+        sendSseMessage(controller, createJsonRpcSuccess(id!, initResult));
+        break;
+
+      case 'tools/list':
+        sendSseMessage(controller, createJsonRpcSuccess(id!, { tools: TOOLS }));
+        break;
+
+      case 'tools/call':
+        if (!validateToolCallParams(params)) {
+          sendSseMessage(
+            controller,
+            createJsonRpcError(id!, JsonRpcErrorCode.INVALID_PARAMS, 'Invalid tool call parameters')
+          );
+          break;
+        }
+
+        const toolResult = await handleToolCall(params);
+        sendSseMessage(controller, createJsonRpcSuccess(id!, toolResult));
+        break;
+
+      default:
+        sendSseMessage(
+          controller,
+          createJsonRpcError(id!, JsonRpcErrorCode.METHOD_NOT_FOUND, `Method not found: ${method}`)
+        );
     }
     }
+  } catch (error) {
+    console.error('[MCP ShopRenter] Error handling request:', error);
+    sendSseMessage(
+      controller,
+      createJsonRpcError(
+        id!,
+        JsonRpcErrorCode.INTERNAL_ERROR,
+        error instanceof Error ? error.message : 'Internal error'
+      )
+    );
   }
   }
 
 
-  // 404 for unknown endpoints
-  return createMcpErrorResponse('NOT_FOUND', 'Endpoint not found. Use /tools or /call', 404);
+  // Close stream
+  closeSseStream(controller);
+  return response;
 });
 });