Dns Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Dns Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Real-time DNS security analysis for AI assistants via MCP. Gives your assistant the ability to investigate domains the way a practitioner would — DNSSEC chain validation, email authentication posture, and registration intelligence — without leaving your chat session.
Built by a cybersecurity professional for SOC investigation workflows. Not a toy — the same queries you would run at the command line, accessible through any MCP-compatible assistant in real time.
dns-mcp is a Streamable HTTP MCP server with OAuth via Pocket ID. Tool implementations are thin wrappers around the dns_tool Python library, which owns all DNS logic. The server itself is ~430 lines of code: auth bootstrap, tool registration, and prompt loading.
Claude.ai / Claude Code / any MCP client
│
│ Streamable HTTP + OAuth bearer (JWT)
▼
Caddy reverse proxy (TLS, DNS-01 / Let's Encrypt)
│
▼
dns-mcp container (FastMCP, OAuth verifier)
│
▼
dns_tool library (DoH client, validators, parsers)
│
▼
doh.lab.deflationhollow.net (Unbound DoH resolver, optional)Three benefits over the previous stdio-only architecture:
ones that can spawn a local subprocess.
identity available to tools via whoami.
dns_tool is published independently and reusable. Thesame code powers a CLI, this MCP server, and (eventually) a REST API.
The old stdio architecture lives at server.py.legacy for porting reference. The remote branch (mcp-shim Go bridge) is deprecated.
dns-mcp 2.0.0 currently exposes 19 tools. Ten additional tools from the 1.x stdio architecture are pending port into dns_tool — see Open work.
| Tool | Description |
|---|---|
ping | Server uptime, current timestamp, dns_tool version + commit hash |
whoami | Authenticated user identity from JWT claims |
| Tool | Description |
|---|---|
dns_query | Standard DNS lookup over DoH — 20 record types (A, AAAA, MX, TXT, NS, SOA, CNAME, PTR, SRV, CAA, DNSKEY, DS, RRSIG, NSEC, NSEC3, TLSA, SSHFP, HTTPS, SVCB, NAPTR) |
dnssec_validate | Full DNSSEC chain walk from IANA root trust anchor down to target. Real cryptographic validation at every zone cut. Returns structured verdict + per-zone findings + event transcript |
nsec_info | NSEC / NSEC3 denial-of-existence analysis — zone walkability assessment, NSEC3 hash parameters, opt-out detection |
| Tool | Description |
|---|---|
check_spf | SPF record parsing with recursive include resolution (RFC 7208 10-lookup limit) |
check_dmarc | DMARC policy retrieval with organizational domain fallback |
check_dkim | DKIM public key record verification for a selector + domain pair |
enumerate_dkim_selectors | Probe a domain for DKIM keys at well-known selector names; returns the selectors that resolve |
check_dane | DANE TLSA records for all MX hosts of a domain |
check_tlsa | Standalone TLSA record lookup at _<port>._<proto>.<host> |
| Tool | Description |
|---|---|
check_rbl | IP reputation against 8 DNS-based RBLs (Spamhaus ZEN, SpamCop, UCEProtect L1/L2, Mailspike, PSBL, Barracuda, SORBS) |
check_dbl | Domain reputation against DNS-based Domain Block Lists (Spamhaus DBL, URIBL, SURBL) |
cymru_asn | ASN lookup via Team Cymru DNS service — BGP prefix, org, country |
check_fast_flux | Fast-flux detection — repeated A/AAAA queries to identify rotating IPs and short TTLs |
detect_hijacking | Test a recursive resolver for tampering — NXDOMAIN wildcards, DNSSEC handling, identity |
| Tool | Description |
|---|---|
rdap_lookup | Domain registration data via RDAP (modern WHOIS replacement) |
| Tool | Description |
|---|---|
session_stats | Per-tool call statistics for the current process — count, error_count, mean_ms, max_ms, first/last_called timestamps; plus session uptime and total call count. Module-level state (resets on container restart). Backed by dns_mcp/tracking.py. |
reset_stats | Clear all tool-call statistics and restart the session clock. |
Downstream consumers (e.g. ~/projects/yahoo batch forensics) call session_stats as the final tool in each investigation to record which DNS tools were consulted; an empty stats dict indicates a "cold read" where the analyst LLM produced a verdict without DNS verification.
All 19 tools use Pydantic Field for parameter descriptors. The LLM sees:
Constraints are advertised in the tool descriptor JSON Schema and enforced at the MCP boundary by FastMCP — invalid input is rejected before dns_tool is called. See src/dns_mcp/server.py for the type alias definitions.
Four analyst prompt templates ship with the server. Any MCP-compatible client that supports prompts can list and invoke them.
| Prompt | What it does |
|---|---|
email_security_audit | SPF, DKIM, DMARC, MTA-STS, BIMI — graded A through F with prioritized recommendations |
dnssec_chain_audit | Full DNSSEC chain-of-trust audit from IANA root down to target |
soc_email_forensics | Forensic phishing analysis of a raw email — TRUSTABLE / SUSPICIOUS / PHISHING / FURTHER ANALYSIS REQUIRED |
nist_800_81r3_audit | Domain security posture audit aligned with NIST SP 800-81r3 |
Prompt invocation requires client-side UI support. Claude Code surfaces them as /mcp__dns-mcp__<prompt_name>. Claude.ai web exposes prompts via the slash-command picker. Use tools ad-hoc in clients that do not support prompts.
Ask your assistant: "Check the email security posture of example.com"
The assistant calls check_spf, check_dmarc, check_dane in sequence and returns a complete analysis:
✅ SPF: Hard fail (-all), 3 lookups (under RFC limit)
✅ DMARC: p=reject, pct=100 — full enforcement, aggregate reporting configured
✅ DANE: TLSA records present and DNSSEC-validatedNo copy-pasting dig commands. No tab-switching. One question.
Mint an admin API key in Pocket ID's UI: Settings → API Keys → Create new key. Name it dns-mcp so you can revoke just this service if needed. Copy the key value (it is shown once).
POCKET_ID_BASE_URL=https://pocketid.example.com
POCKET_ID_API_KEY=<the key from step 1>
SERVER_URL=https://dns-mcp.example.comgit clone https://github.com/mclose/dns-mcp.git
cd dns-mcp
docker compose up -dThe image installs dns_tool as a versioned dependency (URL-pinned in pyproject.toml); make build is also available for direct development.
The container listens on port 8000 (HTTP). Front it with TLS termination:
dns-mcp.example.com {
reverse_proxy dns-mcp:8000 {
flush_interval -1 # required for Streamable HTTP / SSE
}
}If you use mclose/gateway (the Caddy + DNS-01 setup that serves dns-mcp.lab.deflationhollow.net), drop a conf.d/dns-mcp.conf matching the existing pattern.
Add https://dns-mcp.example.com/mcp as a connector in your MCP client. The OAuth flow runs once on first connect — Claude.ai redirects to Pocket ID, you authenticate, the server creates a DCR client on your Pocket ID instance, and returns a JWT. Subsequent tool calls send that JWT as a bearer token; the server verifies against Pocket ID JWKS.
Eleven tools from the 1.x stdio architecture are not yet ported into dns_tool and are therefore not registered in 2.0.0:
check_caa (with CNAME chain tracing and wildcard delegation detection)check_zone_transfer (AXFR enumeration)check_bimi, check_mta_sts, check_smtp_tlsrptcheck_ct_logs (Certificate Transparency log enumeration via crt.sh)timestamp_converter, reverse_dnsenumerate_dkim_selectors, dns_dig_style, dns_query_dotReference implementations live in server.py.legacy. Each port involves moving the function into the appropriate dns_tool module (dns_tool.email, dns_tool.intel, etc.), adding tests on the library side, and registering a one-line wrapper in src/dns_mcp/server.py.
dns-mcp/
├── src/dns_mcp/
│ ├── __init__.py
│ ├── __main__.py # entrypoint — create_server().run(transport="streamable-http")
│ ├── config.py # pydantic-settings Settings class
│ ├── auth.py # JWKSTokenVerifier + JWTAccessToken
│ └── server.py # FastMCP app: OAuth routes + 16 tools + 4 prompts (~430 lines)
├── prompts/ # MCP analyst prompt text files
├── tests/ # pytest unit tests (legacy — pending rewrite)
├── tools/ # operator scripts (smoke tests, deploy helpers)
├── server.py.legacy # 1.x stdio server (5,095 lines), reference for 11 deferred tool ports
├── compose.yaml
├── Dockerfile
├── pyproject.toml # dns_tool URL-pinned to dist tarball
└── Makefile # build/lint/import-check| Command | What it does |
|---|---|
make build | Rebuild the Docker image |
make rebuild | Full clean build, no cache |
make lint | pre-commit run --all-files (ruff check + format) |
make import-check | Build image, run create_server() inside, assert tools register |
make shell | Interactive shell inside the container |
make deploy | Push to GitHub + VPS post-receive hook |
make logs | Tail container logs |
make status | Container status |
dns_tool (dnspython internally), RDAP viarequests
Field constraints enforced at MCP boundary — invalid inputrejected before reaching dns_tool
claude, uid 1000)MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.