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
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes |
Implementation Reference
- urldna_mcp/tools/new_scan.py:8-73 (handler)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) - urldna_mcp/tools/new_scan.py:8-18 (schema)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. """ - urldna_mcp/run.py:17-17 (registration)Registers new_scan tool in stdio transport server.
register_new_scan(mcp) - urldna_mcp/server.py:17-17 (registration)Registers new_scan tool in SSE HTTP server.
register_new_scan(mcp) - urldna_mcp/utils.py:36-89 (helper)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