nmap-mcp-server

sideffect263/nmap-mcp-server

3.2

If you are the rightful owner of nmap-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.

This project implements an MCP-compliant server using the Smithery SDK to wrap the Nmap network scanner.

Tools
2
Resources
0
Prompts
0

Nmap MCP Server (Smithery SDK)

This project implements an MCP-compliant (Model Context Protocol) server using the Smithery SDK. It wraps the Nmap network scanner, exposing its functionality as callable tools within the MCP framework.

Features

  • MCP Standard Endpoint (/mcp): Interacts with the server using JSON-RPC 2.0 requests.
  • nmapScan Tool: Initiates an Nmap scan on a specified target with configurable flags. Returns detailed scan results, including parsed XML output.
  • getInfo Tool: Provides basic information about the Nmap service and its capabilities.
  • Input validation for targets and Nmap flags to enhance security.

Prerequisites

  • Node.js (v16.x or later recommended)
  • Nmap installed and available in your system's PATH.

Installation

  1. Clone the repository (or download the source code).
  2. Navigate to the project directory:
    cd your-project-directory
    
  3. Install dependencies:
    npm install
    

Running the Server

To start the server, run:

npm start

By default, the server will listen on http://localhost:5001. You can set the PORT environment variable to use a different port.

Interacting with the Server (MCP)

All interactions with the server are done via the /mcp endpoint using JSON-RPC 2.0. You will send POST requests with a Content-Type: application/json header.

Calling the nmapScan Tool

To initiate an Nmap scan:

Request Body (JSON):

{
  "jsonrpc": "2.0",
  "id": 1, // Or any unique request ID
  "method": "tools/call",
  "params": {
    "name": "nmapScan",
    "arguments": {
      "target": "scanme.nmap.org",
      "flags": "-A -T4" // Optional, defaults to "-T4 -p 1-1000"
    }
  }
}

Example using cURL:

curl -X POST http://localhost:5001/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "nmapScan",
      "arguments": {
        "target": "scanme.nmap.org",
        "flags": "-A"
      }
    }
  }'

Expected Response Structure (Success):

The server will respond with the results of the Nmap scan. The content array will contain a text object with a summary and the full JSON-parsed Nmap XML output.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Nmap Scan Results for scanme.nmap.org\n\nScan started: ...\n... (summary of open ports) ...\nScan completed: ...\n\nFull XML Output:\n{\n  \"nmaprun\": { ... }\n}"
      }
    ]
  }
}

If the scan fails or inputs are invalid, the text field will contain an error message.

Calling the getInfo Tool

To get information about the service:

Request Body (JSON):

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "getInfo",
    "arguments": {}
  }
}

Example using cURL:

curl -X POST http://localhost:5001/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "getInfo",
      "arguments": {}
    }
  }'

Expected Response Structure (Success):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Nmap Service Information:\n- Service: Network scanning using Nmap\n- Version: 1.0.0\n- Available Tools: nmapScan, getInfo\n- Session ID: N/A\n- Supported Targets: Domain names, IP addresses, CIDR notation\n- Security: Input validation and command sanitization enabled"
      }
    ]
  }
}

Deployment on Smithery

To deploy this server on Smithery, you will typically need to:

  1. Ensure Nmap is available in the Smithery execution environment. This might involve custom Docker images or buildpacks if Nmap isn't pre-installed.
  2. Define the startup command: Smithery will need to know how to start the application (e.g., npm start).
  3. Port configuration: Smithery will expose the application, usually handling port mapping. Ensure the application listens on the port Smithery expects (often configured via the PORT environment variable, which this server supports via the process.env.PORT || 5001 pattern).
  4. Package the application: This usually means providing the source code and package.json (and package-lock.json). Smithery will then build the application, installing dependencies.

Refer to the Smithery documentation for specific deployment instructions and how to configure services that adhere to the Model Context Protocol.

Example MCP Client

Below is an example of how to create a client to interact with this Nmap MCP server. You'll need to have the @modelcontextprotocol/sdk and @smithery/sdk packages installed in your client project.

Save the following code as nmap_mcp_client_example.js:

import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { createSmitheryUrl } from "@smithery/sdk/shared/config.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

// Ensure you have your Smithery API key for the server in an environment variable
const SMITHERY_API_KEY = process.env.SMITHERY_NMAP_API_KEY || "your-smithery-api-key-here"; // Replace with your actual key or ensure env var is set
const NMAP_SERVER_URL = "https://server.smithery.ai/@sideffect263/nmap-mcp-server"; // Replace with your server URL if different

let clientInstance;
let nmapScanTool;
let getInfoTool;

async function initializeNmapClient() {
  if (clientInstance) {
    console.log("Nmap MCP Client already initialized.");
    return clientInstance;
  }

  console.log("Initializing Nmap MCP Client...");

  try {
    const config = {}; // Add any specific config for Smithery if needed
    const serverUrl = createSmitheryUrl(
      NMAP_SERVER_URL,
      { config, apiKey: SMITHERY_API_KEY }
    );

    const transport = new StreamableHTTPClientTransport(serverUrl);

    clientInstance = new Client({
      name: "ExampleNmapClient",
      version: "1.0.0",
    });

    console.log("Connecting to Nmap MCP server...");
    await clientInstance.connect(transport);
    console.log("Successfully connected to Nmap MCP server.");

    const toolsResponse = await clientInstance.listTools();
  
    if (toolsResponse && toolsResponse.tools && Array.isArray(toolsResponse.tools)) {
      console.log(`Available tools: ${toolsResponse.tools.map((t) => t.name).join(", ")}`);
      nmapScanTool = toolsResponse.tools.find(tool => tool.name === 'nmapScan');
      getInfoTool = toolsResponse.tools.find(tool => tool.name === 'getInfo');
    } else {
      throw new Error("Could not list tools from the server.");
    }

    if (!nmapScanTool) {
      throw new Error("Nmap tool ('nmapScan') not found on the MCP server.");
    }
    console.log(`Found Nmap tool: ${nmapScanTool.name}`);

    if (getInfoTool) {
      console.log(`Found GetInfo tool: ${getInfoTool.name}`);
    } else {
      console.warn("GetInfo tool ('getInfo') not found. This might be optional.");
    }
  
    return clientInstance;
  } catch (error) {
    console.error("Failed to initialize Nmap MCP client:", error);
    clientInstance = null; // Reset on failure
    nmapScanTool = null;
    getInfoTool = null;
    throw error;
  }
}

/**
 * Invokes the 'nmapScan' tool on the MCP server.
 * @param {Object} params - Parameters for the Nmap tool.
 * @param {string} params.target - The target IP, hostname, or CIDR.
 * @param {string} params.flags - Nmap flags as a single string (e.g., "-A -T4").
 * @returns {Promise<Object>} - The result from the 'nmapScan' tool.
 */
async function invokeNmapScan(params) {
  if (!clientInstance || !nmapScanTool) {
    console.log("Client not ready. Initializing...");
    await initializeNmapClient();
    if (!clientInstance || !nmapScanTool) {
      throw new Error("Nmap MCP client is not initialized or nmapScan tool not found.");
    }
  }

  const toolInput = {
    target: params.target,
    flags: params.flags 
  };
  console.log(\`Invoking Nmap tool "\${nmapScanTool.name}" with params:\`, toolInput);

  try {
    const result = await clientInstance.callTool({
      name: nmapScanTool.name,
      arguments: toolInput
    });
    console.log("'nmapScan' tool invocation successful. Result:", JSON.stringify(result, null, 2));
    return result;
  } catch (error) {
    console.error(\`Error invoking 'nmapScan' tool for target \${params.target}:\`, error);
    throw error;
  }
}

/**
 * Invokes the 'getInfo' tool on the MCP server.
 * @returns {Promise<Object>} - The result from the 'getInfo' tool.
 */
async function invokeGetInfo() {
  if (!clientInstance || !getInfoTool) {
    console.log("Client or getInfo tool not ready. Initializing...");
    await initializeNmapClient();
    if (!clientInstance || !getInfoTool) {
      throw new Error("Nmap MCP client is not initialized or getInfo tool not found.");
    }
  }
  
  console.log(\`Invoking GetInfo tool "\${getInfoTool.name}"\`);
  try {
    const result = await clientInstance.callTool({
      name: getInfoTool.name,
      arguments: {} // getInfo usually takes no arguments
    });
    console.log("'getInfo' tool invocation successful. Result:", JSON.stringify(result, null, 2));
    return result;
  } catch (error) {
    console.error("Error invoking 'getInfo' tool:", error);
    throw error;
  }
}


// --- Example Usage ---
async function main() {
  try {
    await initializeNmapClient();

    // Example: Get server info
    console.log("\\n--- Calling getInfo ---");
    const info = await invokeGetInfo();
    if (info && info.content && info.content[0] && info.content[0].text) {
        console.log("Server Info:", info.content[0].text);
    }


    // Example: Perform an Nmap scan
    console.log("\\n--- Calling nmapScan ---");
    const scanParams = {
      target: "scanme.nmap.org", // A safe target for testing
      flags: "-T4 -F" // Example flags: Fast scan, default timing
    };
    const scanResult = await invokeNmapScan(scanParams);
    if (scanResult && scanResult.content && scanResult.content[0] && scanResult.content[0].text) {
        console.log(\`Scan results for \${scanParams.target}:\`, scanResult.content[0].text);
    }

  } catch (error) {
    console.error("\\n--- Example Script Failed ---");
    console.error("An error occurred:", error.message);
  } finally {
    if (clientInstance) {
      console.log("\\nDisconnecting client...");
      await clientInstance.disconnect();
      console.log("Client disconnected.");
    }
  }
}

// Run the example
main();

export { initializeNmapClient, invokeNmapScan, invokeGetInfo };

To run this example client:

  1. Ensure you have Node.js installed.
  2. Create a new directory for your client project.
  3. Inside this directory, run npm init -y to create a package.json file.
  4. Install the necessary SDKs:
    npm install @modelcontextprotocol/sdk @smithery/sdk
    
  5. Save the code above as nmap_mcp_client_example.js in this directory.
  6. Modify the SMITHERY_NMAP_API_KEY and NMAP_SERVER_URL constants in the script with your actual Smithery API key and the URL of your deployed Nmap MCP server.
  7. If your package.json doesn't already have "type": "module", add it or change the import/export syntax to CommonJS (require/module.exports). For ES Modules (as written), add:
    // package.json
    {
      // ... other properties
      "type": "module" 
    }
    
  8. Run the client:
    node nmap_mcp_client_example.js
    

This client will connect to your Nmap MCP server, list available tools, call getInfo for server information, and then call nmapScan to perform a scan on scanme.nmap.org.