Skip to main content
Glama
cnych

Backlinks MCP

by cnych

get_backlinks_list

Retrieve a list of backlinks for any domain to analyze SEO performance, including title, URL, and domain rating data.

Instructions

Get backlinks list for the specified domain
Args:
    domain (str): The domain to query
Returns:
    List of backlinks for the domain, containing title, URL, domain rating, etc.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
domainYes

Implementation Reference

  • The primary handler function for the 'get_backlinks_list' tool. Decorated with @mcp.tool() for registration. Orchestrates caching, token acquisition, signature retrieval, and backlinks fetching.
    @mcp.tool()
    def get_backlinks_list(domain: str) -> Optional[Dict[str, Any]]:
        """
        Get backlinks list for the specified domain
        Args:
            domain (str): The domain to query
        Returns:
            List of backlinks for the domain, containing title, URL, domain rating, etc.
        """
        # Try to get signature from cache
        signature, valid_until, overview_data = load_signature_from_cache(domain)
        
        # If no valid signature in cache, get a new one
        if not signature or not valid_until:
            # Step 1: Get token
            site_url = f"https://ahrefs.com/backlink-checker/?input={domain}&mode=subdomains"
            token = get_capsolver_token(site_url)
            if not token:
                raise Exception(f"Failed to get verification token for domain: {domain}")
            
            # Step 2: Get signature and validUntil
            signature, valid_until, overview_data = get_signature_and_overview(token, domain)
            if not signature or not valid_until:
                raise Exception(f"Failed to get signature for domain: {domain}")
        
        # Step 3: Get backlinks list
        backlinks = get_backlinks(signature, valid_until, domain)
        return {
            "overview": overview_data,
            "backlinks": backlinks
        }
  • Core helper function that makes the API request to Ahrefs to retrieve the backlinks list using the provided signature and formats the results.
    def get_backlinks(signature: str, valid_until: str, domain: str) -> Optional[List[Any]]:
        if not signature or not valid_until:
            return None
        
        url = "https://ahrefs.com/v4/stGetFreeBacklinksList"
        payload = {
            "reportType": "TopBacklinks",
            "signedInput": {
                "signature": signature,
                "input": {
                    "validUntil": valid_until,
                    "mode": "subdomains",
                    "url": f"{domain}/"
                }
            }
        }
        
        headers = {
            "Content-Type": "application/json"
        }
        
        response = requests.post(url, json=payload, headers=headers)
        if response.status_code != 200:
            return None
        
        data = response.json()
    
        return format_backlinks(data, domain)
  • Helper function to obtain the API signature, validUntil timestamp, and overview data from Ahrefs using the captcha token obtained from CapSolver.
    def get_signature_and_overview(token: str, domain: str) -> Tuple[Optional[str], Optional[str], Optional[Dict[str, Any]]]:
        """
        Get signature and validUntil parameters using the token
        
        Args:
            token: Verification token
            domain: Domain to query
            
        Returns:
            (signature, valid_until, overview_data) tuple, or (None, None, None) if failed
        """
        url = "https://ahrefs.com/v4/stGetFreeBacklinksOverview"
        payload = {
            "captcha": token,
            "mode": "subdomains",
            "url": domain
        }
        
        headers = {
            "Content-Type": "application/json"
        }
        
        response = requests.post(url, json=payload, headers=headers)
        if response.status_code != 200:
            return None, None, None
        
        data = response.json()
        
        try:
            # Assuming data format is always ['Ok', {signature object}]
            if isinstance(data, list) and len(cast(List[Any], data)) > 1:
                second_element: Dict[str, Any] = cast(Dict[str, Any], data[1])
                signature: str = cast(str, second_element['signedInput']['signature'])
                valid_until: str = cast(str, second_element['signedInput']['input']['validUntil'])
                overview_data: Dict[str, Any] = cast(Dict[str, Any], second_element['data'])
                
                # Save the new signature to cache
                save_signature_to_cache(signature, valid_until, overview_data, domain)
                
                return signature, valid_until, overview_data
            else:
                return None, None, None
        except Exception:
            return None, None, None
  • Helper function to load previously cached signature and overview data for a domain, checking if it's still valid (not expired).
    def load_signature_from_cache(domain: str) -> Tuple[Optional[str], Optional[str], Optional[Dict[str, Any]]]:
        """
        Load signature information for a specific domain from local cache file
        Returns the signature and valid_until if cache is valid, otherwise None
        
        Args:
            domain: Domain to query
            
        Returns:
            (signature, valid_until) tuple, or (None, None) if no valid cache
        """
        if not os.path.exists(SIGNATURE_CACHE_FILE):
            return None, None, None
        
        try:
            with open(SIGNATURE_CACHE_FILE, 'r') as f:
                cache_data = json.load(f)
            
            # Check if cache exists for current domain
            if domain not in cache_data:
                return None, None, None
            
            domain_cache = cache_data[domain]
            
            # Check if signature is expired
            valid_until = domain_cache.get("valid_until")
            
            if valid_until:
                # Convert ISO date string to timestamp for comparison
                valid_until_timestamp = iso_to_timestamp(valid_until)
                current_time = time.time()
                
                if current_time < valid_until_timestamp:
                    return domain_cache.get("signature"), valid_until, domain_cache.get("overview_data")
                else:
                    return None, None, None
            else:
                return None, None, None
        except Exception:
            return None, None, None
  • Helper function to obtain captcha token from CapSolver service, required to bypass Ahrefs protections before getting signatures.
    def get_capsolver_token(site_url: str) -> Optional[str]:
        """
        Use CapSolver to solve the captcha and get a token
        
        Args:
            site_url: Site URL to query
            
        Returns:
            Verification token or None if failed
        """
        if not api_key:
            return None
        
        payload = {
            "clientKey": api_key,
            "task": {
                "type": 'AntiTurnstileTaskProxyLess',
                "websiteKey": "0x4AAAAAAAAzi9ITzSN9xKMi",  # site key of your target site: ahrefs.com,
                "websiteURL": site_url,
                "metadata": {
                    "action": ""  # optional
                }
            }
        }
        res = requests.post("https://api.capsolver.com/createTask", json=payload)
        resp = res.json()
        task_id = resp.get("taskId")
        if not task_id:
            return None
     
        while True:
            time.sleep(1)  # delay
            payload = {"clientKey": api_key, "taskId": task_id}
            res = requests.post("https://api.capsolver.com/getTaskResult", json=payload)
            resp = res.json()
            status = resp.get("status")
            if status == "ready":
                token = resp.get("solution", {}).get('token')
                return token
            if status == "failed" or resp.get("errorId"):
                return None

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Describes return type and fields (title, URL, domain rating). No annotations provided, so description carries burden; it is transparent about the output.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Very concise, all sentences contribute value. Front-loaded purpose with immediate clarity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one parameter, the description covers purpose, input, and output sufficiently.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage 0%; description adds 'The domain to query' which is minimal. Provides no additional context like format or examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states verb and resource: 'Get backlinks list for the specified domain'. Distinguishes from sibling tools like get_traffic and keyword_difficulty.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit when-to-use or when-not-to-use. Implied usage for domain backlinks, but no guidance on alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.