|
@@ -17,6 +17,7 @@ import {
|
|
|
} from '../_shared/internal-api-key-auth.ts';
|
|
} from '../_shared/internal-api-key-auth.ts';
|
|
|
import {
|
|
import {
|
|
|
fetchOrders,
|
|
fetchOrders,
|
|
|
|
|
+ fetchOrder,
|
|
|
fetchCustomers,
|
|
fetchCustomers,
|
|
|
WooCommerceOrder,
|
|
WooCommerceOrder,
|
|
|
WooCommerceCustomer,
|
|
WooCommerceCustomer,
|
|
@@ -59,6 +60,24 @@ const SERVER_VERSION = '2.0.0';
|
|
|
|
|
|
|
|
// MCP Tool Definitions
|
|
// MCP Tool Definitions
|
|
|
const TOOLS: McpTool[] = [
|
|
const TOOLS: McpTool[] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'woocommerce_get_order',
|
|
|
|
|
+ description: 'Get a specific order from a WooCommerce store by order ID or order number. Returns complete order details including customer info, items, status, and totals. Use this when a customer provides their order number.',
|
|
|
|
|
+ inputSchema: {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ shop_id: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ description: 'The UUID of the WooCommerce store from the stores table'
|
|
|
|
|
+ },
|
|
|
|
|
+ order_id: {
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ description: 'The WooCommerce order ID (numeric)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ required: ['shop_id', 'order_id']
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
{
|
|
{
|
|
|
name: 'woocommerce_list_orders',
|
|
name: 'woocommerce_list_orders',
|
|
|
description: 'List orders from a WooCommerce store with filtering. At least one filter is REQUIRED (created_after, created_before, updated_after, updated_before, customer_email, or customer_name). Returns order details including customer info, items, status, and totals. Limited to 20 results maximum.',
|
|
description: 'List orders from a WooCommerce store with filtering. At least one filter is REQUIRED (created_after, created_before, updated_after, updated_before, customer_email, or customer_name). Returns order details including customer info, items, status, and totals. Limited to 20 results maximum.',
|
|
@@ -190,6 +209,78 @@ async function fetchAllOrdersPages(
|
|
|
return allOrders;
|
|
return allOrders;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Handle woocommerce_get_order tool
|
|
|
|
|
+ */
|
|
|
|
|
+async function handleGetOrder(args: Record<string, any>): Promise<ToolCallResult> {
|
|
|
|
|
+ const { shop_id, order_id } = args;
|
|
|
|
|
+
|
|
|
|
|
+ // Validate order_id is provided
|
|
|
|
|
+ if (!order_id) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ error: 'order_id parameter is required'
|
|
|
|
|
+ })
|
|
|
|
|
+ }],
|
|
|
|
|
+ isError: true
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Validate shop exists and is WooCommerce
|
|
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id, platform_name, data_access_permissions')
|
|
|
|
|
+ .eq('id', shop_id)
|
|
|
|
|
+ .eq('platform_name', 'woocommerce')
|
|
|
|
|
+ .single();
|
|
|
|
|
+
|
|
|
|
|
+ if (storeError || !store) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{ type: 'text', text: JSON.stringify({ error: 'WooCommerce store not found' }) }],
|
|
|
|
|
+ isError: true
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check permissions
|
|
|
|
|
+ const permissions = store.data_access_permissions as any;
|
|
|
|
|
+ if (permissions && !permissions.allow_order_access) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{ type: 'text', text: JSON.stringify({ error: 'Order access not allowed for this store' }) }],
|
|
|
|
|
+ isError: true
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Fetch the specific order
|
|
|
|
|
+ const order = await fetchOrder(shop_id, order_id);
|
|
|
|
|
+
|
|
|
|
|
+ // Format for LLM
|
|
|
|
|
+ const formattedOrder = formatOrderForLlm(order);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ order: formattedOrder
|
|
|
|
|
+ })
|
|
|
|
|
+ }]
|
|
|
|
|
+ };
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[MCP WooCommerce] Error fetching order:', error);
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ error: `Failed to fetch order: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
|
|
|
+ })
|
|
|
|
|
+ }],
|
|
|
|
|
+ isError: true
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Handle woocommerce_list_orders tool
|
|
* Handle woocommerce_list_orders tool
|
|
|
*/
|
|
*/
|
|
@@ -258,34 +349,78 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
|
|
|
|
|
|
|
|
// Handle customer_email: WooCommerce requires customer ID (integer), not email
|
|
// Handle customer_email: WooCommerce requires customer ID (integer), not email
|
|
|
if (customer_email) {
|
|
if (customer_email) {
|
|
|
- // First, search for customer by email to get their ID
|
|
|
|
|
- const customers = await fetchCustomers(shop_id, 1, 1, { email: customer_email });
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // First, search for customer by email to get their ID
|
|
|
|
|
+ // Fetch more customers (up to 10) to handle edge cases
|
|
|
|
|
+ const customers = await fetchCustomers(shop_id, 1, 10, { email: customer_email });
|
|
|
|
|
|
|
|
- if (customers.length === 0) {
|
|
|
|
|
- return {
|
|
|
|
|
- content: [{
|
|
|
|
|
- type: 'text',
|
|
|
|
|
- text: JSON.stringify({
|
|
|
|
|
- count: 0,
|
|
|
|
|
- limit: actualLimit,
|
|
|
|
|
- filters_applied: {
|
|
|
|
|
- created_after,
|
|
|
|
|
- created_before,
|
|
|
|
|
- updated_after,
|
|
|
|
|
- updated_before,
|
|
|
|
|
- customer_email,
|
|
|
|
|
- customer_name,
|
|
|
|
|
- status
|
|
|
|
|
- },
|
|
|
|
|
- orders: [],
|
|
|
|
|
- message: `No customer found with email: ${customer_email}`
|
|
|
|
|
- })
|
|
|
|
|
- }]
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ console.log(`[MCP WooCommerce] Customer search for email "${customer_email}": found ${customers.length} customers`);
|
|
|
|
|
+
|
|
|
|
|
+ if (customers.length === 0) {
|
|
|
|
|
+ // Fallback: Try fetching recent orders and filter by billing email locally
|
|
|
|
|
+ console.log(`[MCP WooCommerce] No customer found, trying fallback: fetch orders and filter by billing email`);
|
|
|
|
|
+
|
|
|
|
|
+ const fallbackOrders = await fetchAllOrdersPages(shop_id, 5, filters);
|
|
|
|
|
+ const emailLower = customer_email.toLowerCase();
|
|
|
|
|
+ const matchedOrders = fallbackOrders.filter(order =>
|
|
|
|
|
+ order.billing.email && order.billing.email.toLowerCase() === emailLower
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (matchedOrders.length === 0) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ count: 0,
|
|
|
|
|
+ limit: actualLimit,
|
|
|
|
|
+ filters_applied: {
|
|
|
|
|
+ created_after,
|
|
|
|
|
+ created_before,
|
|
|
|
|
+ updated_after,
|
|
|
|
|
+ updated_before,
|
|
|
|
|
+ customer_email,
|
|
|
|
|
+ customer_name,
|
|
|
|
|
+ status
|
|
|
|
|
+ },
|
|
|
|
|
+ orders: [],
|
|
|
|
|
+ message: `No orders found for email: ${customer_email}`
|
|
|
|
|
+ })
|
|
|
|
|
+ }]
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Use the matched orders
|
|
|
|
|
+ orders = matchedOrders.slice(0, actualLimit);
|
|
|
|
|
+ const formattedOrders = orders.map(formatOrderForLlm);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ count: formattedOrders.length,
|
|
|
|
|
+ limit: actualLimit,
|
|
|
|
|
+ filters_applied: {
|
|
|
|
|
+ created_after,
|
|
|
|
|
+ created_before,
|
|
|
|
|
+ updated_after,
|
|
|
|
|
+ updated_before,
|
|
|
|
|
+ customer_email,
|
|
|
|
|
+ customer_name,
|
|
|
|
|
+ status
|
|
|
|
|
+ },
|
|
|
|
|
+ orders: formattedOrders,
|
|
|
|
|
+ note: 'Found via billing email search (customer not found in customer list)'
|
|
|
|
|
+ })
|
|
|
|
|
+ }]
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Use the customer ID for filtering
|
|
|
|
|
- filters.customer = customers[0].id.toString();
|
|
|
|
|
|
|
+ // Use the customer ID for filtering
|
|
|
|
|
+ filters.customer = customers[0].id.toString();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[MCP WooCommerce] Error searching customer by email:', error);
|
|
|
|
|
+ // Continue without customer filter (will fallback to other filters if available)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Fetch orders from WooCommerce API
|
|
// Fetch orders from WooCommerce API
|
|
@@ -450,6 +585,21 @@ async function handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
|
|
|
|
|
|
|
|
// Route to appropriate handler
|
|
// Route to appropriate handler
|
|
|
switch (name) {
|
|
switch (name) {
|
|
|
|
|
+ case 'woocommerce_get_order':
|
|
|
|
|
+ const orderValidation = validateParams(args, ['shop_id', 'order_id']);
|
|
|
|
|
+ if (!orderValidation.valid) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ error: `Missing required parameters: ${orderValidation.missing?.join(', ')}`
|
|
|
|
|
+ })
|
|
|
|
|
+ }],
|
|
|
|
|
+ isError: true
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return await handleGetOrder(args);
|
|
|
|
|
+
|
|
|
case 'woocommerce_list_orders':
|
|
case 'woocommerce_list_orders':
|
|
|
return await handleListOrders(args);
|
|
return await handleListOrders(args);
|
|
|
|
|
|