Passive external attack-surface mapping: CT subdomains, DNS, TLS, HTTP posture, RDAP/WHOIS, Shodan.
Passive external attack-surface mapping: CT subdomains, DNS, TLS, HTTP posture, RDAP/WHOIS, Shodan via MCP. STDIO or Streamable HTTP.
[!IMPORTANT] Authorized, defensive use only. Point this server only at assets you own or are explicitly authorized to assess. It performs passive, non-intrusive reconnaissance — it reads public records (Certificate Transparency logs, DNS, RDAP/WHOIS) and each target's own published surface (one TLS handshake and one HTTP GET per host). It does not port-scan, exploit, brute-force, fuzz, or probe for vulnerabilities; that capability is excluded from the surface by design, not gated behind a flag. Output is descriptive — what exists and what the security posture is — never an exploitation plan. Every outbound connection passes an SSRF guard that refuses private, loopback, link-local, and cloud-metadata targets.
Passive external attack-surface mapping (EASM) over Certificate Transparency logs, DNS, TLS, HTTP, and RDAP/WHOIS registries, with optional Shodan host intelligence. Discover subdomains, resolve DNS records, and inspect TLS and HTTP security posture across a domain's live hosts. Runs as a stdio process or a local Streamable HTTP server.
| Tool | Description |
|---|---|
attacksurface_map_domain | Flagship workflow. Maps a domain's external surface end to end: CT-log subdomain discovery → DNS liveness → (standard+) DNS records, TLS posture, HTTP headers/tech → optional RDAP/WHOIS → (thorough + key) per-IP Shodan enrichment. Returns a structured surface map and a defensive assessment of observable facts. |
attacksurface_enumerate_subdomains | Passive subdomain discovery from Certificate Transparency logs (crt.sh → Certspotter → TLS-SAN fallback chain), with DNS resolution to mark which names are live. Per-source provenance; no DNS brute-forcing. |
attacksurface_resolve_dns | Resolve and enumerate DNS records (A/AAAA/CNAME/MX/NS/TXT/CAA) for one or more hosts across multiple public resolvers, with optional reverse DNS (PTR). Per-resolver values surface propagation gaps. |
attacksurface_inspect_tls | Inspect TLS/SSL posture via a real read-only handshake: protocol, cipher, full certificate chain, SANs, validity window, days-to-expiry, issuer, validation status. Reports invalid/expired/self-signed certs instead of failing. |
attacksurface_probe_http | Passive HTTP(S) probe: one GET following redirects. Returns status, redirect chain, headers, a security-header audit (HSTS/CSP/X-Frame-Options/cookie flags/CORS reflection), and an evidence-bound technology fingerprint. |
attacksurface_lookup_registration | Registration and ownership lookup via RDAP (JSON; WHOIS fallback). A domain returns registrar, status, lifecycle events, nameservers, DNSSEC; an IP/CIDR returns netblock, allocation CIDRs, origin ASN, country. |
attacksurface_lookup_host | Infrastructure intelligence for a single IP (open ports, banners, software versions, ASN, geo) or a faceted internet-wide search, via Shodan. Requires SHODAN_API_KEY — returns a typed source_unavailable error when unset; the rest of the server is unaffected. |
attacksurface_recon_guidance | Offline synthesis over findings gathered so far. Returns a prioritized defensive review plan plus pre-filled follow-up calls (which certs to renew, which hosts to inspect, which software versions to check for CVEs against an external NVD/OSV server). No external calls. |
| Resource | Description |
|---|---|
attacksurface://surface/{domain} | Read-once snapshot of a domain's mapped external surface (subdomains, live hosts, per-host TLS/HTTP posture summary), equivalent to a standard-depth attacksurface_map_domain call. |
All resource data is also reachable via tools — tool-only clients lose nothing, since attacksurface_map_domain covers the same ground.
attacksurface_map_domain tooldepth control: quick = subdomains + liveness only; standard = + DNS records, TLS, and HTTP posture; thorough = + Shodan enrichment (when a key is present, otherwise skipped with a note)includeRegistration adds an RDAP/WHOIS lookup for the apex at standard+ depthPromise.allSettled — one failed source or unreachable host degrades to a note, never tanks the callATTACKSURFACE_MAX_SUBDOMAINS, default 200) with the cap disclosed when hitassessment block synthesizes only observable facts — expiring certs, missing HSTS/CSP, weak TLS versions, failed chain validation — never an exploitation pathattacksurface_enumerate_subdomains toolincludeUnresolved: false returns only live hostsattacksurface_resolve_dns tool8.8.8.8, 1.1.1.1, 9.9.9.9)blocked_resolver error; one failing host degrades to a per-host errorattacksurface_inspect_tls toolattacksurface_probe_http toolhttp(s):// URL per call; timeout defaults to 10000ms (1000–30000ms range)blocked_target errorattacksurface_lookup_registration tooltarget accepts a domain, IP, or CIDR; type (auto / domain / ip) forces interpretation or lets it auto-detectattacksurface_lookup_host toolmode: "host" (default) — a free single-IP lookup: open ports, service banners, software versions, hostnames, ASN, geomode: "search" — a faceted internet-wide query (optional facets) that consumes paid Shodan query creditsSHODAN_API_KEY; without it the tool returns a typed source_unavailable error and every other tool keeps workingno_data error rather than an empty resultattacksurface_recon_guidance toolnist-nvd-mcp-server) or OSV (osv-advisory-mcp-server) server for CVE contexttopic (triage / posture / coverage) shapes plan emphasis; output is a remediation/visibility plan, never an exploitation playbookattacksurface://surface/{domain} resourceapplication/json: subdomain count, live-host count, and a per-host TLS/HTTP posture summary — equivalent to a standard-depth attacksurface_map_domain callattacksurface_map_domain for the full setBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Attack-surface-specific:
ATTACKSURFACE_ALLOW_PRIVATE_TARGETS)Agent-friendly output:
source: crt.sh | certspotter | tls-san, source: rdap | whois) and per-source status so agents can assess completeness and trusterror fields and operational notes instead of failing the whole call; only malformed input throwssource_unavailable, blocked_target, all_sources_failed) and union output (kind: domain | ip) let callers branch on data, not string parsingAdd the following to your MCP client configuration file. Every tool except attacksurface_lookup_host works with no configuration — the keyless core boots on an empty environment.
{
"mcpServers": {
"attack-surface-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/attack-surface-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"attack-surface-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/attack-surface-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"attack-surface-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/attack-surface-mcp-server:latest"]
}
}
}
To enable Shodan host intelligence (attacksurface_lookup_host), add SHODAN_API_KEY to the env block.
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Refer to "your MCP client configuration file" generically — different clients use different config paths and this server isn't client-specific.
attacksurface_lookup_host, and a Certspotter API key to raise CT-fallback rate limits.git clone https://github.com/cyanheads/attack-surface-mcp-server.git
cd attack-surface-mcp-server
bun install
cp .env.example .env
# edit .env only if you want Shodan, a Certspotter key, or non-default behavior
All variables are optional — the server boots and delivers its keyless core with an empty environment.
| Variable | Description | Default |
|---|---|---|
SHODAN_API_KEY | Enables attacksurface_lookup_host. Absent → that tool returns source_unavailable; every other tool keeps working. | — |
CERTSPOTTER_API_KEY | Raises Certspotter rate limits for the CT-log subdomain fallback. Absent → free unauthenticated tier (rate-limited but functional). | — |
ATTACKSURFACE_DEFAULT_RESOLVERS | Comma-separated default DNS resolver IPs for attacksurface_resolve_dns. | 8.8.8.8,1.1.1.1,9.9.9.9 |
ATTACKSURFACE_HTTP_USER_AGENT | Default User-Agent for attacksurface_probe_http (overridable per call). | attack-surface-mcp-server/passive-recon (+https://github.com/cyanheads/attack-surface-mcp-server) |
ATTACKSURFACE_MAX_SUBDOMAINS | Cap on subdomains resolved during a map_domain run — bounds fan-out cost. | 200 |
ATTACKSURFACE_RDAP_BOOTSTRAP_URL | RDAP bootstrap base URL; override for a private/mirrored RDAP. | https://rdap.org |
ATTACKSURFACE_ALLOW_PRIVATE_TARGETS | Set true to disable the SSRF guard for internal-network assessment. Leave false on any public deployment — it is the safety boundary that keeps the server from being pointed at internal infrastructure. | false |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. The server declares stateless; a value set here overrides it. Ignored under stdio. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t attack-surface-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=http -p 3010:3010 attack-surface-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/attack-surface-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources and inits the six services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services | Domain service integrations (ct, dns, tls, http, registration, shodan). |
src/utils | SSRF guard and input validation. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsassertSafeDomain / assertSafeUrl / assertSafeResolverIp) before connectingIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/attack-surface-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-attack-surface-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/attack-surface-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/attack-surface-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.