amazon-mcp-server
Version:
Model Context Protocol server for Amazon Seller API
506 lines (460 loc) • 15.6 kB
text/typescript
#!/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);