/**
* GoHighLevel Products API Tools for MCP Server
* Provides comprehensive tools for managing products, prices, inventory, collections, and reviews
*/
import {
// MCP Product Types
MCPCreateProductParams,
MCPUpdateProductParams,
MCPListProductsParams,
MCPGetProductParams,
MCPDeleteProductParams,
MCPCreatePriceParams,
MCPUpdatePriceParams,
MCPListPricesParams,
MCPGetPriceParams,
MCPDeletePriceParams,
MCPBulkUpdateProductsParams,
MCPListInventoryParams,
MCPUpdateInventoryParams,
MCPGetProductStoreStatsParams,
MCPUpdateProductStoreParams,
MCPCreateProductCollectionParams,
MCPUpdateProductCollectionParams,
MCPListProductCollectionsParams,
MCPGetProductCollectionParams,
MCPDeleteProductCollectionParams,
MCPListProductReviewsParams,
MCPGetReviewsCountParams,
MCPUpdateProductReviewParams,
MCPDeleteProductReviewParams,
MCPBulkUpdateProductReviewsParams,
// API Client Types
GHLCreateProductRequest,
GHLUpdateProductRequest,
GHLListProductsRequest,
GHLGetProductRequest,
GHLDeleteProductRequest,
GHLCreatePriceRequest,
GHLUpdatePriceRequest,
GHLListPricesRequest,
GHLGetPriceRequest,
GHLDeletePriceRequest,
GHLBulkUpdateRequest,
GHLListInventoryRequest,
GHLUpdateInventoryRequest,
GHLGetProductStoreStatsRequest,
GHLUpdateProductStoreRequest,
GHLCreateProductCollectionRequest,
GHLUpdateProductCollectionRequest,
GHLListProductCollectionsRequest,
GHLGetProductCollectionRequest,
GHLDeleteProductCollectionRequest,
GHLListProductReviewsRequest,
GHLGetReviewsCountRequest,
GHLUpdateProductReviewRequest,
GHLDeleteProductReviewRequest,
GHLBulkUpdateProductReviewsRequest
} from '../types/ghl-types.js';
import { GHLApiClient } from '../clients/ghl-api-client.js';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
export interface ProductsToolResult {
content: {
type: 'text';
text: string;
}[];
}
export class ProductsTools {
constructor(private apiClient: GHLApiClient) {}
// Product Operations
async createProduct(params: MCPCreateProductParams): Promise<ProductsToolResult> {
try {
const request: GHLCreateProductRequest = {
...params,
locationId: params.locationId || this.apiClient.getConfig().locationId
};
const response = await this.apiClient.createProduct(request);
if (!response.data) {
throw new Error('No data returned from API');
}
return {
content: [{
type: 'text',
text: `๐๏ธ **Product Created Successfully!**
๐ฆ **Product Details:**
โข **ID:** ${response.data._id}
โข **Name:** ${response.data.name}
โข **Type:** ${response.data.productType}
โข **Location:** ${response.data.locationId}
โข **Available in Store:** ${response.data.availableInStore ? 'โ
Yes' : 'โ No'}
โข **Created:** ${new Date(response.data.createdAt).toLocaleString()}
${response.data.description ? `๐ **Description:** ${response.data.description}` : ''}
${response.data.image ? `๐ผ๏ธ **Image:** ${response.data.image}` : ''}
${response.data.collectionIds?.length ? `๐ **Collections:** ${response.data.collectionIds.length} assigned` : ''}
${response.data.variants?.length ? `๐ง **Variants:** ${response.data.variants.length} configured` : ''}
${response.data.medias?.length ? `๐ธ **Media Files:** ${response.data.medias.length} attached` : ''}
โจ **Status:** Product successfully created and ready for configuration!`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Creating Product**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async listProducts(params: MCPListProductsParams): Promise<ProductsToolResult> {
try {
const request: GHLListProductsRequest = {
...params,
locationId: params.locationId || this.apiClient.getConfig().locationId
};
const response = await this.apiClient.listProducts(request);
if (!response.data) {
throw new Error('No data returned from API');
}
const products = response.data.products;
const total = response.data.total[0]?.total || 0;
return {
content: [{
type: 'text',
text: `๐๏ธ **Products List** (${products.length} of ${total} total)
${products.length === 0 ? '๐ญ **No products found**' : products.map((product, index) => `
**${index + 1}. ${product.name}** (${product.productType})
โข **ID:** ${product._id}
โข **Store Status:** ${product.availableInStore ? 'โ
Available' : 'โ Not Available'}
โข **Created:** ${new Date(product.createdAt).toLocaleString()}
${product.description ? `โข **Description:** ${product.description.substring(0, 100)}${product.description.length > 100 ? '...' : ''}` : ''}
${product.collectionIds?.length ? `โข **Collections:** ${product.collectionIds.length}` : ''}
`).join('\n')}
๐ **Summary:**
โข **Total Products:** ${total}
โข **Displayed:** ${products.length}
${params.search ? `โข **Search:** "${params.search}"` : ''}
${params.storeId ? `โข **Store Filter:** ${params.storeId}` : ''}
${params.includedInStore !== undefined ? `โข **Store Status:** ${params.includedInStore ? 'Included only' : 'Excluded only'}` : ''}`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Listing Products**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
getTools(): Tool[] {
return [
// Product Management Tools
{
name: 'ghl_create_product',
description: 'Create a new product in GoHighLevel',
inputSchema: {
type: 'object',
properties: {
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
name: { type: 'string', description: 'Product name' },
productType: {
type: 'string',
enum: ['DIGITAL', 'PHYSICAL', 'SERVICE', 'PHYSICAL/DIGITAL'],
description: 'Type of product'
},
description: { type: 'string', description: 'Product description' },
image: { type: 'string', description: 'Product image URL' },
availableInStore: { type: 'boolean', description: 'Whether product is available in store' },
slug: { type: 'string', description: 'Product URL slug' }
},
required: ['name', 'productType']
}
},
{
name: 'ghl_list_products',
description: 'List products with optional filtering',
inputSchema: {
type: 'object',
properties: {
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
limit: { type: 'number', description: 'Maximum number of products to return' },
offset: { type: 'number', description: 'Number of products to skip' },
search: { type: 'string', description: 'Search term for product names' },
storeId: { type: 'string', description: 'Filter by store ID' },
includedInStore: { type: 'boolean', description: 'Filter by store inclusion status' },
availableInStore: { type: 'boolean', description: 'Filter by store availability' }
},
required: []
}
},
{
name: 'ghl_get_product',
description: 'Get a specific product by ID',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string', description: 'Product ID to retrieve' },
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' }
},
required: ['productId']
}
},
{
name: 'ghl_update_product',
description: 'Update an existing product',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string', description: 'Product ID to update' },
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
name: { type: 'string', description: 'Product name' },
productType: {
type: 'string',
enum: ['DIGITAL', 'PHYSICAL', 'SERVICE', 'PHYSICAL/DIGITAL'],
description: 'Type of product'
},
description: { type: 'string', description: 'Product description' },
image: { type: 'string', description: 'Product image URL' },
availableInStore: { type: 'boolean', description: 'Whether product is available in store' }
},
required: ['productId']
}
},
{
name: 'ghl_delete_product',
description: 'Delete a product by ID',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string', description: 'Product ID to delete' },
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' }
},
required: ['productId']
}
},
// Price Management Tools
{
name: 'ghl_create_price',
description: 'Create a price for a product',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string', description: 'Product ID to create price for' },
name: { type: 'string', description: 'Price name/variant name' },
type: {
type: 'string',
enum: ['one_time', 'recurring'],
description: 'Price type'
},
currency: { type: 'string', description: 'Currency code (e.g., USD)' },
amount: { type: 'number', description: 'Price amount in cents' },
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
compareAtPrice: { type: 'number', description: 'Compare at price (for discounts)' }
},
required: ['productId', 'name', 'type', 'currency', 'amount']
}
},
{
name: 'ghl_list_prices',
description: 'List prices for a product',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string', description: 'Product ID to list prices for' },
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
limit: { type: 'number', description: 'Maximum number of prices to return' },
offset: { type: 'number', description: 'Number of prices to skip' }
},
required: ['productId']
}
},
// Inventory Tools
{
name: 'ghl_list_inventory',
description: 'List inventory items with stock levels',
inputSchema: {
type: 'object',
properties: {
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
limit: { type: 'number', description: 'Maximum number of items to return' },
offset: { type: 'number', description: 'Number of items to skip' },
search: { type: 'string', description: 'Search term for inventory items' }
},
required: []
}
},
// Collection Tools
{
name: 'ghl_create_product_collection',
description: 'Create a new product collection',
inputSchema: {
type: 'object',
properties: {
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
name: { type: 'string', description: 'Collection name' },
slug: { type: 'string', description: 'Collection URL slug' },
image: { type: 'string', description: 'Collection image URL' },
seo: {
type: 'object',
properties: {
title: { type: 'string', description: 'SEO title' },
description: { type: 'string', description: 'SEO description' }
}
}
},
required: ['name', 'slug']
}
},
{
name: 'ghl_list_product_collections',
description: 'List product collections',
inputSchema: {
type: 'object',
properties: {
locationId: { type: 'string', description: 'GHL Location ID (optional, uses default if not provided)' },
limit: { type: 'number', description: 'Maximum number of collections to return' },
offset: { type: 'number', description: 'Number of collections to skip' },
name: { type: 'string', description: 'Search by collection name' }
},
required: []
}
}
];
}
async executeProductsTool(toolName: string, params: any): Promise<ProductsToolResult> {
switch (toolName) {
case 'ghl_create_product':
return this.createProduct(params as MCPCreateProductParams);
case 'ghl_list_products':
return this.listProducts(params as MCPListProductsParams);
case 'ghl_get_product':
return this.getProduct(params as MCPGetProductParams);
case 'ghl_update_product':
return this.updateProduct(params as MCPUpdateProductParams);
case 'ghl_delete_product':
return this.deleteProduct(params as MCPDeleteProductParams);
case 'ghl_create_price':
return this.createPrice(params as MCPCreatePriceParams);
case 'ghl_list_prices':
return this.listPrices(params as MCPListPricesParams);
case 'ghl_list_inventory':
return this.listInventory(params as MCPListInventoryParams);
case 'ghl_create_product_collection':
return this.createProductCollection(params as MCPCreateProductCollectionParams);
case 'ghl_list_product_collections':
return this.listProductCollections(params as MCPListProductCollectionsParams);
default:
return {
content: [{
type: 'text',
text: `โ **Unknown Products Tool**: ${toolName}`
}]
};
}
}
// Additional Product Operations
async getProduct(params: MCPGetProductParams): Promise<ProductsToolResult> {
try {
const response = await this.apiClient.getProduct(
params.productId,
params.locationId || this.apiClient.getConfig().locationId
);
if (!response.data) {
throw new Error('No data returned from API');
}
return {
content: [{
type: 'text',
text: `๐๏ธ **Product Details**
๐ฆ **${response.data.name}** (${response.data.productType})
โข **ID:** ${response.data._id}
โข **Location:** ${response.data.locationId}
โข **Available in Store:** ${response.data.availableInStore ? 'โ
Yes' : 'โ No'}
โข **Created:** ${new Date(response.data.createdAt).toLocaleString()}
โข **Updated:** ${new Date(response.data.updatedAt).toLocaleString()}
${response.data.description ? `๐ **Description:** ${response.data.description}` : ''}
${response.data.image ? `๐ผ๏ธ **Image:** ${response.data.image}` : ''}
${response.data.slug ? `๐ **Slug:** ${response.data.slug}` : ''}
${response.data.collectionIds?.length ? `๐ **Collections:** ${response.data.collectionIds.length} assigned` : ''}
${response.data.variants?.length ? `๐ง **Variants:** ${response.data.variants.length} configured` : ''}
${response.data.medias?.length ? `๐ธ **Media Files:** ${response.data.medias.length} attached` : ''}
${response.data.isTaxesEnabled ? `๐ฐ **Taxes:** Enabled` : ''}
${response.data.isLabelEnabled ? `๐ท๏ธ **Labels:** Enabled` : ''}`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Getting Product**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async updateProduct(params: MCPUpdateProductParams): Promise<ProductsToolResult> {
try {
const request: GHLUpdateProductRequest = {
...params,
locationId: params.locationId || this.apiClient.getConfig().locationId
};
const response = await this.apiClient.updateProduct(params.productId, request);
if (!response.data) {
throw new Error('No data returned from API');
}
return {
content: [{
type: 'text',
text: `โ
**Product Updated Successfully!**
๐ฆ **Updated Product:**
โข **ID:** ${response.data._id}
โข **Name:** ${response.data.name}
โข **Type:** ${response.data.productType}
โข **Available in Store:** ${response.data.availableInStore ? 'โ
Yes' : 'โ No'}
โข **Last Updated:** ${new Date(response.data.updatedAt).toLocaleString()}
๐ **Product has been successfully updated with the new information!**`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Updating Product**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async deleteProduct(params: MCPDeleteProductParams): Promise<ProductsToolResult> {
try {
const response = await this.apiClient.deleteProduct(
params.productId,
params.locationId || this.apiClient.getConfig().locationId
);
if (!response.data) {
throw new Error('No data returned from API');
}
return {
content: [{
type: 'text',
text: `๐๏ธ **Product Deleted Successfully!**
โ
**Status:** ${response.data.status ? 'Product successfully deleted' : 'Deletion failed'}
๐๏ธ **Product ID:** ${params.productId}
โ ๏ธ **Note:** This action cannot be undone. The product and all associated data have been permanently removed.`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Deleting Product**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async createPrice(params: MCPCreatePriceParams): Promise<ProductsToolResult> {
try {
const request: GHLCreatePriceRequest = {
...params,
locationId: params.locationId || this.apiClient.getConfig().locationId
};
const response = await this.apiClient.createPrice(params.productId, request);
if (!response.data) {
throw new Error('No data returned from API');
}
return {
content: [{
type: 'text',
text: `๐ฐ **Price Created Successfully!**
๐ท๏ธ **Price Details:**
โข **ID:** ${response.data._id}
โข **Name:** ${response.data.name}
โข **Type:** ${response.data.type}
โข **Amount:** ${response.data.amount / 100} ${response.data.currency}
โข **Product ID:** ${response.data.product}
โข **Created:** ${new Date(response.data.createdAt).toLocaleString()}
${response.data.compareAtPrice ? `๐ธ **Compare At:** ${response.data.compareAtPrice / 100} ${response.data.currency}` : ''}
${response.data.recurring ? `๐ **Recurring:** ${response.data.recurring.intervalCount} ${response.data.recurring.interval}(s)` : ''}
${response.data.sku ? `๐ฆ **SKU:** ${response.data.sku}` : ''}
โจ **Price is ready for use in your product catalog!**`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Creating Price**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async listPrices(params: MCPListPricesParams): Promise<ProductsToolResult> {
try {
const request: GHLListPricesRequest = {
...params,
locationId: params.locationId || this.apiClient.getConfig().locationId
};
const response = await this.apiClient.listPrices(params.productId, request);
if (!response.data) {
throw new Error('No data returned from API');
}
const prices = response.data.prices;
return {
content: [{
type: 'text',
text: `๐ฐ **Product Prices** (${prices.length} of ${response.data.total} total)
${prices.length === 0 ? '๐ญ **No prices found**' : prices.map((price, index) => `
**${index + 1}. ${price.name}** (${price.type})
โข **ID:** ${price._id}
โข **Amount:** ${price.amount / 100} ${price.currency}
${price.compareAtPrice ? `โข **Compare At:** ${price.compareAtPrice / 100} ${price.currency}` : ''}
${price.recurring ? `โข **Recurring:** ${price.recurring.intervalCount} ${price.recurring.interval}(s)` : ''}
${price.sku ? `โข **SKU:** ${price.sku}` : ''}
โข **Created:** ${new Date(price.createdAt).toLocaleString()}
`).join('\n')}
๐ **Summary:**
โข **Total Prices:** ${response.data.total}
โข **Product ID:** ${params.productId}`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Listing Prices**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async listInventory(params: MCPListInventoryParams): Promise<ProductsToolResult> {
try {
const request: GHLListInventoryRequest = {
altId: params.locationId || this.apiClient.getConfig().locationId,
altType: 'location',
...params
};
const response = await this.apiClient.listInventory(request);
if (!response.data) {
throw new Error('No data returned from API');
}
const inventory = response.data.inventory;
const total = response.data.total.total;
return {
content: [{
type: 'text',
text: `๐ฆ **Inventory Items** (${inventory.length} of ${total} total)
${inventory.length === 0 ? '๐ญ **No inventory items found**' : inventory.map((item, index) => `
**${index + 1}. ${item.name}** ${item.productName ? `(${item.productName})` : ''}
โข **ID:** ${item._id}
โข **Available Quantity:** ${item.availableQuantity}
โข **SKU:** ${item.sku || 'N/A'}
โข **Out of Stock Purchases:** ${item.allowOutOfStockPurchases ? 'โ
Allowed' : 'โ Not Allowed'}
โข **Product ID:** ${item.product}
โข **Last Updated:** ${new Date(item.updatedAt).toLocaleString()}
${item.image ? `โข **Image:** ${item.image}` : ''}
`).join('\n')}
๐ **Summary:**
โข **Total Items:** ${total}
โข **Displayed:** ${inventory.length}
${params.search ? `โข **Search:** "${params.search}"` : ''}`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Listing Inventory**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async createProductCollection(params: MCPCreateProductCollectionParams): Promise<ProductsToolResult> {
try {
const request: GHLCreateProductCollectionRequest = {
...params,
altId: params.locationId || this.apiClient.getConfig().locationId,
altType: 'location'
};
const response = await this.apiClient.createProductCollection(request);
if (!response.data?.data) {
throw new Error('No data returned from API');
}
return {
content: [{
type: 'text',
text: `๐ **Product Collection Created Successfully!**
๐ท๏ธ **Collection Details:**
โข **ID:** ${response.data.data._id}
โข **Name:** ${response.data.data.name}
โข **Slug:** ${response.data.data.slug}
โข **Location:** ${response.data.data.altId}
โข **Created:** ${new Date(response.data.data.createdAt).toLocaleString()}
${response.data.data.image ? `๐ผ๏ธ **Image:** ${response.data.data.image}` : ''}
${response.data.data.seo?.title ? `๐ **SEO Title:** ${response.data.data.seo.title}` : ''}
${response.data.data.seo?.description ? `๐ **SEO Description:** ${response.data.data.seo.description}` : ''}
โจ **Collection is ready to organize your products!**`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Creating Collection**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
async listProductCollections(params: MCPListProductCollectionsParams): Promise<ProductsToolResult> {
try {
const request: GHLListProductCollectionsRequest = {
...params,
altId: params.locationId || this.apiClient.getConfig().locationId,
altType: 'location'
};
const response = await this.apiClient.listProductCollections(request);
if (!response.data?.data) {
throw new Error('No data returned from API');
}
const collections = response.data.data;
return {
content: [{
type: 'text',
text: `๐ **Product Collections** (${collections.length} of ${response.data.total} total)
${collections.length === 0 ? '๐ญ **No collections found**' : collections.map((collection: any, index: number) => `
**${index + 1}. ${collection.name}**
โข **ID:** ${collection._id}
โข **Slug:** ${collection.slug}
${collection.image ? `โข **Image:** ${collection.image}` : ''}
${collection.seo?.title ? `โข **SEO Title:** ${collection.seo.title}` : ''}
โข **Created:** ${new Date(collection.createdAt).toLocaleString()}
`).join('\n')}
๐ **Summary:**
โข **Total Collections:** ${response.data.total}
โข **Displayed:** ${collections.length}
${params.name ? `โข **Search:** "${params.name}"` : ''}`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `โ **Error Listing Collections**\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`
}]
};
}
}
}