DataForSEO MCP Server
by Skobyn
Verified
#!/usr/bin/env node
const readline = require('readline');
const axios = require('axios');
const crypto = require('crypto');
const fs = require('fs');
// Parse config options
let config = {};
try {
// Check for --config-file option
const configFileArg = process.argv.find(arg => arg.startsWith('--config-file'));
if (configFileArg) {
const configFilePath = configFileArg.split('=')[1] || process.argv[process.argv.indexOf(configFileArg) + 1];
console.error(`Reading config file: ${configFilePath}`);
const fileContents = fs.readFileSync(configFilePath, 'utf8');
config = JSON.parse(fileContents);
console.error(`Loaded config from file: ${configFilePath}`);
}
// Check for direct --config JSON
else {
const configArg = process.argv.find(arg => arg.startsWith('--config'));
if (configArg) {
let configJson;
if (configArg.includes('=')) {
// Handle --config='{...}'
configJson = configArg.substring(configArg.indexOf('=') + 1);
} else {
// Handle --config '{...}'
const configIndex = process.argv.indexOf(configArg);
if (configIndex !== -1 && configIndex < process.argv.length - 1) {
configJson = process.argv[configIndex + 1];
}
}
if (configJson) {
// Clean up the JSON string (handle PowerShell escaping issues)
configJson = configJson.replace(/^['"]|['"]$/g, ''); // Remove outer quotes if present
config = JSON.parse(configJson);
console.error('Loaded config from command line argument');
}
}
}
} catch (error) {
console.error(`Error parsing config: ${error.message}`);
console.error('Config argument format should be: --config \'{"username":"user","password":"pass"}\'');
console.error('Or use a config file with: --config-file config.json');
}
// Configure credentials (from config or environment variables)
const username = config.username || process.env.DATAFORSEO_USERNAME;
const password = config.password || process.env.DATAFORSEO_PASSWORD;
// Print diagnostic info to stderr (won't affect protocol)
if (username) {
console.error(`Using username: ${username.substring(0, 3)}...${username.substring(username.length - 3)}`);
} else {
console.error('Username not provided in config or environment variables');
}
if (password) {
console.error('Password provided (hidden)');
} else {
console.error('Password not provided in config or environment variables');
}
if (!username || !password) {
console.error('Error: DataForSEO username and password are required');
console.error('Provide credentials using one of these methods:');
console.error('1. Environment variables: DATAFORSEO_USERNAME and DATAFORSEO_PASSWORD');
console.error('2. Config file: --config-file config.json');
console.error('3. Direct config: --config \'{"username":"user","password":"pass"}\'');
process.exit(1);
}
// Initialize API client with basic auth
const apiClient = axios.create({
baseURL: 'https://api.dataforseo.com/v3',
auth: {
username: username,
password: password
},
headers: {
'Content-Type': 'application/json'
}
});
// Output initialization message immediately on startup
console.log(JSON.stringify({
type: 'initialize',
status: 'ready',
version: '1.0.0',
tools: [
'dataforseo_serp',
'dataforseo_keywords_data',
'dataforseo_backlinks',
'dataforseo_onpage',
'dataforseo_domain_analytics',
'dataforseo_app_data',
'dataforseo_merchant',
'dataforseo_business_data'
]
}));
// Set up readline interface for stdio
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
// Process each line from stdin as a request
rl.on('line', async (line) => {
try {
// Parse the incoming JSON request
const request = JSON.parse(line);
// Generate a unique ID for this request
const requestId = crypto.randomUUID();
// Handle initialize request
if (request.type === 'initialize') {
console.log(JSON.stringify({
type: 'initialize',
id: requestId,
status: 'ready',
version: '1.0.0',
tools: [
'dataforseo_serp',
'dataforseo_keywords_data',
'dataforseo_backlinks',
'dataforseo_onpage',
'dataforseo_domain_analytics',
'dataforseo_app_data',
'dataforseo_merchant',
'dataforseo_business_data'
]
}));
return;
}
// Process based on request type
let response;
if (request.type === 'dataforseo_serp') {
// SERP API request
const apiResponse = await apiClient.post('/serp/google/organic/live/advanced', [{
keyword: request.keyword,
location_code: request.location_code || 2840,
language_code: request.language_code || "en",
device: request.device || "desktop",
os: request.os || "windows"
}]);
response = {
type: 'dataforseo_serp',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_keywords_data') {
// Keywords Data API request
const apiResponse = await apiClient.post('/keywords_data/google/search_volume/live', [{
keywords: request.keywords,
location_code: request.location_code || 2840,
language_code: request.language_code || "en"
}]);
response = {
type: 'dataforseo_keywords_data',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_backlinks') {
// Backlinks API request
const apiResponse = await apiClient.post('/backlinks/summary/live', [{
target: request.target,
limit: request.limit || 100
}]);
response = {
type: 'dataforseo_backlinks',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_onpage') {
// On-Page API request
const apiResponse = await apiClient.post('/on_page/instant_pages', [{
url: request.url,
check_spell: request.check_spell || true,
enable_javascript: request.enable_javascript || true
}]);
response = {
type: 'dataforseo_onpage',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_domain_analytics') {
// Domain Analytics API request
const apiResponse = await apiClient.post('/domain_analytics/whois/live', [{
domain: request.domain
}]);
response = {
type: 'dataforseo_domain_analytics',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_app_data') {
// App Data API request
const apiResponse = await apiClient.post('/app_data/google/app_info/live', [{
app_id: request.app_id
}]);
response = {
type: 'dataforseo_app_data',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_merchant') {
// Merchant API request
const apiResponse = await apiClient.post('/merchant/google/products/live', [{
keyword: request.keyword,
location_code: request.location_code || 2840,
language_code: request.language_code || "en"
}]);
response = {
type: 'dataforseo_merchant',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else if (request.type === 'dataforseo_business_data') {
// Business Data API request
const apiResponse = await apiClient.post('/business_data/google/my_business_info/live', [{
keyword: request.keyword,
location_code: request.location_code || 2840,
language_code: request.language_code || "en"
}]);
response = {
type: 'dataforseo_business_data',
id: requestId,
results: apiResponse.data.tasks[0].result,
status: 'success'
};
}
else {
response = {
type: 'error',
id: requestId,
error: 'Unsupported request type',
status: 'error'
};
}
// Send response back to stdout
console.log(JSON.stringify(response));
} catch (error) {
// Handle errors
const errorResponse = {
type: 'error',
error: error.message,
status: 'error'
};
// Add API response details if available
if (error.response && error.response.data) {
errorResponse.details = error.response.data;
}
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...');