@cyanheads/whois-mcp-server 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +349 -0
  2. package/CLAUDE.md +349 -0
  3. package/Dockerfile +99 -0
  4. package/LICENSE +201 -0
  5. package/README.md +316 -0
  6. package/changelog/0.1.x/0.1.1.md +32 -0
  7. package/changelog/template.md +127 -0
  8. package/dist/config/server-config.d.ts +16 -0
  9. package/dist/config/server-config.d.ts.map +1 -0
  10. package/dist/config/server-config.js +30 -0
  11. package/dist/config/server-config.js.map +1 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +38 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/mcp-server/tools/definitions/_fqdn.d.ts +7 -0
  17. package/dist/mcp-server/tools/definitions/_fqdn.d.ts.map +1 -0
  18. package/dist/mcp-server/tools/definitions/_fqdn.js +18 -0
  19. package/dist/mcp-server/tools/definitions/_fqdn.js.map +1 -0
  20. package/dist/mcp-server/tools/definitions/whois-check-availability.tool.d.ts +26 -0
  21. package/dist/mcp-server/tools/definitions/whois-check-availability.tool.d.ts.map +1 -0
  22. package/dist/mcp-server/tools/definitions/whois-check-availability.tool.js +102 -0
  23. package/dist/mcp-server/tools/definitions/whois-check-availability.tool.js.map +1 -0
  24. package/dist/mcp-server/tools/definitions/whois-get-dns.tool.d.ts +49 -0
  25. package/dist/mcp-server/tools/definitions/whois-get-dns.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/whois-get-dns.tool.js +97 -0
  27. package/dist/mcp-server/tools/definitions/whois-get-dns.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/whois-get-dossier.tool.d.ts +41 -0
  29. package/dist/mcp-server/tools/definitions/whois-get-dossier.tool.d.ts.map +1 -0
  30. package/dist/mcp-server/tools/definitions/whois-get-dossier.tool.js +332 -0
  31. package/dist/mcp-server/tools/definitions/whois-get-dossier.tool.js.map +1 -0
  32. package/dist/mcp-server/tools/definitions/whois-lookup-asn.tool.d.ts +30 -0
  33. package/dist/mcp-server/tools/definitions/whois-lookup-asn.tool.d.ts.map +1 -0
  34. package/dist/mcp-server/tools/definitions/whois-lookup-asn.tool.js +83 -0
  35. package/dist/mcp-server/tools/definitions/whois-lookup-asn.tool.js.map +1 -0
  36. package/dist/mcp-server/tools/definitions/whois-lookup-domain.tool.d.ts +40 -0
  37. package/dist/mcp-server/tools/definitions/whois-lookup-domain.tool.d.ts.map +1 -0
  38. package/dist/mcp-server/tools/definitions/whois-lookup-domain.tool.js +124 -0
  39. package/dist/mcp-server/tools/definitions/whois-lookup-domain.tool.js.map +1 -0
  40. package/dist/mcp-server/tools/definitions/whois-lookup-ip.tool.d.ts +39 -0
  41. package/dist/mcp-server/tools/definitions/whois-lookup-ip.tool.d.ts.map +1 -0
  42. package/dist/mcp-server/tools/definitions/whois-lookup-ip.tool.js +111 -0
  43. package/dist/mcp-server/tools/definitions/whois-lookup-ip.tool.js.map +1 -0
  44. package/dist/services/doh/doh-service.d.ts +30 -0
  45. package/dist/services/doh/doh-service.d.ts.map +1 -0
  46. package/dist/services/doh/doh-service.js +114 -0
  47. package/dist/services/doh/doh-service.js.map +1 -0
  48. package/dist/services/doh/types.d.ts +45 -0
  49. package/dist/services/doh/types.d.ts.map +1 -0
  50. package/dist/services/doh/types.js +17 -0
  51. package/dist/services/doh/types.js.map +1 -0
  52. package/dist/services/rdap/rdap-service.d.ts +54 -0
  53. package/dist/services/rdap/rdap-service.d.ts.map +1 -0
  54. package/dist/services/rdap/rdap-service.js +609 -0
  55. package/dist/services/rdap/rdap-service.js.map +1 -0
  56. package/dist/services/rdap/types.d.ts +140 -0
  57. package/dist/services/rdap/types.d.ts.map +1 -0
  58. package/dist/services/rdap/types.js +6 -0
  59. package/dist/services/rdap/types.js.map +1 -0
  60. package/package.json +103 -0
  61. package/server.json +161 -0
package/README.md ADDED
@@ -0,0 +1,316 @@
1
+ <div align="center">
2
+ <h1>@cyanheads/whois-mcp-server</h1>
3
+ <p><b>Look up domain registration, check availability, fetch DNS records, and resolve IPs and ASNs via RDAP and DNS-over-HTTPS via MCP. STDIO or Streamable HTTP.</b>
4
+ <div>6 Tools</div>
5
+ </p>
6
+ </div>
7
+
8
+ <div align="center">
9
+
10
+ [![Version](https://img.shields.io/badge/Version-0.1.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/whois-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/whois-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/whois-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
+
12
+ </div>
13
+
14
+ <div align="center">
15
+
16
+ [![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/whois-mcp-server/releases/latest/download/whois-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=whois-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvd2hvaXMtbWNwLXNlcnZlciJdfQ==) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22whois-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fwhois-mcp-server%22%5D%7D)
17
+
18
+ [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
19
+
20
+ </div>
21
+
22
+ ---
23
+
24
+ ## Tools
25
+
26
+ Six tools covering domain intelligence, DNS, and IP/ASN resolution:
27
+
28
+ | Tool | Description |
29
+ |:-----|:------------|
30
+ | `whois_lookup_domain` | Full domain registration record — registrar, created/expiry dates, nameservers, EPP status, DNSSEC, registrant org |
31
+ | `whois_check_availability` | Check whether a domain is registered or available for registration |
32
+ | `whois_get_dns` | DNS records for any hostname via DNS-over-HTTPS (A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR) |
33
+ | `whois_lookup_ip` | IP or CIDR netblock, org, country, abuse contact, and reverse DNS via RIR RDAP |
34
+ | `whois_lookup_asn` | Resolve an ASN to its org name, country, and RIR source |
35
+ | `whois_get_dossier` | One-call domain triage — registration + DNS in parallel, normalized into a single record with factual signals |
36
+
37
+ ### `whois_lookup_domain`
38
+
39
+ Look up a domain's full RDAP registration record.
40
+
41
+ - RDAP-first via IANA auto-bootstrap — automatically selects the correct registry RDAP server per TLD
42
+ - Returns registrar, creation/expiry dates, nameservers, EPP status codes, DNSSEC delegation flag
43
+ - Surfaces `registrant_redacted: true` explicitly when privacy redaction is in effect (standard post-GDPR for gTLDs)
44
+ - Returns `rdap_coverage: false` for TLDs without RDAP coverage rather than silently failing
45
+ - Includes `last_update_of_rdap_db` event timestamp for data freshness transparency
46
+
47
+ ---
48
+
49
+ ### `whois_check_availability`
50
+
51
+ Check whether a domain name is registered or available to register.
52
+
53
+ - RDAP 404 = available (`available: true`) — exploits the RDAP spec's intended behavior
54
+ - Returns `available: false` with `registrar` and `expiry_date` when registered
55
+ - Returns `available: null` with `rdap_coverage: false` for TLDs without RDAP — cannot determine availability
56
+ - Optimized for bulk name sweeps — thin response, no unnecessary fields
57
+
58
+ ---
59
+
60
+ ### `whois_get_dns`
61
+
62
+ Fetch DNS records via DNS-over-HTTPS.
63
+
64
+ - Cloudflare `1.1.1.1` primary, Google `8.8.8.8` fallback (used for CAA records where Cloudflare returns raw hex)
65
+ - Supports A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR — multiple types in one call
66
+ - Returns records with TTLs and the resolving source (`cloudflare` or `google`)
67
+ - `nxdomain: true` in result (not an error) when the domain doesn't exist in DNS
68
+
69
+ ---
70
+
71
+ ### `whois_lookup_ip`
72
+
73
+ Look up an IP address or CIDR block via RIR RDAP.
74
+
75
+ - Auto-routes to the correct RIR (ARIN, RIPE, APNIC, LACNIC, AFRINIC) via IANA IP bootstrap
76
+ - Returns netblock CIDR, org name, country, abuse contact email
77
+ - Fetches PTR (reverse DNS) via DoH as a best-effort step — `ptr: null` on failure, not an error
78
+ - Validates and rejects private/reserved ranges (RFC 1918, loopback, link-local) with a clear error
79
+
80
+ ---
81
+
82
+ ### `whois_lookup_asn`
83
+
84
+ Resolve an ASN to its org name, country, and RIR.
85
+
86
+ - Accepts `AS15169` or bare integer `15169` format
87
+ - Routes to the correct RIR RDAP endpoint via IANA ASN bootstrap
88
+ - Returns `name`, `org`, `country`, `rir`, `start_autnum`, `end_autnum`
89
+
90
+ ---
91
+
92
+ ### `whois_get_dossier`
93
+
94
+ One-call domain triage aggregating registration and DNS data in parallel.
95
+
96
+ - Runs RDAP domain lookup and DoH (A, MX, NS, TXT) in parallel via `Promise.allSettled`
97
+ - Inferred signals: `age_days`, `privacy_redacted`, `registrar`, `ns_provider` (from NS records), `mx_provider` (from MX records)
98
+ - No synthesized risk scores — factual signals only; the agent decides the verdict
99
+ - Partial results surfaced when one leg fails (`source_error` on the failed leg)
100
+ - Both-legs-fail throws `ServiceUnavailable`; individual leg failures are data, not errors
101
+
102
+ ---
103
+
104
+ ## Features
105
+
106
+ Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
107
+
108
+ - Declarative tool definitions — single file per tool, framework handles registration and validation
109
+ - Unified error handling — handlers throw, framework catches, classifies, and formats
110
+ - Pluggable auth: `none`, `jwt`, `oauth`
111
+ - Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
112
+ - Structured logging with optional OpenTelemetry tracing
113
+ - STDIO and Streamable HTTP transports
114
+
115
+ Domain and network intelligence:
116
+
117
+ - RDAP over HTTPS — no port-43 TCP dependency, runs on Node, Bun, and Cloudflare Workers
118
+ - IANA bootstrap auto-selection — correct registry RDAP server picked per TLD, RIR, or ASN range; bootstrap JSON cached (TTL 24h) in tenant state
119
+ - DNS-over-HTTPS via Cloudflare and Google — resilient dual-provider with per-type routing (Google for CAA; Cloudflare for all others)
120
+ - No API keys required — all sources (IANA, registry RDAP endpoints, RIR RDAP, Cloudflare DoH, Google DoH) are public and keyless
121
+
122
+ Agent-friendly output:
123
+
124
+ - Explicit coverage signals — `rdap_coverage: false` tells the agent the TLD lacks RDAP rather than returning a confusing error
125
+ - Privacy redaction surfaced as a field — `registrant_redacted: true` rather than silently absent contact data
126
+ - Partial failure model — `whois_get_dossier` marks individual legs with `source_error` and continues; only both-legs-fail escalates to an error
127
+ - Factual signals, not scores — `age_days`, `privacy_redacted`, `ns_provider`, `mx_provider` are real data; agents chain into threat-intel or risk servers for enrichment
128
+
129
+ ---
130
+
131
+ ## Getting started
132
+
133
+ No API keys or accounts required. Add the following to your MCP client configuration file.
134
+
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "whois-mcp-server": {
139
+ "type": "stdio",
140
+ "command": "bunx",
141
+ "args": ["@cyanheads/whois-mcp-server@latest"],
142
+ "env": {
143
+ "MCP_TRANSPORT_TYPE": "stdio",
144
+ "MCP_LOG_LEVEL": "info"
145
+ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ Or with npx (no Bun required):
152
+
153
+ ```json
154
+ {
155
+ "mcpServers": {
156
+ "whois-mcp-server": {
157
+ "type": "stdio",
158
+ "command": "npx",
159
+ "args": ["-y", "@cyanheads/whois-mcp-server@latest"],
160
+ "env": {
161
+ "MCP_TRANSPORT_TYPE": "stdio",
162
+ "MCP_LOG_LEVEL": "info"
163
+ }
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ Or with Docker:
170
+
171
+ ```json
172
+ {
173
+ "mcpServers": {
174
+ "whois-mcp-server": {
175
+ "type": "stdio",
176
+ "command": "docker",
177
+ "args": [
178
+ "run", "-i", "--rm",
179
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
180
+ "ghcr.io/cyanheads/whois-mcp-server:latest"
181
+ ]
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ For Streamable HTTP, set the transport and start the server:
188
+
189
+ ```sh
190
+ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
191
+ # Server listens at http://localhost:3010/mcp
192
+ ```
193
+
194
+ ### Prerequisites
195
+
196
+ - [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).
197
+ - No API keys required — all data sources are public.
198
+
199
+ ### Installation
200
+
201
+ 1. **Clone the repository:**
202
+
203
+ ```sh
204
+ git clone https://github.com/cyanheads/whois-mcp-server.git
205
+ ```
206
+
207
+ 2. **Navigate into the directory:**
208
+
209
+ ```sh
210
+ cd whois-mcp-server
211
+ ```
212
+
213
+ 3. **Install dependencies:**
214
+
215
+ ```sh
216
+ bun install
217
+ ```
218
+
219
+ 4. **Configure environment:**
220
+
221
+ ```sh
222
+ cp .env.example .env
223
+ # All vars are optional — defaults work for most use cases
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Configuration
229
+
230
+ | Variable | Description | Default |
231
+ |:---------|:------------|:--------|
232
+ | `RDAP_TIMEOUT_MS` | HTTP timeout for RDAP requests in milliseconds. | `5000` |
233
+ | `DOH_TIMEOUT_MS` | HTTP timeout for DNS-over-HTTPS requests in milliseconds. | `3000` |
234
+ | `RDAP_MAX_RETRIES` | Max retry attempts on transient RDAP failures. | `2` |
235
+ | `DOH_MAX_RETRIES` | Max retry attempts on transient DoH failures. | `2` |
236
+ | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
237
+ | `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
238
+ | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
239
+ | `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
240
+ | `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
241
+
242
+ See [`.env.example`](./.env.example) for the full list of optional overrides.
243
+
244
+ ---
245
+
246
+ ## Running the server
247
+
248
+ ### Local development
249
+
250
+ - **Build and run:**
251
+
252
+ ```sh
253
+ bun run rebuild
254
+ bun run start:stdio
255
+ # or
256
+ bun run start:http
257
+ ```
258
+
259
+ - **Run checks and tests:**
260
+
261
+ ```sh
262
+ bun run devcheck # Lint, format, typecheck, security
263
+ bun run test # Vitest test suite
264
+ bun run lint:mcp # Validate MCP definitions against spec
265
+ ```
266
+
267
+ ### Docker
268
+
269
+ ```sh
270
+ docker build -t whois-mcp-server .
271
+ docker run --rm -p 3010:3010 whois-mcp-server
272
+ ```
273
+
274
+ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/whois-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
275
+
276
+ ---
277
+
278
+ ## Project structure
279
+
280
+ | Path | Purpose |
281
+ |:-----|:--------|
282
+ | `src/index.ts` | `createApp()` entry point — registers tools and inits services. |
283
+ | `src/config/` | Server-specific environment variable parsing and validation (Zod). |
284
+ | `src/services/rdap/` | RDAP client — IANA bootstrap cache, domain/IP/ASN lookup, retry. |
285
+ | `src/services/doh/` | DNS-over-HTTPS client — Cloudflare primary, Google fallback. |
286
+ | `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`). |
287
+ | `tests/` | Vitest tests mirroring `src/`. |
288
+ | `docs/` | Design and API reference documents. |
289
+
290
+ ---
291
+
292
+ ## Development guide
293
+
294
+ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
295
+
296
+ - Handlers throw, framework catches — no `try/catch` in tool logic
297
+ - Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage (IANA bootstrap cache)
298
+ - Register new tools via `src/index.ts` tools array
299
+ - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
300
+
301
+ ---
302
+
303
+ ## Contributing
304
+
305
+ Issues and pull requests are welcome. Run checks and tests before submitting:
306
+
307
+ ```sh
308
+ bun run devcheck
309
+ bun run test
310
+ ```
311
+
312
+ ---
313
+
314
+ ## License
315
+
316
+ Apache-2.0 — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "Initial public release — 6 RDAP + DoH tools for domain registration, availability, DNS records, IP netblock, ASN, and domain dossier; TXT record prompt-injection hardening"
3
+ breaking: false
4
+ security: true
5
+ ---
6
+
7
+ # 0.1.1 — 2026-06-05
8
+
9
+ Initial public release of `@cyanheads/whois-mcp-server`. Provides 6 MCP tools over [RDAP](https://www.iana.org/rdap) (via IANA bootstrap) and [DNS-over-HTTPS](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/) (Cloudflare primary, Google fallback):
10
+
11
+ - **`whois_lookup_domain`** — domain registration record: registrar, created/expiry dates, nameservers, EPP status codes, DNSSEC flag, registrant org
12
+ - **`whois_check_availability`** — domain availability check via RDAP 404 signal
13
+ - **`whois_get_dns`** — DNS records via DoH (A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR)
14
+ - **`whois_lookup_ip`** — IP/CIDR netblock, org, abuse contact via RIR RDAP
15
+ - **`whois_lookup_asn`** — ASN to org/RIR resolution via RIR RDAP
16
+ - **`whois_get_dossier`** — one-call domain triage: registration + DNS records fetched in parallel
17
+
18
+ ## Added
19
+
20
+ - **`whois_lookup_domain`** — RDAP domain lookup with IANA auto-bootstrap; returns registrar, dates, nameservers, EPP status, DNSSEC, and registrant org (where not privacy-redacted)
21
+ - **`whois_check_availability`** — checks domain availability by interpreting RDAP 404 as unregistered; falls back to a registration hint when RDAP coverage is absent for the TLD
22
+ - **`whois_get_dns`** — DNS record fetch via Cloudflare DoH (primary) / Google DoH (fallback); supports A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR record types
23
+ - **`whois_lookup_ip`** — resolves any IPv4/IPv6 address or CIDR prefix to its RIR netblock record (org, country, abuse contact) via the appropriate RIR's RDAP endpoint
24
+ - **`whois_lookup_asn`** — resolves an AS number to org name, country, and RIR via RIR RDAP
25
+ - **`whois_get_dossier`** — parallel domain triage tool combining `whois_lookup_domain` + `whois_get_dns` in a single call; includes derived signals (hosting provider guess, SPF/DMARC/DKIM presence, registrar-lock status)
26
+ - **RDAP service** (`src/services/rdap/`) — IANA bootstrap cache (TTL via `ctx.state`), domain/IP/ASN lookup, RIR routing, normalized types
27
+ - **DoH service** (`src/services/doh/`) — Cloudflare-primary / Google-fallback DNS-over-HTTPS client, all record types, normalized types
28
+ - **Server config** (`src/config/server-config.ts`) — `RDAP_TIMEOUT_MS`, `DOH_TIMEOUT_MS`, `RDAP_MAX_RETRIES`, `DOH_MAX_RETRIES` env vars
29
+
30
+ ## Security
31
+
32
+ - **TXT record prompt-injection hardening** — DNS TXT record values are attacker-controlled free-text; `whois_get_dns` and `whois_get_dossier` now backtick-fence every TXT value in `format()` output before it reaches the LLM context, preventing embedded instructions from executing as prompt content
@@ -0,0 +1,127 @@
1
+ ---
2
+ # FORMAT REFERENCE — do not edit. Copy this file to
3
+ # `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.8.x/0.8.6.md`)
4
+ # to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
5
+ # with a concrete date.
6
+
7
+ # Required. One-line GitHub Release-style headline. 350 character cap.
8
+ # Default short and scannable. Don't pad, don't stitch unrelated changes with
9
+ # semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
10
+ # inside the value as a key separator and fails GitHub's strict parser.
11
+ summary: ""
12
+
13
+ # Set `true` when consumers must change code to upgrade: API removals,
14
+ # signature changes, config renames, behavior changes that break existing
15
+ # usage. Flagged as `Breaking` in the rollup.
16
+ breaking: false
17
+
18
+ # Set `true` if this release contains any security fix. Pairs with the
19
+ # `## Security` section below. Flagged as `Security` in the rollup so
20
+ # users can triage upgrade urgency at a glance.
21
+ security: false
22
+
23
+ # Optional free-form notes for maintenance agents processing this release.
24
+ # Not rendered in CHANGELOG — consumed by agents running `maintenance` on
25
+ # downstream servers. Use for adoption instructions that don't fit the
26
+ # human-facing sections: new files to create, fields to populate, one-time
27
+ # migration steps. Omit the field entirely when there's nothing to say.
28
+ # agent-notes: |
29
+ # <instructions for downstream maintenance agents>
30
+ ---
31
+
32
+ # <version> — YYYY-MM-DD
33
+
34
+ <!--
35
+ AUTHORING GUIDE — applies to the new per-version file you create from this
36
+ template.
37
+
38
+ Audience: someone scanning release notes to decide what affects them. Lead
39
+ each bullet with the symbol or concept name in **bold** so they can skip
40
+ what's irrelevant and zoom in on what's not.
41
+
42
+ Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
43
+ name the symbol, state what changed, stop. Use a second sentence only when
44
+ it carries weight. If a bullet feels long, it is.
45
+
46
+ Cut: mechanism walkthroughs (those belong in JSDoc, CLAUDE.md/AGENTS.md, or the
47
+ relevant skill), ceremonial framings ("This release introduces…",
48
+ backwards-compat paragraphs), file-by-file test enumerations, internal
49
+ implementation notes. Prefer code/symbol names over English re-explanations.
50
+
51
+ Narrative intro: skip by default. Add one short sentence only when the
52
+ release theme genuinely needs framing the bullets can't carry.
53
+
54
+ Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
55
+ Fixed, Security. Include only sections with entries; delete the rest
56
+ (including the commented-out scaffolding below). Don't ship empty headers.
57
+
58
+ Include: every distinct fact a reader needs to adopt or audit the release —
59
+ new exports, signatures, lint rule IDs, env vars, breaking changes, version
60
+ bumps on shipped skills. Nothing more.
61
+
62
+ Links: link issues, PRs, docs, or skills where they help a reader jump to
63
+ context. Once per item per entry — don't re-link the same issue in summary,
64
+ narrative, and bullet. Skip links for inline symbol names; code spans speak
65
+ for themselves.
66
+
67
+ Issue/PR URLs: use full URLs. GitHub's bare `#NN` auto-link only resolves
68
+ inside its own UI, not in npm reads or local editors.
69
+
70
+ [#38](https://github.com/cyanheads/mcp-ts-core/issues/38) ← issue
71
+ [#42](https://github.com/cyanheads/mcp-ts-core/pull/42) ← PR
72
+
73
+ Verify numbers exist before linking (`gh issue view NN`, `gh pr view NN`).
74
+ Never speculate on a future number — `#42` for an upcoming PR silently
75
+ resolves to whatever real item already owns 42, and timeline previews pull
76
+ in that unrelated item's metadata.
77
+
78
+ TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
79
+ via `gh release create --notes-from-tag`. The tag is a derivative of this
80
+ changelog entry — a condensed, scannable version, not a copy. Format:
81
+
82
+ <theme — omit version number, GitHub prepends it>
83
+ ← blank line
84
+ <1-2 sentence context: what this release does>
85
+ ← blank line
86
+ Dependency bumps: ← section header
87
+ ← blank line
88
+ - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
89
+ ← blank line
90
+ Changed: ← only sections with entries
91
+ ← blank line
92
+ - `format()` output includes `query` in text mode
93
+ ← blank line
94
+ Added:
95
+ ← blank line
96
+ - `manifest.json` scaffolded for MCPB bundle support
97
+ - Install badges (Claude Desktop, Cursor, VS Code)
98
+ ← blank line
99
+ <N> tests pass; `bun run devcheck` clean. ← footer
100
+
101
+ Never a flat comma-separated string. Always structured markdown with
102
+ sections. The tag must scan well as a rendered GitHub Release page.
103
+ -->
104
+
105
+ ## Added
106
+
107
+ -
108
+
109
+ ## Changed
110
+
111
+ -
112
+
113
+ <!-- ## Deprecated
114
+
115
+ - -->
116
+
117
+ <!-- ## Removed
118
+
119
+ - -->
120
+
121
+ ## Fixed
122
+
123
+ -
124
+
125
+ <!-- ## Security
126
+
127
+ - -->
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @fileoverview Server-specific environment configuration for whois-mcp-server.
3
+ * @module config/server-config
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ declare const ServerConfigSchema: z.ZodObject<{
7
+ rdapTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
8
+ dohTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
9
+ rdapMaxRetries: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
10
+ dohMaxRetries: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
11
+ }, z.core.$strip>;
12
+ export type ServerConfig = z.infer<typeof ServerConfigSchema>;
13
+ /** Returns the parsed server config, lazy-initialized on first call. */
14
+ export declare function getServerConfig(): ServerConfig;
15
+ export {};
16
+ //# sourceMappingURL=server-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;iBAWtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wEAAwE;AACxE,wBAAgB,eAAe,IAAI,YAAY,CAQ9C"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @fileoverview Server-specific environment configuration for whois-mcp-server.
3
+ * @module config/server-config
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
7
+ const ServerConfigSchema = z.object({
8
+ rdapTimeoutMs: z.coerce.number().default(5000).describe('HTTP timeout for RDAP requests in ms.'),
9
+ dohTimeoutMs: z.coerce.number().default(3000).describe('HTTP timeout for DoH requests in ms.'),
10
+ rdapMaxRetries: z.coerce
11
+ .number()
12
+ .default(2)
13
+ .describe('Max retry attempts on transient RDAP failures.'),
14
+ dohMaxRetries: z.coerce
15
+ .number()
16
+ .default(2)
17
+ .describe('Max retry attempts on transient DoH failures.'),
18
+ });
19
+ let _config;
20
+ /** Returns the parsed server config, lazy-initialized on first call. */
21
+ export function getServerConfig() {
22
+ _config ??= parseEnvConfig(ServerConfigSchema, {
23
+ rdapTimeoutMs: 'RDAP_TIMEOUT_MS',
24
+ dohTimeoutMs: 'DOH_TIMEOUT_MS',
25
+ rdapMaxRetries: 'RDAP_MAX_RETRIES',
26
+ dohMaxRetries: 'DOH_MAX_RETRIES',
27
+ });
28
+ return _config;
29
+ }
30
+ //# sourceMappingURL=server-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IAChG,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IAC9F,cAAc,EAAE,CAAC,CAAC,MAAM;SACrB,MAAM,EAAE;SACR,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,gDAAgD,CAAC;IAC7D,aAAa,EAAE,CAAC,CAAC,MAAM;SACpB,MAAM,EAAE;SACR,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,+CAA+C,CAAC;CAC7D,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,wEAAwE;AACxE,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,aAAa,EAAE,iBAAiB;QAChC,YAAY,EAAE,gBAAgB;QAC9B,cAAc,EAAE,kBAAkB;QAClC,aAAa,EAAE,iBAAiB;KACjC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview whois-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG"}
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview whois-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ import { createApp } from '@cyanheads/mcp-ts-core';
7
+ import { whoisCheckAvailability } from './mcp-server/tools/definitions/whois-check-availability.tool.js';
8
+ import { whoisGetDns } from './mcp-server/tools/definitions/whois-get-dns.tool.js';
9
+ import { whoisGetDossier } from './mcp-server/tools/definitions/whois-get-dossier.tool.js';
10
+ import { whoisLookupAsn } from './mcp-server/tools/definitions/whois-lookup-asn.tool.js';
11
+ import { whoisLookupDomain } from './mcp-server/tools/definitions/whois-lookup-domain.tool.js';
12
+ import { whoisLookupIp } from './mcp-server/tools/definitions/whois-lookup-ip.tool.js';
13
+ import { initDohService } from './services/doh/doh-service.js';
14
+ import { initRdapService } from './services/rdap/rdap-service.js';
15
+ await createApp({
16
+ tools: [
17
+ whoisLookupDomain,
18
+ whoisCheckAvailability,
19
+ whoisGetDns,
20
+ whoisLookupIp,
21
+ whoisLookupAsn,
22
+ whoisGetDossier,
23
+ ],
24
+ resources: [],
25
+ prompts: [],
26
+ instructions: 'whois-mcp-server: domain and IP intelligence via RDAP and DNS-over-HTTPS. No API keys required.\n' +
27
+ '- whois_lookup_domain: full registration record (registrar, dates, status, nameservers)\n' +
28
+ '- whois_check_availability: is a domain available to register? (RDAP 404 = available)\n' +
29
+ '- whois_get_dns: DNS records for any hostname (A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR)\n' +
30
+ '- whois_lookup_ip: IP/CIDR netblock, org, abuse contact, and PTR via RIR RDAP\n' +
31
+ '- whois_lookup_asn: ASN to org/RIR resolution\n' +
32
+ '- whois_get_dossier: one-call domain triage — registration + DNS in parallel with inferred signals',
33
+ setup(core) {
34
+ initRdapService(core.config, core.storage);
35
+ initDohService(core.config, core.storage);
36
+ },
37
+ });
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,WAAW,EAAE,MAAM,sDAAsD,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,yDAAyD,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,wDAAwD,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,SAAS,CAAC;IACd,KAAK,EAAE;QACL,iBAAiB;QACjB,sBAAsB;QACtB,WAAW;QACX,aAAa;QACb,cAAc;QACd,eAAe;KAChB;IACD,SAAS,EAAE,EAAE;IACb,OAAO,EAAE,EAAE;IACX,YAAY,EACV,mGAAmG;QACnG,2FAA2F;QAC3F,yFAAyF;QACzF,8FAA8F;QAC9F,iFAAiF;QACjF,iDAAiD;QACjD,oGAAoG;IACtG,KAAK,CAAC,IAAI;QACR,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Shared FQDN validation used across tool handlers.
3
+ * @module mcp-server/tools/definitions/_fqdn
4
+ */
5
+ /** Simple FQDN validation: labels separated by dots, no consecutive dots, length limits */
6
+ export declare function isValidFqdn(domain: string): boolean;
7
+ //# sourceMappingURL=_fqdn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_fqdn.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/_fqdn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAQnD"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Shared FQDN validation used across tool handlers.
3
+ * @module mcp-server/tools/definitions/_fqdn
4
+ */
5
+ /** Simple FQDN validation: labels separated by dots, no consecutive dots, length limits */
6
+ export function isValidFqdn(domain) {
7
+ if (!domain || domain.length > 253)
8
+ return false;
9
+ const labels = domain.split('.');
10
+ if (labels.length < 2)
11
+ return false;
12
+ return labels.every((label) => {
13
+ if (!label || label.length > 63)
14
+ return false;
15
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/.test(label);
16
+ });
17
+ }
18
+ //# sourceMappingURL=_fqdn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_fqdn.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/_fqdn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2FAA2F;AAC3F,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;YAAE,OAAO,KAAK,CAAC;QAC9C,OAAO,wDAAwD,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview whois_check_availability tool — domain availability check via RDAP 404 semantics.
3
+ * @module mcp-server/tools/definitions/whois-check-availability.tool
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
+ export declare const whoisCheckAvailability: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
8
+ domain: z.ZodString;
9
+ }, z.core.$strip>, z.ZodObject<{
10
+ domain: z.ZodString;
11
+ available: z.ZodNullable<z.ZodBoolean>;
12
+ rdap_coverage: z.ZodBoolean;
13
+ registrar: z.ZodOptional<z.ZodString>;
14
+ expiry_date: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>, readonly [{
16
+ readonly reason: "invalid_domain";
17
+ readonly code: JsonRpcErrorCode.InvalidParams;
18
+ readonly when: "Input is not a valid FQDN.";
19
+ readonly recovery: "Provide a valid fully-qualified domain name like \"example.com\" or \"sub.example.org\".";
20
+ }, {
21
+ readonly reason: "rdap_no_coverage";
22
+ readonly code: JsonRpcErrorCode.NotFound;
23
+ readonly when: "TLD has no RDAP server — available is null, cannot determine registration status.";
24
+ readonly recovery: "This TLD has no RDAP coverage; availability cannot be determined programmatically.";
25
+ }], undefined>;
26
+ //# sourceMappingURL=whois-check-availability.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whois-check-availability.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/whois-check-availability.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;cA4GjC,CAAC"}