timsonner/dotnet-docker-kali-mcp-server
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.