UNPKG

apex-data-for-seo

Version:

A comprehensive MCP server for DataForSEO API

171 lines (153 loc) 5.12 kB
#!/usr/bin/env node const { program } = require('commander'); const readline = require('readline'); const axios = require('axios'); const fs = require('fs'); const { version } = require('../package.json'); program .name('apex-data-for-seo') .description('A comprehensive stdio MCP server for DataForSEO API') .version(version) .option('--dataforseo-login <login>', 'DataForSEO login') .option('--dataforseo-password <password>', 'DataForSEO password'); program.parse(); const options = program.opts(); // Set environment variables const env = { ...process.env }; if (options.dataforseoLogin) env.DATAFORSEO_LOGIN = options.dataforseoLogin; if (options.dataforseoPassword) env.DATAFORSEO_PASSWORD = options.dataforseoPassword; // Validate required credentials if (!env.DATAFORSEO_LOGIN || !env.DATAFORSEO_PASSWORD) { console.error('Error: DataForSEO API credentials are required'); console.error('Please provide them via environment variables or command line arguments:'); console.error(' --dataforseo-login YOUR_LOGIN --dataforseo-password YOUR_PASSWORD'); console.error(' or'); console.error(' DATAFORSEO_LOGIN=your_login DATAFORSEO_PASSWORD=your_password npx apex-data-for-seo'); process.exit(1); } // Create API client const apiClient = axios.create({ baseURL: 'https://api.dataforseo.com/v3', auth: { username: env.DATAFORSEO_LOGIN, password: env.DATAFORSEO_PASSWORD }, headers: { 'Content-Type': 'application/json' } }); // Set up readline interface for stdin/stdout const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); // MCP tools implementation const tools = { // SERP API tools 'DataForSEO_GoogleSearch': async (params) => { try { const response = await apiClient.post('/serp/google/organic/live', [{ keyword: params.keyword, location_name: params.location_name || 'United States', language_name: params.language_name || 'English', device: params.device || 'desktop' }]); return response.data; } catch (error) { return { error: error.message }; } }, 'DataForSEO_KeywordData': async (params) => { try { const response = await apiClient.post('/keywords_data/google/search_volume/live', [{ keywords: [params.keyword], location_name: params.location_name || 'United States', language_name: params.language_name || 'English' }]); return response.data; } catch (error) { return { error: error.message }; } }, 'DataForSEO_DomainOverview': async (params) => { try { const response = await apiClient.post('/domain_analytics/domain_overview/live', [{ target: params.domain, location_name: params.location_name || 'United States', language_name: params.language_name || 'English' }]); return response.data; } catch (error) { return { error: error.message }; } } }; // Process each line from stdin rl.on('line', async (line) => { try { // Parse the incoming JSON request const request = JSON.parse(line); let response; // Handle different request types if (request.type === 'invoke') { const { name, parameters } = request; if (tools[name]) { const result = await tools[name](parameters); response = { type: 'invoke_response', id: request.id, status: 'success', name, content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } else { response = { type: 'invoke_response', id: request.id, status: 'error', error: `Tool ${name} not found` }; } } else if (request.type === 'capabilities') { // MCP capabilities request response = { type: 'capabilities_response', id: request.id, capabilities: { tools: Object.keys(tools).map(name => ({ name, description: `DataForSEO ${name.replace('DataForSEO_', '')} API` })) } }; } else { response = { type: 'error', id: request.id, error: 'Unsupported request type' }; } // Send response back to stdout console.log(JSON.stringify(response)); } catch (error) { // Handle errors console.error(`Error processing request: ${error.message}`); const errorResponse = { type: 'error', error: error.message }; console.log(JSON.stringify(errorResponse)); } }); // Log startup information to stderr (won't affect the protocol) console.error('DataForSEO MCP Server started and waiting for requests...'); console.error(`Using credentials: ${env.DATAFORSEO_LOGIN}`); // Process SIGINT (Ctrl+C) process.on('SIGINT', () => { console.error('Shutting down...'); process.exit(0); });