Guides
AI agents (MCP)unreleased
subenum mcp is on main but not in v0.10.0 yet, so a release binary treats mcp as a domain. To use it now, build from main: go install github.com/TMHSDigital/subenum@main.
subenum mcp serves subdomain enumeration to AI agents and assistants over the
Model Context Protocol, on standard input and
output. The operator who starts the server decides everything that matters for
safety. The model chooses only what to scan within those limits.
| You decide, on the command line | The model decides, per call |
|---|---|
Which domains a live scan may target (-allow) |
A domain within the allow-list |
The resolver (-dns-server) and the lookup timeout (-timeout) |
The words to try, and the record types |
The query rate (-rate) |
A smaller max_queries, never a larger one |
The most names one call may test (-max-queries) and its time budget (-max-duration) |
For lab scans only: the scenario, a rate, and force |
Tool calls run one at a time, so a model cannot multiply the budget by calling
in parallel, and arguments the tools do not define (a resolver, say) are
refused rather than ignored. A client that sends a progressToken with a
scan or lab_scan call gets a progress notification about once a second
(312/1000 tested, 4 found), and can cancel the call at any time.
Tools
lab_scan scans lab.example in one of the lab scenarios,
compiled into the binary and answered by a DNS server on the loopback
interface. No traffic leaves the machine, so it is always offered and always
safe. It is the place for an agent to learn what wildcards, rate limits and a
failing resolver do to a scan. Its scenario argument describes each
scenario, from the opening comment of its zone file, so an agent can pick one
and explain it.
scan enumerates a real domain through the operator’s resolver. It is
offered only when -allow names at least one domain, and it refuses any domain
that is not an allowed one or a subdomain of one. Only allow domains you own or
have permission to test.
explain_stats explains a run-quality report from the command line: pass
the contents of a -stats file, or -format jsonl output, which ends with the
same summary. It says how far the results can be trusted, why, and what to
change next time.
lab_scan and scan return JSON with the names found and their records, how
every lookup ended, any notices, and the scan’s verdict. Both declare it as an
outputSchema, and send it as structuredContent as well as text, so a client
can read the report as data without parsing it:
| Verdict | Meaning for the agent |
|---|---|
complete |
A name missing from the results most likely does not exist. |
degraded |
Some names were not tested or got no answer, so a missing name may still exist. |
unreliable |
Too many lookups failed, or the scan stopped early with an error (see error): a missing name means nothing. |
not_run |
The scan stopped before testing any name, for example on a wildcard zone; error says why. |
When a verdict is not complete, the agent can act on it within the
operator’s limits: retry_inconclusive looks up the names that got no answer
once more at the end, auto_rate slows down while lookups fail instead of
giving up, exclude keeps names or branches (*.corp.example.com) out of the
scan, and depth (up to 3) enumerates again under the names found. The report
counts retried, recovered, rate_backoffs and excluded. Every one of
these stays within max_queries and -max-duration.
Running it
subenum mcp # lab_scan and explain_stats only
subenum mcp -allow example.com,example.org # adds scan, for these domains
| Flag | Default | Meaning |
|---|---|---|
-allow |
none | Comma-separated domains scan may enumerate, each with its subdomains |
-dns-server |
8.8.8.8:53 |
Resolver for live scans: ip:port, tls://host or https://host/path |
-rate |
20 | DNS queries per second for live scans |
-max-queries |
1000 | Most candidate names one call may test |
-max-duration |
2m | Longest one call may scan |
-timeout |
1000 | Live-scan lookup timeout in milliseconds |
-sources |
none | Passive sources (ct, wayback) a scan call may ask for |
-max-calls |
100 | Most scan calls one session may make; with -max-queries it caps the session’s live lookups |
Passive sources are off unless you enable them, because they send the domain
to third parties (crt.sh, the Wayback Machine). With -sources ct,wayback,
scan gains a sources argument: the names a source proposes join the words
after dedupe, count against max_queries, and are each verified by a lookup.
The report lists each source with the number of names it proposed, or the
error it hit; a failed source does not stop the scan.
The caps cannot be turned off: -rate, -max-queries, -max-duration,
-timeout and -max-calls must be positive. A session is one run of the
server: it ends when the client closes it. Calls the client sent before
closing standard input are still answered, but once the client stops
reading the replies, the running call is cancelled and queued ones never
start.
subenum mcp takes its settings from these flags only. The SUBENUM_*
environment variables and the config file that set defaults for scans do not
apply to it, so SUBENUM_DNS_SERVER does not change the agent’s resolver: pass
-dns-server.
A scan call without words uses the bundled list of 5,000 common labels, but
tests no more than -max-queries of them (1,000 by default), so its verdict is
degraded with the rest counted as skipped. Raise -max-queries to cover the
whole list, or have the agent pass a shorter words list.
In a container, keep standard input open with -i, since the protocol runs over
it:
docker run -i --rm ghcr.io/tmhsdigital/subenum:latest mcp -allow example.com
Most MCP clients take a command and its arguments. For example, in a client configuration file:
{
"mcpServers": {
"subenum": {
"command": "subenum",
"args": ["mcp", "-allow", "example.com", "-rate", "20"]
}
}
}
With Claude Code: claude mcp add subenum -- subenum mcp -allow example.com.
The server speaks MCP revisions 2025-06-18, 2025-03-26 and 2024-11-05, offers tools only, and writes nothing but protocol messages to standard output.