dotnet-docker-kali-mcp-server

timsonner/dotnet-docker-kali-mcp-server

3.2

If you are the rightful owner of dotnet-docker-kali-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.

The dotnet-docker-kali-mcp-server is a specialized server setup that allows the execution of commands within a Kali Linux Docker container using the Model Context Protocol (MCP) server, integrated with Visual Studio Code.

dotnet-docker-kali-mcp-server

VS Code client runs commands as root in Kali docker image using "kali-exec" MCP server

Quick Start

Pull kali Docker image

docker pull docker.io/kalilinux/kali-rolling

Clone repo

git clone https://github.com/timsonner/dotnet-docker-kali-mcp-server.git
cd KaliMCP

Install dotnet MCP libraries

dotnet add package ModelContextProtocol --version 0.3.0-preview.4
dotnet add package Microsoft.Extensions.Hosting

Containerize the app

docker build -t kali-mcp-server .

Connect VS Code client to MCP server

docker mcp client connect vscode

Start MCP server

Click "Start" next to "kali-mcp-server" MCP server in mcp.json

Start new CoPilot chat

Hi, copilot. Run this command in terminal "docker run --rm -it -p 80:80 vulnerables/web-dvwa
" Once that completes, perform sqlmap against host.docker.internal:80 using kali-exec.

Manual Project Creation (not Quick Start)

Pull kali Docker image

docker pull docker.io/kalilinux/kali-rolling

Create project

mkdir <name of folder that contains project>
git init
dotnet new console -n <name of project>
mkdir -p .vscode && touch .vscode/mcp.json

Modify mcp.json

mcp.json

{
    "servers": {
        "kali-mcp-server": {
            "command": "docker",
            "args": [
                "run",
                "--rm",
                "-i",
                "--user",
                "root",
                "-v",
                "/var/run/docker.sock:/var/run/docker.sock",
                "kali-mcp-server"
            ],
            "type": "stdio"
        },
        "MCP_DOCKER": {
            "command": "docker",
            "args": [
                "mcp",
                "gateway",
                "run"
            ],
            "type": "stdio"
        }
    }
}   

Install dotnet MCP libraries

cd <project name>
dotnet add package ModelContextProtocol --version 0.3.0-preview.4
dotnet add package Microsoft.Extensions.Hosting

Modify Program.cs

program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);

// Configure all logs to go to stderr (stdout is used for the MCP protocol messages).
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);

// Add the MCP services: the transport to use (stdio) and the tools to register.
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

await builder.Build().RunAsync();

Create KaliLinuxToolset.cs

mkdir -p Tools
touch ./Tools/KaliLinuxToolset.cs

Modify KaliLinuxToolset.cs

using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using ModelContextProtocol.Server;

namespace KaliMCP.Tools;

[McpServerToolType]
public static class KaliLinuxToolset
{
    private const string DefaultImage = "kalilinux/kali-rolling";

    [McpServerTool(Name = "kali-exec"), Description("Runs a shell command inside the Kali Linux Docker image and returns the captured output.")]
    public static async Task<string> RunCommandAsync(
        [Description("The shell command to execute inside the container. The command is passed to bash -lc.")] string command,
        [Description("The Docker image to use. Defaults to kalilinux/kali-rolling.")] string? image,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(command))
        {
            throw new ArgumentException("Command must not be empty.", nameof(command));
        }

        string dockerImage = string.IsNullOrWhiteSpace(image) ? DefaultImage : image;

        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
            StandardOutputEncoding = Encoding.UTF8,
            StandardErrorEncoding = Encoding.UTF8,
        };

        psi.ArgumentList.Add("run");
        psi.ArgumentList.Add("--rm");
        psi.ArgumentList.Add(dockerImage);
        psi.ArgumentList.Add("bash");
        psi.ArgumentList.Add("-lc");
        psi.ArgumentList.Add(command);

        Process? process = null;
        try
        {
            process = new Process { StartInfo = psi };
            if (!process.Start())
            {
                throw new InvalidOperationException("Failed to start the Docker process.");
            }

            Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
            Task<string> stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);

            await process.WaitForExitAsync(cancellationToken);

            string stdout = await stdoutTask;
            string stderr = await stderrTask;

            var builder = new StringBuilder();
            builder.AppendLine($"Image: {dockerImage}");
            builder.AppendLine($"Command: {command}");
            builder.AppendLine($"ExitCode: {process.ExitCode}");

            if (!string.IsNullOrWhiteSpace(stdout))
            {
                builder.AppendLine("Stdout:");
                builder.AppendLine(stdout.TrimEnd());
            }

            if (!string.IsNullOrWhiteSpace(stderr))
            {
                builder.AppendLine("Stderr:");
                builder.AppendLine(stderr.TrimEnd());
            }

            return builder.ToString();
        }
        catch (Win32Exception ex)
        {
            throw new InvalidOperationException("The 'docker' executable was not found. Make sure Docker is installed and available in PATH.", ex);
        }
        catch (OperationCanceledException)
        {
            if (process is { HasExited: false })
            {
                try
                {
                    process.Kill(true);
                }
                catch
                {
                    // Ignore secondary errors when attempting to cancel the process.
                }
            }

            throw;
        }
        finally
        {
            process?.Dispose();
        }
    }
}

Initialize Docker

docker init

Accept defaults for all choices

Project Structure

.vscode
│   └── mcp.json
ā”œā”€ā”€ Program.cs
ā”œā”€ā”€ DockerFile
ā”œā”€ā”€ <project name>.csproj
└── Tools
    └── KaliLinuxToolset.cs

Modify DockerFile

Install Docker CLI inside of the Docker container (inception)

Replace these lines in DockerFile

FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
WORKDIR /app

with

FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final
WORKDIR /app

ARG DOCKER_CLI_VERSION=27.1.1
WORKDIR /app

# Install Docker CLI
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates curl tar \
    && rm -rf /var/lib/apt/lists/*

RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz" -o docker.tgz \
    && tar -xzf docker.tgz --strip-components=1 -C /usr/local/bin docker/docker \
    && rm -rf docker docker.tgz

Containerize dotnet app

docker build -t kali-mcp-server .

Connect VS Code client to MCP server

docker mcp client connect vscode

Reformat mcp.json

Right click > Format Document

Start MCP server

Click "Start" next to "kali-mcp-server" MCP server in mcp.json

Start new CoPilot chat

Hi, copilot. Return the exact output of kali-exec ls

Persistent Kali Docker container

One may have notices the above code interacts with a non-persistent Kali docker container and installed tools are gone at next prompt.

The following implimentation of KaliLinuxToolset.cs communicates with a persistent Kali Docker image, so installed tools persist through the chat session.

Modify KaliLinuxToolset.md

KaliLinuxToolset.cs

using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using ModelContextProtocol.Server;

namespace KaliMCP.Tools;

[McpServerToolType]
public static class KaliLinuxToolset
{
    private const string DefaultImage = "kalilinux/kali-rolling";
    private const string DefaultContainerName = "kali-mcp-persistent";
    private static readonly object _lockObject = new object();
    private static readonly SemaphoreSlim _containerSemaphore = new SemaphoreSlim(1, 1);

    [McpServerTool(Name = "kali-exec"), Description("Runs a shell command inside the persistent Kali Linux Docker container and returns the captured output.")]
    public static async Task<string> RunCommandAsync(
        [Description("The shell command to execute inside the container. The command is passed to bash -lc.")] string command,
        [Description("The Docker image to use. Defaults to kalilinux/kali-rolling.")] string? image,
        [Description("The container name to use. Defaults to kali-mcp-persistent.")] string? containerName,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(command))
        {
            throw new ArgumentException("Command must not be empty.", nameof(command));
        }

        string dockerImage = string.IsNullOrWhiteSpace(image) ? DefaultImage : image;
        string containerNameToUse = string.IsNullOrWhiteSpace(containerName) ? DefaultContainerName : containerName;

        // Ensure the container is running
        await EnsureContainerRunningAsync(dockerImage, containerNameToUse, cancellationToken);

        // Execute the command in the existing container
        return await ExecuteInContainerAsync(containerNameToUse, command, cancellationToken);
    }

    [McpServerTool(Name = "kali-container-status"), Description("Check the status of the persistent Kali Linux container.")]
    public static async Task<string> GetContainerStatusAsync(
        [Description("The container name to check. Defaults to kali-mcp-persistent.")] string? containerName,
        CancellationToken cancellationToken)
    {
        string containerNameToUse = string.IsNullOrWhiteSpace(containerName) ? DefaultContainerName : containerName;
        
        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        
        psi.ArgumentList.Add("ps");
        psi.ArgumentList.Add("-a");
        psi.ArgumentList.Add("--filter");
        psi.ArgumentList.Add($"name={containerNameToUse}");
        psi.ArgumentList.Add("--format");
        psi.ArgumentList.Add("table {{.Names}}\t{{.Status}}\t{{.Image}}");

        using var process = new Process { StartInfo = psi };
        if (!process.Start())
        {
            throw new InvalidOperationException("Failed to start the Docker process.");
        }

        string output = await process.StandardOutput.ReadToEndAsync(cancellationToken);
        await process.WaitForExitAsync(cancellationToken);

        return $"Container Status:\n{output}";
    }

    [McpServerTool(Name = "kali-container-restart"), Description("Restart the persistent Kali Linux container (useful if it becomes unresponsive).")]
    public static async Task<string> RestartContainerAsync(
        [Description("The Docker image to use. Defaults to kalilinux/kali-rolling.")] string? image,
        [Description("The container name to restart. Defaults to kali-mcp-persistent.")] string? containerName,
        CancellationToken cancellationToken)
    {
        string dockerImage = string.IsNullOrWhiteSpace(image) ? DefaultImage : image;
        string containerNameToUse = string.IsNullOrWhiteSpace(containerName) ? DefaultContainerName : containerName;

        lock (_lockObject)
        {
            // Stop and remove existing container
            Task.Run(async () => await StopAndRemoveContainerAsync(containerNameToUse, CancellationToken.None)).Wait();
        }

        // Start a new one
        await EnsureContainerRunningAsync(dockerImage, containerNameToUse, cancellationToken);
        
        return $"Container '{containerNameToUse}' has been restarted successfully.";
    }

    [McpServerTool(Name = "kali-container-stop"), Description("Stop the persistent Kali Linux container to free up system resources.")]
    public static async Task<string> StopContainerAsync(
        [Description("The container name to stop. Defaults to kali-mcp-persistent.")] string? containerName,
        [Description("Whether to also remove the container after stopping. If false, the container can be restarted later. Defaults to false.")] bool removeContainer = false,
        CancellationToken cancellationToken = default)
    {
        string containerNameToUse = string.IsNullOrWhiteSpace(containerName) ? DefaultContainerName : containerName;

        // Check if container exists first
        if (!ContainerExists(containerNameToUse))
        {
            return $"Container '{containerNameToUse}' does not exist.";
        }

        // Check if container is running
        bool wasRunning = IsContainerRunning(containerNameToUse);
        
        if (!wasRunning)
        {
            if (removeContainer)
            {
                await RemoveContainerAsync(containerNameToUse, cancellationToken);
                return $"Container '{containerNameToUse}' was already stopped and has been removed.";
            }
            return $"Container '{containerNameToUse}' is already stopped.";
        }

        // Stop the container
        var stopPsi = new ProcessStartInfo("docker")
        {
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        stopPsi.ArgumentList.Add("stop");
        stopPsi.ArgumentList.Add(containerNameToUse);

        using var stopProcess = new Process { StartInfo = stopPsi };
        if (!stopProcess.Start())
        {
            throw new InvalidOperationException("Failed to start the Docker process.");
        }

        await stopProcess.WaitForExitAsync(cancellationToken);
        
        if (stopProcess.ExitCode != 0)
        {
            string error = await stopProcess.StandardError.ReadToEndAsync(cancellationToken);
            throw new InvalidOperationException($"Failed to stop container: {error}");
        }

        // Optionally remove the container
        if (removeContainer)
        {
            await RemoveContainerAsync(containerNameToUse, cancellationToken);
            return $"Container '{containerNameToUse}' has been stopped and removed successfully.";
        }

        return $"Container '{containerNameToUse}' has been stopped successfully. Use kali-exec to restart it automatically, or use kali-container-restart for manual restart.";
    }

    private static async Task EnsureContainerRunningAsync(string dockerImage, string containerName, CancellationToken cancellationToken)
    {
        await _containerSemaphore.WaitAsync(cancellationToken);
        try
        {
            // Check if container exists and is running
            if (IsContainerRunning(containerName))
            {
                return;
            }

            // Check if container exists but is stopped
            if (ContainerExists(containerName))
            {
                // Start the existing container
                await StartContainerAsync(containerName, cancellationToken);
                return;
            }

            // Create and start a new container
            await CreateAndStartContainerAsync(dockerImage, containerName, cancellationToken);
        }
        finally
        {
            _containerSemaphore.Release();
        }
    }

    private static bool IsContainerRunning(string containerName)
    {
        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        
        psi.ArgumentList.Add("ps");
        psi.ArgumentList.Add("--filter");
        psi.ArgumentList.Add($"name={containerName}");
        psi.ArgumentList.Add("--filter");
        psi.ArgumentList.Add("status=running");
        psi.ArgumentList.Add("--quiet");

        using var process = Process.Start(psi);
        if (process == null) return false;
        
        string output = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        
        return !string.IsNullOrWhiteSpace(output);
    }

    private static bool ContainerExists(string containerName)
    {
        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        
        psi.ArgumentList.Add("ps");
        psi.ArgumentList.Add("-a");
        psi.ArgumentList.Add("--filter");
        psi.ArgumentList.Add($"name={containerName}");
        psi.ArgumentList.Add("--quiet");

        using var process = Process.Start(psi);
        if (process == null) return false;
        
        string output = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        
        return !string.IsNullOrWhiteSpace(output);
    }

    private static async Task CreateAndStartContainerAsync(string dockerImage, string containerName, CancellationToken cancellationToken)
    {
        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };

        psi.ArgumentList.Add("run");
        psi.ArgumentList.Add("-d");  // Detached mode
        psi.ArgumentList.Add("--name");
        psi.ArgumentList.Add(containerName);
        psi.ArgumentList.Add("--workdir");
        psi.ArgumentList.Add("/root");
        psi.ArgumentList.Add(dockerImage);
        psi.ArgumentList.Add("sleep");
        psi.ArgumentList.Add("infinity");  // Keep container running

        using var process = new Process { StartInfo = psi };
        if (!process.Start())
        {
            throw new InvalidOperationException("Failed to start the Docker process.");
        }

        await process.WaitForExitAsync(cancellationToken);
        
        if (process.ExitCode != 0)
        {
            string error = await process.StandardError.ReadToEndAsync(cancellationToken);
            throw new InvalidOperationException($"Failed to create container: {error}");
        }
    }

    private static async Task StartContainerAsync(string containerName, CancellationToken cancellationToken)
    {
        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };

        psi.ArgumentList.Add("start");
        psi.ArgumentList.Add(containerName);

        using var process = new Process { StartInfo = psi };
        if (!process.Start())
        {
            throw new InvalidOperationException("Failed to start the Docker process.");
        }

        await process.WaitForExitAsync(cancellationToken);
        
        if (process.ExitCode != 0)
        {
            string error = await process.StandardError.ReadToEndAsync(cancellationToken);
            throw new InvalidOperationException($"Failed to start container: {error}");
        }
    }

    private static async Task StopAndRemoveContainerAsync(string containerName, CancellationToken cancellationToken)
    {
        // Stop the container
        var stopPsi = new ProcessStartInfo("docker")
        {
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        stopPsi.ArgumentList.Add("stop");
        stopPsi.ArgumentList.Add(containerName);

        using (var stopProcess = Process.Start(stopPsi))
        {
            if (stopProcess != null)
            {
                await stopProcess.WaitForExitAsync(cancellationToken);
            }
        }

        // Remove the container
        await RemoveContainerAsync(containerName, cancellationToken);
    }

    private static async Task RemoveContainerAsync(string containerName, CancellationToken cancellationToken)
    {
        var rmPsi = new ProcessStartInfo("docker")
        {
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        rmPsi.ArgumentList.Add("rm");
        rmPsi.ArgumentList.Add(containerName);

        using var rmProcess = new Process { StartInfo = rmPsi };
        if (!rmProcess.Start())
        {
            throw new InvalidOperationException("Failed to start the Docker process.");
        }

        await rmProcess.WaitForExitAsync(cancellationToken);
        
        if (rmProcess.ExitCode != 0)
        {
            string error = await rmProcess.StandardError.ReadToEndAsync(cancellationToken);
            throw new InvalidOperationException($"Failed to remove container: {error}");
        }
    }

    private static async Task<string> ExecuteInContainerAsync(string containerName, string command, CancellationToken cancellationToken)
    {
        var psi = new ProcessStartInfo("docker")
        {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
            StandardOutputEncoding = Encoding.UTF8,
            StandardErrorEncoding = Encoding.UTF8,
        };

        psi.ArgumentList.Add("exec");
        psi.ArgumentList.Add(containerName);
        psi.ArgumentList.Add("bash");
        psi.ArgumentList.Add("-lc");
        psi.ArgumentList.Add(command);

        Process? process = null;
        try
        {
            process = new Process { StartInfo = psi };
            if (!process.Start())
            {
                throw new InvalidOperationException("Failed to start the Docker process.");
            }

            Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
            Task<string> stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);

            await process.WaitForExitAsync(cancellationToken);

            string stdout = await stdoutTask;
            string stderr = await stderrTask;

            var builder = new StringBuilder();
            builder.AppendLine($"Container: {containerName}");
            builder.AppendLine($"Command: {command}");
            builder.AppendLine($"ExitCode: {process.ExitCode}");

            if (!string.IsNullOrWhiteSpace(stdout))
            {
                builder.AppendLine("Stdout:");
                builder.AppendLine(stdout.TrimEnd());
            }

            if (!string.IsNullOrWhiteSpace(stderr))
            {
                builder.AppendLine("Stderr:");
                builder.AppendLine(stderr.TrimEnd());
            }

            return builder.ToString();
        }
        catch (Win32Exception ex)
        {
            throw new InvalidOperationException("The 'docker' executable was not found. Make sure Docker is installed and available in PATH.", ex);
        }
        catch (OperationCanceledException)
        {
            if (process is { HasExited: false })
            {
                try
                {
                    process.Kill(true);
                }
                catch
                {
                    // Ignore secondary errors when attempting to cancel the process.
                }
            }

            throw;
        }
        finally
        {
            process?.Dispose();
        }
    }
}

Containerize the app

docker build -t kali-mcp-server .

Restart the MCP server

In mcp.json restart the kali-mcp-server MCP server

Use the following MCP tools to manage the Kali Docker container

kali-container-status
kali-container-restart
kali-container-stop
kali-exec

Now u can haz teh hax

Hi, copilot. Run this command in terminal "docker run --rm -it -p 80:80 vulnerables/web-dvwa
" Once that completes, perform sqlmap against host.docker.internal:80 using kali-exec.