├── .github
└── workflows
│ └── npm-publish.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── smithery.yaml
├── src
└── index.ts
└── tsconfig.json
/.github/workflows/npm-publish.yaml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build-and-publish:
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: write
15 | steps:
16 | - uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 | token: ${{ secrets.GITHUB_TOKEN }}
20 |
21 | - name: Setup Node.js
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: '18'
25 | registry-url: 'https://registry.npmjs.org'
26 |
27 | - name: Configure Git
28 | run: |
29 | git config --local user.email "action@github.com"
30 | git config --local user.name "GitHub Action"
31 |
32 | - name: Install dependencies
33 | run: npm ci
34 |
35 | - name: Bump version
36 | if: github.event_name == 'push' && !contains(github.event.head_commit.message, '[skip ci]')
37 | run: |
38 | npm version patch -m "Bump version to %s [skip ci]"
39 | git push
40 | git push --tags
41 |
42 | - name: Build
43 | run: npm run build
44 |
45 | - name: Publish to NPM
46 | if: success()
47 | run: npm publish --access public
48 | env:
49 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 |
4 | # Build output
5 | build/
6 |
7 | # IDE files
8 | .vscode/
9 | .idea/
10 | *.sublime-*
11 |
12 | # Logs
13 | *.log
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 |
18 | # Environment variables
19 | .env
20 | .env.local
21 | .env.*.local
22 |
23 | # Operating System
24 | .DS_Store
25 | Thumbs.db
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | # Start with a Node.js image
3 | FROM node:18-alpine AS builder
4 |
5 | # Set the working directory
6 | WORKDIR /app
7 |
8 | # Copy package.json and package-lock.json
9 | COPY package.json package-lock.json ./
10 |
11 | # Install dependencies
12 | RUN npm install
13 |
14 | # Copy the source code
15 | COPY src ./src
16 |
17 | # Copy the tsconfig file
18 | COPY tsconfig.json ./
19 |
20 | # Build the TypeScript code
21 | RUN npm run build
22 |
23 | # Use a smaller image for runtime
24 | FROM node:18-alpine AS runtime
25 |
26 | # Set the working directory
27 | WORKDIR /app
28 |
29 | # Copy built files and package.json
30 | COPY --from=builder /app/build ./build
31 | COPY --from=builder /app/package.json ./
32 |
33 | # Install only production dependencies
34 | RUN npm install --omit=dev
35 |
36 | # Set the command to run the server
37 | ENTRYPOINT ["node", "build/index.js"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 mcp-dnstwist contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DNStwist MCP Server
2 | [](https://smithery.ai/server/@burtthecoder/mcp-dnstwist)
3 |
4 | A Model Context Protocol (MCP) server for [dnstwist](https://github.com/elceef/dnstwist), a powerful DNS fuzzing tool that helps detect typosquatting, phishing, and corporate espionage. This server provides tools for analyzing domain permutations and identifying potentially malicious domains. It is designed to integrate seamlessly with MCP-compatible applications like [Claude Desktop](https://claude.ai).
5 |
6 |
7 |
8 |
9 | ## ⚠️ Warning
10 |
11 | This tool is designed for legitimate security research purposes. Please:
12 | - Only analyze domains you own or have permission to test
13 | - Respect rate limits and DNS server policies
14 | - Use responsibly and ethically
15 | - Be aware that some DNS servers may rate-limit or block automated queries
16 | - Consider the impact on DNS infrastructure when running large scans
17 |
18 | ## Requirements
19 |
20 | - Node.js (v18 or later)
21 | - Docker
22 | - macOS, Linux, or Windows with Docker Desktop installed
23 |
24 | ## Quick Start
25 |
26 | ### Installing via Smithery
27 |
28 | To install DNStwist for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@burtthecoder/mcp-dnstwist):
29 |
30 | ```bash
31 | npx -y @smithery/cli install @burtthecoder/mcp-dnstwist --client claude
32 | ```
33 |
34 | ### Installing Manually
35 | 1. Install Docker:
36 | - macOS: Install [Docker Desktop](https://www.docker.com/products/docker-desktop)
37 | - Linux: Follow the [Docker Engine installation guide](https://docs.docker.com/engine/install/)
38 |
39 | 2. Install the server globally via npm:
40 | ```bash
41 | npm install -g mcp-dnstwist
42 | ```
43 |
44 | 3. Add to your Claude Desktop configuration file:
45 | ```json
46 | {
47 | "mcpServers": {
48 | "dnstwist": {
49 | "command": "mcp-dnstwist"
50 | }
51 | }
52 | }
53 | ```
54 |
55 | Configuration file location:
56 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
57 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
58 |
59 | 4. Restart Claude Desktop
60 |
61 | ## Alternative Setup (From Source)
62 |
63 | If you prefer to run from source or need to modify the code:
64 |
65 | 1. Clone and build:
66 | ```bash
67 | git clone
68 | cd mcp-dnstwist
69 | npm install
70 | npm run build
71 | ```
72 |
73 | 2. Add to your Claude Desktop configuration:
74 | ```json
75 | {
76 | "mcpServers": {
77 | "dnstwist": {
78 | "command": "node",
79 | "args": ["/absolute/path/to/mcp-dnstwist/build/index.js"]
80 | }
81 | }
82 | }
83 | ```
84 |
85 | ## Features
86 |
87 | - **Domain Fuzzing**: Generate domain permutations using various algorithms
88 | - **Registration Check**: Verify if permutated domains are registered
89 | - **DNS Analysis**: Check A, AAAA, MX, and NS records
90 | - **Web Presence**: Capture HTTP banner information
91 | - **WHOIS Data**: Retrieve registration dates and registrar information
92 | - **Phishing Detection**: Generate fuzzy hashes of web pages
93 | - **Configurable**: Custom DNS servers and parallel processing
94 | - **Multiple Formats**: Support for json, csv, and list output formats
95 |
96 | ## Tools
97 |
98 | ### Domain Fuzzing Tool
99 | - Name: `fuzz_domain`
100 | - Description: Generate and analyze domain permutations to detect potential typosquatting, phishing, and brand impersonation
101 | - Parameters:
102 | * `domain` (required): Domain name to analyze (e.g., example.com)
103 | * `nameservers` (optional, default: "1.1.1.1"): Comma-separated list of DNS servers
104 | * `threads` (optional, default: 50): Number of threads for parallel processing
105 | * `format` (optional, default: "json"): Output format (json, csv, list)
106 | * `registered_only` (optional, default: true): Show only registered domains
107 | * `mxcheck` (optional, default: true): Check for MX records
108 | * `ssdeep` (optional, default: false): Generate fuzzy hashes of web pages
109 | * `banners` (optional, default: true): Capture HTTP banner information
110 |
111 | Example:
112 | ```json
113 | {
114 | "domain": "example.com",
115 | "nameservers": "1.1.1.1,8.8.8.8",
116 | "threads": 50,
117 | "format": "json",
118 | "registered_only": true,
119 | "mxcheck": true,
120 | "banners": true
121 | }
122 | ```
123 |
124 | ## Troubleshooting
125 |
126 | ### Docker Issues
127 |
128 | 1. Verify Docker is installed and running:
129 | ```bash
130 | docker --version
131 | docker ps
132 | ```
133 |
134 | 2. Check Docker permissions:
135 | - Ensure your user has permissions to run Docker commands
136 | - On Linux, add your user to the docker group: `sudo usermod -aG docker $USER`
137 |
138 | ### Common Issues
139 |
140 | 1. DNS resolution problems:
141 | - Verify DNS servers are accessible
142 | - Try alternative DNS servers (e.g., 8.8.8.8)
143 | - Check for rate limiting or blocking
144 |
145 | 2. Performance issues:
146 | - Adjust thread count based on system capabilities
147 | - Consider network bandwidth and latency
148 | - Monitor DNS server response times
149 |
150 | 3. After fixing any issues:
151 | - Save the configuration file
152 | - Restart Claude Desktop
153 |
154 | ## Error Messages
155 |
156 | - "Docker is not installed or not running": Install Docker and start the Docker daemon
157 | - "Failed to parse dnstwist output": Check if the domain is valid and the format is correct
158 | - "Error executing dnstwist": Check Docker logs and ensure proper permissions
159 | - "DNS server not responding": Verify DNS server accessibility and try alternative servers
160 |
161 | ## Contributing
162 |
163 | 1. Fork the repository
164 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
165 | 3. Commit your changes (`git commit -m 'Add amazing feature'`)
166 | 4. Push to the branch (`git push origin feature/amazing-feature`)
167 | 5. Open a Pull Request
168 |
169 | ## License
170 |
171 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
172 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mcp-dnstwist",
3 | "version": "1.0.4",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "mcp-dnstwist",
9 | "version": "1.0.4",
10 | "license": "MIT",
11 | "dependencies": {
12 | "@modelcontextprotocol/sdk": "^0.4.0"
13 | },
14 | "bin": {
15 | "mcp-dnstwist": "build/index.js"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20.0.0",
19 | "typescript": "^5.0.0"
20 | },
21 | "engines": {
22 | "node": ">=18"
23 | }
24 | },
25 | "node_modules/@modelcontextprotocol/sdk": {
26 | "version": "0.4.0",
27 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.4.0.tgz",
28 | "integrity": "sha512-79gx8xh4o9YzdbtqMukOe5WKzvEZpvBA1x8PAgJWL7J5k06+vJx8NK2kWzOazPgqnfDego7cNEO8tjai/nOPAA==",
29 | "dependencies": {
30 | "content-type": "^1.0.5",
31 | "raw-body": "^3.0.0",
32 | "zod": "^3.23.8"
33 | }
34 | },
35 | "node_modules/@types/node": {
36 | "version": "20.17.10",
37 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
38 | "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
39 | "dev": true,
40 | "dependencies": {
41 | "undici-types": "~6.19.2"
42 | }
43 | },
44 | "node_modules/bytes": {
45 | "version": "3.1.2",
46 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
47 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
48 | "engines": {
49 | "node": ">= 0.8"
50 | }
51 | },
52 | "node_modules/content-type": {
53 | "version": "1.0.5",
54 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
55 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
56 | "engines": {
57 | "node": ">= 0.6"
58 | }
59 | },
60 | "node_modules/depd": {
61 | "version": "2.0.0",
62 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
63 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
64 | "engines": {
65 | "node": ">= 0.8"
66 | }
67 | },
68 | "node_modules/http-errors": {
69 | "version": "2.0.0",
70 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
71 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
72 | "dependencies": {
73 | "depd": "2.0.0",
74 | "inherits": "2.0.4",
75 | "setprototypeof": "1.2.0",
76 | "statuses": "2.0.1",
77 | "toidentifier": "1.0.1"
78 | },
79 | "engines": {
80 | "node": ">= 0.8"
81 | }
82 | },
83 | "node_modules/iconv-lite": {
84 | "version": "0.6.3",
85 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
86 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
87 | "dependencies": {
88 | "safer-buffer": ">= 2.1.2 < 3.0.0"
89 | },
90 | "engines": {
91 | "node": ">=0.10.0"
92 | }
93 | },
94 | "node_modules/inherits": {
95 | "version": "2.0.4",
96 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
97 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
98 | },
99 | "node_modules/raw-body": {
100 | "version": "3.0.0",
101 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
102 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
103 | "dependencies": {
104 | "bytes": "3.1.2",
105 | "http-errors": "2.0.0",
106 | "iconv-lite": "0.6.3",
107 | "unpipe": "1.0.0"
108 | },
109 | "engines": {
110 | "node": ">= 0.8"
111 | }
112 | },
113 | "node_modules/safer-buffer": {
114 | "version": "2.1.2",
115 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
116 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
117 | },
118 | "node_modules/setprototypeof": {
119 | "version": "1.2.0",
120 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
121 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
122 | },
123 | "node_modules/statuses": {
124 | "version": "2.0.1",
125 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
126 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
127 | "engines": {
128 | "node": ">= 0.8"
129 | }
130 | },
131 | "node_modules/toidentifier": {
132 | "version": "1.0.1",
133 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
134 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
135 | "engines": {
136 | "node": ">=0.6"
137 | }
138 | },
139 | "node_modules/typescript": {
140 | "version": "5.7.2",
141 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
142 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
143 | "dev": true,
144 | "bin": {
145 | "tsc": "bin/tsc",
146 | "tsserver": "bin/tsserver"
147 | },
148 | "engines": {
149 | "node": ">=14.17"
150 | }
151 | },
152 | "node_modules/undici-types": {
153 | "version": "6.19.8",
154 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
155 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
156 | "dev": true
157 | },
158 | "node_modules/unpipe": {
159 | "version": "1.0.0",
160 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
161 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
162 | "engines": {
163 | "node": ">= 0.8"
164 | }
165 | },
166 | "node_modules/zod": {
167 | "version": "3.24.1",
168 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
169 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
170 | "funding": {
171 | "url": "https://github.com/sponsors/colinhacks"
172 | }
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@burtthecoder/mcp-dnstwist",
3 | "version": "1.0.4",
4 | "description": "MCP server for dnstwist - DNS fuzzing to detect typosquatting, phishing and corporate espionage",
5 | "main": "build/index.js",
6 | "type": "module",
7 | "bin": {
8 | "mcp-dnstwist": "build/index.js"
9 | },
10 | "files": [
11 | "build",
12 | "README.md",
13 | "LICENSE"
14 | ],
15 | "scripts": {
16 | "build": "tsc && chmod +x build/index.js",
17 | "prepublishOnly": "npm run build"
18 | },
19 | "dependencies": {
20 | "@modelcontextprotocol/sdk": "^0.4.0"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^20.0.0",
24 | "typescript": "^5.0.0"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/burtthecoder/mcp-dnstwist.git"
29 | },
30 | "keywords": [
31 | "mcp",
32 | "dnstwist",
33 | "dns",
34 | "security",
35 | "phishing",
36 | "typosquatting",
37 | "domain-security",
38 | "claude",
39 | "claude-desktop"
40 | ],
41 | "author": "burtmacklin",
42 | "license": "MIT",
43 | "engines": {
44 | "node": ">=18"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required:
9 | - dockerImage
10 | properties:
11 | dockerImage:
12 | type: string
13 | default: elceef/dnstwist
14 | description: Docker image for DNStwist
15 | commandFunction:
16 | # A function that produces the CLI command to start the MCP on stdio.
17 | |-
18 | (config) => ({ command: 'node', args: ['build/index.js'], env: {} })
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import {
5 | CallToolRequestSchema,
6 | ErrorCode,
7 | ListToolsRequestSchema,
8 | McpError,
9 | } from '@modelcontextprotocol/sdk/types.js';
10 | import { exec } from 'child_process';
11 | import { promisify } from 'util';
12 |
13 | const execAsync = promisify(exec);
14 | const DOCKER_IMAGE = 'elceef/dnstwist';
15 |
16 | interface DnstwistResult {
17 | banner_http?: string;
18 | dns_a?: string[];
19 | dns_aaaa?: string[];
20 | dns_mx?: string[];
21 | dns_ns?: string[];
22 | domain: string;
23 | fuzzer: string;
24 | whois_created?: string;
25 | whois_registrar?: string;
26 | }
27 |
28 | interface FuzzDomainArgs {
29 | domain: string;
30 | nameservers?: string;
31 | threads?: number;
32 | format?: 'json' | 'csv' | 'list';
33 | registered_only?: boolean;
34 | mxcheck?: boolean;
35 | ssdeep?: boolean;
36 | banners?: boolean;
37 | }
38 |
39 | function isFuzzDomainArgs(args: unknown): args is FuzzDomainArgs {
40 | if (!args || typeof args !== 'object') return false;
41 | const a = args as Record;
42 | return typeof a.domain === 'string' &&
43 | (a.nameservers === undefined || typeof a.nameservers === 'string') &&
44 | (a.threads === undefined || typeof a.threads === 'number') &&
45 | (a.format === undefined || ['json', 'csv', 'list'].includes(a.format as string)) &&
46 | (a.registered_only === undefined || typeof a.registered_only === 'boolean') &&
47 | (a.mxcheck === undefined || typeof a.mxcheck === 'boolean') &&
48 | (a.ssdeep === undefined || typeof a.ssdeep === 'boolean') &&
49 | (a.banners === undefined || typeof a.banners === 'boolean');
50 | }
51 |
52 | class DnstwistServer {
53 | private server: Server;
54 |
55 | constructor() {
56 | this.server = new Server({
57 | name: 'dnstwist-server',
58 | version: '0.1.0',
59 | capabilities: {
60 | tools: {}
61 | }
62 | });
63 |
64 | this.setupToolHandlers();
65 |
66 | this.server.onerror = (error) => console.error('[MCP Error]', error);
67 | process.on('SIGINT', async () => {
68 | await this.server.close();
69 | process.exit(0);
70 | });
71 |
72 | // Trigger setup immediately
73 | this.ensureSetup().catch(error => {
74 | console.error('Failed to setup dnstwist:', error);
75 | process.exit(1);
76 | });
77 | }
78 |
79 | private async execCommand(command: string): Promise<{ stdout: string; stderr: string }> {
80 | console.error('Executing command:', command);
81 | try {
82 | const result = await execAsync(command, {
83 | maxBuffer: 10 * 1024 * 1024
84 | });
85 | console.error('Command output:', result.stdout);
86 | if (result.stderr) console.error('Command stderr:', result.stderr);
87 | return result;
88 | } catch (error) {
89 | console.error('Command failed:', error);
90 | throw error;
91 | }
92 | }
93 |
94 | private async ensureSetup(): Promise {
95 | try {
96 | console.error('Checking Docker...');
97 | try {
98 | await this.execCommand('docker --version');
99 | } catch (error) {
100 | throw new Error('Docker is not installed or not running. Please install Docker and try again.');
101 | }
102 |
103 | console.error('Checking if dnstwist image exists...');
104 | try {
105 | await this.execCommand(`docker image inspect ${DOCKER_IMAGE}`);
106 | console.error('Dnstwist image found');
107 | } catch (error) {
108 | console.error('Dnstwist image not found, pulling...');
109 | await this.execCommand(`docker pull ${DOCKER_IMAGE}`);
110 | console.error('Dnstwist image pulled successfully');
111 | }
112 | } catch (error) {
113 | console.error('Setup failed:', error);
114 | throw error;
115 | }
116 | }
117 |
118 | private setupToolHandlers() {
119 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
120 | tools: [
121 | {
122 | name: 'fuzz_domain',
123 | description: 'Generate and analyze domain permutations to detect potential typosquatting, phishing, and brand impersonation',
124 | inputSchema: {
125 | type: 'object',
126 | properties: {
127 | domain: {
128 | type: 'string',
129 | description: 'Domain name to analyze (e.g., example.com)'
130 | },
131 | nameservers: {
132 | type: 'string',
133 | description: 'Comma-separated list of DNS servers to use (default: 1.1.1.1)',
134 | default: '1.1.1.1'
135 | },
136 | threads: {
137 | type: 'number',
138 | description: 'Number of threads to use for parallel processing',
139 | minimum: 1,
140 | maximum: 100,
141 | default: 50
142 | },
143 | format: {
144 | type: 'string',
145 | enum: ['json', 'csv', 'list'],
146 | description: 'Output format',
147 | default: 'json'
148 | },
149 | registered_only: {
150 | type: 'boolean',
151 | description: 'Show only registered domain permutations',
152 | default: true
153 | },
154 | mxcheck: {
155 | type: 'boolean',
156 | description: 'Check for MX records',
157 | default: true
158 | },
159 | ssdeep: {
160 | type: 'boolean',
161 | description: 'Generate fuzzy hashes of web pages to detect phishing',
162 | default: false
163 | },
164 | banners: {
165 | type: 'boolean',
166 | description: 'Capture HTTP banner information',
167 | default: true
168 | }
169 | },
170 | required: ['domain']
171 | }
172 | }
173 | ]
174 | }));
175 |
176 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
177 | try {
178 | await this.ensureSetup();
179 |
180 | if (request.params.name !== 'fuzz_domain') {
181 | throw new McpError(
182 | ErrorCode.MethodNotFound,
183 | `Unknown tool: ${request.params.name}`
184 | );
185 | }
186 |
187 | if (!isFuzzDomainArgs(request.params.arguments)) {
188 | throw new McpError(
189 | ErrorCode.InvalidParams,
190 | 'Invalid arguments for fuzz_domain'
191 | );
192 | }
193 |
194 | const {
195 | domain,
196 | nameservers = '1.1.1.1',
197 | threads = 50,
198 | format = 'json',
199 | registered_only = true,
200 | mxcheck = true,
201 | ssdeep = false,
202 | banners = true
203 | } = request.params.arguments;
204 |
205 | // Build command arguments
206 | const args = [
207 | domain,
208 | '--format', format
209 | ];
210 |
211 | if (registered_only) args.push('--registered');
212 | if (nameservers) args.push('--nameservers', nameservers);
213 | if (threads) args.push('-t', threads.toString());
214 | if (mxcheck) args.push('--mxcheck');
215 | if (ssdeep) args.push('--ssdeep');
216 | if (banners) args.push('-b');
217 |
218 | // Run dnstwist in Docker
219 | const { stdout, stderr } = await this.execCommand(
220 | `docker run --rm ${DOCKER_IMAGE} ${args.join(' ')}`
221 | );
222 |
223 | let results: DnstwistResult[];
224 | try {
225 | results = JSON.parse(stdout);
226 | } catch (error) {
227 | throw new Error(`Failed to parse dnstwist output: ${error}`);
228 | }
229 |
230 | return {
231 | content: [
232 | {
233 | type: 'text',
234 | text: JSON.stringify(results, null, 2)
235 | }
236 | ]
237 | };
238 | } catch (error) {
239 | const errorMessage = error instanceof Error ? error.message : String(error);
240 | return {
241 | content: [
242 | {
243 | type: 'text',
244 | text: `Error executing dnstwist: ${errorMessage}`
245 | }
246 | ],
247 | isError: true
248 | };
249 | }
250 | });
251 | }
252 |
253 | async run() {
254 | const transport = new StdioServerTransport();
255 | await this.server.connect(transport);
256 | console.error('Dnstwist MCP server running on stdio');
257 | }
258 | }
259 |
260 | const server = new DnstwistServer();
261 | server.run().catch(console.error);
262 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ES2020",
5 | "moduleResolution": "node",
6 | "esModuleInterop": true,
7 | "outDir": "build",
8 | "rootDir": "src",
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules", "build"]
15 | }
16 |
--------------------------------------------------------------------------------