Skip to main content
Glama
server-http.ts9.26 kB
import express, { Request, Response } from 'express'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { setupApiClient } from "./api/client.js"; import { toolRegistry } from "./api/tools.js"; import { registerSerpTools } from "./api/serp/index.js"; import { registerKeywordsTools } from "./api/keywords/index.js"; import { registerLabsTools } from "./api/labs/index.js"; import { registerBacklinksTools } from "./api/backlinks/index.js"; import { registerOnPageTools } from "./api/onpage/index.js"; import { registerDomainAnalyticsTools } from "./api/domain-analytics/index.js"; import { registerContentAnalysisTools } from "./api/content-analysis/index.js"; import { registerContentGenerationTools } from "./api/content-generation/index.js"; import { registerMerchantTools } from "./api/merchant/index.js"; import { registerAppDataTools } from "./api/app-data/index.js"; import { registerBusinessDataTools } from "./api/business-data/index.js"; import { registerLocalFalconTools } from "./api/localfalcon/index.js"; import { registerAiOptimizationTools } from "./api/ai-optimization/index.js"; import zodToJsonSchema from 'zod-to-json-schema'; const app = express(); const port = process.env.PORT || 8080; app.use(express.json({ limit: '10mb' })); // CORS headers app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); // Initialize MCP server once at startup let mcpServer: McpServer | null = null; let registeredTools: any[] = []; function getMcpServer(): McpServer { if (mcpServer) return mcpServer; console.log('Initializing MCP server...'); const dataForSeoLogin = process.env.DATAFORSEO_LOGIN; const dataForSeoPassword = process.env.DATAFORSEO_PASSWORD; if (!dataForSeoLogin || !dataForSeoPassword) { throw new Error("DataForSEO credentials not provided"); } const apiClient = setupApiClient(dataForSeoLogin, dataForSeoPassword); mcpServer = new McpServer({ name: "DataForSEO MCP Server", version: "1.0.0", }); // Register all DataForSEO tools registerSerpTools(mcpServer, apiClient); registerKeywordsTools(mcpServer, apiClient); registerLabsTools(mcpServer, apiClient); registerBacklinksTools(mcpServer, apiClient); registerOnPageTools(mcpServer, apiClient); registerDomainAnalyticsTools(mcpServer, apiClient); registerContentAnalysisTools(mcpServer, apiClient); registerContentGenerationTools(mcpServer, apiClient); registerMerchantTools(mcpServer, apiClient); registerAppDataTools(mcpServer, apiClient); registerBusinessDataTools(mcpServer, apiClient); registerAiOptimizationTools(mcpServer, apiClient); const localFalconApiKey = process.env.LOCALFALCON_API_KEY; if (localFalconApiKey) { console.log("Registering Local Falcon tools"); registerLocalFalconTools(mcpServer, { apiKey: localFalconApiKey, baseUrl: process.env.LOCALFALCON_API_URL }); } else { console.log("Local Falcon not configured (optional)"); } // Store tools list by intercepting the tool registration // We'll call tools/list on the actual MCP server to get the real list console.log('MCP server initialized'); return mcpServer; } // Helper to get tools list from tool registry function getToolsList(): any[] { const tools = Array.from(toolRegistry.values()).map(tool => { // Convert Zod schema to JSON Schema const fullSchema: any = zodToJsonSchema(tool.inputSchema, tool.name); // Extract the actual schema from definitions if using $ref let inputSchema = fullSchema; if (fullSchema.$ref && fullSchema.definitions) { const refParts = fullSchema.$ref.split('/'); const refKey = refParts[refParts.length - 1]; if (refKey && fullSchema.definitions[refKey]) { inputSchema = { ...fullSchema.definitions[refKey] }; } } // Clean up - remove $ref and definitions if (inputSchema.$ref) delete inputSchema.$ref; if (inputSchema.definitions) delete inputSchema.definitions; // Fix arrays without items (OpenAI requires items for all arrays) if (inputSchema.properties) { for (const [propName, propSchema] of Object.entries(inputSchema.properties)) { const prop = propSchema as any; if (prop.type === 'array' && !prop.items) { // Add default items schema prop.items = { type: 'string' }; } } } return { name: tool.name, description: tool.description || `DataForSEO API tool: ${tool.name}`, inputSchema }; }); console.log(`Returning ${tools.length} tools`); return tools; } // Health check app.get('/health', (req: Request, res: Response) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), service: 'dataforseo-mcp-server', version: '1.0.0' }); }); // Root endpoint app.get('/', (req: Request, res: Response) => { res.json({ name: 'DataForSEO MCP Server', version: '1.0.0', description: 'MCP server for DataForSEO API - OpenAI compatible', endpoints: { health: '/health', mcp: '/mcp (POST) - For OpenAI and other MCP clients', tools: '/tools (GET)' } }); }); // Main MCP endpoint - handles JSON-RPC 2.0 app.post('/mcp', async (req: Request, res: Response) => { try { const server = getMcpServer(); const { jsonrpc, method, params, id } = req.body; if (jsonrpc !== '2.0') { return res.status(400).json({ jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: id || null }); } // Handle MCP methods switch (method) { case 'initialize': return res.json({ jsonrpc: '2.0', result: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'DataForSEO MCP Server', version: '1.0.0' } }, id }); case 'tools/list': // Get tools list from registry const toolsList = getToolsList(); return res.json({ jsonrpc: '2.0', result: { tools: toolsList }, id }); case 'tools/call': if (!params?.name) { return res.status(400).json({ jsonrpc: '2.0', error: { code: -32602, message: 'Missing tool name' }, id }); } try { // Get the tool from registry const tool = toolRegistry.get(params.name); if (!tool || !tool.handler) { return res.status(404).json({ jsonrpc: '2.0', error: { code: -32601, message: `Tool not found: ${params.name}` }, id }); } // Call the tool handler directly const result = await tool.handler(params.arguments || {}, {}); return res.json({ jsonrpc: '2.0', result, id }); } catch (error: any) { return res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: error.message }, id }); } default: return res.status(404).json({ jsonrpc: '2.0', error: { code: -32601, message: `Method not found: ${method}` }, id }); } } catch (error: any) { console.error('MCP error:', error); return res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal error', data: error.message }, id: req.body?.id || null }); } }); // List tools endpoint app.get('/tools', (req: Request, res: Response) => { try { getMcpServer(); // Ensure server is initialized const toolsList = getToolsList(); res.json({ jsonrpc: '2.0', result: { tools: toolsList } }); } catch (error: any) { res.status(500).json({ error: 'Failed to list tools', message: error.message }); } }); // Error handler app.use((err: any, req: Request, res: Response, next: any) => { console.error('Error:', err); res.status(500).json({ error: 'Internal server error', message: err.message }); }); // Start server and pre-initialize MCP const server = app.listen(port, () => { console.log(`DataForSEO MCP HTTP Server listening on port ${port}`); console.log(`Health: http://localhost:${port}/health`); console.log(`MCP: http://localhost:${port}/mcp`); // Pre-initialize MCP server try { getMcpServer(); console.log('MCP server ready for requests'); } catch (error) { console.error('Failed to initialize MCP server:', error); } }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully'); server.close(() => { console.log('Server closed'); process.exit(0); }); }); process.on('SIGINT', () => { console.log('SIGINT received, shutting down gracefully'); server.close(() => { console.log('Server closed'); process.exit(0); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Skobyn/dataforseo-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server