dotnet-docker-kali-mcp-server

timsonner/dotnet-docker-kali-mcp-server

3.1

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 dayong@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.

Don't use in production.

edit:

This was my first go at this and it implimented Docker DOOD architecture which binds to the hosts docker.socket, which gives the container control of the host's docker daemon which isn't great for security, but would be great for persistence, especially if the container is burried multiple containers deep as we only have visibility on the outer containers.

Use my newer repo if you want agentic AI pentesting with a Kali docker container.

https://github.com/timsonner/kali-mcp-server

Use this repo as a basis for learning or persistence mechanisms or a base for whatever.

The new new is waaaay better.

01/04/2026

the chat-kitties go meow

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.