Skip to main content
Glama
urldna

urlDNA MCP Server

Official
by urldna

new_scan

Submit URLs for security scanning to detect threats and analyze malicious content using the urlDNA threat intelligence platform.

Instructions

Submit a URL to urlDNA and wait for the scan result.

Args: url (str): URL to submit for scanning. Returns: dict: Truncated scan result JSON. Raises: RuntimeError: If submission or polling fails.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYes

Implementation Reference

  • Executes the new_scan tool: authenticates with API key, POSTs URL to /scan endpoint, polls /scan/{id} every 2s up to 60s for 'DONE' status, truncates and returns result.
    def new_scan(url: str):
        """
        Submit a URL to urlDNA and wait for the scan result.
    
        Args:
            url (str): URL to submit for scanning.
        Returns:
            dict: Truncated scan result JSON.
        Raises:
            RuntimeError: If submission or polling fails.
        """
        # Get urlDNA API key 
        try:
            urlDNA_api_key = get_api_key()
        except Exception as e:
            raise RuntimeError(f"[new_scan] Failed to retrieve API key: {e}")
    
        headers = {
            "Authorization": urlDNA_api_key,
            "Content-Type": "application/json",
            "User-Agent": "urlDNA-MCP"
        }
    
        # Submit new scan
        try:
            response = requests.post(
                f"{config.urlDNA_API_URL}/scan",
                json={"submitted_url": url},
                headers=headers,
                timeout=10
            )
            response.raise_for_status()
        except requests.RequestException as e:
            raise RuntimeError(f"[new_scan] Scan submission failed: {e}")
    
        scan = response.json()
        scan_id = scan.get("id")
        if not scan_id:
            raise RuntimeError("[new_scan] No scan ID returned from submission.")
    
        # Polling for scan completion
        status = scan.get("scan", {}).get("status", "PENDING")
        scan_result = None
        retries = 0
        max_retries = 30
    
        while status not in {"DONE", "ERROR"} and retries < max_retries:
            time.sleep(2)
            retries += 1
            try:
                res = requests.get(
                    f"{config.urlDNA_API_URL}/scan/{scan_id}",
                    headers=headers,
                    timeout=10
                )
                res.raise_for_status()
                scan_result = res.json()
                status = scan_result.get("scan", {}).get("status", "UNKNOWN")
            except requests.RequestException as e:
                raise RuntimeError(f"[new_scan] Failed to fetch scan status: {e}")
    
        if status != "DONE":
            raise RuntimeError(f"[new_scan] Scan did not complete successfully (status: {status})")
    
        return truncate_scan_length(scan_result)
  • Input schema from type annotation and docstring: url (str); Output: dict (truncated scan JSON)
    def new_scan(url: str):
        """
        Submit a URL to urlDNA and wait for the scan result.
    
        Args:
            url (str): URL to submit for scanning.
        Returns:
            dict: Truncated scan result JSON.
        Raises:
            RuntimeError: If submission or polling fails.
        """
  • Registers new_scan tool in stdio transport server.
    register_new_scan(mcp)
  • Registers new_scan tool in SSE HTTP server.
    register_new_scan(mcp)
  • Supporting utility called by new_scan to truncate large scan JSON by dropping heavy fields (dom, http_transactions, page.text) to fit LLM context limits.
    def truncate_scan_length(scan_result):
        """
        Truncate scan result JSON if it exceeds max context length.
        Attributes are removed in the following order until the size is within limit:
        1. "dom"
        2. "http_transactions"
        3. "page.text"
        
        :param scan_result: dict - urlDNA Scan Result JSON
        :return: dict - truncated scan result
        """
        context_length = get_max_context_length()
    
        # Work on a copy to avoid mutating the original input
        truncated = dict(scan_result)
        
        # If context length not provided remove dom anyway
        if context_length <= 0:
            if "dom" in truncated:
                del truncated["dom"]
    
        # Helper to calculate JSON size in characters
        def json_length(obj):
            return len(json.dumps(obj, separators=(',', ':')))
    
        # If already under the limit, return as-is
        if json_length(truncated) <= context_length:
            return truncated
    
        # Truncate in the specified order
        drop_order = [
            ("dom",),
            ("http_transactions",),
            ("page", "text")
        ]
    
        for path in drop_order:
            obj = truncated
            *parents, last = path
    
            # Navigate to the parent
            for key in parents:
                obj = obj.get(key, {})
                if not isinstance(obj, dict):
                    break
            else:
                # Only attempt to remove if the key exists
                if last in obj:
                    obj.pop(last)
                    if json_length(truncated) <= context_length:
                        break
    
        return truncated
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/urldna/mcp'

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