UNPKG

atom-marketplace-mcp-server

Version:

MCP server for Atom marketplace APIs - domain search, availability, trademarks, and appraisals

235 lines (208 loc) 6.36 kB
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 } }); } };