Browse Source

feat: implement MCP SSE protocol for n8n integration #76

- Add MCP SSE protocol helper functions (_shared/mcp-sse.ts)
- Update mcp-woocommerce to use JSON-RPC 2.0 and SSE streaming
- Support initialize, tools/list, and tools/call MCP methods
- Compatible with n8n HTTP Streamable configuration
Claude 5 months ago
parent
commit
9488bb5fd5
2 changed files with 579 additions and 172 deletions
  1. 268 0
      supabase/functions/_shared/mcp-sse.ts
  2. 311 172
      supabase/functions/mcp-woocommerce/index.ts

+ 268 - 0
supabase/functions/_shared/mcp-sse.ts

@@ -0,0 +1,268 @@
+/**
+ * MCP SSE (Server-Sent Events) Protocol Implementation
+ *
+ * Implements the Model Context Protocol over HTTP using Server-Sent Events
+ * for streaming communication with LLM clients like n8n.
+ *
+ * Protocol: https://spec.modelcontextprotocol.io/specification/2024-11-05/server/transports/#http-with-sse
+ */
+
+import { McpTool } from './mcp-types.ts';
+
+/**
+ * JSON-RPC 2.0 Request
+ */
+export interface JsonRpcRequest {
+  jsonrpc: '2.0';
+  id?: string | number | null;
+  method: string;
+  params?: any;
+}
+
+/**
+ * JSON-RPC 2.0 Success Response
+ */
+export interface JsonRpcSuccessResponse {
+  jsonrpc: '2.0';
+  id: string | number | null;
+  result: any;
+}
+
+/**
+ * JSON-RPC 2.0 Error Response
+ */
+export interface JsonRpcErrorResponse {
+  jsonrpc: '2.0';
+  id: string | number | null;
+  error: {
+    code: number;
+    message: string;
+    data?: any;
+  };
+}
+
+export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
+
+/**
+ * MCP Initialize Request Params
+ */
+export interface InitializeParams {
+  protocolVersion: string;
+  capabilities: {
+    roots?: { listChanged?: boolean };
+    sampling?: {};
+  };
+  clientInfo: {
+    name: string;
+    version: string;
+  };
+}
+
+/**
+ * MCP Initialize Result
+ */
+export interface InitializeResult {
+  protocolVersion: string;
+  capabilities: {
+    tools?: { listChanged?: boolean };
+    prompts?: { listChanged?: boolean };
+    resources?: { listChanged?: boolean };
+  };
+  serverInfo: {
+    name: string;
+    version: string;
+  };
+}
+
+/**
+ * MCP Tools List Result
+ */
+export interface ToolsListResult {
+  tools: McpTool[];
+}
+
+/**
+ * MCP Tool Call Params
+ */
+export interface ToolCallParams {
+  name: string;
+  arguments?: Record<string, any>;
+}
+
+/**
+ * MCP Tool Call Result
+ */
+export interface ToolCallResult {
+  content: Array<{
+    type: 'text' | 'image' | 'resource';
+    text?: string;
+    data?: string;
+    mimeType?: string;
+  }>;
+  isError?: boolean;
+}
+
+/**
+ * Create SSE message data
+ */
+export function createSseMessage(data: any): string {
+  return `data: ${JSON.stringify(data)}\n\n`;
+}
+
+/**
+ * Create JSON-RPC success response
+ */
+export function createJsonRpcSuccess(
+  id: string | number | null,
+  result: any
+): JsonRpcSuccessResponse {
+  return {
+    jsonrpc: '2.0',
+    id,
+    result
+  };
+}
+
+/**
+ * Create JSON-RPC error response
+ */
+export function createJsonRpcError(
+  id: string | number | null,
+  code: number,
+  message: string,
+  data?: any
+): JsonRpcErrorResponse {
+  return {
+    jsonrpc: '2.0',
+    id,
+    error: { code, message, data }
+  };
+}
+
+/**
+ * JSON-RPC Error Codes
+ */
+export const JsonRpcErrorCode = {
+  PARSE_ERROR: -32700,
+  INVALID_REQUEST: -32600,
+  METHOD_NOT_FOUND: -32601,
+  INVALID_PARAMS: -32602,
+  INTERNAL_ERROR: -32603,
+  SERVER_ERROR: -32000
+} as const;
+
+/**
+ * Create SSE response with streaming
+ */
+export function createSseResponse(): {
+  response: Response;
+  controller: ReadableStreamDefaultController;
+} {
+  const encoder = new TextEncoder();
+  let controller: ReadableStreamDefaultController;
+
+  const stream = new ReadableStream({
+    start(ctrl) {
+      controller = ctrl;
+    }
+  });
+
+  const response = new Response(stream, {
+    headers: {
+      'Content-Type': 'text/event-stream',
+      'Cache-Control': 'no-cache',
+      'Connection': 'keep-alive',
+      'Access-Control-Allow-Origin': '*',
+      'Access-Control-Allow-Methods': 'POST, OPTIONS',
+      'Access-Control-Allow-Headers': 'Content-Type, Authorization'
+    }
+  });
+
+  return { response, controller: controller! };
+}
+
+/**
+ * Send SSE message through controller
+ */
+export function sendSseMessage(
+  controller: ReadableStreamDefaultController,
+  data: JsonRpcResponse
+): void {
+  const encoder = new TextEncoder();
+  const message = createSseMessage(data);
+  controller.enqueue(encoder.encode(message));
+}
+
+/**
+ * Close SSE stream
+ */
+export function closeSseStream(controller: ReadableStreamDefaultController): void {
+  try {
+    controller.close();
+  } catch (error) {
+    // Stream may already be closed
+    console.error('Error closing SSE stream:', error);
+  }
+}
+
+/**
+ * Parse JSON-RPC request from body
+ */
+export async function parseJsonRpcRequest(req: Request): Promise<JsonRpcRequest> {
+  try {
+    const body = await req.json();
+
+    if (!body.jsonrpc || body.jsonrpc !== '2.0') {
+      throw new Error('Invalid JSON-RPC version');
+    }
+
+    if (!body.method || typeof body.method !== 'string') {
+      throw new Error('Invalid or missing method');
+    }
+
+    return body as JsonRpcRequest;
+  } catch (error) {
+    throw new Error(`Invalid JSON-RPC request: ${error instanceof Error ? error.message : 'Unknown error'}`);
+  }
+}
+
+/**
+ * Create MCP initialize response
+ */
+export function createInitializeResult(serverName: string, serverVersion: string): InitializeResult {
+  return {
+    protocolVersion: '2024-11-05',
+    capabilities: {
+      tools: { listChanged: false },
+      prompts: { listChanged: false },
+      resources: { listChanged: false }
+    },
+    serverInfo: {
+      name: serverName,
+      version: serverVersion
+    }
+  };
+}
+
+/**
+ * Validate initialize params
+ */
+export function validateInitializeParams(params: any): params is InitializeParams {
+  return (
+    params &&
+    typeof params.protocolVersion === 'string' &&
+    params.clientInfo &&
+    typeof params.clientInfo.name === 'string' &&
+    typeof params.clientInfo.version === 'string'
+  );
+}
+
+/**
+ * Validate tool call params
+ */
+export function validateToolCallParams(params: any): params is ToolCallParams {
+  return (
+    params &&
+    typeof params.name === 'string'
+  );
+}

+ 311 - 172
supabase/functions/mcp-woocommerce/index.ts

@@ -1,11 +1,12 @@
 /**
 /**
- * MCP HTTP Server for WooCommerce
+ * MCP SSE Server for WooCommerce
  *
  *
  * Provides MCP tools for accessing WooCommerce orders and customers data
  * Provides MCP tools for accessing WooCommerce 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 {
   WooCommerceCustomer
   WooCommerceCustomer
 } from '../_shared/woocommerce-client.ts';
 } from '../_shared/woocommerce-client.ts';
 import {
 import {
-  McpToolsListResponse,
-  McpToolCallRequest,
+  McpTool,
   LlmCustomer,
   LlmCustomer,
   LlmOrder
   LlmOrder
 } from '../_shared/mcp-types.ts';
 } from '../_shared/mcp-types.ts';
@@ -31,83 +31,99 @@ 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-woocommerce';
+const SERVER_VERSION = '1.0.0';
+
 // MCP Tool Definitions
 // MCP Tool Definitions
-const TOOLS: McpToolsListResponse = {
-  tools: [
-    {
-      name: 'woocommerce_list_orders',
-      description: 'List orders from a WooCommerce store. Returns order details including customer info, items, status, and totals.',
-      inputSchema: {
-        type: 'object',
-        properties: {
-          shop_id: {
-            type: 'string',
-            description: 'The UUID of the WooCommerce store from the stores table'
-          },
-          status: {
-            type: 'string',
-            description: 'Filter by order status: any, pending, processing, on-hold, completed, cancelled, refunded, failed'
-          },
-          page: {
-            type: 'number',
-            description: 'Page number for pagination (default: 1)'
-          },
-          per_page: {
-            type: 'number',
-            description: 'Number of orders per page (default: 25, max: 100)'
-          }
+const TOOLS: McpTool[] = [
+  {
+    name: 'woocommerce_list_orders',
+    description: 'List orders from a WooCommerce store. Returns order details including customer info, items, status, and totals.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the WooCommerce store from the stores table'
         },
         },
-        required: ['shop_id']
-      }
-    },
-    {
-      name: 'woocommerce_list_customers',
-      description: 'List customers from a WooCommerce store. Returns customer details including contact info, order count, and total spent.',
-      inputSchema: {
-        type: 'object',
-        properties: {
-          shop_id: {
-            type: 'string',
-            description: 'The UUID of the WooCommerce store from the stores table'
-          },
-          page: {
-            type: 'number',
-            description: 'Page number for pagination (default: 1)'
-          },
-          per_page: {
-            type: 'number',
-            description: 'Number of customers per page (default: 25, max: 100)'
-          }
+        status: {
+          type: 'string',
+          description: 'Filter by order status: any, pending, processing, on-hold, completed, cancelled, refunded, failed'
         },
         },
-        required: ['shop_id']
-      }
-    },
-    {
-      name: 'woocommerce_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 WooCommerce store from the stores table'
-          },
-          customer_email: {
-            type: 'string',
-            description: 'The customer email address'
-          }
+        page: {
+          type: 'number',
+          description: 'Page number for pagination (default: 1)'
         },
         },
-        required: ['shop_id', 'customer_email']
-      }
+        per_page: {
+          type: 'number',
+          description: 'Number of orders per page (default: 25, max: 100)'
+        }
+      },
+      required: ['shop_id']
+    }
+  },
+  {
+    name: 'woocommerce_list_customers',
+    description: 'List customers from a WooCommerce store. Returns customer details including contact info, order count, and total spent.',
+    inputSchema: {
+      type: 'object',
+      properties: {
+        shop_id: {
+          type: 'string',
+          description: 'The UUID of the WooCommerce store from the stores table'
+        },
+        page: {
+          type: 'number',
+          description: 'Page number for pagination (default: 1)'
+        },
+        per_page: {
+          type: 'number',
+          description: 'Number of customers per page (default: 25, max: 100)'
+        }
+      },
+      required: ['shop_id']
+    }
+  },
+  {
+    name: 'woocommerce_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 WooCommerce store from the stores table'
+        },
+        customer_email: {
+          type: 'string',
+          description: 'The customer email address'
+        }
+      },
+      required: ['shop_id', 'customer_email']
     }
     }
-  ]
-};
+  }
+];
 
 
 /**
 /**
  * Convert WooCommerce customer to LLM-friendly format
  * Convert WooCommerce customer to LLM-friendly format
@@ -166,7 +182,7 @@ async function fetchAllOrdersPages(
       hasMore = false;
       hasMore = false;
     } else {
     } else {
       allOrders.push(...orders);
       allOrders.push(...orders);
-      hasMore = orders.length === 100; // If we got max results, there might be more
+      hasMore = orders.length === 100;
       page++;
       page++;
     }
     }
   }
   }
@@ -177,7 +193,7 @@ async function fetchAllOrdersPages(
 /**
 /**
  * Handle woocommerce_list_orders tool
  * Handle woocommerce_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, page = 1, per_page = 25 } = args;
   const { shop_id, status, page = 1, per_page = 25 } = args;
 
 
   // Validate shop exists and is WooCommerce
   // Validate shop exists and is WooCommerce
@@ -189,13 +205,19 @@ async function handleListOrders(args: Record<string, any>): Promise<Response> {
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'WooCommerce store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'WooCommerce 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 {
@@ -205,26 +227,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,
-      per_page,
-      orders: formattedOrders
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          count: formattedOrders.length,
+          page,
+          per_page,
+          orders: formattedOrders
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP WooCommerce] Error fetching orders:', error);
     console.error('[MCP WooCommerce] 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 woocommerce_list_customers tool
  * Handle woocommerce_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, per_page = 25 } = args;
   const { shop_id, page = 1, per_page = 25 } = args;
 
 
   // Validate shop exists and is WooCommerce
   // Validate shop exists and is WooCommerce
@@ -236,13 +267,19 @@ async function handleListCustomers(args: Record<string, any>): Promise<Response>
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'WooCommerce store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'WooCommerce 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 {
@@ -252,26 +289,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,
-      per_page,
-      customers: formattedCustomers
-    });
+    return {
+      content: [{
+        type: 'text',
+        text: JSON.stringify({
+          count: formattedCustomers.length,
+          page,
+          per_page,
+          customers: formattedCustomers
+        })
+      }]
+    };
   } catch (error) {
   } catch (error) {
     console.error('[MCP WooCommerce] Error fetching customers:', error);
     console.error('[MCP WooCommerce] 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 woocommerce_get_customer_orders tool
  * Handle woocommerce_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 WooCommerce
   // Validate shop exists and is WooCommerce
@@ -283,13 +329,19 @@ async function handleGetCustomerOrders(args: Record<string, any>): Promise<Respo
     .single();
     .single();
 
 
   if (storeError || !store) {
   if (storeError || !store) {
-    return createMcpErrorResponse('STORE_NOT_FOUND', 'WooCommerce store not found', 404);
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'WooCommerce 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 {
@@ -302,101 +354,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 WooCommerce] Error fetching customer orders:', error);
     console.error('[MCP WooCommerce] 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 'woocommerce_list_orders':
+      return await handleListOrders(args);
+
+    case 'woocommerce_list_customers':
+      return await handleListCustomers(args);
+
+    case 'woocommerce_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;
+  // 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 'woocommerce_list_orders':
-          return await handleListOrders(args);
-
-        case 'woocommerce_list_customers':
-          return await handleListCustomers(args);
-
-        case 'woocommerce_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 WooCommerce] Error handling request:', error);
-      return createMcpErrorResponse(
-        'INTERNAL_ERROR',
-        error instanceof Error ? error.message : 'Internal server error',
-        500
-      );
+  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 WooCommerce] 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;
 });
 });