#
tokens: 6067/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   └── workflows
│       └── npm-publish.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.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 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # DNStwist MCP Server
  2 | [![smithery badge](https://smithery.ai/badge/@burtthecoder/mcp-dnstwist)](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 | <a href="https://glama.ai/mcp/servers/it7izu3ufb"><img width="380" height="200" src="https://glama.ai/mcp/servers/it7izu3ufb/badge" alt="mcp-dnstwist MCP server" /></a>
  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 <repository_url>
 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 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```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 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```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: {} })
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```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"]
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```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 | 
```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yaml:
--------------------------------------------------------------------------------

```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 "[email protected]"
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 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  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<string, unknown>;
 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<void> {
 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 | 
```