import { z } from 'zod';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
/**
* Docker Tools
* Provides comprehensive Docker operations for autonomous container management
* Enables full container orchestration and debugging workflows
*/
// ========== HELPER TYPE ==========
type ToolResponse = {
content: Array<{ type: "text"; text: string }>;
isError?: boolean;
};
// Helper function to execute docker commands
async function executeDockerCommand(command: string, cwd?: string): Promise<ToolResponse> {
try {
const { stdout, stderr } = await execAsync(command, {
cwd: cwd || process.cwd(),
shell: '/bin/bash',
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for logs
timeout: 60000 // 60 second timeout for builds
});
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
success: true,
command: command,
stdout: stdout.trim(),
stderr: stderr.trim(),
cwd: cwd || process.cwd()
}, null, 2)
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
success: false,
command: command,
stdout: error.stdout?.trim() || '',
stderr: error.stderr?.trim() || error.message,
exitCode: error.code || 1,
cwd: cwd || process.cwd()
}, null, 2)
}
],
isError: true
};
}
}
// Helper function to detect docker-compose or docker compose (V2)
async function getDockerComposeCommand(): Promise<string> {
try {
// Try docker compose (V2) first
await execAsync('docker compose version', { timeout: 5000 });
return 'docker compose';
} catch {
// Fall back to docker-compose (V1)
return 'docker-compose';
}
}
// Cache for docker compose command to avoid repeated detection
let cachedDockerComposeCmd: string | null = null;
async function getComposeCmd(): Promise<string> {
if (cachedDockerComposeCmd) {
return cachedDockerComposeCmd;
}
cachedDockerComposeCmd = await getDockerComposeCommand();
return cachedDockerComposeCmd;
}
// ========== TOOL SCHEMAS ==========
export const dockerPsSchema = z.object({
all: z.boolean().optional().default(false).describe('Show all containers (default shows just running)'),
filter: z.string().optional().describe('Filter output based on conditions (e.g., "status=running")'),
format: z.enum(['table', 'json']).optional().default('table').describe('Output format'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerLogsSchema = z.object({
container: z.string().describe('Container name or ID'),
tail: z.number().optional().describe('Number of lines to show from the end of logs'),
follow: z.boolean().optional().default(false).describe('Follow log output (not recommended for MCP)'),
since: z.string().optional().describe('Show logs since timestamp (e.g., "2023-01-01T00:00:00")'),
timestamps: z.boolean().optional().default(false).describe('Show timestamps'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerExecSchema = z.object({
container: z.string().describe('Container name or ID'),
command: z.string().describe('Command to execute'),
workdir: z.string().optional().describe('Working directory inside container'),
user: z.string().optional().describe('User to run command as'),
env: z.record(z.string()).optional().describe('Environment variables'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerStartSchema = z.object({
containers: z.union([z.string(), z.array(z.string())]).describe('Container name(s) or ID(s)'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerStopSchema = z.object({
containers: z.union([z.string(), z.array(z.string())]).describe('Container name(s) or ID(s)'),
timeout: z.number().optional().describe('Seconds to wait before killing container'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerRestartSchema = z.object({
containers: z.union([z.string(), z.array(z.string())]).describe('Container name(s) or ID(s)'),
timeout: z.number().optional().describe('Seconds to wait before killing container'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerInspectSchema = z.object({
target: z.string().describe('Container, image, network, or volume name/ID'),
type: z.enum(['container', 'image', 'network', 'volume']).optional().describe('Type of object to inspect'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerBuildSchema = z.object({
path: z.string().describe('Build context path (directory containing Dockerfile)'),
tag: z.string().optional().describe('Name and optionally a tag (format: "name:tag")'),
dockerfile: z.string().optional().describe('Name of Dockerfile (default: Dockerfile)'),
buildArgs: z.record(z.string()).optional().describe('Build-time variables'),
target: z.string().optional().describe('Set target build stage'),
noCache: z.boolean().optional().default(false).describe('Do not use cache when building'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerImagesSchema = z.object({
filter: z.string().optional().describe('Filter images (e.g., "reference=node:*")'),
all: z.boolean().optional().default(false).describe('Show all images (default hides intermediate)'),
format: z.enum(['table', 'json']).optional().default('table').describe('Output format'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerPullSchema = z.object({
image: z.string().describe('Image name and tag (e.g., "nginx:latest")'),
allTags: z.boolean().optional().default(false).describe('Download all tagged images'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerComposeUpSchema = z.object({
detach: z.boolean().optional().default(true).describe('Detached mode: run in background'),
build: z.boolean().optional().default(false).describe('Build images before starting'),
services: z.array(z.string()).optional().describe('Only start specific services'),
file: z.string().optional().describe('Path to compose file (default: docker-compose.yml)'),
cwd: z.string().optional().describe('Working directory (where docker-compose.yml is located)')
});
export const dockerComposeDownSchema = z.object({
volumes: z.boolean().optional().default(false).describe('Remove named volumes'),
removeOrphans: z.boolean().optional().default(false).describe('Remove containers for services not in compose file'),
file: z.string().optional().describe('Path to compose file'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerComposePsSchema = z.object({
services: z.array(z.string()).optional().describe('Only show specific services'),
all: z.boolean().optional().default(false).describe('Show all stopped containers'),
file: z.string().optional().describe('Path to compose file'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerComposeLogsSchema = z.object({
services: z.array(z.string()).optional().describe('Only show logs for specific services'),
tail: z.number().optional().describe('Number of lines to show from end of logs'),
since: z.string().optional().describe('Show logs since timestamp'),
timestamps: z.boolean().optional().default(false).describe('Show timestamps'),
file: z.string().optional().describe('Path to compose file'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerRmSchema = z.object({
containers: z.union([z.string(), z.array(z.string())]).describe('Container name(s) or ID(s) to remove'),
force: z.boolean().optional().default(false).describe('Force removal of running containers'),
volumes: z.boolean().optional().default(false).describe('Remove anonymous volumes'),
cwd: z.string().optional().describe('Working directory')
});
export const dockerRmiSchema = z.object({
images: z.union([z.string(), z.array(z.string())]).describe('Image name(s) or ID(s) to remove'),
force: z.boolean().optional().default(false).describe('Force removal'),
cwd: z.string().optional().describe('Working directory')
});
// ========== TOOL IMPLEMENTATIONS ==========
export async function dockerPs(args: z.infer<typeof dockerPsSchema>): Promise<ToolResponse> {
const allFlag = args.all ? '-a' : '';
const filterFlag = args.filter ? `--filter "${args.filter}"` : '';
const formatFlag = args.format === 'json'
? '--format "{{json .}}"'
: '--format "table {{.ID}}\\t{{.Image}}\\t{{.Status}}\\t{{.Names}}\\t{{.Ports}}"';
return executeDockerCommand(`docker ps ${allFlag} ${filterFlag} ${formatFlag}`.trim(), args.cwd);
}
export async function dockerLogs(args: z.infer<typeof dockerLogsSchema>): Promise<ToolResponse> {
const tailFlag = args.tail ? `--tail ${args.tail}` : '';
const followFlag = args.follow ? '-f' : '';
const sinceFlag = args.since ? `--since ${args.since}` : '';
const timestampsFlag = args.timestamps ? '-t' : '';
return executeDockerCommand(
`docker logs ${tailFlag} ${followFlag} ${sinceFlag} ${timestampsFlag} ${args.container}`.trim(),
args.cwd
);
}
export async function dockerExec(args: z.infer<typeof dockerExecSchema>): Promise<ToolResponse> {
const workdirFlag = args.workdir ? `-w ${args.workdir}` : '';
const userFlag = args.user ? `-u ${args.user}` : '';
const envFlags = args.env
? Object.entries(args.env).map(([key, value]) => `-e ${key}="${value}"`).join(' ')
: '';
// Escape the command for shell execution
const escapedCommand = args.command.replace(/"/g, '\\"');
return executeDockerCommand(
`docker exec ${workdirFlag} ${userFlag} ${envFlags} ${args.container} sh -c "${escapedCommand}"`.trim(),
args.cwd
);
}
export async function dockerStart(args: z.infer<typeof dockerStartSchema>): Promise<ToolResponse> {
const containers = Array.isArray(args.containers) ? args.containers.join(' ') : args.containers;
return executeDockerCommand(`docker start ${containers}`, args.cwd);
}
export async function dockerStop(args: z.infer<typeof dockerStopSchema>): Promise<ToolResponse> {
const containers = Array.isArray(args.containers) ? args.containers.join(' ') : args.containers;
const timeoutFlag = args.timeout ? `-t ${args.timeout}` : '';
return executeDockerCommand(`docker stop ${timeoutFlag} ${containers}`.trim(), args.cwd);
}
export async function dockerRestart(args: z.infer<typeof dockerRestartSchema>): Promise<ToolResponse> {
const containers = Array.isArray(args.containers) ? args.containers.join(' ') : args.containers;
const timeoutFlag = args.timeout ? `-t ${args.timeout}` : '';
return executeDockerCommand(`docker restart ${timeoutFlag} ${containers}`.trim(), args.cwd);
}
export async function dockerInspect(args: z.infer<typeof dockerInspectSchema>): Promise<ToolResponse> {
const typeFlag = args.type ? `--type ${args.type}` : '';
return executeDockerCommand(`docker inspect ${typeFlag} ${args.target}`.trim(), args.cwd);
}
export async function dockerBuild(args: z.infer<typeof dockerBuildSchema>): Promise<ToolResponse> {
const tagFlag = args.tag ? `-t ${args.tag}` : '';
const dockerfileFlag = args.dockerfile ? `-f ${args.dockerfile}` : '';
const buildArgsFlags = args.buildArgs
? Object.entries(args.buildArgs).map(([key, value]) => `--build-arg ${key}="${value}"`).join(' ')
: '';
const targetFlag = args.target ? `--target ${args.target}` : '';
const noCacheFlag = args.noCache ? '--no-cache' : '';
return executeDockerCommand(
`docker build ${tagFlag} ${dockerfileFlag} ${buildArgsFlags} ${targetFlag} ${noCacheFlag} ${args.path}`.trim(),
args.cwd
);
}
export async function dockerImages(args: z.infer<typeof dockerImagesSchema>): Promise<ToolResponse> {
const allFlag = args.all ? '-a' : '';
const filterFlag = args.filter ? `--filter "${args.filter}"` : '';
const formatFlag = args.format === 'json'
? '--format "{{json .}}"'
: '--format "table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}"';
return executeDockerCommand(`docker images ${allFlag} ${filterFlag} ${formatFlag}`.trim(), args.cwd);
}
export async function dockerPull(args: z.infer<typeof dockerPullSchema>): Promise<ToolResponse> {
const allTagsFlag = args.allTags ? '-a' : '';
return executeDockerCommand(`docker pull ${allTagsFlag} ${args.image}`.trim(), args.cwd);
}
export async function dockerComposeUp(args: z.infer<typeof dockerComposeUpSchema>): Promise<ToolResponse> {
const detachFlag = args.detach ? '-d' : '';
const buildFlag = args.build ? '--build' : '';
const services = args.services ? args.services.join(' ') : '';
const fileFlag = args.file ? `-f ${args.file}` : '';
const composeCmd = await getComposeCmd();
return executeDockerCommand(
`${composeCmd} ${fileFlag} up ${detachFlag} ${buildFlag} ${services}`.trim(),
args.cwd
);
}
export async function dockerComposeDown(args: z.infer<typeof dockerComposeDownSchema>): Promise<ToolResponse> {
const volumesFlag = args.volumes ? '-v' : '';
const orphansFlag = args.removeOrphans ? '--remove-orphans' : '';
const fileFlag = args.file ? `-f ${args.file}` : '';
const composeCmd = await getComposeCmd();
return executeDockerCommand(
`${composeCmd} ${fileFlag} down ${volumesFlag} ${orphansFlag}`.trim(),
args.cwd
);
}
export async function dockerComposePs(args: z.infer<typeof dockerComposePsSchema>): Promise<ToolResponse> {
const allFlag = args.all ? '-a' : '';
const services = args.services ? args.services.join(' ') : '';
const fileFlag = args.file ? `-f ${args.file}` : '';
const composeCmd = await getComposeCmd();
return executeDockerCommand(
`${composeCmd} ${fileFlag} ps ${allFlag} ${services}`.trim(),
args.cwd
);
}
export async function dockerComposeLogs(args: z.infer<typeof dockerComposeLogsSchema>): Promise<ToolResponse> {
const tailFlag = args.tail ? `--tail ${args.tail}` : '';
const sinceFlag = args.since ? `--since ${args.since}` : '';
const timestampsFlag = args.timestamps ? '-t' : '';
const services = args.services ? args.services.join(' ') : '';
const fileFlag = args.file ? `-f ${args.file}` : '';
const composeCmd = await getComposeCmd();
return executeDockerCommand(
`${composeCmd} ${fileFlag} logs ${tailFlag} ${sinceFlag} ${timestampsFlag} ${services}`.trim(),
args.cwd
);
}
export async function dockerRm(args: z.infer<typeof dockerRmSchema>): Promise<ToolResponse> {
const containers = Array.isArray(args.containers) ? args.containers.join(' ') : args.containers;
const forceFlag = args.force ? '-f' : '';
const volumesFlag = args.volumes ? '-v' : '';
return executeDockerCommand(`docker rm ${forceFlag} ${volumesFlag} ${containers}`.trim(), args.cwd);
}
export async function dockerRmi(args: z.infer<typeof dockerRmiSchema>): Promise<ToolResponse> {
const images = Array.isArray(args.images) ? args.images.join(' ') : args.images;
const forceFlag = args.force ? '-f' : '';
return executeDockerCommand(`docker rmi ${forceFlag} ${images}`.trim(), args.cwd);
}
// ========== TOOL DEFINITIONS FOR MCP ==========
export const dockerTools = [
{
name: 'docker_ps',
description: 'List Docker containers with their status',
inputSchema: {
type: 'object',
properties: {
all: { type: 'boolean', default: false, description: 'Show all containers (default shows just running)' },
filter: { type: 'string', description: 'Filter output based on conditions (e.g., "status=running")' },
format: { type: 'string', enum: ['table', 'json'], default: 'table', description: 'Output format' },
cwd: { type: 'string', description: 'Working directory' }
}
}
},
{
name: 'docker_logs',
description: 'Fetch logs from a container',
inputSchema: {
type: 'object',
properties: {
container: { type: 'string', description: 'Container name or ID' },
tail: { type: 'number', description: 'Number of lines to show from the end of logs' },
follow: { type: 'boolean', default: false, description: 'Follow log output (not recommended for MCP)' },
since: { type: 'string', description: 'Show logs since timestamp (e.g., "2023-01-01T00:00:00")' },
timestamps: { type: 'boolean', default: false, description: 'Show timestamps' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['container']
}
},
{
name: 'docker_exec',
description: 'Execute a command inside a running container',
inputSchema: {
type: 'object',
properties: {
container: { type: 'string', description: 'Container name or ID' },
command: { type: 'string', description: 'Command to execute' },
workdir: { type: 'string', description: 'Working directory inside container' },
user: { type: 'string', description: 'User to run command as' },
env: { type: 'object', additionalProperties: { type: 'string' }, description: 'Environment variables' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['container', 'command']
}
},
{
name: 'docker_start',
description: 'Start one or more stopped containers',
inputSchema: {
type: 'object',
properties: {
containers: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Container name(s) or ID(s)'
},
cwd: { type: 'string', description: 'Working directory' }
},
required: ['containers']
}
},
{
name: 'docker_stop',
description: 'Stop one or more running containers',
inputSchema: {
type: 'object',
properties: {
containers: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Container name(s) or ID(s)'
},
timeout: { type: 'number', description: 'Seconds to wait before killing container' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['containers']
}
},
{
name: 'docker_restart',
description: 'Restart one or more containers',
inputSchema: {
type: 'object',
properties: {
containers: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Container name(s) or ID(s)'
},
timeout: { type: 'number', description: 'Seconds to wait before killing container' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['containers']
}
},
{
name: 'docker_inspect',
description: 'Return low-level information on Docker objects (containers, images, networks, volumes)',
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Container, image, network, or volume name/ID' },
type: { type: 'string', enum: ['container', 'image', 'network', 'volume'], description: 'Type of object to inspect' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['target']
}
},
{
name: 'docker_build',
description: 'Build a Docker image from a Dockerfile',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Build context path (directory containing Dockerfile)' },
tag: { type: 'string', description: 'Name and optionally a tag (format: "name:tag")' },
dockerfile: { type: 'string', description: 'Name of Dockerfile (default: Dockerfile)' },
buildArgs: { type: 'object', additionalProperties: { type: 'string' }, description: 'Build-time variables' },
target: { type: 'string', description: 'Set target build stage' },
noCache: { type: 'boolean', default: false, description: 'Do not use cache when building' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['path']
}
},
{
name: 'docker_images',
description: 'List Docker images',
inputSchema: {
type: 'object',
properties: {
filter: { type: 'string', description: 'Filter images (e.g., "reference=node:*")' },
all: { type: 'boolean', default: false, description: 'Show all images (default hides intermediate)' },
format: { type: 'string', enum: ['table', 'json'], default: 'table', description: 'Output format' },
cwd: { type: 'string', description: 'Working directory' }
}
}
},
{
name: 'docker_pull',
description: 'Pull an image or repository from a registry',
inputSchema: {
type: 'object',
properties: {
image: { type: 'string', description: 'Image name and tag (e.g., "nginx:latest")' },
allTags: { type: 'boolean', default: false, description: 'Download all tagged images' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['image']
}
},
{
name: 'docker_compose_up',
description: 'Create and start containers defined in docker-compose.yml',
inputSchema: {
type: 'object',
properties: {
detach: { type: 'boolean', default: true, description: 'Detached mode: run in background' },
build: { type: 'boolean', default: false, description: 'Build images before starting' },
services: { type: 'array', items: { type: 'string' }, description: 'Only start specific services' },
file: { type: 'string', description: 'Path to compose file (default: docker-compose.yml)' },
cwd: { type: 'string', description: 'Working directory (where docker-compose.yml is located)' }
}
}
},
{
name: 'docker_compose_down',
description: 'Stop and remove containers, networks created by docker-compose up',
inputSchema: {
type: 'object',
properties: {
volumes: { type: 'boolean', default: false, description: 'Remove named volumes' },
removeOrphans: { type: 'boolean', default: false, description: 'Remove containers for services not in compose file' },
file: { type: 'string', description: 'Path to compose file' },
cwd: { type: 'string', description: 'Working directory' }
}
}
},
{
name: 'docker_compose_ps',
description: 'List containers in a docker-compose stack',
inputSchema: {
type: 'object',
properties: {
services: { type: 'array', items: { type: 'string' }, description: 'Only show specific services' },
all: { type: 'boolean', default: false, description: 'Show all stopped containers' },
file: { type: 'string', description: 'Path to compose file' },
cwd: { type: 'string', description: 'Working directory' }
}
}
},
{
name: 'docker_compose_logs',
description: 'View output from containers in a docker-compose stack',
inputSchema: {
type: 'object',
properties: {
services: { type: 'array', items: { type: 'string' }, description: 'Only show logs for specific services' },
tail: { type: 'number', description: 'Number of lines to show from end of logs' },
since: { type: 'string', description: 'Show logs since timestamp' },
timestamps: { type: 'boolean', default: false, description: 'Show timestamps' },
file: { type: 'string', description: 'Path to compose file' },
cwd: { type: 'string', description: 'Working directory' }
}
}
},
{
name: 'docker_rm',
description: 'Remove one or more containers',
inputSchema: {
type: 'object',
properties: {
containers: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Container name(s) or ID(s) to remove'
},
force: { type: 'boolean', default: false, description: 'Force removal of running containers' },
volumes: { type: 'boolean', default: false, description: 'Remove anonymous volumes' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['containers']
}
},
{
name: 'docker_rmi',
description: 'Remove one or more images',
inputSchema: {
type: 'object',
properties: {
images: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Image name(s) or ID(s) to remove'
},
force: { type: 'boolean', default: false, description: 'Force removal' },
cwd: { type: 'string', description: 'Working directory' }
},
required: ['images']
}
}
];