traceroute
Trace the network path to any host, showing each hop and latency. Diagnose connectivity issues by identifying routing problems.
Instructions
Trace the network route to a host.
Wraps the system traceroute command and parses output.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| host | Yes | ||
| max_hops | No |
Implementation Reference
- src/sounding/server.py:114-148 (handler)The main handler function for the traceroute tool. It validates input via validate_host, enforces max_hops 1-64, spawns the system 'traceroute' subprocess, parses the output into structured hop data, and returns a dict with host, max_hops, hops list, and raw output.
@mcp.tool() async def traceroute(host: str, max_hops: int = 30) -> dict: """Trace the network route to a host. Wraps the system ``traceroute`` command and parses output. """ host = validate_host(host) if max_hops < 1 or max_hops > 64: raise ValueError("max_hops must be between 1 and 64") try: proc = await asyncio.create_subprocess_exec( "traceroute", "-m", str(max_hops), "-w", "3", host, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120) except FileNotFoundError: return {"error": "traceroute command not found — install it on the host system"} except asyncio.TimeoutError: return {"error": "traceroute timed out"} lines = stdout.decode(errors="replace").strip().splitlines() hops: list[dict] = [] for line in lines[1:]: # skip header parts = line.split() if len(parts) >= 2: hops.append({"hop": parts[0], "detail": " ".join(parts[1:])}) return { "host": host, "max_hops": max_hops, "hops": hops, "raw": stdout.decode(errors="replace"), } - src/sounding/server.py:114-114 (registration)The tool is registered with MCP using the @mcp.tool() decorator on the traceroute async function.
@mcp.tool() - src/sounding/validators.py:101-143 (helper)The validate_host helper is used by traceroute to sanitize the host input. The docstring notes that allow_internal defaults to True since tools like traceroute legitimately target internal networks.
def validate_host(host: str, *, allow_internal: bool = True) -> str: """Validate a hostname or IP address. Args: host: The hostname or IP to validate. allow_internal: If False, reject internal/private/loopback IPs and hostnames that resolve to them (SSRF protection). Defaults to True since tools like ping and traceroute legitimately target internal networks. Returns the cleaned host string. Raises ``ValueError`` on anything suspicious. """ host = host.strip() if not host: raise ValueError("Host must not be empty") if _SHELL_META.search(host): raise ValueError(f"Host contains forbidden characters: {host!r}") # Accept valid IP addresses directly. try: addr = ipaddress.ip_address(host) if not allow_internal and is_internal_ip(host): raise ValueError( f"Host {host} is an internal/private/loopback address — " "requests to internal addresses are blocked" ) return host except ValueError as exc: # Re-raise if it's our own SSRF block, not an ip_address parse error if "internal" in str(exc): raise pass if not _HOSTNAME_RE.match(host): raise ValueError(f"Invalid hostname: {host!r}") # If internal not allowed, resolve and check the IP if not allow_internal: _resolve_and_check(host) return host