atom-marketplace-mcp-server
Version:
MCP server for Atom marketplace APIs - domain search, availability, trademarks, and appraisals
235 lines (208 loc) • 6.36 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { AtomMarketplaceMCPServer } from '../server.js';
// Simple in-memory rate limiting (for production, use Redis)
const rateLimitStore = new Map();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX_REQUESTS = 100; // 100 requests per minute
function checkRateLimit(ip) {
const now = Date.now();
const windowStart = now - RATE_LIMIT_WINDOW;
if (!rateLimitStore.has(ip)) {
rateLimitStore.set(ip, []);
}
const requests = rateLimitStore.get(ip);
const recentRequests = requests.filter(timestamp => timestamp > windowStart);
if (recentRequests.length >= RATE_LIMIT_MAX_REQUESTS) {
return false;
}
recentRequests.push(now);
rateLimitStore.set(ip, recentRequests);
return true;
}
function getClientIP(req) {
return req.headers['x-forwarded-for'] ||
req.headers['x-real-ip'] ||
req.connection?.remoteAddress ||
'unknown';
}
// Vercel serverless function handler
export default async (req, res) => {
const clientIP = getClientIP(req);
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-MCP-API-Key');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
// Validate request method
if (req.method !== 'POST') {
res.status(405).json({
error: 'Method not allowed',
message: 'Only POST requests are supported'
});
return;
}
// API Key Authentication - Required for all MCP requests
const providedKey = req.headers['x-mcp-api-key'] ||
req.headers['X-MCP-API-Key'] ||
req.headers['authorization']?.replace('Bearer ', '') ||
req.headers['Authorization']?.replace('Bearer ', '');
if (!providedKey) {
res.status(401).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32600,
message: 'Missing API key. Please provide X-MCP-API-Key header or Authorization: Bearer token.'
}
});
return;
}
// Enhanced key validation with partner support - STRICT MODE ONLY
const validKeys = process.env.VALID_API_KEYS ?
process.env.VALID_API_KEYS.split(',').map(key => key.trim()) :
[];
// ALWAYS require valid API keys - no fallback to basic validation
if (validKeys.length === 0) {
res.status(401).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32600,
message: 'Server configuration error - no valid API keys configured.'
}
});
return;
}
// Validate against whitelist - STRICT validation
if (!validKeys.includes(providedKey)) {
res.status(401).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32600,
message: 'Invalid API key. Key not found in authorized list.'
}
});
return;
}
// Rate limiting
if (!checkRateLimit(clientIP)) {
res.status(429).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32600,
message: 'Rate limit exceeded. Please try again later.'
}
});
return;
}
// Validate request body
if (!req.body || typeof req.body !== 'object') {
res.status(400).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32700,
message: 'Invalid JSON-RPC request'
}
});
return;
}
try {
// Check if we have the required environment variables
if (!process.env.ATOM_BASE_URL || !process.env.ATOM_API_TOKEN || !process.env.ATOM_USER_ID) {
console.error('Missing required environment variables');
res.status(500).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32603,
message: 'Server configuration error - missing environment variables',
data: {
ATOM_BASE_URL: process.env.ATOM_BASE_URL ? 'SET' : 'NOT SET',
ATOM_API_TOKEN: process.env.ATOM_API_TOKEN ? 'SET' : 'NOT SET',
ATOM_USER_ID: process.env.ATOM_USER_ID ? 'SET' : 'NOT SET'
}
}
});
return;
}
// Initialize MCP server
const mcpServer = new AtomMarketplaceMCPServer();
// Handle MCP requests
const { method, params } = req.body;
// Validate JSON-RPC version
if (req.body.jsonrpc !== '2.0') {
res.status(400).json({
jsonrpc: '2.0',
id: req.body.id,
error: {
code: -32600,
message: 'Invalid JSON-RPC version'
}
});
return;
}
if (method === 'tools/list') {
const tools = mcpServer.getTools();
res.json({
jsonrpc: '2.0',
id: req.body.id,
result: {
tools: tools
}
});
} else if (method === 'tools/call') {
// Validate tool call parameters
if (!params || !params.name || !params.arguments) {
res.status(400).json({
jsonrpc: '2.0',
id: req.body.id,
error: {
code: -32602,
message: 'Invalid parameters for tools/call'
}
});
return;
}
const result = await mcpServer.handleRequest(params.name, params.arguments);
res.json({
jsonrpc: '2.0',
id: req.body.id,
result: result
});
} else {
res.status(400).json({
jsonrpc: '2.0',
id: req.body.id,
error: {
code: -32601,
message: 'Method not found'
}
});
}
} catch (error) {
console.error('MCP Server error:', error);
// Don't expose internal errors in production
const errorMessage = process.env.NODE_ENV === 'production'
? 'Internal server error'
: error.message;
res.status(500).json({
jsonrpc: '2.0',
id: req.body?.id,
error: {
code: -32603,
message: errorMessage
}
});
}
};