#
tokens: 18995/50000 12/13 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/boldcommerce/magento2-mcp?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:
--------------------------------------------------------------------------------

```
# Node.js
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
.npm/
.yarn/
*.tgz
.pnp.*
.yarn-integrity

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Build output
dist/
build/
out/
.output/

# Docker
.docker/
docker-compose.override.yml

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

# IDE - JetBrains (WebStorm, IntelliJ, etc)
.idea/
*.iml
*.iws
*.ipr
.idea_modules/

# IDE - Other
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
.netbeans/

# OS specific
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Testing
coverage/
.nyc_output/

# Temporary files
tmp/
temp/
.tmp/
.temp/
*.tmp
*.temp

# MCP specific
claude_desktop_config.json

```

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

```
# Cline Rules for Magento 2 MCP Server

## Project Structure
- The main server implementation is in `mcp-server.js`
- The test client is in `test-mcp-server.js`
- Memory Bank files are stored in the `memory-bank` directory

## Coding Patterns
- Use camelCase for variable and function names
- Use PascalCase for class names
- Use UPPER_SNAKE_CASE for constants
- Use async/await for asynchronous operations
- Use try/catch blocks for error handling
- Use the zod library for input validation
- Use the axios library for HTTP requests
- Use the dotenv library for environment variables

## MCP Tool Implementation Pattern
When implementing a new MCP tool, follow this pattern:
```javascript
server.tool(
  "tool_name",
  "Tool description",
  {
    param1: z.string().describe("Parameter 1 description"),
    param2: z.number().optional().describe("Parameter 2 description")
  },
  async ({ param1, param2 }) => {
    try {
      // Implementation
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);
```

## Date Handling
- Use ISO 8601 format (YYYY-MM-DD) for date representation
- Implement a date parser that can handle relative date expressions
- Use the date-fns library for date manipulation

## Error Handling
- Always wrap API calls in try/catch blocks
- Return detailed error messages to the client
- Log errors to the console for debugging
- Include the original error message in the response

## Response Formatting
- Use JSON.stringify with null, 2 for pretty-printing JSON responses
- Include metadata about the query in the response
- Format numbers with appropriate precision
- Format dates in a human-readable format

## Testing
- Use the test client to verify tool functionality
- Test with various input parameters
- Test error handling
- Test with edge cases

## Documentation
- Document all tools with clear descriptions
- Document all parameters with descriptions
- Document the expected response format
- Document any error conditions

```

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

```markdown
# Magento 2 MCP Server

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.

## Features

### Product Features
- Query product information by SKU or ID
- Search for products using various criteria
- Get product categories
- Get related products
- Get product stock information
- Get product attributes
- Update product attributes by specifying attribute code and value
- Advanced product search with filtering and sorting

### Customer Features
- Get all ordered products for a customer by email address

### Order and Revenue Features
- Get order count for specific date ranges
- Get revenue for specific date ranges
- Get revenue filtered by country for specific date ranges
- Get product sales statistics including quantity sold and top-selling products
- Support for relative date expressions like "today", "yesterday", "last week", "this month", "YTD"
- Support for country filtering using both country codes and country names

## Prerequisites

- Node.js (v14 or higher)
- A Magento 2 instance with REST API access
- API token for the Magento 2 instance

## Installation

1. Clone this repository
2. Install dependencies:

```bash
npm install
```

## Usage

### Running the server directly

```bash
node mcp-server.js
```

### Testing with the test client

```bash
node test-mcp-server.js
```

### Using with Claude Desktop

1. Check your path node with `which node`
2. Go to the Developer settings and click "Edit config". This will open a JSON file.
3. Add the following snippet within the `mcpServers`:

```
    "magento2": {
      "command": "/path/to/your/node",
      "args": ["/path/to/mcp-server.js"],
      "env": {
        "MAGENTO_BASE_URL": "https://YOUR_DOMAIN/rest/V1",
        "MAGENTO_API_TOKEN": "your-api-token"
      }
    }
```

3. Replace `/path/to/your/node` with the path you checked in step 1
4. Replace `/path/to/mcp-server.js` with the path where you cloned this repo
5. You can get an API token from System > Integrations in the Magento admin
6. Restart Claude Desktop.
7. You should now be able to ask Claude questions about products in your Magento store.

## Available Tools

The server exposes the following tools:

### Product Tools
- `get_product_by_sku`: Get detailed information about a product by its SKU
- `search_products`: Search for products using Magento search criteria
- `get_product_categories`: Get categories for a specific product by SKU
- `get_related_products`: Get products related to a specific product by SKU
- `get_product_stock`: Get stock information for a product by SKU
- `get_product_attributes`: Get all attributes for a product by SKU
- `get_product_by_id`: Get detailed information about a product by its ID
- `advanced_product_search`: Search for products with advanced filtering options
- `update_product_attribute`: Update a specific attribute of a product by SKU

### Customer Tools
- `get_customer_ordered_products_by_email`: Get all ordered products for a customer by email address

### Order and Revenue Tools
- `get_order_count`: Get the number of orders for a given date range
- `get_revenue`: Get the total revenue for a given date range
- `get_revenue_by_country`: Get revenue filtered by country for a given date range
- `get_product_sales`: Get statistics about the quantity of products sold in a given date range

## Example Queries for Claude

Once the MCP server is connected to Claude Desktop, you can ask questions like:

### Product Queries
- "What products do you have that are shirts?"
- "Tell me about product with SKU SKU-xxx"
- "What categories does product SKU-xxx belong to?"
- "Are there any related products to SKU-SKU-xxx?"
- "What's the stock status of product SKU-xxx?"
- "Show me all products sorted by price"
- "Update the price of product SKU-xxx to $49.99"
- "Change the description of product ABC-123 to describe it as water-resistant"
- "Set the status of product XYZ-456 to 'enabled'"

### Customer Queries
- "What products has customer [email protected] ordered?"
- "Show me the order history and products for customer with email [email protected]"

### Order and Revenue Queries
- "How many orders do we have today?"
- "What's our order count for last week?"
- "How much revenue did we generate yesterday?"
- "What was our total revenue last month?"
- "How much revenue did we make in The Netherlands this year to date?"
- "What's our revenue in Germany for the last week?"
- "Compare our revenue between the US and Canada for this month"
- "What's our average order value for completed orders this month?"
- "How many products did we sell last month?"
- "What are our top-selling products this year?"
- "What's the average number of products per order?"
- "How many units of product XYZ-123 did we sell in Germany last quarter?"
- "Which products generated the most revenue in the US this month?"


## Development

### SSL Certificate Verification

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.

### Adding New Tools

To add new tools, follow the pattern in the existing code. Each tool is defined with:

1. A unique name
2. A description
3. Input parameters with validation using Zod
4. An async handler function that processes the request and returns a response

## License

ISC

```

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

```json
{
  "name": "mcp-magento2",
  "version": "1.0.0",
  "description": "",
  "main": "claude-magento-client.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@anthropic-ai/sdk": "^0.39.0",
    "@modelcontextprotocol/sdk": "^1.6.1",
    "axios": "^1.8.1",
    "body-parser": "^1.20.3",
    "date-fns": "^3.6.0",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "zod": "^3.24.2"
  }
}

```

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

```markdown
# Project Brief: Magento 2 MCP Server

## Overview
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.

## Core Requirements
1. Provide tools to query product information from Magento 2
2. Provide tools to query customer information from Magento 2
3. Provide tools to query order information from Magento 2
4. Provide tools to query revenue and sales metrics from Magento 2
5. Support filtering by date ranges, including relative dates like "today", "last week", "YTD"
6. Support filtering by geographic regions like countries

## Goals
- Enable natural language queries about Magento 2 store data
- Provide accurate and timely information about sales, orders, and revenue
- Support business intelligence and reporting needs
- Make e-commerce data easily accessible through conversational interfaces

## Success Criteria
- Successfully retrieve accurate order counts for specified date ranges
- Successfully retrieve accurate revenue figures for specified date ranges
- Successfully filter data by geographic regions
- Support common date range expressions like "today", "yesterday", "last week", "this month", "YTD"
- Provide clear and concise responses to queries

## Constraints
- Requires valid Magento 2 API credentials
- Depends on the Magento 2 API being available and responsive
- Limited to the data and capabilities exposed by the Magento 2 API

```

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

```markdown
# Product Context: Magento 2 MCP Server

## Why This Project Exists
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.

## Problems It Solves
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.

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".

3. **Decision Support**: By making it easier to access sales and revenue data, the server supports better and faster business decision-making.

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.

## How It Should Work
1. The user asks a question about Magento 2 data in natural language.
2. The AI assistant (Claude) interprets the question and calls the appropriate MCP tool with the right parameters.
3. The MCP server translates these parameters into Magento 2 API calls.
4. The server processes the response from Magento 2 and formats it in a way that's easy for the AI assistant to understand.
5. The AI assistant presents the information to the user in a natural, conversational way.

## User Experience Goals
1. **Simplicity**: Users should be able to get the information they need without understanding the underlying technical details.

2. **Accuracy**: The data provided should be accurate and consistent with what's available in the Magento 2 admin interface.

3. **Contextual Understanding**: The system should understand relative date ranges like "today", "last week", or "YTD" without requiring explicit date formatting.

4. **Geographical Filtering**: Users should be able to filter data by geographical regions like countries or regions.

5. **Comprehensive Coverage**: The system should cover all key e-commerce metrics, including orders, revenue, products, and customers.

6. **Responsiveness**: Queries should be processed quickly, providing near real-time access to e-commerce data.

```

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

```markdown
# Active Context: Magento 2 MCP Server

## Current Work Focus
The current focus is on enhancing and refining the MCP server tools for fetching data about revenue and orders. This includes:

1. Improving error handling for the new tools
2. Adding comprehensive documentation for the new tools
3. Creating additional test cases for the new functionality
4. Optimizing performance for large result sets

## Recent Changes
- Implementation of the `get_order_count` tool to retrieve the number of orders for a given date range
- Implementation of the `get_revenue` tool to retrieve the total revenue for a given date range
- Implementation of the `get_revenue_by_country` tool to retrieve revenue filtered by country for a given date range
- Implementation of date parsing utilities to handle relative date expressions like "today", "last week", "YTD"
- Implementation of country normalization to handle both country codes and names
- Update of the test client to test the new tools
- Update of the Claude client to demonstrate the new tools

## Next Steps
1. Implement pagination handling for large result sets
2. Add caching mechanism for frequently requested data
3. Improve error handling for Magento 2 API rate limits
4. Add comprehensive documentation for the API endpoints and parameters
5. Create automated tests for the server functionality
6. Implement a logging system for debugging and monitoring

## Active Decisions and Considerations

### Date Range Parsing
We need to implement a robust date parsing system that can handle various relative date expressions:
- "today" -> Current day
- "yesterday" -> Previous day
- "last week" -> Previous 7 days
- "this month" -> Current month
- "last month" -> Previous month
- "YTD" (Year to Date) -> January 1st of current year to current date

### Country Filtering
For the `get_revenue_by_country` tool, we need to:
- Determine how countries are represented in the Magento 2 API (country codes, full names, etc.)
- Handle case-insensitive matching for country names
- Support filtering by multiple countries

### Performance Optimization
For large date ranges or high-volume stores, we need to consider:
- Pagination of results
- Efficient filtering at the API level rather than client-side
- Potential caching of frequently requested data

### Error Handling
We need robust error handling for:
- Invalid date ranges (e.g., end date before start date)
- Unknown country names
- API errors from Magento 2
- Rate limiting or timeout issues

### Response Formatting
The response format should be:
- Consistent across all tools
- Easy for Claude to parse and present to users
- Include metadata about the query (date range, filters applied, etc.)
- Include summary statistics where appropriate

```

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

```markdown
# Progress: Magento 2 MCP Server

## What Works
- Basic MCP server setup with stdio transport
- Authentication with Magento 2 API using API tokens
- Product-related tools:
  - `get_product_by_sku`: Get detailed information about a product by its SKU
  - `search_products`: Search for products using Magento search criteria
  - `get_product_categories`: Get categories for a specific product by SKU
  - `get_related_products`: Get products related to a specific product by SKU
  - `get_product_stock`: Get stock information for a product by SKU
  - `get_product_attributes`: Get all attributes for a product by SKU
  - `get_product_by_id`: Get detailed information about a product by its ID
  - `advanced_product_search`: Search for products with advanced filtering options
  - `update_product_attribute`: Update a specific attribute of a product by SKU
- Customer-related tools:
  - `get_customer_ordered_products_by_email`: Get all ordered products for a customer by email address
- Test client for verifying server functionality

## What's Left to Build
- Enhanced error handling for the new tools
- Documentation for the new tools
- Additional test cases for the new functionality

## Current Status
- The basic MCP server infrastructure is in place and working
- Product-related tools are implemented and tested
- Customer-related tools are implemented
- Order and revenue related tools are implemented:
  - `get_order_count`: Get the number of orders for a given date range
  - `get_revenue`: Get the total revenue for a given date range
  - `get_revenue_by_country`: Get revenue filtered by country for a given date range
  - `get_product_sales`: Get statistics about the quantity of products sold in a given date range
- Date parsing utilities are implemented, supporting:
  - "today"
  - "yesterday"
  - "this week"
  - "last week"
  - "this month"
  - "last month"
  - "ytd" (Year to Date)
  - "last year"
  - Specific dates in ISO format
  - Date ranges in "YYYY-MM-DD to YYYY-MM-DD" format
- Country filtering functionality is implemented, supporting:
  - Country codes (e.g., "US", "NL", "GB")
  - Country names (e.g., "United States", "The Netherlands", "United Kingdom")
  - Common variations (e.g., "USA", "Holland", "UK")

## Known Issues
- No comprehensive error handling for Magento 2 API rate limits
- No caching mechanism for frequently requested data
- No pagination handling for large result sets
- No authentication mechanism for the MCP server itself (relies on the security of the stdio transport)
- No logging system for debugging and monitoring
- No automated tests for the server functionality
- No documentation for the API endpoints and parameters

## Next Milestone
Enhance the existing tools with additional features and optimizations:
- Implement pagination handling for large result sets
- Add caching mechanism for frequently requested data
- Improve error handling for Magento 2 API rate limits
- Add comprehensive documentation for the API endpoints and parameters
- Create automated tests for the server functionality
- Implement a logging system for debugging and monitoring

This milestone will be considered complete when all these enhancements are implemented and tested.

```

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

```markdown
# Technical Context: Magento 2 MCP Server

## Technologies Used

### Core Technologies
- **Node.js**: The runtime environment for the MCP server
- **JavaScript**: The programming language used for implementation
- **Model Context Protocol (MCP)**: The protocol for communication between Claude and the server
- **Magento 2 REST API**: The API used to interact with the Magento 2 e-commerce platform

### Key Libraries and Dependencies
- **@modelcontextprotocol/sdk**: The official MCP SDK for implementing MCP servers and clients
- **axios**: HTTP client for making requests to the Magento 2 API
- **zod**: Schema validation library for defining tool input schemas
- **dotenv**: For loading environment variables from a .env file
- **date-fns**: For date manipulation and parsing

## Development Setup

### Environment Variables
The server requires the following environment variables:
- `MAGENTO_BASE_URL`: The base URL of the Magento 2 REST API
- `MAGENTO_API_TOKEN`: The API token for authenticating with the Magento 2 API

These can be set in a `.env` file in the project root or provided directly when running the server.

### Running the Server
The server can be run directly with Node.js:
```bash
node mcp-server.js
```

Or it can be run through the test client:
```bash
node test-mcp-server.js
```

### Testing
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.

## Technical Constraints

### Magento 2 API Limitations
- The Magento 2 API may have rate limits that could affect performance
- Some operations may be slow for large datasets
- Not all Magento 2 data is exposed through the API
- API structure and capabilities may vary between Magento 2 versions

### MCP Protocol Constraints
- Communication is synchronous and request-response based
- Tools must have well-defined input schemas
- Complex data structures must be serialized to JSON

### Performance Considerations
- Large result sets should be paginated
- Expensive operations should be optimized or cached
- Error handling should be robust to prevent crashes

## Dependencies

### External Systems
- **Magento 2 E-commerce Platform**: The primary data source for the MCP server
- **Claude AI Assistant**: The primary client for the MCP server

### Internal Dependencies
- **callMagentoApi**: Helper function for making authenticated requests to the Magento 2 API
- **Date parsing utilities**: For converting relative date expressions to concrete date ranges
- **Formatting functions**: For formatting API responses for better readability

## Integration Points

### Magento 2 API Endpoints
- `/orders`: For retrieving order information
- `/invoices`: For retrieving invoice and revenue information
- `/customers`: For retrieving customer information
- `/products`: For retrieving product information
- `/store/storeConfigs`: For retrieving store configuration information

### MCP Tools
The server exposes various tools for interacting with the Magento 2 API, including:
- Tools for retrieving product information
- Tools for searching products
- Tools for retrieving customer information
- Tools for retrieving order information
- Tools for retrieving revenue information

```

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

```javascript
#!/usr/bin/env node
const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');

async function main() {
  try {
    console.log('Connecting to Magento MCP Server...');
    
    // Create a transport that will start the server process
    const transport = new StdioClientTransport({
      command: 'node',
      args: ['./mcp-server.js']
    });
    
    // Create a client
    const client = new Client(
      {
        name: 'test-client',
        version: '1.0.0'
      },
      {
        capabilities: {
          tools: {}
        }
      }
    );
    
    // Connect to the server
    await client.connect(transport);
    console.log('Connected to Magento MCP Server');
    
    // List available tools
    const tools = await client.listTools();
    console.log('Available tools:');
    tools.tools.forEach(tool => {
      console.log(`- ${tool.name}: ${tool.description}`);
    });
    
    // Test a tool: search for products
    console.log('\nSearching for products with "shirt"...');
    const searchResult = await client.callTool({
      name: 'search_products',
      arguments: {
        query: 'shirt',
        page_size: 5
      }
    });
    
    console.log('Search results:');
    console.log(searchResult.content[0].text);
    
    // Test the order count tool
    console.log('\nGetting order count for today...');
    try {
      const orderCountResult = await client.callTool({
        name: 'get_order_count',
        arguments: {
          date_range: 'today'
        }
      });
      
      console.log('Order count:');
      console.log(orderCountResult.content[0].text);
    } catch (error) {
      console.log('Error getting order count:', error.message);
    }
    
    // Test the revenue tool
    console.log('\nGetting revenue for last week...');
    try {
      const revenueResult = await client.callTool({
        name: 'get_revenue',
        arguments: {
          date_range: 'last week',
          include_tax: true
        }
      });
      
      console.log('Revenue:');
      console.log(revenueResult.content[0].text);
    } catch (error) {
      console.log('Error getting revenue:', error.message);
    }
    
    // Test the revenue by country tool
    console.log('\nGetting revenue for The Netherlands this YTD...');
    try {
      const revenueByCountryResult = await client.callTool({
        name: 'get_revenue_by_country',
        arguments: {
          date_range: 'ytd',
          country: 'The Netherlands',
          include_tax: true
        }
      });
      
      console.log('Revenue by country:');
      console.log(revenueByCountryResult.content[0].text);
    } catch (error) {
      console.log('Error getting revenue by country:', error.message);
    }
    
    // Test the product sales tool
    console.log('\nGetting product sales statistics for last month...');
    try {
      const productSalesResult = await client.callTool({
        name: 'get_product_sales',
        arguments: {
          date_range: 'last month'
        }
      });
      
      console.log('Product sales statistics:');
      console.log(productSalesResult.content[0].text);
    } catch (error) {
      console.log('Error getting product sales statistics:', error.message);
    }
    
    // Test the customer ordered products by email tool
    console.log('\nGetting ordered products for customer by email...');
    try {
      const customerOrdersResult = await client.callTool({
        name: 'get_customer_ordered_products_by_email',
        arguments: {
          email: '[email protected]' // Replace with a valid customer email
        }
      });
      
      console.log('Customer ordered products:');
      console.log(customerOrdersResult.content[0].text);
    } catch (error) {
      console.log('Error getting customer ordered products:', error.message);
    }
    
    // Close the connection
    await client.close();
    console.log('Connection closed');
  } catch (error) {
    console.error('Error:', error);
  }
}

main().catch(console.error);

```

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

```markdown
# MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)

## Table of Contents
- [MCP TypeScript SDK  ](#mcp-typescript-sdk--)
  - [Table of Contents](#table-of-contents)
  - [Overview](#overview)
  - [Installation](#installation)
  - [Quick Start](#quick-start)
  - [What is MCP?](#what-is-mcp)
  - [Core Concepts](#core-concepts)
    - [Server](#server)
    - [Resources](#resources)
    - [Tools](#tools)
    - [Prompts](#prompts)
  - [Running Your Server](#running-your-server)
    - [stdio](#stdio)
    - [HTTP with SSE](#http-with-sse)
    - [Testing and Debugging](#testing-and-debugging)
  - [Examples](#examples)
    - [Echo Server](#echo-server)
    - [SQLite Explorer](#sqlite-explorer)
  - [Advanced Usage](#advanced-usage)
    - [Low-Level Server](#low-level-server)
    - [Writing MCP Clients](#writing-mcp-clients)
  - [Documentation](#documentation)
  - [Contributing](#contributing)
  - [License](#license)

## Overview

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:

- Build MCP clients that can connect to any MCP server
- Create MCP servers that expose resources, prompts and tools
- Use standard transports like stdio and SSE
- Handle all MCP protocol messages and lifecycle events

## Installation

```bash
npm install @modelcontextprotocol/sdk
```

## Quick Start

Let's create a simple MCP server that exposes a calculator tool and some data:

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create an MCP server
const server = new McpServer({
  name: "Demo",
  version: "1.0.0"
});

// Add an addition tool
server.tool("add",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }]
  })
);

// Add a dynamic greeting resource
server.resource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  async (uri, { name }) => ({
    contents: [{
      uri: uri.href,
      text: `Hello, ${name}!`
    }]
  })
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
```

## What is MCP?

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:

- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
- And more!

## Core Concepts

### Server

The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:

```typescript
const server = new McpServer({
  name: "My App",
  version: "1.0.0"
});
```

### Resources

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:

```typescript
// Static resource
server.resource(
  "config",
  "config://app",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// Dynamic resource with parameters
server.resource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);
```

### Tools

Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:

```typescript
// Simple tool with parameters
server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

// Async tool with external API call
server.tool(
  "fetch-weather",
  { city: z.string() },
  async ({ city }) => {
    const response = await fetch(`https://api.weather.com/${city}`);
    const data = await response.text();
    return {
      content: [{ type: "text", text: data }]
    };
  }
);
```

### Prompts

Prompts are reusable templates that help LLMs interact with your server effectively:

```typescript
server.prompt(
  "review-code",
  { code: z.string() },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please review this code:\n\n${code}`
      }
    }]
  })
);
```

## Running Your Server

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:

### stdio

For command-line tools and direct integrations:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const transport = new StdioServerTransport();
await server.connect(transport);
```

### HTTP with SSE

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:

```typescript
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const app = express();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  // Note: to support multiple simultaneous connections, these messages will
  // need to be routed to a specific matching transport. (This logic isn't
  // implemented here, for simplicity.)
  await transport.handlePostMessage(req, res);
});

app.listen(3001);
```

### Testing and Debugging

To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.

## Examples

### Echo Server

A simple server demonstrating resources, tools, and prompts:

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "Echo",
  version: "1.0.0"
});

server.resource(
  "echo",
  new ResourceTemplate("echo://{message}", { list: undefined }),
  async (uri, { message }) => ({
    contents: [{
      uri: uri.href,
      text: `Resource echo: ${message}`
    }]
  })
);

server.tool(
  "echo",
  { message: z.string() },
  async ({ message }) => ({
    content: [{ type: "text", text: `Tool echo: ${message}` }]
  })
);

server.prompt(
  "echo",
  { message: z.string() },
  ({ message }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please process this message: ${message}`
      }
    }]
  })
);
```

### SQLite Explorer

A more complex example showing database integration:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";

const server = new McpServer({
  name: "SQLite Explorer",
  version: "1.0.0"
});

// Helper to create DB connection
const getDb = () => {
  const db = new sqlite3.Database("database.db");
  return {
    all: promisify<string, any[]>(db.all.bind(db)),
    close: promisify(db.close.bind(db))
  };
};

server.resource(
  "schema",
  "schema://main",
  async (uri) => {
    const db = getDb();
    try {
      const tables = await db.all(
        "SELECT sql FROM sqlite_master WHERE type='table'"
      );
      return {
        contents: [{
          uri: uri.href,
          text: tables.map((t: {sql: string}) => t.sql).join("\n")
        }]
      };
    } finally {
      await db.close();
    }
  }
);

server.tool(
  "query",
  { sql: z.string() },
  async ({ sql }) => {
    const db = getDb();
    try {
      const results = await db.all(sql);
      return {
        content: [{
          type: "text",
          text: JSON.stringify(results, null, 2)
        }]
      };
    } catch (err: unknown) {
      const error = err as Error;
      return {
        content: [{
          type: "text",
          text: `Error: ${error.message}`
        }],
        isError: true
      };
    } finally {
      await db.close();
    }
  }
);
```

## Advanced Usage

### Low-Level Server

For more control, you can use the low-level Server class directly:

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListPromptsRequestSchema,
  GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {}
    }
  }
);

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [{
      name: "example-prompt",
      description: "An example prompt template",
      arguments: [{
        name: "arg1",
        description: "Example argument",
        required: true
      }]
    }]
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  if (request.params.name !== "example-prompt") {
    throw new Error("Unknown prompt");
  }
  return {
    description: "Example prompt",
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: "Example prompt text"
      }
    }]
  };
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Writing MCP Clients

The SDK provides a high-level client interface:

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"]
});

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {},
      resources: {},
      tools: {}
    }
  }
);

await client.connect(transport);

// List prompts
const prompts = await client.listPrompts();

// Get a prompt
const prompt = await client.getPrompt("example-prompt", {
  arg1: "value"
});

// List resources
const resources = await client.listResources();

// Read a resource
const resource = await client.readResource("file:///example.txt");

// Call a tool
const result = await client.callTool({
  name: "example-tool",
  arguments: {
    arg1: "value"
  }
});
```

## Documentation

- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [MCP Specification](https://spec.modelcontextprotocol.io)
- [Example Servers](https://github.com/modelcontextprotocol/servers)

## Contributing

Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.

## License

This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.

```

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

```javascript
#!/usr/bin/env node
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const { z } = require('zod');
const axios = require('axios');
const dotenv = require('dotenv');
const { format, parse, parseISO, isValid, addDays, subDays, startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfYear, isAfter, isBefore } = require('date-fns');

// Load environment variables from .env file
dotenv.config();

// Magento 2 API Configuration
const MAGENTO_BASE_URL = process.env.MAGENTO_BASE_URL || 'https://your-magento-store.com/rest/V1';
const MAGENTO_API_TOKEN = process.env.MAGENTO_API_TOKEN;

// Validate environment variables
if (!MAGENTO_API_TOKEN) {
  console.error('ERROR: MAGENTO_API_TOKEN environment variable is required');
  process.exit(1);
}

// Date parsing utilities
function parseDateExpression(dateExpression) {
  const now = new Date();
  const currentYear = now.getFullYear();
  const currentMonth = now.getMonth();
  const currentDay = now.getDate();
  
  // Normalize the date expression
  const normalizedExpression = dateExpression.toLowerCase().trim();
  
  // Handle relative date expressions
  switch (normalizedExpression) {
    case 'today':
      return {
        startDate: startOfDay(now),
        endDate: endOfDay(now),
        description: 'Today'
      };
    case 'yesterday':
      const yesterday = subDays(now, 1);
      return {
        startDate: startOfDay(yesterday),
        endDate: endOfDay(yesterday),
        description: 'Yesterday'
      };
    case 'this week':
      return {
        startDate: startOfWeek(now, { weekStartsOn: 1 }), // Week starts on Monday
        endDate: endOfDay(now),
        description: 'This week'
      };
    case 'last week':
      const lastWeekStart = subDays(startOfWeek(now, { weekStartsOn: 1 }), 7);
      const lastWeekEnd = subDays(endOfWeek(now, { weekStartsOn: 1 }), 7);
      return {
        startDate: lastWeekStart,
        endDate: lastWeekEnd,
        description: 'Last week'
      };
    case 'this month':
      return {
        startDate: startOfMonth(now),
        endDate: endOfDay(now),
        description: 'This month'
      };
    case 'last month':
      const lastMonth = new Date(currentYear, currentMonth - 1, 1);
      return {
        startDate: startOfMonth(lastMonth),
        endDate: endOfMonth(lastMonth),
        description: 'Last month'
      };
    case 'ytd':
    case 'this ytd':
    case 'this year to date':
    case 'year to date':
      return {
        startDate: startOfYear(now),
        endDate: endOfDay(now),
        description: 'Year to date'
      };
    case 'last year':
      const lastYear = new Date(currentYear - 1, 0, 1);
      return {
        startDate: startOfYear(lastYear),
        endDate: endOfYear(lastYear),
        description: 'Last year'
      };
    default:
      // Try to parse as ISO date or other common formats
      try {
        // Check if it's a single date (not a range)
        const parsedDate = parseISO(normalizedExpression);
        if (isValid(parsedDate)) {
          return {
            startDate: startOfDay(parsedDate),
            endDate: endOfDay(parsedDate),
            description: format(parsedDate, 'yyyy-MM-dd')
          };
        }
        
        // Check if it's a date range in format "YYYY-MM-DD to YYYY-MM-DD"
        const rangeParts = normalizedExpression.split(' to ');
        if (rangeParts.length === 2) {
          const startDate = parseISO(rangeParts[0]);
          const endDate = parseISO(rangeParts[1]);
          
          if (isValid(startDate) && isValid(endDate)) {
            return {
              startDate: startOfDay(startDate),
              endDate: endOfDay(endDate),
              description: `${format(startDate, 'yyyy-MM-dd')} to ${format(endDate, 'yyyy-MM-dd')}`
            };
          }
        }
        
        // If we can't parse it, throw an error
        throw new Error(`Unable to parse date expression: ${dateExpression}`);
      } catch (error) {
        throw new Error(`Invalid date expression: ${dateExpression}. ${error.message}`);
      }
  }
}

// Helper function to get the end of a year
function endOfYear(date) {
  return new Date(date.getFullYear(), 11, 31, 23, 59, 59, 999);
}

// Helper function to format a date for Magento API
function formatDateForMagento(date) {
  return format(date, "yyyy-MM-dd HH:mm:ss");
}

// Helper function to build date range filter for Magento API
function buildDateRangeFilter(field, startDate, endDate) {
  const formattedStartDate = formatDateForMagento(startDate);
  const formattedEndDate = formatDateForMagento(endDate);
  
  return [
    `searchCriteria[filter_groups][0][filters][0][field]=${field}`,
    `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(formattedStartDate)}`,
    `searchCriteria[filter_groups][0][filters][0][condition_type]=gteq`,
    `searchCriteria[filter_groups][1][filters][0][field]=${field}`,
    `searchCriteria[filter_groups][1][filters][0][value]=${encodeURIComponent(formattedEndDate)}`,
    `searchCriteria[filter_groups][1][filters][0][condition_type]=lteq`
  ].join('&');
}

// Helper function to normalize country input
function normalizeCountry(country) {
  // Normalize the country input (handle both country codes and names)
  const countryInput = country.trim().toLowerCase();
  
  // Map of common country names to ISO country codes
  const countryMap = {
    // Common variations for The Netherlands
    'netherlands': 'NL',
    'the netherlands': 'NL',
    'holland': 'NL',
    'nl': 'NL',
    
    // Common variations for United States
    'united states': 'US',
    'usa': 'US',
    'us': 'US',
    'america': 'US',
    
    // Common variations for United Kingdom
    'united kingdom': 'GB',
    'uk': 'GB',
    'great britain': 'GB',
    'gb': 'GB',
    'england': 'GB',
    
    // Add more countries as needed
    'canada': 'CA',
    'ca': 'CA',
    
    'australia': 'AU',
    'au': 'AU',
    
    'germany': 'DE',
    'de': 'DE',
    
    'france': 'FR',
    'fr': 'FR',
    
    'italy': 'IT',
    'it': 'IT',
    
    'spain': 'ES',
    'es': 'ES',
    
    'belgium': 'BE',
    'be': 'BE',
    
    'sweden': 'SE',
    'se': 'SE',
    
    'norway': 'NO',
    'no': 'NO',
    
    'denmark': 'DK',
    'dk': 'DK',
    
    'finland': 'FI',
    'fi': 'FI',
    
    'ireland': 'IE',
    'ie': 'IE',
    
    'switzerland': 'CH',
    'ch': 'CH',
    
    'austria': 'AT',
    'at': 'AT',
    
    'portugal': 'PT',
    'pt': 'PT',
    
    'greece': 'GR',
    'gr': 'GR',
    
    'poland': 'PL',
    'pl': 'PL',
    
    'japan': 'JP',
    'jp': 'JP',
    
    'china': 'CN',
    'cn': 'CN',
    
    'india': 'IN',
    'in': 'IN',
    
    'brazil': 'BR',
    'br': 'BR',
    
    'mexico': 'MX',
    'mx': 'MX',
    
    'south africa': 'ZA',
    'za': 'ZA'
  };
  
  // Check if the input is in our map
  if (countryMap[countryInput]) {
    return [countryMap[countryInput]];
  }
  
  // If it's not in our map, assume it's a country code or name and return as is
  // For a more robust solution, we would validate against a complete list of country codes
  return [countryInput.toUpperCase()];
}

// Helper function to fetch all pages for a given search criteria
async function fetchAllPages(endpoint, baseSearchCriteria) {
  const pageSize = 100; // Or make this configurable if needed
  let currentPage = 1;
  let allItems = [];
  let totalCount = 0;
  
  do {
    // Build search criteria for the current page, ensuring baseSearchCriteria doesn't already have pagination
    let currentPageSearchCriteria = baseSearchCriteria;
    if (!currentPageSearchCriteria.includes('searchCriteria[pageSize]')) {
      currentPageSearchCriteria += `&searchCriteria[pageSize]=${pageSize}`;
    }
    if (!currentPageSearchCriteria.includes('searchCriteria[currentPage]')) {
      currentPageSearchCriteria += `&searchCriteria[currentPage]=${currentPage}`;
    } else {
      // If currentPage is already there, replace it (less common case)
      currentPageSearchCriteria = currentPageSearchCriteria.replace(/searchCriteria\[currentPage\]=\d+/, `searchCriteria[currentPage]=${currentPage}`);
    }

    // Make the API call for the current page
    const responseData = await callMagentoApi(`${endpoint}?${currentPageSearchCriteria}`);
    
    if (responseData.items && Array.isArray(responseData.items)) {
      allItems = allItems.concat(responseData.items);
    }
    
    // Update total count (only needs to be set once)
    if (currentPage === 1) {
      totalCount = responseData.total_count || 0;
    }
    
    // Check if we need to fetch more pages
    if (totalCount <= allItems.length || !responseData.items || responseData.items.length < pageSize) {
      break; // Exit loop if all items are fetched or last page had less than pageSize items
    }
    
    currentPage++;
    
  } while (true); // Loop continues until break
  
  return allItems; // Return the aggregated list of items
}

// Create an MCP server
const server = new McpServer({
  name: "magento-mcp-server",
  version: "1.0.0"
});

// Helper function to make authenticated requests to Magento 2 API
async function callMagentoApi(endpoint, method = 'GET', data = null) {
  try {
    const url = `${MAGENTO_BASE_URL}${endpoint}`;
    const headers = {
      'Authorization': `Bearer ${MAGENTO_API_TOKEN}`,
      'Content-Type': 'application/json'
    };
    
    const config = {
      method,
      url,
      headers,
      data: data ? JSON.stringify(data) : undefined,
      // Bypass SSL certificate verification for development
      httpsAgent: new (require('https').Agent)({
        rejectUnauthorized: false
      })
    };
    
    const response = await axios(config);
    return response.data;
  } catch (error) {
    console.error('Magento API Error:', error.response?.data || error.message);
    throw error;
  }
}

// Format product data for better readability
function formatProduct(product) {
  if (!product) return "Product not found";
  
  // Extract custom attributes into a more readable format
  const customAttributes = {};
  if (product.custom_attributes && Array.isArray(product.custom_attributes)) {
    product.custom_attributes.forEach(attr => {
      customAttributes[attr.attribute_code] = attr.value;
    });
  }
  
  return {
    id: product.id,
    sku: product.sku,
    name: product.name,
    price: product.price,
    status: product.status,
    visibility: product.visibility,
    type_id: product.type_id,
    created_at: product.created_at,
    updated_at: product.updated_at,
    extension_attributes: product.extension_attributes,
    custom_attributes: customAttributes
  };
}

// Format search results for better readability
function formatSearchResults(results) {
  if (!results || !results.items || !Array.isArray(results.items)) {
    return "No products found";
  }
  
  return {
    total_count: results.total_count,
    items: results.items.map(item => ({
      id: item.id,
      sku: item.sku,
      name: item.name,
      price: item.price,
      status: item.status,
      type_id: item.type_id
    }))
  };
}

// Tool: Get product by SKU
server.tool(
  "get_product_by_sku",
  "Get detailed information about a product by its SKU",
  {
    sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
  },
  async ({ sku }) => {
    try {
      const productData = await callMagentoApi(`/products/${sku}`);
      const formattedProduct = formatProduct(productData);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(formattedProduct, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching product: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Search products
server.tool(
  "search_products",
  "Search for products using Magento search criteria",
  {
    query: z.string().describe("Search query (product name, description, etc.)"),
    page_size: z.number().optional().describe("Number of results per page (default: 10)"),
    current_page: z.number().optional().describe("Page number (default: 1)")
  },
  async ({ query, page_size = 10, current_page = 1 }) => {
    try {
      // Build search criteria for a simple name search
      const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=name&` +
                            `searchCriteria[filter_groups][0][filters][0][value]=%25${encodeURIComponent(query)}%25&` +
                            `searchCriteria[filter_groups][0][filters][0][condition_type]=like&` +
                            `searchCriteria[pageSize]=${page_size}&` +
                            `searchCriteria[currentPage]=${current_page}`;
      
      const productData = await callMagentoApi(`/products?${searchCriteria}`);
      const formattedResults = formatSearchResults(productData);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(formattedResults, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error searching products: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get product categories
server.tool(
  "get_product_categories",
  "Get categories for a specific product by SKU",
  {
    sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
  },
  async ({ sku }) => {
    try {
      // First get the product to find its category IDs
      const productData = await callMagentoApi(`/products/${sku}`);
      
      // Find category IDs in custom attributes
      const categoryAttribute = productData.custom_attributes?.find(
        attr => attr.attribute_code === 'category_ids'
      );
      
      if (!categoryAttribute || !categoryAttribute.value) {
        return {
          content: [
            {
              type: "text",
              text: `No categories found for product with SKU: ${sku}`
            }
          ]
        };
      }
      
      // Parse category IDs (they might be in string format)
      let categoryIds = categoryAttribute.value;
      if (typeof categoryIds === 'string') {
        try {
          categoryIds = JSON.parse(categoryIds);
        } catch (e) {
          // If it's not valid JSON, split by comma
          categoryIds = categoryIds.split(',').map(id => id.trim());
        }
      }
      
      if (!Array.isArray(categoryIds)) {
        categoryIds = [categoryIds];
      }
      
      // Get category details for each ID
      const categoryPromises = categoryIds.map(id => 
        callMagentoApi(`/categories/${id}`)
          .catch(err => ({ id, error: err.message }))
      );
      
      const categories = await Promise.all(categoryPromises);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(categories, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching product categories: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get related products
server.tool(
  "get_related_products",
  "Get products related to a specific product by SKU",
  {
    sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
  },
  async ({ sku }) => {
    try {
      const relatedProducts = await callMagentoApi(`/products/${sku}/links/related`);
      
      if (!relatedProducts || relatedProducts.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No related products found for SKU: ${sku}`
            }
          ]
        };
      }
      
      // Get full details for each related product
      const productPromises = relatedProducts.map(related => 
        callMagentoApi(`/products/${related.linked_product_sku}`)
          .then(product => formatProduct(product))
          .catch(err => ({ sku: related.linked_product_sku, error: err.message }))
      );
      
      const products = await Promise.all(productPromises);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(products, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching related products: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get product stock information
server.tool(
  "get_product_stock",
  "Get stock information for a product by SKU",
  {
    sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
  },
  async ({ sku }) => {
    try {
      const stockData = await callMagentoApi(`/stockItems/${sku}`);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(stockData, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching stock information: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get product attributes
server.tool(
  "get_product_attributes",
  "Get all attributes for a product by SKU",
  {
    sku: z.string().describe("The SKU (Stock Keeping Unit) of the product")
  },
  async ({ sku }) => {
    try {
      const productData = await callMagentoApi(`/products/${sku}`);
      
      // Extract and format attributes
      const attributes = {
        base_attributes: {
          id: productData.id,
          sku: productData.sku,
          name: productData.name,
          price: productData.price,
          status: productData.status,
          visibility: productData.visibility,
          type_id: productData.type_id
        },
        custom_attributes: {}
      };
      
      if (productData.custom_attributes && Array.isArray(productData.custom_attributes)) {
        productData.custom_attributes.forEach(attr => {
          attributes.custom_attributes[attr.attribute_code] = attr.value;
        });
      }
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(attributes, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching product attributes: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get product by ID
server.tool(
  "get_product_by_id",
  "Get detailed information about a product by its ID",
  {
    id: z.number().describe("The ID of the product")
  },
  async ({ id }) => {
    try {
      // First we need to search for the product by ID to get its SKU
      const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=entity_id&` +
                            `searchCriteria[filter_groups][0][filters][0][value]=${id}&` +
                            `searchCriteria[filter_groups][0][filters][0][condition_type]=eq`;
      
      const searchResults = await callMagentoApi(`/products?${searchCriteria}`);
      
      if (!searchResults.items || searchResults.items.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No product found with ID: ${id}`
            }
          ]
        };
      }
      
      // Get the SKU from the search results
      const sku = searchResults.items[0].sku;
      
      // Now get the full product details using the SKU
      const productData = await callMagentoApi(`/products/${sku}`);
      const formattedProduct = formatProduct(productData);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(formattedProduct, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching product: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Advanced product search
server.tool(
  "advanced_product_search",
  "Search for products with advanced filtering options",
  {
    field: z.string().describe("Field to search on (e.g., name, sku, price, status)"),
    value: z.string().describe("Value to search for"),
    condition_type: z.string().optional().describe("Condition type (eq, like, gt, lt, etc.). Default: eq"),
    page_size: z.number().optional().describe("Number of results per page (default: 10)"),
    current_page: z.number().optional().describe("Page number (default: 1)"),
    sort_field: z.string().optional().describe("Field to sort by (default: entity_id)"),
    sort_direction: z.string().optional().describe("Sort direction (ASC or DESC, default: DESC)")
  },
  async ({ field, value, condition_type = 'eq', page_size = 10, current_page = 1, sort_field = 'entity_id', sort_direction = 'DESC' }) => {
    try {
      // Build search criteria
      const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=${encodeURIComponent(field)}&` +
                            `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(value)}&` +
                            `searchCriteria[filter_groups][0][filters][0][condition_type]=${encodeURIComponent(condition_type)}&` +
                            `searchCriteria[pageSize]=${page_size}&` +
                            `searchCriteria[currentPage]=${current_page}&` +
                            `searchCriteria[sortOrders][0][field]=${encodeURIComponent(sort_field)}&` +
                            `searchCriteria[sortOrders][0][direction]=${encodeURIComponent(sort_direction)}`;
      
      const productData = await callMagentoApi(`/products?${searchCriteria}`);
      const formattedResults = formatSearchResults(productData);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(formattedResults, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error performing advanced search: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Update product attribute
server.tool(
  "update_product_attribute",
  "Update a specific attribute of a product by SKU",
  {
    sku: z.string().describe("The SKU (Stock Keeping Unit) of the product"),
    attribute_code: z.string().describe("The code of the attribute to update (e.g., name, price, description, status, etc.)"),
    value: z.any().describe("The new value for the attribute")
  },
  async ({ sku, attribute_code, value }) => {
    try {
      // First, check if the product exists
      const productData = await callMagentoApi(`/products/${sku}`).catch(() => null);
      
      if (!productData) {
        return {
          content: [
            {
              type: "text",
              text: `Product with SKU '${sku}' not found`
            }
          ],
          isError: true
        };
      }
      
      // Prepare the update data with the correct structure
      // Magento 2 API requires a "product" wrapper object
      let updateData = {
        product: {}
      };
      
      // Determine if this is a standard attribute or custom attribute
      const isCustomAttribute = productData.custom_attributes && 
                               productData.custom_attributes.some(attr => attr.attribute_code === attribute_code);
      
      if (isCustomAttribute) {
        // For custom attributes, we need to use the custom_attributes array
        updateData.product.custom_attributes = [
          {
            attribute_code,
            value
          }
        ];
      } else {
        // For standard attributes, we set them directly on the product object
        updateData.product[attribute_code] = value;
      }
      
      // Make the API call to update the product
      const result = await callMagentoApi(`/products/${sku}`, 'PUT', updateData);
      
      return {
        content: [
          {
            type: "text",
            text: `Successfully updated '${attribute_code}' for product with SKU '${sku}'. Updated product: ${JSON.stringify(formatProduct(result), null, 2)}`
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error updating product attribute: ${error.response?.data?.message || error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get revenue
server.tool(
  "get_revenue",
  "Get the total revenue for a given date range",
  {
    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')"),
    status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
    include_tax: z.boolean().optional().describe("Whether to include tax in the revenue calculation (default: true)")
  },
  async ({ date_range, status, include_tax = true }) => {
    try {
      // Parse the date range expression
      const dateRange = parseDateExpression(date_range);
      
      // Build the search criteria for the date range
      let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
      
      // Add status filter if provided
      if (status) {
        searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
                          `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
                          `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
      }
      
      // Fetch all orders using the helper function
      const allOrders = await fetchAllPages('/orders', searchCriteria);
      
      // Calculate total revenue
      let totalRevenue = 0;
      let totalTax = 0;
      let orderCount = 0;
      
      if (allOrders && Array.isArray(allOrders)) {
        orderCount = allOrders.length;
        
        allOrders.forEach(order => {
          // Use grand_total which includes tax, shipping, etc.
          totalRevenue += parseFloat(order.grand_total || 0);
          
          // Track tax separately
          totalTax += parseFloat(order.tax_amount || 0);
        });
      }
      
      // Adjust revenue if tax should be excluded
      const revenueWithoutTax = totalRevenue - totalTax;
      const finalRevenue = include_tax ? totalRevenue : revenueWithoutTax;
      
      // Format the response
      const result = {
        query: {
          date_range: dateRange.description,
          status: status || 'All',
          include_tax: include_tax,
          period: {
            start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
            end_date: format(dateRange.endDate, 'yyyy-MM-dd')
          }
        },
        result: {
          revenue: parseFloat(finalRevenue.toFixed(2)),
          currency: 'USD', // This should be dynamically determined from the store configuration
          order_count: orderCount,
          average_order_value: orderCount > 0 ? parseFloat((finalRevenue / orderCount).toFixed(2)) : 0,
          tax_amount: parseFloat(totalTax.toFixed(2))
        }
      };
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching revenue: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get order count
server.tool(
  "get_order_count",
  "Get the number of orders for a given date range",
  {
    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')"),
    status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')")
  },
  async ({ date_range, status }) => {
    try {
      // Parse the date range expression
      const dateRange = parseDateExpression(date_range);
      
      // Build the search criteria for the date range
      let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
      
      // Add status filter if provided
      if (status) {
        searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
                          `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
                          `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
      }
      
      // Add pagination to get all results
      searchCriteria += `&searchCriteria[pageSize]=1&searchCriteria[currentPage]=1`;
      
      // Make the API call to get orders
      const ordersData = await callMagentoApi(`/orders?${searchCriteria}`);
      
      // Format the response
      const result = {
        query: {
          date_range: dateRange.description,
          status: status || 'All',
          period: {
            start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
            end_date: format(dateRange.endDate, 'yyyy-MM-dd')
          }
        },
        result: {
          order_count: ordersData.total_count || 0
        }
      };
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching order count: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get product sales
server.tool(
  "get_product_sales",
  "Get statistics about the quantity of products sold in a given date range",
  {
    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')"),
    status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
    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')")
  },
  async ({ date_range, status, country }) => {
    try {
      // Parse the date range expression
      const dateRange = parseDateExpression(date_range);
      
      // Build the search criteria for the date range
      let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
      
      // Add status filter if provided
      if (status) {
        searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
                          `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
                          `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
      }
      
      // Fetch all orders using the helper function
      const allOrders = await fetchAllPages('/orders', searchCriteria);
      
      // Filter orders by country if provided
      let filteredOrders = allOrders;
      if (country) {
        // Normalize country input
        const normalizedCountry = normalizeCountry(country);
        
        // Filter orders by country
        filteredOrders = filteredOrders.filter(order => {
          // Check billing address country
          const billingCountry = order.billing_address?.country_id;
          
          // Check shipping address country
          const shippingCountry = order.extension_attributes?.shipping_assignments?.[0]?.shipping?.address?.country_id;
          
          // Match if either billing or shipping country matches
          return normalizedCountry.includes(billingCountry) || normalizedCountry.includes(shippingCountry);
        });
      }
      
      // Calculate statistics
      let totalOrders = filteredOrders.length;
      let totalOrderItems = 0;
      let totalProductQuantity = 0;
      let totalRevenue = 0;
      let productCounts = {};
      
      // Process each order
      filteredOrders.forEach(order => {
        // Add to total revenue
        totalRevenue += parseFloat(order.grand_total || 0);
        
        // Process order items
        if (order.items && Array.isArray(order.items)) {
          // Count total order items (order lines)
          totalOrderItems += order.items.length;
          
          // Process each item
          order.items.forEach(item => {
            // Add to total product quantity
            const quantity = parseFloat(item.qty_ordered || 0);
            totalProductQuantity += quantity;
            
            // Track product counts by SKU
            const sku = item.sku;
            if (sku) {
              if (!productCounts[sku]) {
                productCounts[sku] = {
                  name: item.name,
                  quantity: 0,
                  revenue: 0
                };
              }
              productCounts[sku].quantity += quantity;
              productCounts[sku].revenue += parseFloat(item.row_total || 0);
            }
          });
        }
      });
      
      // Convert product counts to array and sort by quantity
      const topProducts = Object.entries(productCounts)
        .map(([sku, data]) => ({
          sku,
          name: data.name,
          quantity: data.quantity,
          revenue: data.revenue
        }))
        .sort((a, b) => b.quantity - a.quantity)
        .slice(0, 10); // Top 10 products
      
      // Format the response
      const result = {
        query: {
          date_range: dateRange.description,
          status: status || 'All',
          country: country || 'All',
          period: {
            start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
            end_date: format(dateRange.endDate, 'yyyy-MM-dd')
          }
        },
        result: {
          total_orders: totalOrders,
          total_order_items: totalOrderItems,
          total_product_quantity: totalProductQuantity,
          average_products_per_order: totalOrders > 0 ? parseFloat((totalProductQuantity / totalOrders).toFixed(2)) : 0,
          total_revenue: parseFloat(totalRevenue.toFixed(2)),
          average_revenue_per_product: totalProductQuantity > 0 ? parseFloat((totalRevenue / totalProductQuantity).toFixed(2)) : 0,
          top_products: topProducts
        }
      };
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching product sales: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get revenue by country
server.tool(
  "get_revenue_by_country",
  "Get revenue filtered by country for a given date range",
  {
    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')"),
    country: z.string().describe("Country code (e.g., 'US', 'NL', 'GB') or country name (e.g., 'United States', 'The Netherlands', 'United Kingdom')"),
    status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
    include_tax: z.boolean().optional().describe("Whether to include tax in the revenue calculation (default: true)")
  },
  async ({ date_range, country, status, include_tax = true }) => {
    try {
      // Parse the date range expression
      const dateRange = parseDateExpression(date_range);
      
      // Normalize country input (handle both country codes and names)
      const normalizedCountry = normalizeCountry(country);
      
      // Build the search criteria for the date range
      let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
      
      // Add status filter if provided
      if (status) {
        searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
                          `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
                          `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
      }
      
      // Fetch all orders using the helper function
      const allOrders = await fetchAllPages('/orders', searchCriteria);
      
      // Filter orders by country and calculate revenue
      let totalRevenue = 0;
      let totalTax = 0;
      let orderCount = 0;
      let filteredOrders = [];
      
      if (allOrders && Array.isArray(allOrders)) {
        // Filter orders by country
        filteredOrders = allOrders.filter(order => {
          // Check billing address country
          const billingCountry = order.billing_address?.country_id;
          
          // Check shipping address country
          const shippingCountry = order.extension_attributes?.shipping_assignments?.[0]?.shipping?.address?.country_id;
          
          // Match if either billing or shipping country matches
          return normalizedCountry.includes(billingCountry) || normalizedCountry.includes(shippingCountry);
        });
        
        orderCount = filteredOrders.length;
        
        // Calculate revenue for filtered orders
        filteredOrders.forEach(order => {
          // Use grand_total which includes tax, shipping, etc.
          totalRevenue += parseFloat(order.grand_total || 0);
          
          // Track tax separately
          totalTax += parseFloat(order.tax_amount || 0);
        });
      }
      
      // Adjust revenue if tax should be excluded
      const revenueWithoutTax = totalRevenue - totalTax;
      const finalRevenue = include_tax ? totalRevenue : revenueWithoutTax;
      
      // Format the response
      const result = {
        query: {
          date_range: dateRange.description,
          country: country,
          normalized_country: normalizedCountry.join(', '),
          status: status || 'All',
          include_tax: include_tax,
          period: {
            start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
            end_date: format(dateRange.endDate, 'yyyy-MM-dd')
          }
        },
        result: {
          revenue: parseFloat(finalRevenue.toFixed(2)),
          currency: 'USD', // This should be dynamically determined from the store configuration
          order_count: orderCount,
          average_order_value: orderCount > 0 ? parseFloat((finalRevenue / orderCount).toFixed(2)) : 0,
          tax_amount: parseFloat(totalTax.toFixed(2))
        }
      };
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching revenue by country: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Tool: Get customer ordered products by email
server.tool(
  "get_customer_ordered_products_by_email",
  "Get all ordered products for a customer by email address",
  {
    email: z.string().email().describe("The email address of the customer")
  },
  async ({ email }) => {
    try {
      // Step 1: Find the customer by email
      const searchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=email&` +
                            `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(email)}&` +
                            `searchCriteria[filter_groups][0][filters][0][condition_type]=eq`;
      
      const customersData = await callMagentoApi(`/customers/search?${searchCriteria}`);
      
      if (!customersData.items || customersData.items.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No customer found with email: ${email}`
            }
          ]
        };
      }
      
      const customer = customersData.items[0];
      
      // Step 2: Get the customer's orders
      const orderSearchCriteria = `searchCriteria[filter_groups][0][filters][0][field]=customer_email&` +
                                 `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(email)}&` +
                                 `searchCriteria[filter_groups][0][filters][0][condition_type]=eq`;
      
      // Fetch all orders for the customer using the helper function
      const allCustomerOrders = await fetchAllPages('/orders', orderSearchCriteria);
      
      if (!allCustomerOrders || allCustomerOrders.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No orders found for customer with email: ${email}`
            }
          ]
        };
      }
      
      // Step 3: Extract and format the ordered products
      const orderedProducts = [];
      const productSkus = new Set();
      
      // First, collect all unique product SKUs from all orders
      allCustomerOrders.forEach(order => {
        if (order.items && Array.isArray(order.items)) {
          order.items.forEach(item => {
            if (item.sku) {
              productSkus.add(item.sku);
            }
          });
        }
      });
      
      // Get detailed product information for each SKU
      const productPromises = Array.from(productSkus).map(sku => 
        callMagentoApi(`/products/${sku}`)
          .then(product => formatProduct(product))
          .catch(err => ({ sku, error: err.message }))
      );
      
      const productDetails = await Promise.all(productPromises);
      
      // Create a map of SKU to product details for easy lookup
      const productMap = {};
      productDetails.forEach(product => {
        if (product.sku) {
          productMap[product.sku] = product;
        }
      });
      
      // Format the result with order information and product details
      const result = {
        customer: {
          id: customer.id,
          email: customer.email,
          firstname: customer.firstname,
          lastname: customer.lastname
        },
        orders: allCustomerOrders.map(order => ({
          order_id: order.entity_id,
          increment_id: order.increment_id,
          created_at: order.created_at,
          status: order.status,
          total: order.grand_total,
          items: order.items.map(item => {
            const productDetail = productMap[item.sku] || {};
            return {
              sku: item.sku,
              name: item.name,
              price: item.price,
              qty_ordered: item.qty_ordered,
              product_details: productDetail
            };
          })
        }))
      };
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error fetching customer ordered products: ${error.message}`
          }
        ],
        isError: true
      };
    }
  }
);

// Start the MCP server with stdio transport
async function main() {
  try {
    console.error('Starting Magento MCP Server...');
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error('Magento MCP Server running on stdio');
  } catch (error) {
    console.error('Error starting MCP server:', error);
    process.exit(1);
  }
}

main().catch(console.error);

```
Page 1/2FirstPrevNextLast