Ads Manager MCP Server
by amekala
Verified
#!/usr/bin/env node
// Amazon Advertising MCP Server for Claude Desktop
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import dotenv from 'dotenv';
import { supabase, validateApiKey } from './src/config/supabase.js';
import { AdvertiserService } from './src/services/advertiser.service.js';
import fs from 'fs';
import { z } from 'zod';
// Load environment variables
dotenv.config();
// Set up debugging log
const logFile = fs.createWriteStream('mcp-debug.log', { flags: 'a' });
function debug(message) {
const timestamp = new Date().toISOString();
const logMessage = `${timestamp} - ${message}\n`;
logFile.write(logMessage);
// Also log to stderr for console viewing during development
console.error(logMessage);
}
debug('Starting Amazon Advertising MCP Server...');
debug(`MCP Server Version: ${process.env.MCP_SERVER_VERSION || 'dev'}`);
debug(`Received API_KEY: ${process.env.API_KEY ? 'YES (set)' : 'NO (not set)'}`);
debug(`Environment variables: ${JSON.stringify(process.env, (key, value) => {
// Mask sensitive values but show which variables exist
if (key.toUpperCase().includes('KEY') || key.toUpperCase().includes('SECRET')) {
return value ? '[SET]' : '[NOT SET]';
}
return value;
}, 2)}`);
// Initialize MCP Server
const server = new McpServer({
name: "Amazon Advertising",
version: process.env.MCP_SERVER_VERSION || '1.0.0'
});
// Define the getAdvertiserInfo tool with proper schema format
server.tool("getAdvertiserInfo",
{}, // Empty schema since no parameters needed
async () => {
debug('Received request for getAdvertiserInfo');
try {
// For testing, get the first advertiser from the database
const { data: advertisers, error } = await supabase
.from('advertisers')
.select('*')
.limit(1);
if (error || !advertisers || advertisers.length === 0) {
debug(`Error or no advertisers found: ${error ? error.message : 'No advertisers'}`);
return {
content: [{ type: "text", text: "Error: No advertiser accounts found" }],
isError: true
};
}
const advertiser = advertisers[0];
debug(`Found advertiser: ${advertiser.account_name}`);
return {
content: [{
type: "text",
text: JSON.stringify({
id: advertiser.id,
name: advertiser.account_name,
marketplace: advertiser.marketplace,
accountType: advertiser.account_type,
profileId: advertiser.profile_id,
countryCode: advertiser.metadata?.countryCode || 'Unknown',
currencyCode: advertiser.metadata?.currencyCode || 'USD'
}, null, 2)
}]
};
} catch (error) {
debug(`Error getting advertiser info: ${error.message}`);
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true
};
}
}
);
// Define the listAdvertiserAccounts tool with proper schema format
server.tool("listAdvertiserAccounts",
{}, // Empty schema since no parameters needed
async () => {
debug('Received request for listAdvertiserAccounts');
try {
// Get all advertisers
const { data: advertisers, error } = await supabase
.from('advertisers')
.select('id, account_name, marketplace, account_type, metadata')
.order('account_name', { ascending: true });
if (error) {
debug(`Error fetching advertisers: ${error.message}`);
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true
};
}
if (!advertisers || advertisers.length === 0) {
debug('No advertisers found');
return {
content: [{
type: "text",
text: "No advertiser accounts were found in the database"
}]
};
}
// Format the advertiser data
const formattedAdvertisers = advertisers.map(adv => ({
id: adv.id,
accountName: adv.account_name,
marketplace: adv.marketplace,
accountType: adv.account_type,
countryCode: adv.metadata?.countryCode || 'Unknown',
currencyCode: adv.metadata?.currencyCode || 'USD'
}));
debug(`Found ${formattedAdvertisers.length} advertisers`);
return {
content: [{
type: "text",
text: JSON.stringify(formattedAdvertisers, null, 2)
}]
};
} catch (error) {
debug(`Error listing advertiser accounts: ${error.message}`);
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true
};
}
}
);
// Add a simple non-database dependent tool for testing connectivity
server.tool("ping",
{}, // No parameters needed
async () => {
debug('Received ping request - testing connection');
return {
content: [{
type: "text",
text: `Server is running correctly!
Time: ${new Date().toISOString()}
Server name: Amazon Advertising
Server version: ${process.env.MCP_SERVER_VERSION || '1.0.0'}`
}]
};
}
);
// Add an echo tool for testing with parameters
server.tool("echo",
{ message: z.string() },
async ({ message }) => {
debug(`Received echo request with message: ${message}`);
return {
content: [{
type: "text",
text: `You said: ${message}`
}]
};
}
);
// Add a tool for validating API key that doesn't rely on database access
server.tool("validateApiKey",
{}, // No parameters needed
async () => {
debug('Received request to validate API key');
const apiKey = process.env.API_KEY;
if (!apiKey) {
debug('No API key provided in environment variables');
return {
content: [{
type: "text",
text: `Error: No API_KEY provided in environment variables.`
}],
isError: true
};
}
try {
debug(`Attempting to validate API key: ${apiKey.substring(0, 8)}...`);
const { valid, advertiserId, error } = await validateApiKey(apiKey);
if (!valid) {
debug(`API key validation failed: ${error}`);
return {
content: [{
type: "text",
text: `API key validation failed: ${error || 'Unknown error'}`
}],
isError: true
};
}
debug(`API key validated successfully for advertiser ID: ${advertiserId}`);
return {
content: [{
type: "text",
text: `API key is valid for advertiser ID: ${advertiserId}`
}]
};
} catch (error) {
debug(`Error validating API key: ${error.message}`);
return {
content: [{
type: "text",
text: `Error validating API key: ${error.message}. This could be due to missing database credentials.`
}],
isError: true
};
}
}
);
// Start the server with stdin/stdout transport
debug('Starting MCP server with stdio transport');
const transport = new StdioServerTransport();
// Add better error handling for server connection
try {
debug('Attempting to connect MCP server to transport');
server.connect(transport).then(() => {
debug('MCP server successfully connected to transport and ready to handle requests from Claude Desktop');
// Keep the process alive
process.on('SIGINT', () => {
debug('Received SIGINT, shutting down server...');
process.exit(0);
});
}).catch(error => {
debug(`Error connecting MCP server to transport: ${error.message}\n${error.stack}`);
});
} catch (error) {
debug(`Critical error starting MCP server: ${error.message}\n${error.stack}`);
}
// Add heartbeat logging to confirm server is alive
setInterval(() => {
debug('MCP server heartbeat - still running');
}, 60000); // Log every minute
// Add error handling for uncaught exceptions
process.on('uncaughtException', (error) => {
debug(`Uncaught exception: ${error.message}\n${error.stack}`);
// Keep the server running despite errors
});
process.on('unhandledRejection', (reason, promise) => {
debug(`Unhandled promise rejection: ${reason}`);
// Keep the server running despite errors
});