dev.dnsdoctor/dns-doctor
REMOTE · DNSDOCTOR.DEV · SCANNED AUG 3
Scan and fix email auth (SPF, DMARC, DKIM, MX, blacklists, expiry) + DNS checks. Validated fixes.
Available components
How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. How we score →
Endpoint Security77
- The endpoint's TLS certificate is valid, in date, and uses a strong key. View diagnostics → Pass
- No authorisation is required to call this server. Every tool declares its destructiveHint and none is destructive, so open access doesn't expose one. See how to fix → View diagnostics → Partial
- HTTPS is enforced; there's no plaintext access path. View diagnostics → Pass
- HSTS check failed: the Strict-Transport-Security header is absent. See how to fix → View diagnostics → Fail
- DNSSEC is configured correctly; the domain's records validate against the full chain to the root. View diagnostics → Pass
Transport & Reachability100
- Verified streamable-http transport via a live MCP handshake. View diagnostics → Pass
Schema Quality & AI Usability77
- 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 3883 tokens (~277/item across 14 items; 13 tools + 1 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management2
- Stability check failed: schema churn in the 8 days we've observed: 1 tool removals, 0 breaking changes, 0 auth/transport breaks, 10 additions. See how to fix → Fail
Tool Coverage100
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 100% of tool parameters carry a description.Pass
- Structured output schemas are declared (100% of tools); any adoption earns full credit.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
Add this component to your MCP client. Where a client-specific snippet is available, pick your client below and copy it straight into your config; otherwise use the connection detail shown.
remote · dnsdoctor.dev
claude mcp add --transport http dev-dnsdoctor-dns-doctor https://dnsdoctor.dev/mcp
[mcp_servers.dev-dnsdoctor-dns-doctor] url = "https://dnsdoctor.dev/mcp"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"dev-dnsdoctor-dns-doctor": {
"type": "remote",
"url": "https://dnsdoctor.dev/mcp",
"enabled": true
}
}
} openclaw mcp add dev-dnsdoctor-dns-doctor --url https://dnsdoctor.dev/mcp --transport streamable-http
mcp_servers:
dev-dnsdoctor-dns-doctor:
url: "https://dnsdoctor.dev/mcp" {
"mcpServers": {
"dev-dnsdoctor-dns-doctor": {
"type": "http",
"url": "https://dnsdoctor.dev/mcp"
}
}
} The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.
Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 3 Aug 26 0
- The server rewrote its instructions, which are the text every model session reads security
- Tool “generate_dmarc_record” rewrote its description, which is the text the model reads security
- Tool “build_dmarc_upgrade” rewrote its description, which is the text the model reads security
- Schema quality: 242 → 277 ▼ functional
- Server version: 1.2.1 → 1.3.0 functional
- New tool “build_parked_domain_records” functional
- New tool “audit_spf_includes” functional
- 31 Jul 26 +3
- We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
- 30 Jul 26 0
- We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
- 29 Jul 26 +3
- The server rewrote its instructions, which are the text every model session reads security
- Tool “count_spf_lookups” rewrote its description, which is the text the model reads security
- Tool “get_report” rewrote its description, which is the text the model reads security
- Tool “scan_domain” rewrote its description, which is the text the model reads security
- Schema quality: 150 → 242 ▼ functional
- Schema quality: 150 → 178 ▼ functional
- Tool coverage: 0% → 100% ▲ functional
- Server version: 1.2.0 → 1.2.1 functional
- Server version: 1.1.0 → 1.2.0 functional
- “generate_dmarc_record” reworded the description of “policy” cosmetic
- “build_dmarc_upgrade” reworded the description of “domain” cosmetic
- “check_dkim_selector” reworded the description of “domain” cosmetic
- “check_dkim_selector” reworded the description of “selector” cosmetic
- “check_record” reworded the description of “domain” cosmetic
- “check_record” reworded the description of “host” cosmetic
- “check_record” reworded the description of “kind” cosmetic
- “check_reverse_dns” reworded the description of “ip” cosmetic
- “count_spf_lookups” reworded the description of “domain” cosmetic
- “count_spf_lookups” reworded the description of “record” cosmetic
- “generate_dmarc_record” reworded the description of “rua_email” cosmetic
- “generate_dmarc_record” reworded the description of “strict_alignment” cosmetic
- “generate_dmarc_record” reworded the description of “subdomain_policy” cosmetic
- “get_report” reworded the description of “domain” cosmetic
- “parse_dmarc_report” reworded the description of “content_base64” cosmetic
- “parse_dmarc_report” reworded the description of “filename” cosmetic
- “scan_domain” reworded the description of “domain” cosmetic
- “start_monitoring_signup” reworded the description of “domain” cosmetic
- “validate_dmarc_record” reworded the description of “record” cosmetic
- 28 Jul 26 0
- Server version: 1.28.1 → 1.1.0 functional
- 27 Jul 26 +5
- Stability: unverified → fail ▼ security
- A breaking change shipped without a version bump: still 1.28.1 ▼ security
- Tool “enroll_monitoring_trial” was removed ▼ security
- Authorization: fail → partial ▲ security
- The server rewrote its instructions, which are the text every model session reads security
- Schema quality: 172 → 150 ▲ functional
- New tool “check_record” functional
- New tool “parse_dmarc_report” functional
- New tool “start_monitoring_signup” functional
- New tool “validate_dmarc_record” functional
- New tool “check_dkim_selector” functional
- New tool “generate_dmarc_record” functional
- New tool “count_spf_lookups” functional
- New tool “check_reverse_dns” functional
- 26 Jul 26 61
First indexed and scored.
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 3 Aug 2026 · Probed https://dnsdoctor.dev/mcp
TLS valid
Negotiated TLS 1.3 with TLS_AES_128_GCM_SHA256 .
| Subject | Issuer | Valid from | Valid until | Key | Signature | Serial |
|---|---|---|---|---|---|---|
| CN=dnsdoctor.dev | CN=WE1,O=Google Trust Services,C=US | 2 Jul 2026 | 30 Sept 2026 | ECDSA 256 | ECDSA-SHA256 | 77cc5219fc0f9217135336c063b97e2e |
| SANs: dnsdoctor.dev, *.dnsdoctor.dev | ||||||
| CN=WE1,O=Google Trust Services,C=US (CA) | CN=GTS Root R4,O=Google Trust Services LLC,C=US | 13 Dec 2023 | 20 Feb 2029 | ECDSA 256 | ECDSA-SHA384 | 7ff31977972c224a76155d13b6d685e3 |
| CN=GTS Root R4,O=Google Trust Services LLC,C=US (CA) | CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE | 15 Nov 2023 | 28 Jan 2028 | ECDSA 384 | SHA256-RSA | 7fe530bf331343bedd821610493d8a1b |
DNSSEC secure
Validation of dnsdoctor.dev. — Secure
| Zone | DS | Keys | Algorithms | Outcome |
|---|---|---|---|---|
| . | trust_anchor | 20326, 38696 | 8, 8 | Verified |
| dev. | present | 60074 | 8 | Verified |
| dnsdoctor.dev. | present | 2371 | 13 | Verified |
| dnsdoctor.dev. | Verified address RRset verified with the apex keys |
Authentication No authorisation required
The endpoint answered without asking for a token. Anyone who knows the URL can reach it.
| Result | No authorisation required |
|---|---|
| HTTP status | 200 |
Transports 2 probes
| Transport | URL | Outcome | Status | Location |
|---|---|---|---|---|
| streamable-http | https://dnsdoctor.dev/mcp | Verified | 200 | |
| http (plaintext) | http://dnsdoctor.dev/mcp | HTTPS enforced | 301 | https://dnsdoctor.dev/mcp |
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability.
audit_spf_includes ~359
Audit a domain's SPF supply chain: walks every include and redirect it delegates to, and reports who can transitively send as it. Returns the resolved tree, per-node lookup attribution, the total authorized IPv4 address count, and typed findings — include_broken (a target that no longer publishes SPF, a PermError today), include_registrable (a delegated-to domain that does not exist, so a stranger who registers it becomes an authorized sender), include_expiring (registration lapsing within 30 days), pass_all_nested (a +all deep in the chain) and spf_record_unusable (the audited domain's OWN record is missing or does not parse, so there is no chain to walk). A domain we could not verify is reported as unverified and NEVER as available — never tell anyone a name is free on this tool's say-so unless the finding is include_registrable AND carries registry_confirmed: true. A registry_confirmed: false finding rests on DNS alone, which cannot tell an unsold name from one in redemption or on clientHold: report the mechanism as broken and the takeover risk as possible, but never as an available domain. Findings are risk analysis, not instructions: no SPF fix record exists here or anywhere else in DNS Doctor, because dropping a mechanism can silently de-authorize a real sender — relay the findings and let the domain's owner decide. Use count_spf_lookups instead when the question is only the 10-lookup limit.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
Structured output declared, but exposes no named fields.
No examples provided.
build_dmarc_upgrade ~231
Return a validated DMARC enforcement-upgrade record for a domain. The alignment gate (whether p=reject is safe) is derived server-side from a scan — a caller can never assert alignment. `record` is null when there is no honest upgrade to offer (the domain does not exist; the DMARC lookup itself hit NXDOMAIN while the existence probe did not resolve; the DMARC lookup temp-failed; or the domain already applies a policy at least as strong as this scan justifies): relay `rationale` and never compose a record to fill the gap. A returned record also carries np=reject (the DMARCbis tag covering non-existent subdomains, which can have no legitimate aligned mail) unless the domain already publishes an np tag, which is preserved as-is. Present a returned record verbatim; a human must approve before publishing.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
Structured output declared, but exposes no named fields.
No examples provided.
build_parked_domain_records ~381
Build the three-record hardening pack that makes a NON-SENDING domain unusable for spoofing: a Null MX, a hard-fail SPF record, and a p=reject; np=reject DMARC record. For parked, redirect and brand-defensive domains only — NEVER for a domain that sends any mail, including transactional or one legacy system. Do NOT set confirm_no_mail on your own judgment or because a scan looked quiet: only the human who owns the domain can confirm it sends nothing, so ask them first. That flag unlocks the question, not the answer — the server re-checks DNS itself (existence, MX, SPF, DKIM selectors) and returns records: null with a rationale when it finds evidence of mail; relay that rationale rather than retrying. A lookup failure is reported as a failure, never as a pack. Publishing is the human's decision: present the records verbatim, in the order given, and let them approve each one.
| Name | Type | Req | Description |
|---|---|---|---|
| confirm_no_mail | boolean | yes | Must be true, and only the HUMAN who owns the domain may decide it: it records their confirmation that this domain sends no email at all. Never set it on your own judgment or because a scan looked qu… |
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
| rua_email | — | — | Mailbox to receive DMARC aggregate (RUA) reports, as a plain address like [email protected]. Strongly recommended: without it nobody can see who sends as the domain. |
Structured output declared, but exposes no named fields.
No examples provided.
check_dkim_selector ~204
Check ONE specific DKIM selector on a domain — the exact selector the sending platform uses (e.g. `google`, `s1`), which a full scan's common-selector sweep may miss. Returns the verdict, its explanation, and the published key record when one resolves. No fix record is returned: a DKIM key is generated by the sending platform, so the fix is always to publish what that platform gives the owner.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
| selector | string | yes | The DKIM selector to probe — the name before ._domainkey, e.g. 'google', 'selector1', or a dotted form like 's1.prod'. The sending platform's settings page names it; it is not guessable from the doma… |
Structured output declared, but exposes no named fields.
No examples provided.
check_record ~337
Check whether a DNS change has landed: reads the record from the domain's OWN nameservers (cache-free) and from two public caching resolvers, and reports whether they agree. `kind` is one of spf|dmarc|txt|mx|cname|a|aaaa — pass the kind, not a query name: `dmarc` reads TXT at _dmarc.<domain> and `spf` reads apex TXT, each filtered to the matching record. `host` prepends a label (txt, cname, a and aaaa only). Empty values mean the record is genuinely absent. When in_sync is false, max_wait_seconds is the largest remaining cached TTL — the wait before those resolvers refresh. This samples two resolvers, so never describe it as worldwide or as propagation coverage.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
| host | — | — | Optional label to prepend to the domain (e.g. 'mail' to check mail.<domain>) — honored for txt, cname, a and aaaa only; spf, dmarc and mx derive their own query name. |
| kind | string | yes | Which record to read; the right query is derived from it — 'dmarc' reads TXT at _dmarc.<domain> filtered to v=DMARC1, 'spf' reads the apex TXT filtered to v=spf1, so don't prefix the domain yourself. |
Structured output declared, but exposes no named fields.
No examples provided.
check_reverse_dns ~197
Check one sending IP's forward-confirmed reverse DNS (FCrDNS): reads the IP's PTR record, then resolves that hostname back and reports whether it returns to the same IP. `verdict` is confirmed (the pair agrees — what receivers want to see), ptr_missing (the IP publishes no reverse record), or mismatch (a PTR that does not resolve back). A PTR on its own proves nothing, because the IP's operator writes its own reverse zone — only the forward confirmation is evidence, so never report a bare PTR as verified. The fix is always made by whoever controls the IP (the hosting or mail provider), never in the sending domain's own DNS. Pass a public IPv4 or IPv6 address.
| Name | Type | Req | Description |
|---|---|---|---|
| ip | string | yes | The sending IP to check, IPv4 or IPv6. Must be a public address — private, loopback and CGNAT ranges have no meaningful reverse DNS and are refused. |
Structured output declared, but exposes no named fields.
No examples provided.
count_spf_lookups ~300
Validate an SPF record and count what it costs. Returns `record_valid` (the record parses as RFC 7208 SPF), `findings` (per-term diagnostics), `has_pass_all` (a `+all` that authorizes the whole internet to send as this domain), `multiple_all` (more than one `all`, which makes everything after the first unreachable), the parsed `terms`, and the lookup count against the limit of 10 with `over_limit`/`near_limit` and the `offending_mechanisms` that push it over. Pass EXACTLY ONE of `domain` (resolves the published record and counts recursively through nested includes) or `record` (parses a pasted record, its own terms only). This is the SPF validator — there is no separate one. Diagnose-only: no SPF fix record is ever returned, because removing a mechanism can silently de-authorize a real sender — relay the findings and let the domain's owner decide what to drop.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | — | — | Domain whose PUBLISHED SPF record should be resolved and counted recursively (nested includes cost lookups too). Pass exactly one of domain or record, never both. |
| record | — | — | A pasted SPF record to parse instead of resolving one, e.g. 'v=spf1 include:_spf.google.com ~all'. Counts this record's own terms only. Pass exactly one of domain or record, never both. |
Structured output declared, but exposes no named fields.
No examples provided.
generate_dmarc_record ~297
Build a DMARC record from scratch for a domain that has none, using a validating engine — never compose one yourself. `policy` is none|quarantine|reject; optional `rua_email` (aggregate-report destination), `subdomain_policy`, and `strict_alignment`. Every generated record carries np=reject — the DMARCbis tag for non-existent subdomains, which can have no legitimate aligned mail — independently of the p= you choose. The generated record is re-validated before it is returned. Present it verbatim; a human must approve before publishing.
| Name | Type | Req | Description |
|---|---|---|---|
| policy | string | yes | The requested p= policy: 'none' monitors only, 'quarantine' sends failing mail to spam, 'reject' refuses it outright. Start at 'none' unless the domain's aggregate reports already justify enforcement. |
| rua_email | — | — | Mailbox to receive DMARC aggregate (RUA) reports, as a plain address like [email protected]. Strongly recommended: without it nobody can see who sends as the domain. |
| strict_alignment | boolean | — | Set true to emit strict alignment (aspf=s adkim=s), requiring an exact domain match instead of the organizational-domain match. Leave false unless you know every sender aligns strictly. |
| subdomain_policy | — | — | Optional sp= policy for subdomains when it should differ from the main p= policy. Omit to let subdomains inherit p=. |
Structured output declared, but exposes no named fields.
No examples provided.
get_report ~167
Return the stored report for a domain, scanning once only if none exists yet — the cheap read, and the right default for a first look. Returns the same seven-check report as scan_domain (SPF, DKIM, DMARC, MX, DNS hardening, domain/TLS expiry, blacklist; each with a status, the observed record and any fixengine fix_record), including `scanned_at` so you can judge staleness yourself. Prefer scan_domain when you specifically need state re-read right now — for example after a DNS change.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
Structured output declared, but exposes no named fields.
No examples provided.
parse_dmarc_report ~182
Parse ONE DMARC aggregate (RUA) report into readable per-source aggregates: who sent mail as the domain, how much, and what share was SPF/DKIM aligned. Pass the file's bytes base64-encoded in `content_base64` (XML, .gz or .zip; up to 2 MiB decoded) with an optional `filename`. Nothing is stored — the report is parsed and discarded.
| Name | Type | Req | Description |
|---|---|---|---|
| content_base64 | string | yes | One DMARC aggregate (RUA) report file, base64-encoded: the .xml, .xml.gz or .zip attachment exactly as received, up to 2 MiB decoded. Encode the file bytes — do not paste raw XML here. |
| filename | — | — | Optional original attachment filename, recorded in logs only — format detection is content-based, so this changes nothing about parsing. |
Structured output declared, but exposes no named fields.
No examples provided.
scan_domain ~167
Force a fresh scan of a domain and return its full report: seven deterministic checks — SPF, DKIM, DMARC, MX, DNS hardening, domain/TLS expiry and blacklist listing — each with a status (pass/warn/fail/info/temperror), the observed record, and a fixengine-generated fix_record where one exists. Explanations are cache-first. A repeat scan of the same domain within a minute reuses the stored report. Use this when you need current state; use get_report when a recent cached answer will do.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
Structured output declared, but exposes no named fields.
No examples provided.
start_monitoring_signup ~162
Return a sign-up link to give to the HUMAN who owns a domain, so they can start monitoring it themselves. Nothing is created and no email is sent by this call: the returned signup_url opens a page that explains monitoring and asks them to sign in with their own email address; the domain is carried over to their dashboard, already filled in, only after that, and monitoring itself starts once they prove ownership with a TXT record. Hand them the URL and relay `message` — never sign in on their behalf.
| Name | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode. |
Structured output declared, but exposes no named fields.
No examples provided.
validate_dmarc_record ~142
Validate a pasted DMARC record: parsed tags, level'd findings, and whether it is valid. No DNS lookup — pass the record string itself. `upgrade_record` previews a stronger policy and is capped at p=quarantine: a pasted record carries no alignment evidence, so p=reject needs a full scan (use build_dmarc_upgrade). Present any returned record verbatim.
| Name | Type | Req | Description |
|---|---|---|---|
| record | string | yes | The DMARC record text to validate, e.g. 'v=DMARC1; p=none; rua=mailto:[email protected]'. The record value only — not the _dmarc hostname it is published at. |
Structured output declared, but exposes no named fields.
No examples provided.