UNPKG

@spryker-sdk/spryker-mcp-server

Version:

MCP server for Spryker e-commerce API integration

186 lines 8.17 kB
/** * Product Search Tool * * Searches for products in the Spryker catalog using various filters * and search criteria. */ import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { ApiError, SprykerApiService } from '../services/spryker-api.js'; import { logger } from '../utils/logger.js'; /** * Input schema for product search */ const ProductSearchSchema = z.object({ q: z.string().optional().describe('Search query text'), rangeFacets: z.array(z.object({ name: z.string().describe('Range facet name'), min: z.number().optional().describe('Minimum value'), max: z.number().optional().describe('Maximum value'), })).optional().describe('Range facet values. This can be used for price ranges or other numeric filters. Multiple values can be specified only if filter has flag isMultiValued = true.'), valueFacets: z.array(z.object({ name: z.string().describe('Exact value facet name'), value: z.string().describe('Exact value facet value'), })).optional().describe('Exact value facet filters. This can be used for by filtering by labels, brands etc. Multiple values can be specified only if filter has flag isMultiValued = true.'), sort: z.string().describe('Sort parameter name to use for sorting results').optional(), ipp: z.number().min(1).max(100).default(20).describe('Number of results to return'), page: z.number().min(0).default(0).describe('Result offset for pagination'), }); /** * Product search implementation */ async function searchProducts(args) { const apiService = SprykerApiService.getInstance(); try { logger.info('Searching products', args); // Build query parameters matching Spryker API format const queryParams = {}; // Build the main query if (args.q && args.q.trim() !== '*') { queryParams.q = args.q; } else { queryParams.q = ''; } // Add pagination parameters queryParams['ipp'] = args.ipp.toString(); queryParams['page'] = args.page.toString(); // Add sorting parameter if specified if (args.sort) { queryParams['sort'] = args.sort.toString(); } // Add exact facet filters if (args.valueFacets && args.valueFacets.length > 0) { args.valueFacets.forEach((facet) => { const facetName = facet.name.toString(); if (queryParams[facetName] !== undefined) { if (!Array.isArray(queryParams[facetName])) { const facetValue = queryParams[facetName]; queryParams[facetName] = [facetValue]; // Convert to array if already exists } // Add new value to existing array queryParams[facetName].push(facet.value.toString()); return; } queryParams[facetName] = facet.value.toString(); }); } // Add range facet filters if (args.rangeFacets && args.rangeFacets.length > 0) { args.rangeFacets.forEach((facet) => { if (facet.min !== undefined) { queryParams[`${facet.name.toString()}[min]`] = facet.min.toString(); } if (facet.max !== undefined) { queryParams[`${facet.name.toString()}[max]`] = facet.max.toString(); } }); } // Make API request using the general request method const response = await apiService.request('GET', 'catalog-search', { params: queryParams }); logger.info('Raw API Response received', { hasData: !!response.data, dataKeys: response.data ? Object.keys(response.data) : [], dataType: Array.isArray(response.data.data) ? 'array' : typeof response.data.data, dataLength: Array.isArray(response.data.data) ? response.data.data.length : 'not array' }); // Check if data exists and contains search results if (!response.data.data || !Array.isArray(response.data.data) || response.data.data.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'No search results found', products: [], pagination: { total: 0, count: 0, ipp: args.ipp, limit: args.page }, query: args, }, null, 2), }], }; } const searchResult = response.data.data[0]; if (!searchResult?.attributes) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'Invalid search result structure', products: [], pagination: { total: 0, count: 0, ipp: args.ipp, limit: args.page }, sort: [], valueFacets: [], rangeFacets: [], query: args, }, null, 2), }], }; } const abstractProducts = searchResult.attributes.abstractProducts || []; const products = abstractProducts.map(item => ({ sku: item.abstractSku, name: item.abstractName, price: item.price, priceFormatted: item.prices?.[0] ? `${item.prices[0].grossAmount / 100} ${item.prices[0].currency.symbol}` : `${item.price / 100} EUR`, images: item.images, })); const pagination = searchResult.attributes.pagination; logger.info(`Found ${products.length} products`, { total: pagination?.numFound, page: pagination?.currentPage }); return { content: [{ type: 'text', text: JSON.stringify({ products, pagination: { total: pagination?.numFound || 0, count: products.length, ipp: args.ipp, page: args.page, currentPage: pagination?.currentPage || 1, maxPage: pagination?.maxPage || 1, }, sort: searchResult.attributes.sort || [], valueFacets: searchResult.attributes.valueFacets || [], rangeFacets: searchResult.attributes.rangeFacets || [], query: args, }, null, 2), }], }; } catch (error) { logger.error('Product search failed', error); return { content: [{ type: 'text', text: JSON.stringify({ error: 'Product search failed', message: error instanceof Error ? error.message : 'Unknown error occurred', responseData: error instanceof ApiError ? error.responseData : [], products: [], pagination: { total: 0, count: 0, ipp: args.ipp, page: args.page }, sort: [], valueFacets: [], rangeFacets: [], query: args, }, null, 2), }], }; } } /** * Product search tool definition */ export const productSearchTool = { name: 'product-search', description: 'Search for abstract products catalog with filters and pagination', inputSchema: zodToJsonSchema(ProductSearchSchema), handler: async (args) => { const validatedArgs = ProductSearchSchema.parse(args); return await searchProducts(validatedArgs); }, }; //# sourceMappingURL=product-search.js.map