UNPKG

amazon-mcp-server

Version:

Model Context Protocol server for Amazon Seller API

438 lines (437 loc) 19.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { AmazonAuthManager } from './auth-manager.js'; import { AmazonCatalogApi } from './catalog-api.js'; class AmazonSellerApiServer { constructor() { this.server = new Server({ name: 'amazon-seller-api-server', version: '0.1.0', }, { capabilities: { tools: {}, }, }); // Get configuration from environment variables const clientId = process.env.AMAZON_CLIENT_ID; const clientSecret = process.env.AMAZON_CLIENT_SECRET; const refreshToken = process.env.AMAZON_REFRESH_TOKEN; const region = process.env.AMAZON_REGION || 'eu'; const marketplaceIdsStr = process.env.AMAZON_MARKETPLACE_IDS; if (!clientId || !clientSecret || !refreshToken || !marketplaceIdsStr) { throw new Error('AMAZON_CLIENT_ID, AMAZON_CLIENT_SECRET, AMAZON_REFRESH_TOKEN, and AMAZON_MARKETPLACE_IDS environment variables are required'); } const marketplaceIds = marketplaceIdsStr.split(',').map(id => id.trim()); this.config = { clientId, clientSecret, refreshToken, region, marketplaceIds, }; // Initialize the auth manager this.authManager = new AmazonAuthManager(this.config.clientId, this.config.clientSecret, this.config.refreshToken); // Initialize the catalog API this.catalogApi = new AmazonCatalogApi(this.authManager, this.config.region, this.config.marketplaceIds); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { this.authManager.stopTokenRefreshCycle(); await this.server.close(); process.exit(0); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_product_by_ean', description: 'Get product details by EAN code', inputSchema: { type: 'object', properties: { ean: { type: 'string', description: 'EAN code', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs (optional)', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response (optional)', }, }, required: ['ean'], }, }, { name: 'get_product_by_asin', description: 'Get product details by ASIN', inputSchema: { type: 'object', properties: { asin: { type: 'string', description: 'ASIN code', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs (optional)', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response (optional)', }, }, required: ['asin'], }, }, { name: 'get_product_by_sku', description: 'Get product details by SKU', inputSchema: { type: 'object', properties: { sku: { type: 'string', description: 'SKU code', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs (optional)', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response (optional)', }, }, required: ['sku'], }, }, { name: 'search_products_by_keywords', description: 'Search products by keywords', inputSchema: { type: 'object', properties: { keywords: { type: 'string', description: 'Search keywords', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs (optional)', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response (optional)', }, }, required: ['keywords'], }, }, { name: 'search_products_by_brand', description: 'Search products by brand name', inputSchema: { type: 'object', properties: { brandNames: { type: 'array', items: { type: 'string' }, description: 'Brand names', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs (optional)', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response (optional)', }, }, required: ['brandNames'], }, }, { name: 'search_products', description: 'Search products with advanced parameters', inputSchema: { type: 'object', properties: { identifiers: { oneOf: [ { type: 'string' }, { type: 'array', items: { type: 'string' } } ], description: 'Product identifiers', }, identifiersType: { type: 'string', enum: ['EAN', 'UPC', 'ISBN', 'ASIN', 'SKU', 'JAN'], description: 'Type of identifiers', }, keywords: { type: 'string', description: 'Search keywords', }, brandNames: { type: 'array', items: { type: 'string' }, description: 'Brand names', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response', }, pageSize: { type: 'number', description: 'Number of results per page', }, pageToken: { type: 'string', description: 'Token for pagination', }, }, required: ['marketplaceIds'], }, }, { name: 'get_item_details', description: 'Get detailed information for a specific ASIN', inputSchema: { type: 'object', properties: { asin: { type: 'string', description: 'ASIN code', }, marketplaceIds: { type: 'array', items: { type: 'string' }, description: 'Marketplace IDs (optional)', }, includedData: { type: 'array', items: { type: 'string' }, description: 'Data to include in the response (optional)', }, }, required: ['asin'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { switch (request.params.name) { case 'get_product_by_ean': return await this.getProductByEan(request.params.arguments); case 'get_product_by_asin': return await this.getProductByAsin(request.params.arguments); case 'get_product_by_sku': return await this.getProductBySku(request.params.arguments); case 'search_products_by_keywords': return await this.searchProductsByKeywords(request.params.arguments); case 'search_products_by_brand': return await this.searchProductsByBrand(request.params.arguments); case 'search_products': return await this.searchProducts(request.params.arguments); case 'get_item_details': return await this.getItemDetails(request.params.arguments); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } } catch (error) { if (error instanceof McpError) { throw error; } console.error('Error executing tool:', error); return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); } async getProductByEan(args) { const { ean, marketplaceIds, includedData } = args; if (!ean) { throw new McpError(ErrorCode.InvalidParams, 'EAN code is required'); } try { const products = await this.catalogApi.getProductByEan(ean, marketplaceIds, includedData); return this.formatProductResponse(products); } catch (error) { console.error('Error getting product by EAN:', error); throw new McpError(ErrorCode.InternalError, `Failed to get product by EAN: ${error instanceof Error ? error.message : String(error)}`); } } async getProductByAsin(args) { const { asin, marketplaceIds, includedData } = args; if (!asin) { throw new McpError(ErrorCode.InvalidParams, 'ASIN is required'); } try { const products = await this.catalogApi.getProductByAsin(asin, marketplaceIds, includedData); return this.formatProductResponse(products); } catch (error) { console.error('Error getting product by ASIN:', error); throw new McpError(ErrorCode.InternalError, `Failed to get product by ASIN: ${error instanceof Error ? error.message : String(error)}`); } } async getProductBySku(args) { const { sku, marketplaceIds, includedData } = args; if (!sku) { throw new McpError(ErrorCode.InvalidParams, 'SKU is required'); } try { const products = await this.catalogApi.getProductBySku(sku, marketplaceIds, includedData); return this.formatProductResponse(products); } catch (error) { console.error('Error getting product by SKU:', error); throw new McpError(ErrorCode.InternalError, `Failed to get product by SKU: ${error instanceof Error ? error.message : String(error)}`); } } async searchProductsByKeywords(args) { const { keywords, marketplaceIds, includedData } = args; if (!keywords) { throw new McpError(ErrorCode.InvalidParams, 'Keywords are required'); } try { const products = await this.catalogApi.getProductsByKeywords(keywords, marketplaceIds, includedData); return this.formatProductResponse(products); } catch (error) { console.error('Error searching products by keywords:', error); throw new McpError(ErrorCode.InternalError, `Failed to search products by keywords: ${error instanceof Error ? error.message : String(error)}`); } } async searchProductsByBrand(args) { const { brandNames, marketplaceIds, includedData } = args; if (!brandNames || !Array.isArray(brandNames) || brandNames.length === 0) { throw new McpError(ErrorCode.InvalidParams, 'Brand names are required'); } try { const products = await this.catalogApi.getProductsByBrand(brandNames, marketplaceIds, includedData); return this.formatProductResponse(products); } catch (error) { console.error('Error searching products by brand:', error); throw new McpError(ErrorCode.InternalError, `Failed to search products by brand: ${error instanceof Error ? error.message : String(error)}`); } } async searchProducts(args) { try { const products = await this.catalogApi.searchProducts(args); return this.formatProductResponse(products); } catch (error) { console.error('Error searching products:', error); throw new McpError(ErrorCode.InternalError, `Failed to search products: ${error instanceof Error ? error.message : String(error)}`); } } async getItemDetails(args) { const { asin, marketplaceIds, includedData } = args; if (!asin) { throw new McpError(ErrorCode.InvalidParams, 'ASIN is required'); } try { const product = await this.catalogApi.getItemDetails(asin, marketplaceIds, includedData); return { content: [ { type: 'text', text: JSON.stringify(product, null, 2), }, ], }; } catch (error) { console.error('Error getting item details:', error); throw new McpError(ErrorCode.InternalError, `Failed to get item details: ${error instanceof Error ? error.message : String(error)}`); } } formatProductResponse(products) { return { content: [ { type: 'text', text: JSON.stringify(products, null, 2), }, ], }; } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Amazon Seller API MCP server running on stdio'); } } const server = new AmazonSellerApiServer(); server.run().catch(console.error);