{
  "markdown": "# DNS MCP Server\n\nReal-time DNS security analysis for AI assistants via MCP. Gives your\nassistant the ability to investigate domains the way a practitioner would —\nDNSSEC chain validation, email authentication posture, and registration\nintelligence — without leaving your chat session.\n\nBuilt by a cybersecurity professional for SOC investigation workflows.\nNot a toy — the same queries you would run at the command line, accessible\nthrough any MCP-compatible assistant in real time.\n\n## Architecture (2.0.0)\n\ndns-mcp is a Streamable HTTP MCP server with OAuth via [Pocket\nID](https://pocket-id.org/). Tool implementations are thin wrappers around the\n[`dns_tool`](https://github.com/mclose/claude-packages) Python library, which\nowns all DNS logic. The server itself is ~430 lines of code: auth bootstrap,\ntool registration, and prompt loading.\n\n```\nClaude.ai / Claude Code / any MCP client\n              │\n              │ Streamable HTTP + OAuth bearer (JWT)\n              ▼\n       Caddy reverse proxy           (TLS, DNS-01 / Let's Encrypt)\n              │\n              ▼\n     dns-mcp container               (FastMCP, OAuth verifier)\n              │\n              ▼\n        dns_tool library             (DoH client, validators, parsers)\n              │\n              ▼\n   doh.lab.deflationhollow.net      (Unbound DoH resolver, optional)\n```\n\nThree benefits over the previous stdio-only architecture:\n\n1. **Network-accessible** — hosted MCP servers can serve any client, not just\n   ones that can spawn a local subprocess.\n2. **OAuth-protected** — bearer JWTs verified against Pocket ID JWKS; per-user\n   identity available to tools via `whoami`.\n3. **Library-first** — `dns_tool` is published independently and reusable. The\n   same code powers a CLI, this MCP server, and (eventually) a REST API.\n\nThe old stdio architecture lives at `server.py.legacy` for porting reference.\nThe `remote` branch (mcp-shim Go bridge) is deprecated.\n\n## Tools\n\ndns-mcp 2.0.0 currently exposes **19 tools**. Ten additional tools from the\n1.x stdio architecture are pending port into `dns_tool` — see\n[Open work](#open-work).\n\n### Meta\n\n| Tool | Description |\n|------|-------------|\n| `ping` | Server uptime, current timestamp, dns_tool version + commit hash |\n| `whoami` | Authenticated user identity from JWT claims |\n\n### DNS\n\n| Tool | Description |\n|------|-------------|\n| `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) |\n| `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 |\n| `nsec_info` | NSEC / NSEC3 denial-of-existence analysis — zone walkability assessment, NSEC3 hash parameters, opt-out detection |\n\n### Email security\n\n| Tool | Description |\n|------|-------------|\n| `check_spf` | SPF record parsing with recursive include resolution (RFC 7208 10-lookup limit) |\n| `check_dmarc` | DMARC policy retrieval with organizational domain fallback |\n| `check_dkim` | DKIM public key record verification for a selector + domain pair |\n| `enumerate_dkim_selectors` | Probe a domain for DKIM keys at well-known selector names; returns the selectors that resolve |\n| `check_dane` | DANE TLSA records for all MX hosts of a domain |\n| `check_tlsa` | Standalone TLSA record lookup at `_<port>._<proto>.<host>` |\n\n### Threat intelligence\n\n| Tool | Description |\n|------|-------------|\n| `check_rbl` | IP reputation against 8 DNS-based RBLs (Spamhaus ZEN, SpamCop, UCEProtect L1/L2, Mailspike, PSBL, Barracuda, SORBS) |\n| `check_dbl` | Domain reputation against DNS-based Domain Block Lists (Spamhaus DBL, URIBL, SURBL) |\n| `cymru_asn` | ASN lookup via Team Cymru DNS service — BGP prefix, org, country |\n| `check_fast_flux` | Fast-flux detection — repeated A/AAAA queries to identify rotating IPs and short TTLs |\n| `detect_hijacking` | Test a recursive resolver for tampering — NXDOMAIN wildcards, DNSSEC handling, identity |\n\n### Registration\n\n| Tool | Description |\n|------|-------------|\n| `rdap_lookup` | Domain registration data via RDAP (modern WHOIS replacement) |\n\n### Observability\n\n| Tool | Description |\n|------|-------------|\n| `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`. |\n| `reset_stats` | Clear all tool-call statistics and restart the session clock. |\n\nDownstream consumers (e.g. `~/projects/yahoo` batch forensics) call\n`session_stats` as the final tool in each investigation to record which\nDNS tools were consulted; an empty stats dict indicates a \"cold read\"\nwhere the analyst LLM produced a verdict without DNS verification.\n\n## Tool descriptors\n\nAll 19 tools use Pydantic `Field` for parameter descriptors. The LLM sees:\n\n- **Per-parameter descriptions** explaining what the parameter means\n- **`Literal[...]` enums** for record types and protocols (no string-guessing)\n- **Regex patterns** validating FQDN syntax, IPv4 dotted-quad, DKIM selector format\n- **Length and range constraints** (port 1–65535, FQDN max 253 chars, etc.)\n\nConstraints are advertised in the tool descriptor JSON Schema and enforced at\nthe MCP boundary by FastMCP — invalid input is rejected before `dns_tool` is\ncalled. See `src/dns_mcp/server.py` for the type alias definitions.\n\n## Analyst Prompts\n\nFour analyst prompt templates ship with the server. Any MCP-compatible client\nthat supports prompts can list and invoke them.\n\n| Prompt | What it does |\n|--------|-------------|\n| `email_security_audit` | SPF, DKIM, DMARC, MTA-STS, BIMI — graded A through F with prioritized recommendations |\n| `dnssec_chain_audit` | Full DNSSEC chain-of-trust audit from IANA root down to target |\n| `soc_email_forensics` | Forensic phishing analysis of a raw email — TRUSTABLE / SUSPICIOUS / PHISHING / FURTHER ANALYSIS REQUIRED |\n| `nist_800_81r3_audit` | Domain security posture audit aligned with NIST SP 800-81r3 |\n\nPrompt invocation requires client-side UI support. Claude Code surfaces them\nas `/mcp__dns-mcp__<prompt_name>`. Claude.ai web exposes prompts via the\nslash-command picker. Use tools ad-hoc in clients that do not support prompts.\n\n## Example\n\nAsk your assistant: *\"Check the email security posture of example.com\"*\n\nThe assistant calls `check_spf`, `check_dmarc`, `check_dane` in sequence and\nreturns a complete analysis:\n\n```\n✅ SPF:     Hard fail (-all), 3 lookups (under RFC limit)\n✅ DMARC:   p=reject, pct=100 — full enforcement, aggregate reporting configured\n✅ DANE:    TLSA records present and DNSSEC-validated\n```\n\nNo copy-pasting dig commands. No tab-switching. One question.\n\n## Quick Start\n\n### Prerequisites\n\n- Docker\n- A Pocket ID instance (or any OIDC provider supporting Dynamic Client Registration)\n- A reverse proxy with TLS termination (Caddy, nginx, etc.)\n- A domain name pointing at your reverse proxy\n\n### 1. Pocket ID\n\nMint an admin API key in Pocket ID's UI: **Settings → API Keys → Create new\nkey**. Name it `dns-mcp` so you can revoke just this service if needed. Copy\nthe key value (it is shown once).\n\n### 2. .env\n\n```bash\nPOCKET_ID_BASE_URL=https://pocketid.example.com\nPOCKET_ID_API_KEY=<the key from step 1>\nSERVER_URL=https://dns-mcp.example.com\n```\n\n### 3. Deploy\n\n```bash\ngit clone https://github.com/mclose/dns-mcp.git\ncd dns-mcp\ndocker compose up -d\n```\n\nThe image installs `dns_tool` as a versioned dependency (URL-pinned in\n`pyproject.toml`); `make build` is also available for direct development.\n\n### 4. Reverse proxy\n\nThe container listens on port 8000 (HTTP). Front it with TLS termination:\n\n```caddy\ndns-mcp.example.com {\n    reverse_proxy dns-mcp:8000 {\n        flush_interval -1   # required for Streamable HTTP / SSE\n    }\n}\n```\n\nIf you use [`mclose/gateway`](https://github.com/mclose/gateway) (the Caddy +\nDNS-01 setup that serves dns-mcp.lab.deflationhollow.net), drop a\n`conf.d/dns-mcp.conf` matching the existing pattern.\n\n### 5. Connect\n\nAdd `https://dns-mcp.example.com/mcp` as a connector in your MCP client. The\nOAuth flow runs once on first connect — Claude.ai redirects to Pocket ID, you\nauthenticate, the server creates a DCR client on your Pocket ID instance, and\nreturns a JWT. Subsequent tool calls send that JWT as a bearer token; the\nserver verifies against Pocket ID JWKS.\n\n## Open work\n\nEleven tools from the 1.x stdio architecture are not yet ported into\n`dns_tool` and are therefore not registered in 2.0.0:\n\n- `check_caa` (with CNAME chain tracing and wildcard delegation detection)\n- `check_zone_transfer` (AXFR enumeration)\n- `check_bimi`, `check_mta_sts`, `check_smtp_tlsrpt`\n- `check_ct_logs` (Certificate Transparency log enumeration via crt.sh)\n- `timestamp_converter`, `reverse_dns`\n- `enumerate_dkim_selectors`, `dns_dig_style`, `dns_query_dot`\n\nReference implementations live in `server.py.legacy`. Each port involves\nmoving the function into the appropriate `dns_tool` module\n(`dns_tool.email`, `dns_tool.intel`, etc.), adding tests on the library\nside, and registering a one-line wrapper in `src/dns_mcp/server.py`.\n\n## File structure\n\n```\ndns-mcp/\n├── src/dns_mcp/\n│   ├── __init__.py\n│   ├── __main__.py            # entrypoint — create_server().run(transport=\"streamable-http\")\n│   ├── config.py              # pydantic-settings Settings class\n│   ├── auth.py                # JWKSTokenVerifier + JWTAccessToken\n│   └── server.py              # FastMCP app: OAuth routes + 16 tools + 4 prompts (~430 lines)\n├── prompts/                   # MCP analyst prompt text files\n├── tests/                     # pytest unit tests (legacy — pending rewrite)\n├── tools/                     # operator scripts (smoke tests, deploy helpers)\n├── server.py.legacy           # 1.x stdio server (5,095 lines), reference for 11 deferred tool ports\n├── compose.yaml\n├── Dockerfile\n├── pyproject.toml             # dns_tool URL-pinned to dist tarball\n└── Makefile                   # build/lint/import-check\n```\n\n## Day-to-day\n\n| Command | What it does |\n|---------|-------------|\n| `make build` | Rebuild the Docker image |\n| `make rebuild` | Full clean build, no cache |\n| `make lint` | `pre-commit run --all-files` (ruff check + format) |\n| `make import-check` | Build image, run `create_server()` inside, assert tools register |\n| `make shell` | Interactive shell inside the container |\n| `make deploy` | Push to GitHub + VPS post-receive hook |\n| `make logs` | Tail container logs |\n| `make status` | Container status |\n\n## Security\n\n- No shell execution — all DNS via `dns_tool` (dnspython internally), RDAP via\n  `requests`\n- OAuth bearer JWT verification against Pocket ID JWKS on every tool call\n- Pydantic `Field` constraints enforced at MCP boundary — invalid input\n  rejected before reaching `dns_tool`\n- Non-root container user (`claude`, uid 1000)\n- SPF recursion limit enforced (RFC 7208)\n- RDAP: 10s timeout, max 3 redirects\n\n## License\n\nMIT\n",
  "bytes": 11107,
  "sha": "e3cab0520b173766db387916150cb19d911661abf13d90e173da62f665ffee6b",
  "repo_slug": "mclose/dns-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_mclose_dns_mcp_eb218272/readme"
}