Skip to main content
Glama
metehan777

Semrush MCP Server

by metehan777

domain_organic_search

Analyze organic search keywords driving traffic to any domain to identify ranking opportunities and optimize SEO strategies.

Instructions

Get organic search keywords for a domain

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
domainYes
databaseNo
limitNo
offsetNo

Implementation Reference

  • The handler function for the 'domain_organic_search' tool. It validates input using the schema, then calls the Semrush API with the 'domain_organic' report type and specified parameters to retrieve organic search keywords for the given domain.
    case 'domain_organic_search': {
      const { domain, database, limit, offset } = DomainOrganicSearchSchema.parse(args);
      data = await callSemrushAPI('domain_organic', {
        domain,
        database,
        display_limit: limit,
        display_offset: offset,
        export_columns: 'Ph,Po,Nq,Cp,Ur,Tr,Tc,Co,Nr,Td',
      });
      break;
  • Zod schema defining the input parameters for the domain_organic_search tool, including domain, database, limit, and offset.
    const DomainOrganicSearchSchema = z.object({
      domain: z.string().describe('Domain to analyze'),
      database: z.string().default('us').describe('Database code'),
      limit: z.coerce.number().default(10).describe('Number of results'),
      offset: z.coerce.number().default(0).describe('Offset for pagination'),
    });
  • src/index.ts:193-200 (registration)
    Registration of the 'domain_organic_search' tool in the ListTools response, including name, description, and input schema reference.
      name: 'domain_organic_search',
      description: 'Get organic search keywords for a domain',
      inputSchema: {
        type: 'object',
        properties: DomainOrganicSearchSchema.shape,
        required: ['domain'],
      },
    },
  • Shared helper function that performs API calls to Semrush, handles both standard and analytics/v1 endpoints, parses CSV responses using parseCSV, and manages errors.
    async function callSemrushAPI(reportType: string, params: Record<string, any>, isAnalyticsV1: boolean = false) {
      try {
        let requestUrl: string;
        let queryParams: Record<string, any>;
    
        if (isAnalyticsV1) {
          requestUrl = `${SEMRUSH_API_BASE}/analytics/v1/`; // Note the trailing slash for consistency
          queryParams = {
            key: API_KEY,
            type: reportType, // e.g., 'backlinks_overview'
            ...params,
          };
        } else {
          requestUrl = SEMRUSH_API_BASE + '/'; // Main API endpoint: https://api.semrush.com/
          queryParams = {
            key: API_KEY,
            type: reportType, // e.g., 'domain_ranks'
            ...params,
          };
        }
        
        console.error(`Calling Semrush API. URL: ${requestUrl}, Params: ${JSON.stringify(queryParams)}`);
    
        const response = await axios.get(requestUrl, {
          params: queryParams,
        });
        
        if (typeof response.data === 'string') {
            const parsed = parseCSV(response.data);
            // If CSV parsing itself returns an error object, or if it's an array but empty (parsed from only headers or error string)
            if (parsed && typeof parsed === 'object' && ('error' in parsed || (Array.isArray(parsed) && 'headers' in parsed && parsed.length === 0))) {
                return parsed; // Return the error object or {error: ..., headers:..., data: []}
            }
            return parsed; // Otherwise, return the array of parsed objects or results from parseCSV
        }
        return response.data; // If not a string, return as is (might be JSON error from API)
      } catch (error: any) {
        console.error(`Semrush API call failed for type/path ${reportType}. Error: ${error.message}`);
        if (error.response) {
          console.error('Semrush API Error Response Status:', error.response.status);
          console.error('Semrush API Error Response Data:', error.response.data);
          let errorMessage = error.response.data;
          if (typeof errorMessage === 'string') {
            const semrushErrorMatch = errorMessage.match(/ERROR :: (.+)/);
            if (semrushErrorMatch && semrushErrorMatch[1]) {
              errorMessage = semrushErrorMatch[1];
            }
          }
          throw new Error(`Semrush API error (${error.response.status}): ${errorMessage || error.response.statusText}`);
        }
        throw new Error(`Semrush API request failed: ${error.message}`);
      }
    }
  • Helper function to parse semicolon-delimited CSV data returned by Semrush API into an array of objects or error object.
    function parseCSV(csvData: string): Record<string, any>[] | { error: string } {
      if (!csvData || typeof csvData !== 'string') {
        return { error: 'Invalid or empty CSV data' };
      }
      try {
        const lines = csvData.trim().split('\n').filter(line => line.trim().length > 0);
        if (lines.length === 0) {
            return { error: 'CSV data is empty after trimming' };
        }
        if (lines.length === 1 && lines[0].startsWith('ERROR ::')) {
            return { error: lines[0] };
        }
        if (lines.length <= 1 && !lines[0].includes(';')) { // Likely not a valid CSV if only one line and no delimiter
            return { error: 'No data rows found or invalid CSV header' };
        }
        const headers = lines[0].split(';');
        const dataRows = lines.slice(1);
        if (dataRows.length === 0 && lines.length === 1) { // Only header row means no data
            // Return headers so client knows what fields were expected, but indicate no data
            return { error: 'No data results, only headers returned' }; 
        }
        const results = dataRows.map(line => {
          const values = line.split(';');
          const row: Record<string, string> = {};
          headers.forEach((header, index) => {
            row[header.trim()] = values[index]?.trim() || '';
          });
          return row;
        });
        return results;
      } catch (error: any) {
        console.error('Error parsing CSV:', error.message);
        return { error: 'Failed to parse CSV data' };
      }
    }

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/metehan777/semrush-mcp'

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