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
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. While it mentions the return format ('List of backlinks... containing title, URL, domain rating, etc.'), it doesn't address important behavioral aspects like whether this is a read-only operation, rate limits, authentication requirements, pagination, or error conditions. The description provides basic output information but lacks comprehensive behavioral context.

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

Conciseness4/5

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

The description is appropriately sized and well-structured with clear sections: purpose statement, Args section, and Returns section. Each sentence earns its place by providing essential information. It could be slightly more concise by combining the purpose and Args sections, but overall it's efficient.

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

Completeness3/5

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

Given the tool's moderate complexity (single parameter query tool), no annotations, and no output schema, the description provides basic purpose and parameter documentation but lacks comprehensive behavioral context. The Returns section helps compensate for the missing output schema, but important operational details (rate limits, authentication, error handling) are missing. This is adequate but has clear gaps.

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

Parameters4/5

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

The description explicitly documents the single parameter ('domain (str): The domain to query'), adding semantic meaning beyond the schema which has 0% description coverage. This fully compensates for the schema's lack of parameter documentation. The parameter count is low (1), making this documentation adequate.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Get backlinks list for the specified domain' - a specific verb ('Get') and resource ('backlinks list') with the target ('domain'). However, it doesn't distinguish this from sibling tools like 'get_traffic' or 'keyword_difficulty', which appear to be related SEO tools but have different functions.

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

Usage Guidelines2/5

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

No guidance is provided on when to use this tool versus alternatives. The description doesn't mention sibling tools, prerequisites, or specific contexts where this tool is appropriate versus other SEO-related tools. It simply states what the tool does without usage context.

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

Install Server

Other Tools

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/cnych/backlinks-mcp'

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