UNPKG

amazon-mcp-server

Version:

Model Context Protocol server for Amazon Seller API

506 lines (460 loc) 15.6 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, ProductDetails } from './catalog-api.js'; // Amazon Seller API configuration interface AmazonConfig { clientId: string; clientSecret: string; refreshToken: string; region: string; marketplaceIds: string[]; } class AmazonSellerApiServer { private server: Server; private authManager: AmazonAuthManager; private catalogApi: AmazonCatalogApi; private config: AmazonConfig; 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); }); } private 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, }; } }); } private async getProductByEan(args: any) { 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)}`); } } private async getProductByAsin(args: any) { 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)}`); } } private async getProductBySku(args: any) { 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)}`); } } private async searchProductsByKeywords(args: any) { 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)}`); } } private async searchProductsByBrand(args: any) { 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)}`); } } private async searchProducts(args: any) { 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)}`); } } private async getItemDetails(args: any) { 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)}`); } } private formatProductResponse(products: ProductDetails[]) { 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);