├── .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 | [![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 | mcp-dnstwist MCP server 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 | --------------------------------------------------------------------------------