#
tokens: 26528/50000 12/13 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/boldcommerce/magento2-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .clinerules
├── .gitignore
├── LICENSE
├── mcp-instructions
│   ├── modelcontextprotocol.md
│   └── typescript-sdk.md
├── mcp-server.js
├── memory-bank
│   ├── activeContext.md
│   ├── productContext.md
│   ├── progress.md
│   ├── projectbrief.md
│   ├── systemPatterns.md
│   └── techContext.md
├── package-lock.json
├── package.json
├── README.md
└── test-mcp-server.js
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Node.js
 2 | node_modules/
 3 | npm-debug.log
 4 | yarn-debug.log
 5 | yarn-error.log
 6 | .npm/
 7 | .yarn/
 8 | *.tgz
 9 | .pnp.*
10 | .yarn-integrity
11 | 
12 | # Environment variables
13 | .env
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 | 
19 | # Logs
20 | logs/
21 | *.log
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | lerna-debug.log*
26 | 
27 | # Build output
28 | dist/
29 | build/
30 | out/
31 | .output/
32 | 
33 | # Docker
34 | .docker/
35 | docker-compose.override.yml
36 | 
37 | # IDE - VSCode
38 | .vscode/*
39 | !.vscode/settings.json
40 | !.vscode/tasks.json
41 | !.vscode/launch.json
42 | !.vscode/extensions.json
43 | *.code-workspace
44 | 
45 | # IDE - JetBrains (WebStorm, IntelliJ, etc)
46 | .idea/
47 | *.iml
48 | *.iws
49 | *.ipr
50 | .idea_modules/
51 | 
52 | # IDE - Other
53 | .project
54 | .classpath
55 | .c9/
56 | *.launch
57 | .settings/
58 | *.sublime-workspace
59 | .netbeans/
60 | 
61 | # OS specific
62 | .DS_Store
63 | .DS_Store?
64 | ._*
65 | .Spotlight-V100
66 | .Trashes
67 | ehthumbs.db
68 | Thumbs.db
69 | 
70 | # Testing
71 | coverage/
72 | .nyc_output/
73 | 
74 | # Temporary files
75 | tmp/
76 | temp/
77 | .tmp/
78 | .temp/
79 | *.tmp
80 | *.temp
81 | 
82 | # MCP specific
83 | claude_desktop_config.json
84 | 
```

--------------------------------------------------------------------------------
/.clinerules:
--------------------------------------------------------------------------------

```
 1 | # Cline Rules for Magento 2 MCP Server
 2 | 
 3 | ## Project Structure
 4 | - The main server implementation is in `mcp-server.js`
 5 | - The test client is in `test-mcp-server.js`
 6 | - Memory Bank files are stored in the `memory-bank` directory
 7 | 
 8 | ## Coding Patterns
 9 | - Use camelCase for variable and function names
10 | - Use PascalCase for class names
11 | - Use UPPER_SNAKE_CASE for constants
12 | - Use async/await for asynchronous operations
13 | - Use try/catch blocks for error handling
14 | - Use the zod library for input validation
15 | - Use the axios library for HTTP requests
16 | - Use the dotenv library for environment variables
17 | 
18 | ## MCP Tool Implementation Pattern
19 | When implementing a new MCP tool, follow this pattern:
20 | ```javascript
21 | server.tool(
22 |   "tool_name",
23 |   "Tool description",
24 |   {
25 |     param1: z.string().describe("Parameter 1 description"),
26 |     param2: z.number().optional().describe("Parameter 2 description")
27 |   },
28 |   async ({ param1, param2 }) => {
29 |     try {
30 |       // Implementation
31 |       return {
32 |         content: [
33 |           {
34 |             type: "text",
35 |             text: JSON.stringify(result, null, 2)
36 |           }
37 |         ]
38 |       };
39 |     } catch (error) {
40 |       return {
41 |         content: [
42 |           {
43 |             type: "text",
44 |             text: `Error: ${error.message}`
45 |           }
46 |         ],
47 |         isError: true
48 |       };
49 |     }
50 |   }
51 | );
52 | ```
53 | 
54 | ## Date Handling
55 | - Use ISO 8601 format (YYYY-MM-DD) for date representation
56 | - Implement a date parser that can handle relative date expressions
57 | - Use the date-fns library for date manipulation
58 | 
59 | ## Error Handling
60 | - Always wrap API calls in try/catch blocks
61 | - Return detailed error messages to the client
62 | - Log errors to the console for debugging
63 | - Include the original error message in the response
64 | 
65 | ## Response Formatting
66 | - Use JSON.stringify with null, 2 for pretty-printing JSON responses
67 | - Include metadata about the query in the response
68 | - Format numbers with appropriate precision
69 | - Format dates in a human-readable format
70 | 
71 | ## Testing
72 | - Use the test client to verify tool functionality
73 | - Test with various input parameters
74 | - Test error handling
75 | - Test with edge cases
76 | 
77 | ## Documentation
78 | - Document all tools with clear descriptions
79 | - Document all parameters with descriptions
80 | - Document the expected response format
81 | - Document any error conditions
82 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Magento 2 MCP Server
  2 | 
  3 | This is a Model Context Protocol (MCP) server that connects to a Magento 2 REST API, allowing Claude and other MCP clients to query product information from a Magento store.
  4 | 
  5 | ## Features
  6 | 
  7 | ### Product Features
  8 | - Query product information by SKU or ID
  9 | - Search for products using various criteria
 10 | - Get product categories
 11 | - Get related products
 12 | - Get product stock information
 13 | - Get product attributes
 14 | - Update product attributes by specifying attribute code and value
 15 | - Advanced product search with filtering and sorting
 16 | 
 17 | ### Customer Features
 18 | - Get all ordered products for a customer by email address
 19 | 
 20 | ### Order and Revenue Features
 21 | - Get order count for specific date ranges
 22 | - Get revenue for specific date ranges
 23 | - Get revenue filtered by country for specific date ranges
 24 | - Get product sales statistics including quantity sold and top-selling products
 25 | - Support for relative date expressions like "today", "yesterday", "last week", "this month", "YTD"
 26 | - Support for country filtering using both country codes and country names
 27 | 
 28 | ## Prerequisites
 29 | 
 30 | - Node.js (v14 or higher)
 31 | - A Magento 2 instance with REST API access
 32 | - API token for the Magento 2 instance
 33 | 
 34 | ## Installation
 35 | 
 36 | 1. Clone this repository
 37 | 2. Install dependencies:
 38 | 
 39 | ```bash
 40 | npm install
 41 | ```
 42 | 
 43 | ## Usage
 44 | 
 45 | ### Running the server directly
 46 | 
 47 | ```bash
 48 | node mcp-server.js
 49 | ```
 50 | 
 51 | ### Testing with the test client
 52 | 
 53 | ```bash
 54 | node test-mcp-server.js
 55 | ```
 56 | 
 57 | ### Using with Claude Desktop
 58 | 
 59 | 1. Check your path node with `which node`
 60 | 2. Go to the Developer settings and click "Edit config". This will open a JSON file.
 61 | 3. Add the following snippet within the `mcpServers`:
 62 | 
 63 | ```
 64 |     "magento2": {
 65 |       "command": "/path/to/your/node",
 66 |       "args": ["/path/to/mcp-server.js"],
 67 |       "env": {
 68 |         "MAGENTO_BASE_URL": "https://YOUR_DOMAIN/rest/V1",
 69 |         "MAGENTO_API_TOKEN": "your-api-token"
 70 |       }
 71 |     }
 72 | ```
 73 | 
 74 | 3. Replace `/path/to/your/node` with the path you checked in step 1
 75 | 4. Replace `/path/to/mcp-server.js` with the path where you cloned this repo
 76 | 5. You can get an API token from System > Integrations in the Magento admin
 77 | 6. Restart Claude Desktop.
 78 | 7. You should now be able to ask Claude questions about products in your Magento store.
 79 | 
 80 | ## Available Tools
 81 | 
 82 | The server exposes the following tools:
 83 | 
 84 | ### Product Tools
 85 | - `get_product_by_sku`: Get detailed information about a product by its SKU
 86 | - `search_products`: Search for products using Magento search criteria
 87 | - `get_product_categories`: Get categories for a specific product by SKU
 88 | - `get_related_products`: Get products related to a specific product by SKU
 89 | - `get_product_stock`: Get stock information for a product by SKU
 90 | - `get_product_attributes`: Get all attributes for a product by SKU
 91 | - `get_product_by_id`: Get detailed information about a product by its ID
 92 | - `advanced_product_search`: Search for products with advanced filtering options
 93 | - `update_product_attribute`: Update a specific attribute of a product by SKU
 94 | 
 95 | ### Customer Tools
 96 | - `get_customer_ordered_products_by_email`: Get all ordered products for a customer by email address
 97 | 
 98 | ### Order and Revenue Tools
 99 | - `get_order_count`: Get the number of orders for a given date range
100 | - `get_revenue`: Get the total revenue for a given date range
101 | - `get_revenue_by_country`: Get revenue filtered by country for a given date range
102 | - `get_product_sales`: Get statistics about the quantity of products sold in a given date range
103 | 
104 | ## Example Queries for Claude
105 | 
106 | Once the MCP server is connected to Claude Desktop, you can ask questions like:
107 | 
108 | ### Product Queries
109 | - "What products do you have that are shirts?"
110 | - "Tell me about product with SKU SKU-xxx"
111 | - "What categories does product SKU-xxx belong to?"
112 | - "Are there any related products to SKU-SKU-xxx?"
113 | - "What's the stock status of product SKU-xxx?"
114 | - "Show me all products sorted by price"
115 | - "Update the price of product SKU-xxx to $49.99"
116 | - "Change the description of product ABC-123 to describe it as water-resistant"
117 | - "Set the status of product XYZ-456 to 'enabled'"
118 | 
119 | ### Customer Queries
120 | - "What products has customer [email protected] ordered?"
121 | - "Show me the order history and products for customer with email [email protected]"
122 | 
123 | ### Order and Revenue Queries
124 | - "How many orders do we have today?"
125 | - "What's our order count for last week?"
126 | - "How much revenue did we generate yesterday?"
127 | - "What was our total revenue last month?"
128 | - "How much revenue did we make in The Netherlands this year to date?"
129 | - "What's our revenue in Germany for the last week?"
130 | - "Compare our revenue between the US and Canada for this month"
131 | - "What's our average order value for completed orders this month?"
132 | - "How many products did we sell last month?"
133 | - "What are our top-selling products this year?"
134 | - "What's the average number of products per order?"
135 | - "How many units of product XYZ-123 did we sell in Germany last quarter?"
136 | - "Which products generated the most revenue in the US this month?"
137 | 
138 | 
139 | ## Development
140 | 
141 | ### SSL Certificate Verification
142 | 
143 | For development purposes, the server is configured to bypass SSL certificate verification. In a production environment, you should use proper SSL certificates and remove the `httpsAgent` configuration from the `callMagentoApi` function.
144 | 
145 | ### Adding New Tools
146 | 
147 | To add new tools, follow the pattern in the existing code. Each tool is defined with:
148 | 
149 | 1. A unique name
150 | 2. A description
151 | 3. Input parameters with validation using Zod
152 | 4. An async handler function that processes the request and returns a response
153 | 
154 | ## License
155 | 
156 | ISC
157 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-magento2",
 3 |   "version": "1.0.0",
 4 |   "description": "",
 5 |   "main": "claude-magento-client.js",
 6 |   "scripts": {
 7 |     "test": "echo \"Error: no test specified\" && exit 1"
 8 |   },
 9 |   "keywords": [],
10 |   "author": "",
11 |   "license": "ISC",
12 |   "dependencies": {
13 |     "@anthropic-ai/sdk": "^0.39.0",
14 |     "@modelcontextprotocol/sdk": "^1.6.1",
15 |     "axios": "^1.8.1",
16 |     "body-parser": "^1.20.3",
17 |     "date-fns": "^3.6.0",
18 |     "dotenv": "^16.4.7",
19 |     "express": "^4.21.2",
20 |     "zod": "^3.24.2"
21 |   }
22 | }
23 | 
```

--------------------------------------------------------------------------------
/memory-bank/projectbrief.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Project Brief: Magento 2 MCP Server
 2 | 
 3 | ## Overview
 4 | This project implements a Model Context Protocol (MCP) server that provides tools for interacting with a Magento 2 e-commerce platform. The server exposes various capabilities through tools that can be used to query and manipulate Magento 2 data.
 5 | 
 6 | ## Core Requirements
 7 | 1. Provide tools to query product information from Magento 2
 8 | 2. Provide tools to query customer information from Magento 2
 9 | 3. Provide tools to query order information from Magento 2
10 | 4. Provide tools to query revenue and sales metrics from Magento 2
11 | 5. Support filtering by date ranges, including relative dates like "today", "last week", "YTD"
12 | 6. Support filtering by geographic regions like countries
13 | 
14 | ## Goals
15 | - Enable natural language queries about Magento 2 store data
16 | - Provide accurate and timely information about sales, orders, and revenue
17 | - Support business intelligence and reporting needs
18 | - Make e-commerce data easily accessible through conversational interfaces
19 | 
20 | ## Success Criteria
21 | - Successfully retrieve accurate order counts for specified date ranges
22 | - Successfully retrieve accurate revenue figures for specified date ranges
23 | - Successfully filter data by geographic regions
24 | - Support common date range expressions like "today", "yesterday", "last week", "this month", "YTD"
25 | - Provide clear and concise responses to queries
26 | 
27 | ## Constraints
28 | - Requires valid Magento 2 API credentials
29 | - Depends on the Magento 2 API being available and responsive
30 | - Limited to the data and capabilities exposed by the Magento 2 API
31 | 
```

--------------------------------------------------------------------------------
/memory-bank/productContext.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Product Context: Magento 2 MCP Server
 2 | 
 3 | ## Why This Project Exists
 4 | The Magento 2 MCP Server exists to bridge the gap between natural language interfaces (like Claude) and the structured data in a Magento 2 e-commerce platform. It enables users to query business-critical information using conversational language rather than having to learn complex query languages or navigate through multiple admin screens.
 5 | 
 6 | ## Problems It Solves
 7 | 1. **Accessibility of Data**: E-commerce data is often locked behind complex admin interfaces or requires technical knowledge to query. This server makes that data accessible through natural language.
 8 | 
 9 | 2. **Time Efficiency**: Instead of navigating through multiple screens or running reports, users can simply ask questions like "how many orders do we have today" or "what is our revenue in The Netherlands this YTD".
10 | 
11 | 3. **Decision Support**: By making it easier to access sales and revenue data, the server supports better and faster business decision-making.
12 | 
13 | 4. **Integration with AI Assistants**: The MCP server enables AI assistants like Claude to directly interact with Magento 2 data, expanding their capabilities in the e-commerce domain.
14 | 
15 | ## How It Should Work
16 | 1. The user asks a question about Magento 2 data in natural language.
17 | 2. The AI assistant (Claude) interprets the question and calls the appropriate MCP tool with the right parameters.
18 | 3. The MCP server translates these parameters into Magento 2 API calls.
19 | 4. The server processes the response from Magento 2 and formats it in a way that's easy for the AI assistant to understand.
20 | 5. The AI assistant presents the information to the user in a natural, conversational way.
21 | 
22 | ## User Experience Goals
23 | 1. **Simplicity**: Users should be able to get the information they need without understanding the underlying technical details.
24 | 
25 | 2. **Accuracy**: The data provided should be accurate and consistent with what's available in the Magento 2 admin interface.
26 | 
27 | 3. **Contextual Understanding**: The system should understand relative date ranges like "today", "last week", or "YTD" without requiring explicit date formatting.
28 | 
29 | 4. **Geographical Filtering**: Users should be able to filter data by geographical regions like countries or regions.
30 | 
31 | 5. **Comprehensive Coverage**: The system should cover all key e-commerce metrics, including orders, revenue, products, and customers.
32 | 
33 | 6. **Responsiveness**: Queries should be processed quickly, providing near real-time access to e-commerce data.
34 | 
```

--------------------------------------------------------------------------------
/memory-bank/activeContext.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Active Context: Magento 2 MCP Server
 2 | 
 3 | ## Current Work Focus
 4 | The current focus is on enhancing and refining the MCP server tools for fetching data about revenue and orders. This includes:
 5 | 
 6 | 1. Improving error handling for the new tools
 7 | 2. Adding comprehensive documentation for the new tools
 8 | 3. Creating additional test cases for the new functionality
 9 | 4. Optimizing performance for large result sets
10 | 
11 | ## Recent Changes
12 | - Implementation of the `get_order_count` tool to retrieve the number of orders for a given date range
13 | - Implementation of the `get_revenue` tool to retrieve the total revenue for a given date range
14 | - Implementation of the `get_revenue_by_country` tool to retrieve revenue filtered by country for a given date range
15 | - Implementation of date parsing utilities to handle relative date expressions like "today", "last week", "YTD"
16 | - Implementation of country normalization to handle both country codes and names
17 | - Update of the test client to test the new tools
18 | - Update of the Claude client to demonstrate the new tools
19 | 
20 | ## Next Steps
21 | 1. Implement pagination handling for large result sets
22 | 2. Add caching mechanism for frequently requested data
23 | 3. Improve error handling for Magento 2 API rate limits
24 | 4. Add comprehensive documentation for the API endpoints and parameters
25 | 5. Create automated tests for the server functionality
26 | 6. Implement a logging system for debugging and monitoring
27 | 
28 | ## Active Decisions and Considerations
29 | 
30 | ### Date Range Parsing
31 | We need to implement a robust date parsing system that can handle various relative date expressions:
32 | - "today" -> Current day
33 | - "yesterday" -> Previous day
34 | - "last week" -> Previous 7 days
35 | - "this month" -> Current month
36 | - "last month" -> Previous month
37 | - "YTD" (Year to Date) -> January 1st of current year to current date
38 | 
39 | ### Country Filtering
40 | For the `get_revenue_by_country` tool, we need to:
41 | - Determine how countries are represented in the Magento 2 API (country codes, full names, etc.)
42 | - Handle case-insensitive matching for country names
43 | - Support filtering by multiple countries
44 | 
45 | ### Performance Optimization
46 | For large date ranges or high-volume stores, we need to consider:
47 | - Pagination of results
48 | - Efficient filtering at the API level rather than client-side
49 | - Potential caching of frequently requested data
50 | 
51 | ### Error Handling
52 | We need robust error handling for:
53 | - Invalid date ranges (e.g., end date before start date)
54 | - Unknown country names
55 | - API errors from Magento 2
56 | - Rate limiting or timeout issues
57 | 
58 | ### Response Formatting
59 | The response format should be:
60 | - Consistent across all tools
61 | - Easy for Claude to parse and present to users
62 | - Include metadata about the query (date range, filters applied, etc.)
63 | - Include summary statistics where appropriate
64 | 
```

--------------------------------------------------------------------------------
/memory-bank/progress.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Progress: Magento 2 MCP Server
 2 | 
 3 | ## What Works
 4 | - Basic MCP server setup with stdio transport
 5 | - Authentication with Magento 2 API using API tokens
 6 | - Product-related tools:
 7 |   - `get_product_by_sku`: Get detailed information about a product by its SKU
 8 |   - `search_products`: Search for products using Magento search criteria
 9 |   - `get_product_categories`: Get categories for a specific product by SKU
10 |   - `get_related_products`: Get products related to a specific product by SKU
11 |   - `get_product_stock`: Get stock information for a product by SKU
12 |   - `get_product_attributes`: Get all attributes for a product by SKU
13 |   - `get_product_by_id`: Get detailed information about a product by its ID
14 |   - `advanced_product_search`: Search for products with advanced filtering options
15 |   - `update_product_attribute`: Update a specific attribute of a product by SKU
16 | - Customer-related tools:
17 |   - `get_customer_ordered_products_by_email`: Get all ordered products for a customer by email address
18 | - Test client for verifying server functionality
19 | 
20 | ## What's Left to Build
21 | - Enhanced error handling for the new tools
22 | - Documentation for the new tools
23 | - Additional test cases for the new functionality
24 | 
25 | ## Current Status
26 | - The basic MCP server infrastructure is in place and working
27 | - Product-related tools are implemented and tested
28 | - Customer-related tools are implemented
29 | - Order and revenue related tools are implemented:
30 |   - `get_order_count`: Get the number of orders for a given date range
31 |   - `get_revenue`: Get the total revenue for a given date range
32 |   - `get_revenue_by_country`: Get revenue filtered by country for a given date range
33 |   - `get_product_sales`: Get statistics about the quantity of products sold in a given date range
34 | - Date parsing utilities are implemented, supporting:
35 |   - "today"
36 |   - "yesterday"
37 |   - "this week"
38 |   - "last week"
39 |   - "this month"
40 |   - "last month"
41 |   - "ytd" (Year to Date)
42 |   - "last year"
43 |   - Specific dates in ISO format
44 |   - Date ranges in "YYYY-MM-DD to YYYY-MM-DD" format
45 | - Country filtering functionality is implemented, supporting:
46 |   - Country codes (e.g., "US", "NL", "GB")
47 |   - Country names (e.g., "United States", "The Netherlands", "United Kingdom")
48 |   - Common variations (e.g., "USA", "Holland", "UK")
49 | 
50 | ## Known Issues
51 | - No comprehensive error handling for Magento 2 API rate limits
52 | - No caching mechanism for frequently requested data
53 | - No pagination handling for large result sets
54 | - No authentication mechanism for the MCP server itself (relies on the security of the stdio transport)
55 | - No logging system for debugging and monitoring
56 | - No automated tests for the server functionality
57 | - No documentation for the API endpoints and parameters
58 | 
59 | ## Next Milestone
60 | Enhance the existing tools with additional features and optimizations:
61 | - Implement pagination handling for large result sets
62 | - Add caching mechanism for frequently requested data
63 | - Improve error handling for Magento 2 API rate limits
64 | - Add comprehensive documentation for the API endpoints and parameters
65 | - Create automated tests for the server functionality
66 | - Implement a logging system for debugging and monitoring
67 | 
68 | This milestone will be considered complete when all these enhancements are implemented and tested.
69 | 
```

--------------------------------------------------------------------------------
/memory-bank/techContext.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Technical Context: Magento 2 MCP Server
 2 | 
 3 | ## Technologies Used
 4 | 
 5 | ### Core Technologies
 6 | - **Node.js**: The runtime environment for the MCP server
 7 | - **JavaScript**: The programming language used for implementation
 8 | - **Model Context Protocol (MCP)**: The protocol for communication between Claude and the server
 9 | - **Magento 2 REST API**: The API used to interact with the Magento 2 e-commerce platform
10 | 
11 | ### Key Libraries and Dependencies
12 | - **@modelcontextprotocol/sdk**: The official MCP SDK for implementing MCP servers and clients
13 | - **axios**: HTTP client for making requests to the Magento 2 API
14 | - **zod**: Schema validation library for defining tool input schemas
15 | - **dotenv**: For loading environment variables from a .env file
16 | - **date-fns**: For date manipulation and parsing
17 | 
18 | ## Development Setup
19 | 
20 | ### Environment Variables
21 | The server requires the following environment variables:
22 | - `MAGENTO_BASE_URL`: The base URL of the Magento 2 REST API
23 | - `MAGENTO_API_TOKEN`: The API token for authenticating with the Magento 2 API
24 | 
25 | These can be set in a `.env` file in the project root or provided directly when running the server.
26 | 
27 | ### Running the Server
28 | The server can be run directly with Node.js:
29 | ```bash
30 | node mcp-server.js
31 | ```
32 | 
33 | Or it can be run through the test client:
34 | ```bash
35 | node test-mcp-server.js
36 | ```
37 | 
38 | ### Testing
39 | The `test-mcp-server.js` file provides a simple client for testing the MCP server. It connects to the server, lists available tools, and tests some of the tools with sample parameters.
40 | 
41 | ## Technical Constraints
42 | 
43 | ### Magento 2 API Limitations
44 | - The Magento 2 API may have rate limits that could affect performance
45 | - Some operations may be slow for large datasets
46 | - Not all Magento 2 data is exposed through the API
47 | - API structure and capabilities may vary between Magento 2 versions
48 | 
49 | ### MCP Protocol Constraints
50 | - Communication is synchronous and request-response based
51 | - Tools must have well-defined input schemas
52 | - Complex data structures must be serialized to JSON
53 | 
54 | ### Performance Considerations
55 | - Large result sets should be paginated
56 | - Expensive operations should be optimized or cached
57 | - Error handling should be robust to prevent crashes
58 | 
59 | ## Dependencies
60 | 
61 | ### External Systems
62 | - **Magento 2 E-commerce Platform**: The primary data source for the MCP server
63 | - **Claude AI Assistant**: The primary client for the MCP server
64 | 
65 | ### Internal Dependencies
66 | - **callMagentoApi**: Helper function for making authenticated requests to the Magento 2 API
67 | - **Date parsing utilities**: For converting relative date expressions to concrete date ranges
68 | - **Formatting functions**: For formatting API responses for better readability
69 | 
70 | ## Integration Points
71 | 
72 | ### Magento 2 API Endpoints
73 | - `/orders`: For retrieving order information
74 | - `/invoices`: For retrieving invoice and revenue information
75 | - `/customers`: For retrieving customer information
76 | - `/products`: For retrieving product information
77 | - `/store/storeConfigs`: For retrieving store configuration information
78 | 
79 | ### MCP Tools
80 | The server exposes various tools for interacting with the Magento 2 API, including:
81 | - Tools for retrieving product information
82 | - Tools for searching products
83 | - Tools for retrieving customer information
84 | - Tools for retrieving order information
85 | - Tools for retrieving revenue information
86 | 
```

--------------------------------------------------------------------------------
/test-mcp-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
  3 | const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
  4 | 
  5 | async function main() {
  6 |   try {
  7 |     console.log('Connecting to Magento MCP Server...');
  8 |     
  9 |     // Create a transport that will start the server process
 10 |     const transport = new StdioClientTransport({
 11 |       command: 'node',
 12 |       args: ['./mcp-server.js']
 13 |     });
 14 |     
 15 |     // Create a client
 16 |     const client = new Client(
 17 |       {
 18 |         name: 'test-client',
 19 |         version: '1.0.0'
 20 |       },
 21 |       {
 22 |         capabilities: {
 23 |           tools: {}
 24 |         }
 25 |       }
 26 |     );
 27 |     
 28 |     // Connect to the server
 29 |     await client.connect(transport);
 30 |     console.log('Connected to Magento MCP Server');
 31 |     
 32 |     // List available tools
 33 |     const tools = await client.listTools();
 34 |     console.log('Available tools:');
 35 |     tools.tools.forEach(tool => {
 36 |       console.log(`- ${tool.name}: ${tool.description}`);
 37 |     });
 38 |     
 39 |     // Test a tool: search for products
 40 |     console.log('\nSearching for products with "shirt"...');
 41 |     const searchResult = await client.callTool({
 42 |       name: 'search_products',
 43 |       arguments: {
 44 |         query: 'shirt',
 45 |         page_size: 5
 46 |       }
 47 |     });
 48 |     
 49 |     console.log('Search results:');
 50 |     console.log(searchResult.content[0].text);
 51 |     
 52 |     // Test the order count tool
 53 |     console.log('\nGetting order count for today...');
 54 |     try {
 55 |       const orderCountResult = await client.callTool({
 56 |         name: 'get_order_count',
 57 |         arguments: {
 58 |           date_range: 'today'
 59 |         }
 60 |       });
 61 |       
 62 |       console.log('Order count:');
 63 |       console.log(orderCountResult.content[0].text);
 64 |     } catch (error) {
 65 |       console.log('Error getting order count:', error.message);
 66 |     }
 67 |     
 68 |     // Test the revenue tool
 69 |     console.log('\nGetting revenue for last week...');
 70 |     try {
 71 |       const revenueResult = await client.callTool({
 72 |         name: 'get_revenue',
 73 |         arguments: {
 74 |           date_range: 'last week',
 75 |           include_tax: true
 76 |         }
 77 |       });
 78 |       
 79 |       console.log('Revenue:');
 80 |       console.log(revenueResult.content[0].text);
 81 |     } catch (error) {
 82 |       console.log('Error getting revenue:', error.message);
 83 |     }
 84 |     
 85 |     // Test the revenue by country tool
 86 |     console.log('\nGetting revenue for The Netherlands this YTD...');
 87 |     try {
 88 |       const revenueByCountryResult = await client.callTool({
 89 |         name: 'get_revenue_by_country',
 90 |         arguments: {
 91 |           date_range: 'ytd',
 92 |           country: 'The Netherlands',
 93 |           include_tax: true
 94 |         }
 95 |       });
 96 |       
 97 |       console.log('Revenue by country:');
 98 |       console.log(revenueByCountryResult.content[0].text);
 99 |     } catch (error) {
100 |       console.log('Error getting revenue by country:', error.message);
101 |     }
102 |     
103 |     // Test the product sales tool
104 |     console.log('\nGetting product sales statistics for last month...');
105 |     try {
106 |       const productSalesResult = await client.callTool({
107 |         name: 'get_product_sales',
108 |         arguments: {
109 |           date_range: 'last month'
110 |         }
111 |       });
112 |       
113 |       console.log('Product sales statistics:');
114 |       console.log(productSalesResult.content[0].text);
115 |     } catch (error) {
116 |       console.log('Error getting product sales statistics:', error.message);
117 |     }
118 |     
119 |     // Test the customer ordered products by email tool
120 |     console.log('\nGetting ordered products for customer by email...');
121 |     try {
122 |       const customerOrdersResult = await client.callTool({
123 |         name: 'get_customer_ordered_products_by_email',
124 |         arguments: {
125 |           email: '[email protected]' // Replace with a valid customer email
126 |         }
127 |       });
128 |       
129 |       console.log('Customer ordered products:');
130 |       console.log(customerOrdersResult.content[0].text);
131 |     } catch (error) {
132 |       console.log('Error getting customer ordered products:', error.message);
133 |     }
134 |     
135 |     // Close the connection
136 |     await client.close();
137 |     console.log('Connection closed');
138 |   } catch (error) {
139 |     console.error('Error:', error);
140 |   }
141 | }
142 | 
143 | main().catch(console.error);
144 | 
```

--------------------------------------------------------------------------------
/mcp-instructions/typescript-sdk.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)
  2 | 
  3 | ## Table of Contents
  4 | - [MCP TypeScript SDK  ](#mcp-typescript-sdk--)
  5 |   - [Table of Contents](#table-of-contents)
  6 |   - [Overview](#overview)
  7 |   - [Installation](#installation)
  8 |   - [Quick Start](#quick-start)
  9 |   - [What is MCP?](#what-is-mcp)
 10 |   - [Core Concepts](#core-concepts)
 11 |     - [Server](#server)
 12 |     - [Resources](#resources)
 13 |     - [Tools](#tools)
 14 |     - [Prompts](#prompts)
 15 |   - [Running Your Server](#running-your-server)
 16 |     - [stdio](#stdio)
 17 |     - [HTTP with SSE](#http-with-sse)
 18 |     - [Testing and Debugging](#testing-and-debugging)
 19 |   - [Examples](#examples)
 20 |     - [Echo Server](#echo-server)
 21 |     - [SQLite Explorer](#sqlite-explorer)
 22 |   - [Advanced Usage](#advanced-usage)
 23 |     - [Low-Level Server](#low-level-server)
 24 |     - [Writing MCP Clients](#writing-mcp-clients)
 25 |   - [Documentation](#documentation)
 26 |   - [Contributing](#contributing)
 27 |   - [License](#license)
 28 | 
 29 | ## Overview
 30 | 
 31 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:
 32 | 
 33 | - Build MCP clients that can connect to any MCP server
 34 | - Create MCP servers that expose resources, prompts and tools
 35 | - Use standard transports like stdio and SSE
 36 | - Handle all MCP protocol messages and lifecycle events
 37 | 
 38 | ## Installation
 39 | 
 40 | ```bash
 41 | npm install @modelcontextprotocol/sdk
 42 | ```
 43 | 
 44 | ## Quick Start
 45 | 
 46 | Let's create a simple MCP server that exposes a calculator tool and some data:
 47 | 
 48 | ```typescript
 49 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
 50 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 51 | import { z } from "zod";
 52 | 
 53 | // Create an MCP server
 54 | const server = new McpServer({
 55 |   name: "Demo",
 56 |   version: "1.0.0"
 57 | });
 58 | 
 59 | // Add an addition tool
 60 | server.tool("add",
 61 |   { a: z.number(), b: z.number() },
 62 |   async ({ a, b }) => ({
 63 |     content: [{ type: "text", text: String(a + b) }]
 64 |   })
 65 | );
 66 | 
 67 | // Add a dynamic greeting resource
 68 | server.resource(
 69 |   "greeting",
 70 |   new ResourceTemplate("greeting://{name}", { list: undefined }),
 71 |   async (uri, { name }) => ({
 72 |     contents: [{
 73 |       uri: uri.href,
 74 |       text: `Hello, ${name}!`
 75 |     }]
 76 |   })
 77 | );
 78 | 
 79 | // Start receiving messages on stdin and sending messages on stdout
 80 | const transport = new StdioServerTransport();
 81 | await server.connect(transport);
 82 | ```
 83 | 
 84 | ## What is MCP?
 85 | 
 86 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
 87 | 
 88 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
 89 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
 90 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
 91 | - And more!
 92 | 
 93 | ## Core Concepts
 94 | 
 95 | ### Server
 96 | 
 97 | The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
 98 | 
 99 | ```typescript
100 | const server = new McpServer({
101 |   name: "My App",
102 |   version: "1.0.0"
103 | });
104 | ```
105 | 
106 | ### Resources
107 | 
108 | Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
109 | 
110 | ```typescript
111 | // Static resource
112 | server.resource(
113 |   "config",
114 |   "config://app",
115 |   async (uri) => ({
116 |     contents: [{
117 |       uri: uri.href,
118 |       text: "App configuration here"
119 |     }]
120 |   })
121 | );
122 | 
123 | // Dynamic resource with parameters
124 | server.resource(
125 |   "user-profile",
126 |   new ResourceTemplate("users://{userId}/profile", { list: undefined }),
127 |   async (uri, { userId }) => ({
128 |     contents: [{
129 |       uri: uri.href,
130 |       text: `Profile data for user ${userId}`
131 |     }]
132 |   })
133 | );
134 | ```
135 | 
136 | ### Tools
137 | 
138 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
139 | 
140 | ```typescript
141 | // Simple tool with parameters
142 | server.tool(
143 |   "calculate-bmi",
144 |   {
145 |     weightKg: z.number(),
146 |     heightM: z.number()
147 |   },
148 |   async ({ weightKg, heightM }) => ({
149 |     content: [{
150 |       type: "text",
151 |       text: String(weightKg / (heightM * heightM))
152 |     }]
153 |   })
154 | );
155 | 
156 | // Async tool with external API call
157 | server.tool(
158 |   "fetch-weather",
159 |   { city: z.string() },
160 |   async ({ city }) => {
161 |     const response = await fetch(`https://api.weather.com/${city}`);
162 |     const data = await response.text();
163 |     return {
164 |       content: [{ type: "text", text: data }]
165 |     };
166 |   }
167 | );
168 | ```
169 | 
170 | ### Prompts
171 | 
172 | Prompts are reusable templates that help LLMs interact with your server effectively:
173 | 
174 | ```typescript
175 | server.prompt(
176 |   "review-code",
177 |   { code: z.string() },
178 |   ({ code }) => ({
179 |     messages: [{
180 |       role: "user",
181 |       content: {
182 |         type: "text",
183 |         text: `Please review this code:\n\n${code}`
184 |       }
185 |     }]
186 |   })
187 | );
188 | ```
189 | 
190 | ## Running Your Server
191 | 
192 | MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
193 | 
194 | ### stdio
195 | 
196 | For command-line tools and direct integrations:
197 | 
198 | ```typescript
199 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
200 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
201 | 
202 | const server = new McpServer({
203 |   name: "example-server",
204 |   version: "1.0.0"
205 | });
206 | 
207 | // ... set up server resources, tools, and prompts ...
208 | 
209 | const transport = new StdioServerTransport();
210 | await server.connect(transport);
211 | ```
212 | 
213 | ### HTTP with SSE
214 | 
215 | For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:
216 | 
217 | ```typescript
218 | import express from "express";
219 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
220 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
221 | 
222 | const server = new McpServer({
223 |   name: "example-server",
224 |   version: "1.0.0"
225 | });
226 | 
227 | // ... set up server resources, tools, and prompts ...
228 | 
229 | const app = express();
230 | 
231 | app.get("/sse", async (req, res) => {
232 |   const transport = new SSEServerTransport("/messages", res);
233 |   await server.connect(transport);
234 | });
235 | 
236 | app.post("/messages", async (req, res) => {
237 |   // Note: to support multiple simultaneous connections, these messages will
238 |   // need to be routed to a specific matching transport. (This logic isn't
239 |   // implemented here, for simplicity.)
240 |   await transport.handlePostMessage(req, res);
241 | });
242 | 
243 | app.listen(3001);
244 | ```
245 | 
246 | ### Testing and Debugging
247 | 
248 | To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.
249 | 
250 | ## Examples
251 | 
252 | ### Echo Server
253 | 
254 | A simple server demonstrating resources, tools, and prompts:
255 | 
256 | ```typescript
257 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
258 | import { z } from "zod";
259 | 
260 | const server = new McpServer({
261 |   name: "Echo",
262 |   version: "1.0.0"
263 | });
264 | 
265 | server.resource(
266 |   "echo",
267 |   new ResourceTemplate("echo://{message}", { list: undefined }),
268 |   async (uri, { message }) => ({
269 |     contents: [{
270 |       uri: uri.href,
271 |       text: `Resource echo: ${message}`
272 |     }]
273 |   })
274 | );
275 | 
276 | server.tool(
277 |   "echo",
278 |   { message: z.string() },
279 |   async ({ message }) => ({
280 |     content: [{ type: "text", text: `Tool echo: ${message}` }]
281 |   })
282 | );
283 | 
284 | server.prompt(
285 |   "echo",
286 |   { message: z.string() },
287 |   ({ message }) => ({
288 |     messages: [{
289 |       role: "user",
290 |       content: {
291 |         type: "text",
292 |         text: `Please process this message: ${message}`
293 |       }
294 |     }]
295 |   })
296 | );
297 | ```
298 | 
299 | ### SQLite Explorer
300 | 
301 | A more complex example showing database integration:
302 | 
303 | ```typescript
304 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
305 | import sqlite3 from "sqlite3";
306 | import { promisify } from "util";
307 | import { z } from "zod";
308 | 
309 | const server = new McpServer({
310 |   name: "SQLite Explorer",
311 |   version: "1.0.0"
312 | });
313 | 
314 | // Helper to create DB connection
315 | const getDb = () => {
316 |   const db = new sqlite3.Database("database.db");
317 |   return {
318 |     all: promisify<string, any[]>(db.all.bind(db)),
319 |     close: promisify(db.close.bind(db))
320 |   };
321 | };
322 | 
323 | server.resource(
324 |   "schema",
325 |   "schema://main",
326 |   async (uri) => {
327 |     const db = getDb();
328 |     try {
329 |       const tables = await db.all(
330 |         "SELECT sql FROM sqlite_master WHERE type='table'"
331 |       );
332 |       return {
333 |         contents: [{
334 |           uri: uri.href,
335 |           text: tables.map((t: {sql: string}) => t.sql).join("\n")
336 |         }]
337 |       };
338 |     } finally {
339 |       await db.close();
340 |     }
341 |   }
342 | );
343 | 
344 | server.tool(
345 |   "query",
346 |   { sql: z.string() },
347 |   async ({ sql }) => {
348 |     const db = getDb();
349 |     try {
350 |       const results = await db.all(sql);
351 |       return {
352 |         content: [{
353 |           type: "text",
354 |           text: JSON.stringify(results, null, 2)
355 |         }]
356 |       };
357 |     } catch (err: unknown) {
358 |       const error = err as Error;
359 |       return {
360 |         content: [{
361 |           type: "text",
362 |           text: `Error: ${error.message}`
363 |         }],
364 |         isError: true
365 |       };
366 |     } finally {
367 |       await db.close();
368 |     }
369 |   }
370 | );
371 | ```
372 | 
373 | ## Advanced Usage
374 | 
375 | ### Low-Level Server
376 | 
377 | For more control, you can use the low-level Server class directly:
378 | 
379 | ```typescript
380 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
381 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
382 | import {
383 |   ListPromptsRequestSchema,
384 |   GetPromptRequestSchema
385 | } from "@modelcontextprotocol/sdk/types.js";
386 | 
387 | const server = new Server(
388 |   {
389 |     name: "example-server",
390 |     version: "1.0.0"
391 |   },
392 |   {
393 |     capabilities: {
394 |       prompts: {}
395 |     }
396 |   }
397 | );
398 | 
399 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
400 |   return {
401 |     prompts: [{
402 |       name: "example-prompt",
403 |       description: "An example prompt template",
404 |       arguments: [{
405 |         name: "arg1",
406 |         description: "Example argument",
407 |         required: true
408 |       }]
409 |     }]
410 |   };
411 | });
412 | 
413 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
414 |   if (request.params.name !== "example-prompt") {
415 |     throw new Error("Unknown prompt");
416 |   }
417 |   return {
418 |     description: "Example prompt",
419 |     messages: [{
420 |       role: "user",
421 |       content: {
422 |         type: "text",
423 |         text: "Example prompt text"
424 |       }
425 |     }]
426 |   };
427 | });
428 | 
429 | const transport = new StdioServerTransport();
430 | await server.connect(transport);
431 | ```
432 | 
433 | ### Writing MCP Clients
434 | 
435 | The SDK provides a high-level client interface:
436 | 
437 | ```typescript
438 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
439 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
440 | 
441 | const transport = new StdioClientTransport({
442 |   command: "node",
443 |   args: ["server.js"]
444 | });
445 | 
446 | const client = new Client(
447 |   {
448 |     name: "example-client",
449 |     version: "1.0.0"
450 |   },
451 |   {
452 |     capabilities: {
453 |       prompts: {},
454 |       resources: {},
455 |       tools: {}
456 |     }
457 |   }
458 | );
459 | 
460 | await client.connect(transport);
461 | 
462 | // List prompts
463 | const prompts = await client.listPrompts();
464 | 
465 | // Get a prompt
466 | const prompt = await client.getPrompt("example-prompt", {
467 |   arg1: "value"
468 | });
469 | 
470 | // List resources
471 | const resources = await client.listResources();
472 | 
473 | // Read a resource
474 | const resource = await client.readResource("file:///example.txt");
475 | 
476 | // Call a tool
477 | const result = await client.callTool({
478 |   name: "example-tool",
479 |   arguments: {
480 |     arg1: "value"
481 |   }
482 | });
483 | ```
484 | 
485 | ## Documentation
486 | 
487 | - [Model Context Protocol documentation](https://modelcontextprotocol.io)
488 | - [MCP Specification](https://spec.modelcontextprotocol.io)
489 | - [Example Servers](https://github.com/modelcontextprotocol/servers)
490 | 
491 | ## Contributing
492 | 
493 | Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.
494 | 
495 | ## License
496 | 
497 | This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
498 | 
```

--------------------------------------------------------------------------------
/mcp-server.js:
--------------------------------------------------------------------------------

```javascript
   1 | #!/usr/bin/env node
   2 | const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
   3 | const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
   4 | const { z } = require('zod');
   5 | const axios = require('axios');
   6 | const dotenv = require('dotenv');
   7 | const { format, parse, parseISO, isValid, addDays, subDays, startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfYear, isAfter, isBefore } = require('date-fns');
   8 | 
   9 | // Load environment variables from .env file
  10 | dotenv.config();
  11 | 
  12 | // Magento 2 API Configuration
  13 | const MAGENTO_BASE_URL = process.env.MAGENTO_BASE_URL || 'https://your-magento-store.com/rest/V1';
  14 | const MAGENTO_API_TOKEN = process.env.MAGENTO_API_TOKEN;
  15 | 
  16 | // Validate environment variables
  17 | if (!MAGENTO_API_TOKEN) {
  18 |   console.error('ERROR: MAGENTO_API_TOKEN environment variable is required');
  19 |   process.exit(1);
  20 | }
  21 | 
  22 | // Date parsing utilities
  23 | function parseDateExpression(dateExpression) {
  24 |   const now = new Date();
  25 |   const currentYear = now.getFullYear();
  26 |   const currentMonth = now.getMonth();
  27 |   const currentDay = now.getDate();
  28 |   
  29 |   // Normalize the date expression
  30 |   const normalizedExpression = dateExpression.toLowerCase().trim();
  31 |   
  32 |   // Handle relative date expressions
  33 |   switch (normalizedExpression) {
  34 |     case 'today':
  35 |       return {
  36 |         startDate: startOfDay(now),
  37 |         endDate: endOfDay(now),
  38 |         description: 'Today'
  39 |       };
  40 |     case 'yesterday':
  41 |       const yesterday = subDays(now, 1);
  42 |       return {
  43 |         startDate: startOfDay(yesterday),
  44 |         endDate: endOfDay(yesterday),
  45 |         description: 'Yesterday'
  46 |       };
  47 |     case 'this week':
  48 |       return {
  49 |         startDate: startOfWeek(now, { weekStartsOn: 1 }), // Week starts on Monday
  50 |         endDate: endOfDay(now),
  51 |         description: 'This week'
  52 |       };
  53 |     case 'last week':
  54 |       const lastWeekStart = subDays(startOfWeek(now, { weekStartsOn: 1 }), 7);
  55 |       const lastWeekEnd = subDays(endOfWeek(now, { weekStartsOn: 1 }), 7);
  56 |       return {
  57 |         startDate: lastWeekStart,
  58 |         endDate: lastWeekEnd,
  59 |         description: 'Last week'
  60 |       };
  61 |     case 'this month':
  62 |       return {
  63 |         startDate: startOfMonth(now),
  64 |         endDate: endOfDay(now),
  65 |         description: 'This month'
  66 |       };
  67 |     case 'last month':
  68 |       const lastMonth = new Date(currentYear, currentMonth - 1, 1);
  69 |       return {
  70 |         startDate: startOfMonth(lastMonth),
  71 |         endDate: endOfMonth(lastMonth),
  72 |         description: 'Last month'
  73 |       };
  74 |     case 'ytd':
  75 |     case 'this ytd':
  76 |     case 'this year to date':
  77 |     case 'year to date':
  78 |       return {
  79 |         startDate: startOfYear(now),
  80 |         endDate: endOfDay(now),
  81 |         description: 'Year to date'
  82 |       };
  83 |     case 'last year':
  84 |       const lastYear = new Date(currentYear - 1, 0, 1);
  85 |       return {
  86 |         startDate: startOfYear(lastYear),
  87 |         endDate: endOfYear(lastYear),
  88 |         description: 'Last year'
  89 |       };
  90 |     default:
  91 |       // Try to parse as ISO date or other common formats
  92 |       try {
  93 |         // Check if it's a single date (not a range)
  94 |         const parsedDate = parseISO(normalizedExpression);
  95 |         if (isValid(parsedDate)) {
  96 |           return {
  97 |             startDate: startOfDay(parsedDate),
  98 |             endDate: endOfDay(parsedDate),
  99 |             description: format(parsedDate, 'yyyy-MM-dd')
 100 |           };
 101 |         }
 102 |         
 103 |         // Check if it's a date range in format "YYYY-MM-DD to YYYY-MM-DD"
 104 |         const rangeParts = normalizedExpression.split(' to ');
 105 |         if (rangeParts.length === 2) {
 106 |           const startDate = parseISO(rangeParts[0]);
 107 |           const endDate = parseISO(rangeParts[1]);
 108 |           
 109 |           if (isValid(startDate) && isValid(endDate)) {
 110 |             return {
 111 |               startDate: startOfDay(startDate),
 112 |               endDate: endOfDay(endDate),
 113 |               description: `${format(startDate, 'yyyy-MM-dd')} to ${format(endDate, 'yyyy-MM-dd')}`
 114 |             };
 115 |           }
 116 |         }
 117 |         
 118 |         // If we can't parse it, throw an error
 119 |         throw new Error(`Unable to parse date expression: ${dateExpression}`);
 120 |       } catch (error) {
 121 |         throw new Error(`Invalid date expression: ${dateExpression}. ${error.message}`);
 122 |       }
 123 |   }
 124 | }
 125 | 
 126 | // Helper function to get the end of a year
 127 | function endOfYear(date) {
 128 |   return new Date(date.getFullYear(), 11, 31, 23, 59, 59, 999);
 129 | }
 130 | 
 131 | // Helper function to format a date for Magento API
 132 | function formatDateForMagento(date) {
 133 |   return format(date, "yyyy-MM-dd HH:mm:ss");
 134 | }
 135 | 
 136 | // Helper function to build date range filter for Magento API
 137 | function buildDateRangeFilter(field, startDate, endDate) {
 138 |   const formattedStartDate = formatDateForMagento(startDate);
 139 |   const formattedEndDate = formatDateForMagento(endDate);
 140 |   
 141 |   return [
 142 |     `searchCriteria[filter_groups][0][filters][0][field]=${field}`,
 143 |     `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(formattedStartDate)}`,
 144 |     `searchCriteria[filter_groups][0][filters][0][condition_type]=gteq`,
 145 |     `searchCriteria[filter_groups][1][filters][0][field]=${field}`,
 146 |     `searchCriteria[filter_groups][1][filters][0][value]=${encodeURIComponent(formattedEndDate)}`,
 147 |     `searchCriteria[filter_groups][1][filters][0][condition_type]=lteq`
 148 |   ].join('&');
 149 | }
 150 | 
 151 | // Helper function to normalize country input
 152 | function normalizeCountry(country) {
 153 |   // Normalize the country input (handle both country codes and names)
 154 |   const countryInput = country.trim().toLowerCase();
 155 |   
 156 |   // Map of common country names to ISO country codes
 157 |   const countryMap = {
 158 |     // Common variations for The Netherlands
 159 |     'netherlands': 'NL',
 160 |     'the netherlands': 'NL',
 161 |     'holland': 'NL',
 162 |     'nl': 'NL',
 163 |     
 164 |     // Common variations for United States
 165 |     'united states': 'US',
 166 |     'usa': 'US',
 167 |     'us': 'US',
 168 |     'america': 'US',
 169 |     
 170 |     // Common variations for United Kingdom
 171 |     'united kingdom': 'GB',
 172 |     'uk': 'GB',
 173 |     'great britain': 'GB',
 174 |     'gb': 'GB',
 175 |     'england': 'GB',
 176 |     
 177 |     // Add more countries as needed
 178 |     'canada': 'CA',
 179 |     'ca': 'CA',
 180 |     
 181 |     'australia': 'AU',
 182 |     'au': 'AU',
 183 |     
 184 |     'germany': 'DE',
 185 |     'de': 'DE',
 186 |     
 187 |     'france': 'FR',
 188 |     'fr': 'FR',
 189 |     
 190 |     'italy': 'IT',
 191 |     'it': 'IT',
 192 |     
 193 |     'spain': 'ES',
 194 |     'es': 'ES',
 195 |     
 196 |     'belgium': 'BE',
 197 |     'be': 'BE',
 198 |     
 199 |     'sweden': 'SE',
 200 |     'se': 'SE',
 201 |     
 202 |     'norway': 'NO',
 203 |     'no': 'NO',
 204 |     
 205 |     'denmark': 'DK',
 206 |     'dk': 'DK',
 207 |     
 208 |     'finland': 'FI',
 209 |     'fi': 'FI',
 210 |     
 211 |     'ireland': 'IE',
 212 |     'ie': 'IE',
 213 |     
 214 |     'switzerland': 'CH',
 215 |     'ch': 'CH',
 216 |     
 217 |     'austria': 'AT',
 218 |     'at': 'AT',
 219 |     
 220 |     'portugal': 'PT',
 221 |     'pt': 'PT',
 222 |     
 223 |     'greece': 'GR',
 224 |     'gr': 'GR',
 225 |     
 226 |     'poland': 'PL',
 227 |     'pl': 'PL',
 228 |     
 229 |     'japan': 'JP',
 230 |     'jp': 'JP',
 231 |     
 232 |     'china': 'CN',
 233 |     'cn': 'CN',
 234 |     
 235 |     'india': 'IN',
 236 |     'in': 'IN',
 237 |     
 238 |     'brazil': 'BR',
 239 |     'br': 'BR',
 240 |     
 241 |     'mexico': 'MX',
 242 |     'mx': 'MX',
 243 |     
 244 |     'south africa': 'ZA',
 245 |     'za': 'ZA'
 246 |   };
 247 |   
 248 |   // Check if the input is in our map
 249 |   if (countryMap[countryInput]) {
 250 |     return [countryMap[countryInput]];
 251 |   }
 252 |   
 253 |   // If it's not in our map, assume it's a country code or name and return as is
 254 |   // For a more robust solution, we would validate against a complete list of country codes
 255 |   return [countryInput.toUpperCase()];
 256 | }
 257 | 
 258 | // Helper function to fetch all pages for a given search criteria
 259 | async function fetchAllPages(endpoint, baseSearchCriteria) {
 260 |   const pageSize = 100; // Or make this configurable if needed
 261 |   let currentPage = 1;
 262 |   let allItems = [];
 263 |   let totalCount = 0;
 264 |   
 265 |   do {
 266 |     // Build search criteria for the current page, ensuring baseSearchCriteria doesn't already have pagination
 267 |     let currentPageSearchCriteria = baseSearchCriteria;
 268 |     if (!currentPageSearchCriteria.includes('searchCriteria[pageSize]')) {
 269 |       currentPageSearchCriteria += `&searchCriteria[pageSize]=${pageSize}`;
 270 |     }
 271 |     if (!currentPageSearchCriteria.includes('searchCriteria[currentPage]')) {
 272 |       currentPageSearchCriteria += `&searchCriteria[currentPage]=${currentPage}`;
 273 |     } else {
 274 |       // If currentPage is already there, replace it (less common case)
 275 |       currentPageSearchCriteria = currentPageSearchCriteria.replace(/searchCriteria\[currentPage\]=\d+/, `searchCriteria[currentPage]=${currentPage}`);
 276 |     }
 277 | 
 278 |     // Make the API call for the current page
 279 |     const responseData = await callMagentoApi(`${endpoint}?${currentPageSearchCriteria}`);
 280 |     
 281 |     if (responseData.items && Array.isArray(responseData.items)) {
 282 |       allItems = allItems.concat(responseData.items);
 283 |     }
 284 |     
 285 |     // Update total count (only needs to be set once)
 286 |     if (currentPage === 1) {
 287 |       totalCount = responseData.total_count || 0;
 288 |     }
 289 |     
 290 |     // Check if we need to fetch more pages
 291 |     if (totalCount <= allItems.length || !responseData.items || responseData.items.length < pageSize) {
 292 |       break; // Exit loop if all items are fetched or last page had less than pageSize items
 293 |     }
 294 |     
 295 |     currentPage++;
 296 |     
 297 |   } while (true); // Loop continues until break
 298 |   
 299 |   return allItems; // Return the aggregated list of items
 300 | }
 301 | 
 302 | // Create an MCP server
 303 | const server = new McpServer({
 304 |   name: "magento-mcp-server",
 305 |   version: "1.0.0"
 306 | });
 307 | 
 308 | // Helper function to make authenticated requests to Magento 2 API
 309 | async function callMagentoApi(endpoint, method = 'GET', data = null) {
 310 |   try {
 311 |     const url = `${MAGENTO_BASE_URL}${endpoint}`;
 312 |     const headers = {
 313 |       'Authorization': `Bearer ${MAGENTO_API_TOKEN}`,
 314 |       'Content-Type': 'application/json'
 315 |     };
 316 |     
 317 |     const config = {
 318 |       method,
 319 |       url,
 320 |       headers,
 321 |       data: data ? JSON.stringify(data) : undefined,
 322 |       // Bypass SSL certificate verification for development
 323 |       httpsAgent: new (require('https').Agent)({
 324 |         rejectUnauthorized: false
 325 |       })
 326 |     };
 327 |     
 328 |     const response = await axios(config);
 329 |     return response.data;
 330 |   } catch (error) {
 331 |     console.error('Magento API Error:', error.response?.data || error.message);
 332 |     throw error;
 333 |   }
 334 | }
 335 | 
 336 | // Format product data for better readability
 337 | function formatProduct(product) {
 338 |   if (!product) return "Product not found";
 339 |   
 340 |   // Extract custom attributes into a more readable format
 341 |   const customAttributes = {};
 342 |   if (product.custom_attributes && Array.isArray(product.custom_attributes)) {
 343 |     product.custom_attributes.forEach(attr => {
 344 |       customAttributes[attr.attribute_code] = attr.value;
 345 |     });
 346 |   }
 347 |   
 348 |   return {
 349 |     id: product.id,
 350 |     sku: product.sku,
 351 |     name: product.name,
 352 |     price: product.price,
 353 |     status: product.status,
 354 |     visibility: product.visibility,
 355 |     type_id: product.type_id,
 356 |     created_at: product.created_at,
 357 |     updated_at: product.updated_at,
 358 |     extension_attributes: product.extension_attributes,
 359 |     custom_attributes: customAttributes
 360 |   };
 361 | }
 362 | 
 363 | // Format search results for better readability
 364 | function formatSearchResults(results) {
 365 |   if (!results || !results.items || !Array.isArray(results.items)) {
 366 |     return "No products found";
 367 |   }
 368 |   
 369 |   return {
 370 |     total_count: results.total_count,
 371 |     items: results.items.map(item => ({
 372 |       id: item.id,
 373 |       sku: item.sku,
 374 |       name: item.name,
 375 |       price: item.price,
 376 |       status: item.status,
 377 |       type_id: item.type_id
 378 |     }))
 379 |   };
 380 | }
 381 | 
 382 | // Tool: Get product by SKU
 383 | server.tool(
 384 |   "get_product_by_sku",
 385 |   "Get detailed information about a product by its SKU",
 386 |   {
 387 |     sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
 388 |   },
 389 |   async ({ sku }) => {
 390 |     try {
 391 |       const productData = await callMagentoApi(`/products/${sku}`);
 392 |       const formattedProduct = formatProduct(productData);
 393 |       
 394 |       return {
 395 |         content: [
 396 |           {
 397 |             type: "text",
 398 |             text: JSON.stringify(formattedProduct, null, 2)
 399 |           }
 400 |         ]
 401 |       };
 402 |     } catch (error) {
 403 |       return {
 404 |         content: [
 405 |           {
 406 |             type: "text",
 407 |             text: `Error fetching product: ${error.message}`
 408 |           }
 409 |         ],
 410 |         isError: true
 411 |       };
 412 |     }
 413 |   }
 414 | );
 415 | 
 416 | // Tool: Search products
 417 | server.tool(
 418 |   "search_products",
 419 |   "Search for products using Magento search criteria",
 420 |   {
 421 |     query: z.string().describe("Search query (product name, description, etc.)"),
 422 |     page_size: z.number().optional().describe("Number of results per page (default: 10)"),
 423 |     current_page: z.number().optional().describe("Page number (default: 1)")
 424 |   },
 425 |   async ({ query, page_size = 10, current_page = 1 }) => {
 426 |     try {
 427 |       // Build search criteria for a simple name search
 428 |       const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=name&` +
 429 |                             `searchCriteria[filter_groups][0][filters][0][value]=%25${encodeURIComponent(query)}%25&` +
 430 |                             `searchCriteria[filter_groups][0][filters][0][condition_type]=like&` +
 431 |                             `searchCriteria[pageSize]=${page_size}&` +
 432 |                             `searchCriteria[currentPage]=${current_page}`;
 433 |       
 434 |       const productData = await callMagentoApi(`/products?${searchCriteria}`);
 435 |       const formattedResults = formatSearchResults(productData);
 436 |       
 437 |       return {
 438 |         content: [
 439 |           {
 440 |             type: "text",
 441 |             text: JSON.stringify(formattedResults, null, 2)
 442 |           }
 443 |         ]
 444 |       };
 445 |     } catch (error) {
 446 |       return {
 447 |         content: [
 448 |           {
 449 |             type: "text",
 450 |             text: `Error searching products: ${error.message}`
 451 |           }
 452 |         ],
 453 |         isError: true
 454 |       };
 455 |     }
 456 |   }
 457 | );
 458 | 
 459 | // Tool: Get product categories
 460 | server.tool(
 461 |   "get_product_categories",
 462 |   "Get categories for a specific product by SKU",
 463 |   {
 464 |     sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
 465 |   },
 466 |   async ({ sku }) => {
 467 |     try {
 468 |       // First get the product to find its category IDs
 469 |       const productData = await callMagentoApi(`/products/${sku}`);
 470 |       
 471 |       // Find category IDs in custom attributes
 472 |       const categoryAttribute = productData.custom_attributes?.find(
 473 |         attr => attr.attribute_code === 'category_ids'
 474 |       );
 475 |       
 476 |       if (!categoryAttribute || !categoryAttribute.value) {
 477 |         return {
 478 |           content: [
 479 |             {
 480 |               type: "text",
 481 |               text: `No categories found for product with SKU: ${sku}`
 482 |             }
 483 |           ]
 484 |         };
 485 |       }
 486 |       
 487 |       // Parse category IDs (they might be in string format)
 488 |       let categoryIds = categoryAttribute.value;
 489 |       if (typeof categoryIds === 'string') {
 490 |         try {
 491 |           categoryIds = JSON.parse(categoryIds);
 492 |         } catch (e) {
 493 |           // If it's not valid JSON, split by comma
 494 |           categoryIds = categoryIds.split(',').map(id => id.trim());
 495 |         }
 496 |       }
 497 |       
 498 |       if (!Array.isArray(categoryIds)) {
 499 |         categoryIds = [categoryIds];
 500 |       }
 501 |       
 502 |       // Get category details for each ID
 503 |       const categoryPromises = categoryIds.map(id => 
 504 |         callMagentoApi(`/categories/${id}`)
 505 |           .catch(err => ({ id, error: err.message }))
 506 |       );
 507 |       
 508 |       const categories = await Promise.all(categoryPromises);
 509 |       
 510 |       return {
 511 |         content: [
 512 |           {
 513 |             type: "text",
 514 |             text: JSON.stringify(categories, null, 2)
 515 |           }
 516 |         ]
 517 |       };
 518 |     } catch (error) {
 519 |       return {
 520 |         content: [
 521 |           {
 522 |             type: "text",
 523 |             text: `Error fetching product categories: ${error.message}`
 524 |           }
 525 |         ],
 526 |         isError: true
 527 |       };
 528 |     }
 529 |   }
 530 | );
 531 | 
 532 | // Tool: Get related products
 533 | server.tool(
 534 |   "get_related_products",
 535 |   "Get products related to a specific product by SKU",
 536 |   {
 537 |     sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
 538 |   },
 539 |   async ({ sku }) => {
 540 |     try {
 541 |       const relatedProducts = await callMagentoApi(`/products/${sku}/links/related`);
 542 |       
 543 |       if (!relatedProducts || relatedProducts.length === 0) {
 544 |         return {
 545 |           content: [
 546 |             {
 547 |               type: "text",
 548 |               text: `No related products found for SKU: ${sku}`
 549 |             }
 550 |           ]
 551 |         };
 552 |       }
 553 |       
 554 |       // Get full details for each related product
 555 |       const productPromises = relatedProducts.map(related => 
 556 |         callMagentoApi(`/products/${related.linked_product_sku}`)
 557 |           .then(product => formatProduct(product))
 558 |           .catch(err => ({ sku: related.linked_product_sku, error: err.message }))
 559 |       );
 560 |       
 561 |       const products = await Promise.all(productPromises);
 562 |       
 563 |       return {
 564 |         content: [
 565 |           {
 566 |             type: "text",
 567 |             text: JSON.stringify(products, null, 2)
 568 |           }
 569 |         ]
 570 |       };
 571 |     } catch (error) {
 572 |       return {
 573 |         content: [
 574 |           {
 575 |             type: "text",
 576 |             text: `Error fetching related products: ${error.message}`
 577 |           }
 578 |         ],
 579 |         isError: true
 580 |       };
 581 |     }
 582 |   }
 583 | );
 584 | 
 585 | // Tool: Get product stock information
 586 | server.tool(
 587 |   "get_product_stock",
 588 |   "Get stock information for a product by SKU",
 589 |   {
 590 |     sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
 591 |   },
 592 |   async ({ sku }) => {
 593 |     try {
 594 |       const stockData = await callMagentoApi(`/stockItems/${sku}`);
 595 |       
 596 |       return {
 597 |         content: [
 598 |           {
 599 |             type: "text",
 600 |             text: JSON.stringify(stockData, null, 2)
 601 |           }
 602 |         ]
 603 |       };
 604 |     } catch (error) {
 605 |       return {
 606 |         content: [
 607 |           {
 608 |             type: "text",
 609 |             text: `Error fetching stock information: ${error.message}`
 610 |           }
 611 |         ],
 612 |         isError: true
 613 |       };
 614 |     }
 615 |   }
 616 | );
 617 | 
 618 | // Tool: Get product attributes
 619 | server.tool(
 620 |   "get_product_attributes",
 621 |   "Get all attributes for a product by SKU",
 622 |   {
 623 |     sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
 624 |   },
 625 |   async ({ sku }) => {
 626 |     try {
 627 |       const productData = await callMagentoApi(`/products/${sku}`);
 628 |       
 629 |       // Extract and format attributes
 630 |       const attributes = {
 631 |         base_attributes: {
 632 |           id: productData.id,
 633 |           sku: productData.sku,
 634 |           name: productData.name,
 635 |           price: productData.price,
 636 |           status: productData.status,
 637 |           visibility: productData.visibility,
 638 |           type_id: productData.type_id
 639 |         },
 640 |         custom_attributes: {}
 641 |       };
 642 |       
 643 |       if (productData.custom_attributes && Array.isArray(productData.custom_attributes)) {
 644 |         productData.custom_attributes.forEach(attr => {
 645 |           attributes.custom_attributes[attr.attribute_code] = attr.value;
 646 |         });
 647 |       }
 648 |       
 649 |       return {
 650 |         content: [
 651 |           {
 652 |             type: "text",
 653 |             text: JSON.stringify(attributes, null, 2)
 654 |           }
 655 |         ]
 656 |       };
 657 |     } catch (error) {
 658 |       return {
 659 |         content: [
 660 |           {
 661 |             type: "text",
 662 |             text: `Error fetching product attributes: ${error.message}`
 663 |           }
 664 |         ],
 665 |         isError: true
 666 |       };
 667 |     }
 668 |   }
 669 | );
 670 | 
 671 | // Tool: Get product by ID
 672 | server.tool(
 673 |   "get_product_by_id",
 674 |   "Get detailed information about a product by its ID",
 675 |   {
 676 |     id: z.number().describe("The ID of the product")
 677 |   },
 678 |   async ({ id }) => {
 679 |     try {
 680 |       // First we need to search for the product by ID to get its SKU
 681 |       const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=entity_id&` +
 682 |                             `searchCriteria[filter_groups][0][filters][0][value]=${id}&` +
 683 |                             `searchCriteria[filter_groups][0][filters][0][condition_type]=eq`;
 684 |       
 685 |       const searchResults = await callMagentoApi(`/products?${searchCriteria}`);
 686 |       
 687 |       if (!searchResults.items || searchResults.items.length === 0) {
 688 |         return {
 689 |           content: [
 690 |             {
 691 |               type: "text",
 692 |               text: `No product found with ID: ${id}`
 693 |             }
 694 |           ]
 695 |         };
 696 |       }
 697 |       
 698 |       // Get the SKU from the search results
 699 |       const sku = searchResults.items[0].sku;
 700 |       
 701 |       // Now get the full product details using the SKU
 702 |       const productData = await callMagentoApi(`/products/${sku}`);
 703 |       const formattedProduct = formatProduct(productData);
 704 |       
 705 |       return {
 706 |         content: [
 707 |           {
 708 |             type: "text",
 709 |             text: JSON.stringify(formattedProduct, null, 2)
 710 |           }
 711 |         ]
 712 |       };
 713 |     } catch (error) {
 714 |       return {
 715 |         content: [
 716 |           {
 717 |             type: "text",
 718 |             text: `Error fetching product: ${error.message}`
 719 |           }
 720 |         ],
 721 |         isError: true
 722 |       };
 723 |     }
 724 |   }
 725 | );
 726 | 
 727 | // Tool: Advanced product search
 728 | server.tool(
 729 |   "advanced_product_search",
 730 |   "Search for products with advanced filtering options",
 731 |   {
 732 |     field: z.string().describe("Field to search on (e.g., name, sku, price, status)"),
 733 |     value: z.string().describe("Value to search for"),
 734 |     condition_type: z.string().optional().describe("Condition type (eq, like, gt, lt, etc.). Default: eq"),
 735 |     page_size: z.number().optional().describe("Number of results per page (default: 10)"),
 736 |     current_page: z.number().optional().describe("Page number (default: 1)"),
 737 |     sort_field: z.string().optional().describe("Field to sort by (default: entity_id)"),
 738 |     sort_direction: z.string().optional().describe("Sort direction (ASC or DESC, default: DESC)")
 739 |   },
 740 |   async ({ field, value, condition_type = 'eq', page_size = 10, current_page = 1, sort_field = 'entity_id', sort_direction = 'DESC' }) => {
 741 |     try {
 742 |       // Build search criteria
 743 |       const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=${encodeURIComponent(field)}&` +
 744 |                             `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(value)}&` +
 745 |                             `searchCriteria[filter_groups][0][filters][0][condition_type]=${encodeURIComponent(condition_type)}&` +
 746 |                             `searchCriteria[pageSize]=${page_size}&` +
 747 |                             `searchCriteria[currentPage]=${current_page}&` +
 748 |                             `searchCriteria[sortOrders][0][field]=${encodeURIComponent(sort_field)}&` +
 749 |                             `searchCriteria[sortOrders][0][direction]=${encodeURIComponent(sort_direction)}`;
 750 |       
 751 |       const productData = await callMagentoApi(`/products?${searchCriteria}`);
 752 |       const formattedResults = formatSearchResults(productData);
 753 |       
 754 |       return {
 755 |         content: [
 756 |           {
 757 |             type: "text",
 758 |             text: JSON.stringify(formattedResults, null, 2)
 759 |           }
 760 |         ]
 761 |       };
 762 |     } catch (error) {
 763 |       return {
 764 |         content: [
 765 |           {
 766 |             type: "text",
 767 |             text: `Error performing advanced search: ${error.message}`
 768 |           }
 769 |         ],
 770 |         isError: true
 771 |       };
 772 |     }
 773 |   }
 774 | );
 775 | 
 776 | // Tool: Update product attribute
 777 | server.tool(
 778 |   "update_product_attribute",
 779 |   "Update a specific attribute of a product by SKU",
 780 |   {
 781 |     sku: z.string().describe("The SKU (Stock Keeping Unit) of the product"),
 782 |     attribute_code: z.string().describe("The code of the attribute to update (e.g., name, price, description, status, etc.)"),
 783 |     value: z.any().describe("The new value for the attribute")
 784 |   },
 785 |   async ({ sku, attribute_code, value }) => {
 786 |     try {
 787 |       // First, check if the product exists
 788 |       const productData = await callMagentoApi(`/products/${sku}`).catch(() => null);
 789 |       
 790 |       if (!productData) {
 791 |         return {
 792 |           content: [
 793 |             {
 794 |               type: "text",
 795 |               text: `Product with SKU '${sku}' not found`
 796 |             }
 797 |           ],
 798 |           isError: true
 799 |         };
 800 |       }
 801 |       
 802 |       // Prepare the update data with the correct structure
 803 |       // Magento 2 API requires a "product" wrapper object
 804 |       let updateData = {
 805 |         product: {}
 806 |       };
 807 |       
 808 |       // Determine if this is a standard attribute or custom attribute
 809 |       const isCustomAttribute = productData.custom_attributes && 
 810 |                                productData.custom_attributes.some(attr => attr.attribute_code === attribute_code);
 811 |       
 812 |       if (isCustomAttribute) {
 813 |         // For custom attributes, we need to use the custom_attributes array
 814 |         updateData.product.custom_attributes = [
 815 |           {
 816 |             attribute_code,
 817 |             value
 818 |           }
 819 |         ];
 820 |       } else {
 821 |         // For standard attributes, we set them directly on the product object
 822 |         updateData.product[attribute_code] = value;
 823 |       }
 824 |       
 825 |       // Make the API call to update the product
 826 |       const result = await callMagentoApi(`/products/${sku}`, 'PUT', updateData);
 827 |       
 828 |       return {
 829 |         content: [
 830 |           {
 831 |             type: "text",
 832 |             text: `Successfully updated '${attribute_code}' for product with SKU '${sku}'. Updated product: ${JSON.stringify(formatProduct(result), null, 2)}`
 833 |           }
 834 |         ]
 835 |       };
 836 |     } catch (error) {
 837 |       return {
 838 |         content: [
 839 |           {
 840 |             type: "text",
 841 |             text: `Error updating product attribute: ${error.response?.data?.message || error.message}`
 842 |           }
 843 |         ],
 844 |         isError: true
 845 |       };
 846 |     }
 847 |   }
 848 | );
 849 | 
 850 | // Tool: Get revenue
 851 | server.tool(
 852 |   "get_revenue",
 853 |   "Get the total revenue for a given date range",
 854 |   {
 855 |     date_range: z.string().describe("Date range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')"),
 856 |     status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
 857 |     include_tax: z.boolean().optional().describe("Whether to include tax in the revenue calculation (default: true)")
 858 |   },
 859 |   async ({ date_range, status, include_tax = true }) => {
 860 |     try {
 861 |       // Parse the date range expression
 862 |       const dateRange = parseDateExpression(date_range);
 863 |       
 864 |       // Build the search criteria for the date range
 865 |       let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
 866 |       
 867 |       // Add status filter if provided
 868 |       if (status) {
 869 |         searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
 870 |                           `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
 871 |                           `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
 872 |       }
 873 |       
 874 |       // Fetch all orders using the helper function
 875 |       const allOrders = await fetchAllPages('/orders', searchCriteria);
 876 |       
 877 |       // Calculate total revenue
 878 |       let totalRevenue = 0;
 879 |       let totalTax = 0;
 880 |       let orderCount = 0;
 881 |       
 882 |       if (allOrders && Array.isArray(allOrders)) {
 883 |         orderCount = allOrders.length;
 884 |         
 885 |         allOrders.forEach(order => {
 886 |           // Use grand_total which includes tax, shipping, etc.
 887 |           totalRevenue += parseFloat(order.grand_total || 0);
 888 |           
 889 |           // Track tax separately
 890 |           totalTax += parseFloat(order.tax_amount || 0);
 891 |         });
 892 |       }
 893 |       
 894 |       // Adjust revenue if tax should be excluded
 895 |       const revenueWithoutTax = totalRevenue - totalTax;
 896 |       const finalRevenue = include_tax ? totalRevenue : revenueWithoutTax;
 897 |       
 898 |       // Format the response
 899 |       const result = {
 900 |         query: {
 901 |           date_range: dateRange.description,
 902 |           status: status || 'All',
 903 |           include_tax: include_tax,
 904 |           period: {
 905 |             start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
 906 |             end_date: format(dateRange.endDate, 'yyyy-MM-dd')
 907 |           }
 908 |         },
 909 |         result: {
 910 |           revenue: parseFloat(finalRevenue.toFixed(2)),
 911 |           currency: 'USD', // This should be dynamically determined from the store configuration
 912 |           order_count: orderCount,
 913 |           average_order_value: orderCount > 0 ? parseFloat((finalRevenue / orderCount).toFixed(2)) : 0,
 914 |           tax_amount: parseFloat(totalTax.toFixed(2))
 915 |         }
 916 |       };
 917 |       
 918 |       return {
 919 |         content: [
 920 |           {
 921 |             type: "text",
 922 |             text: JSON.stringify(result, null, 2)
 923 |           }
 924 |         ]
 925 |       };
 926 |     } catch (error) {
 927 |       return {
 928 |         content: [
 929 |           {
 930 |             type: "text",
 931 |             text: `Error fetching revenue: ${error.message}`
 932 |           }
 933 |         ],
 934 |         isError: true
 935 |       };
 936 |     }
 937 |   }
 938 | );
 939 | 
 940 | // Tool: Get order count
 941 | server.tool(
 942 |   "get_order_count",
 943 |   "Get the number of orders for a given date range",
 944 |   {
 945 |     date_range: z.string().describe("Date range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')"),
 946 |     status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')")
 947 |   },
 948 |   async ({ date_range, status }) => {
 949 |     try {
 950 |       // Parse the date range expression
 951 |       const dateRange = parseDateExpression(date_range);
 952 |       
 953 |       // Build the search criteria for the date range
 954 |       let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
 955 |       
 956 |       // Add status filter if provided
 957 |       if (status) {
 958 |         searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
 959 |                           `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
 960 |                           `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
 961 |       }
 962 |       
 963 |       // Add pagination to get all results
 964 |       searchCriteria += `&searchCriteria[pageSize]=1&searchCriteria[currentPage]=1`;
 965 |       
 966 |       // Make the API call to get orders
 967 |       const ordersData = await callMagentoApi(`/orders?${searchCriteria}`);
 968 |       
 969 |       // Format the response
 970 |       const result = {
 971 |         query: {
 972 |           date_range: dateRange.description,
 973 |           status: status || 'All',
 974 |           period: {
 975 |             start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
 976 |             end_date: format(dateRange.endDate, 'yyyy-MM-dd')
 977 |           }
 978 |         },
 979 |         result: {
 980 |           order_count: ordersData.total_count || 0
 981 |         }
 982 |       };
 983 |       
 984 |       return {
 985 |         content: [
 986 |           {
 987 |             type: "text",
 988 |             text: JSON.stringify(result, null, 2)
 989 |           }
 990 |         ]
 991 |       };
 992 |     } catch (error) {
 993 |       return {
 994 |         content: [
 995 |           {
 996 |             type: "text",
 997 |             text: `Error fetching order count: ${error.message}`
 998 |           }
 999 |         ],
1000 |         isError: true
1001 |       };
1002 |     }
1003 |   }
1004 | );
1005 | 
1006 | // Tool: Get product sales
1007 | server.tool(
1008 |   "get_product_sales",
1009 |   "Get statistics about the quantity of products sold in a given date range",
1010 |   {
1011 |     date_range: z.string().describe("Date range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')"),
1012 |     status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
1013 |     country: z.string().optional().describe("Filter by country code (e.g., 'US', 'NL', 'GB') or country name (e.g., 'United States', 'The Netherlands', 'United Kingdom')")
1014 |   },
1015 |   async ({ date_range, status, country }) => {
1016 |     try {
1017 |       // Parse the date range expression
1018 |       const dateRange = parseDateExpression(date_range);
1019 |       
1020 |       // Build the search criteria for the date range
1021 |       let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
1022 |       
1023 |       // Add status filter if provided
1024 |       if (status) {
1025 |         searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
1026 |                           `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
1027 |                           `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
1028 |       }
1029 |       
1030 |       // Fetch all orders using the helper function
1031 |       const allOrders = await fetchAllPages('/orders', searchCriteria);
1032 |       
1033 |       // Filter orders by country if provided
1034 |       let filteredOrders = allOrders;
1035 |       if (country) {
1036 |         // Normalize country input
1037 |         const normalizedCountry = normalizeCountry(country);
1038 |         
1039 |         // Filter orders by country
1040 |         filteredOrders = filteredOrders.filter(order => {
1041 |           // Check billing address country
1042 |           const billingCountry = order.billing_address?.country_id;
1043 |           
1044 |           // Check shipping address country
1045 |           const shippingCountry = order.extension_attributes?.shipping_assignments?.[0]?.shipping?.address?.country_id;
1046 |           
1047 |           // Match if either billing or shipping country matches
1048 |           return normalizedCountry.includes(billingCountry) || normalizedCountry.includes(shippingCountry);
1049 |         });
1050 |       }
1051 |       
1052 |       // Calculate statistics
1053 |       let totalOrders = filteredOrders.length;
1054 |       let totalOrderItems = 0;
1055 |       let totalProductQuantity = 0;
1056 |       let totalRevenue = 0;
1057 |       let productCounts = {};
1058 |       
1059 |       // Process each order
1060 |       filteredOrders.forEach(order => {
1061 |         // Add to total revenue
1062 |         totalRevenue += parseFloat(order.grand_total || 0);
1063 |         
1064 |         // Process order items
1065 |         if (order.items && Array.isArray(order.items)) {
1066 |           // Count total order items (order lines)
1067 |           totalOrderItems += order.items.length;
1068 |           
1069 |           // Process each item
1070 |           order.items.forEach(item => {
1071 |             // Add to total product quantity
1072 |             const quantity = parseFloat(item.qty_ordered || 0);
1073 |             totalProductQuantity += quantity;
1074 |             
1075 |             // Track product counts by SKU
1076 |             const sku = item.sku;
1077 |             if (sku) {
1078 |               if (!productCounts[sku]) {
1079 |                 productCounts[sku] = {
1080 |                   name: item.name,
1081 |                   quantity: 0,
1082 |                   revenue: 0
1083 |                 };
1084 |               }
1085 |               productCounts[sku].quantity += quantity;
1086 |               productCounts[sku].revenue += parseFloat(item.row_total || 0);
1087 |             }
1088 |           });
1089 |         }
1090 |       });
1091 |       
1092 |       // Convert product counts to array and sort by quantity
1093 |       const topProducts = Object.entries(productCounts)
1094 |         .map(([sku, data]) => ({
1095 |           sku,
1096 |           name: data.name,
1097 |           quantity: data.quantity,
1098 |           revenue: data.revenue
1099 |         }))
1100 |         .sort((a, b) => b.quantity - a.quantity)
1101 |         .slice(0, 10); // Top 10 products
1102 |       
1103 |       // Format the response
1104 |       const result = {
1105 |         query: {
1106 |           date_range: dateRange.description,
1107 |           status: status || 'All',
1108 |           country: country || 'All',
1109 |           period: {
1110 |             start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
1111 |             end_date: format(dateRange.endDate, 'yyyy-MM-dd')
1112 |           }
1113 |         },
1114 |         result: {
1115 |           total_orders: totalOrders,
1116 |           total_order_items: totalOrderItems,
1117 |           total_product_quantity: totalProductQuantity,
1118 |           average_products_per_order: totalOrders > 0 ? parseFloat((totalProductQuantity / totalOrders).toFixed(2)) : 0,
1119 |           total_revenue: parseFloat(totalRevenue.toFixed(2)),
1120 |           average_revenue_per_product: totalProductQuantity > 0 ? parseFloat((totalRevenue / totalProductQuantity).toFixed(2)) : 0,
1121 |           top_products: topProducts
1122 |         }
1123 |       };
1124 |       
1125 |       return {
1126 |         content: [
1127 |           {
1128 |             type: "text",
1129 |             text: JSON.stringify(result, null, 2)
1130 |           }
1131 |         ]
1132 |       };
1133 |     } catch (error) {
1134 |       return {
1135 |         content: [
1136 |           {
1137 |             type: "text",
1138 |             text: `Error fetching product sales: ${error.message}`
1139 |           }
1140 |         ],
1141 |         isError: true
1142 |       };
1143 |     }
1144 |   }
1145 | );
1146 | 
1147 | // Tool: Get revenue by country
1148 | server.tool(
1149 |   "get_revenue_by_country",
1150 |   "Get revenue filtered by country for a given date range",
1151 |   {
1152 |     date_range: z.string().describe("Date range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')"),
1153 |     country: z.string().describe("Country code (e.g., 'US', 'NL', 'GB') or country name (e.g., 'United States', 'The Netherlands', 'United Kingdom')"),
1154 |     status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
1155 |     include_tax: z.boolean().optional().describe("Whether to include tax in the revenue calculation (default: true)")
1156 |   },
1157 |   async ({ date_range, country, status, include_tax = true }) => {
1158 |     try {
1159 |       // Parse the date range expression
1160 |       const dateRange = parseDateExpression(date_range);
1161 |       
1162 |       // Normalize country input (handle both country codes and names)
1163 |       const normalizedCountry = normalizeCountry(country);
1164 |       
1165 |       // Build the search criteria for the date range
1166 |       let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
1167 |       
1168 |       // Add status filter if provided
1169 |       if (status) {
1170 |         searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
1171 |                           `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
1172 |                           `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
1173 |       }
1174 |       
1175 |       // Fetch all orders using the helper function
1176 |       const allOrders = await fetchAllPages('/orders', searchCriteria);
1177 |       
1178 |       // Filter orders by country and calculate revenue
1179 |       let totalRevenue = 0;
1180 |       let totalTax = 0;
1181 |       let orderCount = 0;
1182 |       let filteredOrders = [];
1183 |       
1184 |       if (allOrders && Array.isArray(allOrders)) {
1185 |         // Filter orders by country
1186 |         filteredOrders = allOrders.filter(order => {
1187 |           // Check billing address country
1188 |           const billingCountry = order.billing_address?.country_id;
1189 |           
1190 |           // Check shipping address country
1191 |           const shippingCountry = order.extension_attributes?.shipping_assignments?.[0]?.shipping?.address?.country_id;
1192 |           
1193 |           // Match if either billing or shipping country matches
1194 |           return normalizedCountry.includes(billingCountry) || normalizedCountry.includes(shippingCountry);
1195 |         });
1196 |         
1197 |         orderCount = filteredOrders.length;
1198 |         
1199 |         // Calculate revenue for filtered orders
1200 |         filteredOrders.forEach(order => {
1201 |           // Use grand_total which includes tax, shipping, etc.
1202 |           totalRevenue += parseFloat(order.grand_total || 0);
1203 |           
1204 |           // Track tax separately
1205 |           totalTax += parseFloat(order.tax_amount || 0);
1206 |         });
1207 |       }
1208 |       
1209 |       // Adjust revenue if tax should be excluded
1210 |       const revenueWithoutTax = totalRevenue - totalTax;
1211 |       const finalRevenue = include_tax ? totalRevenue : revenueWithoutTax;
1212 |       
1213 |       // Format the response
1214 |       const result = {
1215 |         query: {
1216 |           date_range: dateRange.description,
1217 |           country: country,
1218 |           normalized_country: normalizedCountry.join(', '),
1219 |           status: status || 'All',
1220 |           include_tax: include_tax,
1221 |           period: {
1222 |             start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
1223 |             end_date: format(dateRange.endDate, 'yyyy-MM-dd')
1224 |           }
1225 |         },
1226 |         result: {
1227 |           revenue: parseFloat(finalRevenue.toFixed(2)),
1228 |           currency: 'USD', // This should be dynamically determined from the store configuration
1229 |           order_count: orderCount,
1230 |           average_order_value: orderCount > 0 ? parseFloat((finalRevenue / orderCount).toFixed(2)) : 0,
1231 |           tax_amount: parseFloat(totalTax.toFixed(2))
1232 |         }
1233 |       };
1234 |       
1235 |       return {
1236 |         content: [
1237 |           {
1238 |             type: "text",
1239 |             text: JSON.stringify(result, null, 2)
1240 |           }
1241 |         ]
1242 |       };
1243 |     } catch (error) {
1244 |       return {
1245 |         content: [
1246 |           {
1247 |             type: "text",
1248 |             text: `Error fetching revenue by country: ${error.message}`
1249 |           }
1250 |         ],
1251 |         isError: true
1252 |       };
1253 |     }
1254 |   }
1255 | );
1256 | 
1257 | // Tool: Get customer ordered products by email
1258 | server.tool(
1259 |   "get_customer_ordered_products_by_email",
1260 |   "Get all ordered products for a customer by email address",
1261 |   {
1262 |     email: z.string().email().describe("The email address of the customer")
1263 |   },
1264 |   async ({ email }) => {
1265 |     try {
1266 |       // Step 1: Find the customer by email
1267 |       const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=email&` +
1268 |                             `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(email)}&` +
1269 |                             `searchCriteria[filter_groups][0][filters][0][condition_type]=eq`;
1270 |       
1271 |       const customersData = await callMagentoApi(`/customers/search?${searchCriteria}`);
1272 |       
1273 |       if (!customersData.items || customersData.items.length === 0) {
1274 |         return {
1275 |           content: [
1276 |             {
1277 |               type: "text",
1278 |               text: `No customer found with email: ${email}`
1279 |             }
1280 |           ]
1281 |         };
1282 |       }
1283 |       
1284 |       const customer = customersData.items[0];
1285 |       
1286 |       // Step 2: Get the customer's orders
1287 |       const orderSearchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=customer_email&` +
1288 |                                  `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(email)}&` +
1289 |                                  `searchCriteria[filter_groups][0][filters][0][condition_type]=eq`;
1290 |       
1291 |       // Fetch all orders for the customer using the helper function
1292 |       const allCustomerOrders = await fetchAllPages('/orders', orderSearchCriteria);
1293 |       
1294 |       if (!allCustomerOrders || allCustomerOrders.length === 0) {
1295 |         return {
1296 |           content: [
1297 |             {
1298 |               type: "text",
1299 |               text: `No orders found for customer with email: ${email}`
1300 |             }
1301 |           ]
1302 |         };
1303 |       }
1304 |       
1305 |       // Step 3: Extract and format the ordered products
1306 |       const orderedProducts = [];
1307 |       const productSkus = new Set();
1308 |       
1309 |       // First, collect all unique product SKUs from all orders
1310 |       allCustomerOrders.forEach(order => {
1311 |         if (order.items && Array.isArray(order.items)) {
1312 |           order.items.forEach(item => {
1313 |             if (item.sku) {
1314 |               productSkus.add(item.sku);
1315 |             }
1316 |           });
1317 |         }
1318 |       });
1319 |       
1320 |       // Get detailed product information for each SKU
1321 |       const productPromises = Array.from(productSkus).map(sku => 
1322 |         callMagentoApi(`/products/${sku}`)
1323 |           .then(product => formatProduct(product))
1324 |           .catch(err => ({ sku, error: err.message }))
1325 |       );
1326 |       
1327 |       const productDetails = await Promise.all(productPromises);
1328 |       
1329 |       // Create a map of SKU to product details for easy lookup
1330 |       const productMap = {};
1331 |       productDetails.forEach(product => {
1332 |         if (product.sku) {
1333 |           productMap[product.sku] = product;
1334 |         }
1335 |       });
1336 |       
1337 |       // Format the result with order information and product details
1338 |       const result = {
1339 |         customer: {
1340 |           id: customer.id,
1341 |           email: customer.email,
1342 |           firstname: customer.firstname,
1343 |           lastname: customer.lastname
1344 |         },
1345 |         orders: allCustomerOrders.map(order => ({
1346 |           order_id: order.entity_id,
1347 |           increment_id: order.increment_id,
1348 |           created_at: order.created_at,
1349 |           status: order.status,
1350 |           total: order.grand_total,
1351 |           items: order.items.map(item => {
1352 |             const productDetail = productMap[item.sku] || {};
1353 |             return {
1354 |               sku: item.sku,
1355 |               name: item.name,
1356 |               price: item.price,
1357 |               qty_ordered: item.qty_ordered,
1358 |               product_details: productDetail
1359 |             };
1360 |           })
1361 |         }))
1362 |       };
1363 |       
1364 |       return {
1365 |         content: [
1366 |           {
1367 |             type: "text",
1368 |             text: JSON.stringify(result, null, 2)
1369 |           }
1370 |         ]
1371 |       };
1372 |     } catch (error) {
1373 |       return {
1374 |         content: [
1375 |           {
1376 |             type: "text",
1377 |             text: `Error fetching customer ordered products: ${error.message}`
1378 |           }
1379 |         ],
1380 |         isError: true
1381 |       };
1382 |     }
1383 |   }
1384 | );
1385 | 
1386 | // Start the MCP server with stdio transport
1387 | async function main() {
1388 |   try {
1389 |     console.error('Starting Magento MCP Server...');
1390 |     const transport = new StdioServerTransport();
1391 |     await server.connect(transport);
1392 |     console.error('Magento MCP Server running on stdio');
1393 |   } catch (error) {
1394 |     console.error('Error starting MCP server:', error);
1395 |     process.exit(1);
1396 |   }
1397 | }
1398 | 
1399 | main().catch(console.error);
1400 | 
```
Page 1/2FirstPrevNextLast