docker_compose_advanced
Manage Docker Compose projects using natural language with plan and apply workflows for container orchestration.
Instructions
Manage Docker projects with natural language using plan+apply workflow
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform | |
| projectName | Yes | Unique name of the project | |
| containers | No | Describe containers you want (required for plan) | |
| command | No | Special commands |
Implementation Reference
- src/index.ts:1076-1155 (registration)Registration of the 'docker_compose_advanced' MCP tool, including input schema definition and the handler function that orchestrates plan/apply/destroy/status actions using supporting manager classes."docker_compose_advanced", { title: "Advanced Docker Compose Manager", description: "Manage Docker projects with natural language using plan+apply workflow", inputSchema: { action: z.enum(["plan", "apply", "destroy", "status"]).describe("Action to perform"), projectName: z.string().describe("Unique name of the project"), containers: z.string().optional().describe("Describe containers you want (required for plan)"), command: z.enum(["help", "apply", "down", "ps", "quiet", "verbose", "destroy"]).optional().describe("Special commands") } }, async ({ action, projectName, containers, command }) => { try { switch (action) { case "plan": if (!containers) { throw new Error("Container description is required for planning"); } const project = await DockerComposeManager.parseNaturalLanguage(containers, projectName); DockerComposeManager.setProject(projectName, project); const currentResources = await ProjectManager.getProjectResources(projectName); const plan = await DockerComposeManager.generatePlan(project, currentResources); return { content: [ { type: "text", text: `## Docker Compose Manager - Project: ${projectName}\n\n${plan}\n\n### Resources Currently Present:\n**Containers:** ${currentResources.containers.length}\n**Networks:** ${currentResources.networks.length}\n**Volumes:** ${currentResources.volumes.length}` } ] }; case "apply": const applyResult = await DockerComposeManager.applyPlan(projectName); return { content: [ { type: "text", text: applyResult } ] }; case "destroy": const destroyResult = await DockerComposeManager.destroyProject(projectName); return { content: [ { type: "text", text: destroyResult } ] }; case "status": const resources = await ProjectManager.getProjectResources(projectName); return { content: [ { type: "text", text: `## Project Status: ${projectName}\n\n**Containers:** ${resources.containers.length} (${resources.containers.filter(c => c.State === 'running').length} running)\n**Networks:** ${resources.networks.length}\n**Volumes:** ${resources.volumes.length}\n\n### Containers:\n${resources.containers.map(c => `- ${c.Names || c.name}: ${c.State || c.status}`).join('\n') || 'None'}\n\n### Networks:\n${resources.networks.map(n => `- ${n.Name || n.name}`).join('\n') || 'None'}\n\n### Volumes:\n${resources.volumes.map(v => `- ${v.Name || v.name}`).join('\n') || 'None'}` } ] }; } } catch (error) { return { content: [ { type: "text", text: `Error with Docker Compose operation: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } );
- src/index.ts:1081-1085 (schema)Input schema using Zod for validating tool parameters: action (plan/apply/destroy/status), projectName, optional containers description, and command.action: z.enum(["plan", "apply", "destroy", "status"]).describe("Action to perform"), projectName: z.string().describe("Unique name of the project"), containers: z.string().optional().describe("Describe containers you want (required for plan)"), command: z.enum(["help", "apply", "down", "ps", "quiet", "verbose", "destroy"]).optional().describe("Special commands") }
- src/index.ts:1087-1155 (handler)Handler function implementing the tool logic: parses natural language for plan, applies/destroys projects, and shows status by calling DockerComposeManager and ProjectManager methods.async ({ action, projectName, containers, command }) => { try { switch (action) { case "plan": if (!containers) { throw new Error("Container description is required for planning"); } const project = await DockerComposeManager.parseNaturalLanguage(containers, projectName); DockerComposeManager.setProject(projectName, project); const currentResources = await ProjectManager.getProjectResources(projectName); const plan = await DockerComposeManager.generatePlan(project, currentResources); return { content: [ { type: "text", text: `## Docker Compose Manager - Project: ${projectName}\n\n${plan}\n\n### Resources Currently Present:\n**Containers:** ${currentResources.containers.length}\n**Networks:** ${currentResources.networks.length}\n**Volumes:** ${currentResources.volumes.length}` } ] }; case "apply": const applyResult = await DockerComposeManager.applyPlan(projectName); return { content: [ { type: "text", text: applyResult } ] }; case "destroy": const destroyResult = await DockerComposeManager.destroyProject(projectName); return { content: [ { type: "text", text: destroyResult } ] }; case "status": const resources = await ProjectManager.getProjectResources(projectName); return { content: [ { type: "text", text: `## Project Status: ${projectName}\n\n**Containers:** ${resources.containers.length} (${resources.containers.filter(c => c.State === 'running').length} running)\n**Networks:** ${resources.networks.length}\n**Volumes:** ${resources.volumes.length}\n\n### Containers:\n${resources.containers.map(c => `- ${c.Names || c.name}: ${c.State || c.status}`).join('\n') || 'None'}\n\n### Networks:\n${resources.networks.map(n => `- ${n.Name || n.name}`).join('\n') || 'None'}\n\n### Volumes:\n${resources.volumes.map(v => `- ${v.Name || v.name}`).join('\n') || 'None'}` } ] }; } } catch (error) { return { content: [ { type: "text", text: `Error with Docker Compose operation: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } );
- src/index.ts:90-318 (helper)Core helper class providing advanced Docker Compose functionality: natural language parsing to service configs, plan generation (diff current vs desired), apply (create/start resources), and destroy.class DockerComposeManager { private static projects: Map<string, any> = new Map(); static async parseNaturalLanguage(description: string, projectName: string): Promise<ComposeProject> { // Advanced natural language parsing for multi-service deployments const services: Record<string, ServiceConfig> = {}; const networks: Record<string, any> = {}; const volumes: Record<string, any> = {}; // Parse common patterns if (description.includes('wordpress') || description.includes('wp')) { services.wordpress = { image: 'wordpress:latest', ports: ['9000:80'], environment: { WORDPRESS_DB_HOST: 'mysql', WORDPRESS_DB_USER: 'wordpress', WORDPRESS_DB_PASSWORD: 'wordpress', WORDPRESS_DB_NAME: 'wordpress' }, depends_on: ['mysql'] }; services.mysql = { image: 'mysql:8.0', environment: { MYSQL_DATABASE: 'wordpress', MYSQL_USER: 'wordpress', MYSQL_PASSWORD: 'wordpress', MYSQL_ROOT_PASSWORD: 'rootpassword' }, volumes: ['mysql-data:/var/lib/mysql'] }; volumes['mysql-data'] = {}; } if (description.includes('nginx')) { const port = description.match(/port\s+(\d+)/)?.[1] || '80'; services.nginx = { image: 'nginx:latest', ports: [`${port}:80`] }; } if (description.includes('redis')) { services.redis = { image: 'redis:alpine', ports: ['6379:6379'] }; } if (description.includes('postgres')) { services.postgres = { image: 'postgres:15', environment: { POSTGRES_DB: 'myapp', POSTGRES_USER: 'user', POSTGRES_PASSWORD: 'password' }, volumes: ['postgres-data:/var/lib/postgresql/data'] }; volumes['postgres-data'] = {}; } return { name: projectName, services, networks, volumes }; } static async generatePlan(project: any, currentResources: DockerProject): Promise<string> { const actions: string[] = []; // Compare desired vs current state const currentContainerNames = currentResources.containers.map(c => c.Names || c.name); const desiredServiceNames = Object.keys(project.services); // Plan container actions for (const serviceName of desiredServiceNames) { const containerName = `${project.name}-${serviceName}`; if (!currentContainerNames.some(name => name.includes(serviceName))) { actions.push(`CREATE container ${containerName} from ${project.services[serviceName].image}`); } } // Plan volume actions const currentVolumeNames = currentResources.volumes.map(v => v.Name || v.name); const desiredVolumeNames = Object.keys(project.volumes || {}); for (const volumeName of desiredVolumeNames) { const fullVolumeName = `${project.name}-${volumeName}`; if (!currentVolumeNames.includes(fullVolumeName)) { actions.push(`CREATE volume ${fullVolumeName}`); } } // Plan network actions if needed if (Object.keys(project.services).length > 1) { const networkName = `${project.name}-network`; const currentNetworkNames = currentResources.networks.map(n => n.Name || n.name); if (!currentNetworkNames.includes(networkName)) { actions.push(`CREATE network ${networkName}`); } } if (actions.length === 0) { return "No changes to make; project is up-to-date."; } return `## Plan\n\nI plan to take the following actions:\n\n${actions.map((action, i) => `${i + 1}. ${action}`).join('\n')}\n\nRespond \`apply\` to apply this plan. Otherwise, provide feedback and I will present you with an updated plan.`; } static async applyPlan(projectName: string): Promise<string> { const project = this.projects.get(projectName); if (!project) { throw new Error(`No plan found for project ${projectName}`); } const results: string[] = []; try { // Create network first if needed if (Object.keys(project.services).length > 1) { const networkName = `${projectName}-network`; const networkCmd = ProjectManager.addProjectLabel(`docker network create ${networkName}`, projectName); await executeDockerCommand(networkCmd); results.push(`✅ Created network ${networkName}`); } // Create volumes for (const volumeName of Object.keys(project.volumes || {})) { const fullVolumeName = `${projectName}-${volumeName}`; const volumeCmd = ProjectManager.addProjectLabel(`docker volume create ${fullVolumeName}`, projectName); await executeDockerCommand(volumeCmd); results.push(`✅ Created volume ${fullVolumeName}`); } // Create and start containers for (const [serviceName, config] of Object.entries(project.services) as [string, ServiceConfig][]) { const containerName = `${projectName}-${serviceName}`; let runCmd = `docker run -d --name ${containerName}`; // Add ports if (config.ports) { config.ports.forEach((port: string) => { runCmd += ` -p ${port}`; }); } // Add environment variables if (config.environment) { Object.entries(config.environment).forEach(([key, value]) => { runCmd += ` -e ${key}=${value}`; }); } // Add volumes if (config.volumes) { config.volumes.forEach((volume: string) => { if (volume.includes(':')) { // Replace volume name with project-prefixed name const [volumeName, mountPoint] = volume.split(':'); const fullVolumeName = `${projectName}-${volumeName}`; runCmd += ` -v ${fullVolumeName}:${mountPoint}`; } else { runCmd += ` -v ${volume}`; } }); } // Add network if (Object.keys(project.services).length > 1) { runCmd += ` --network ${projectName}-network`; } runCmd += ` ${config.image}`; const labeledCmd = ProjectManager.addProjectLabel(runCmd, projectName); await executeDockerCommand(labeledCmd); results.push(`✅ Created and started container ${containerName}`); } return `## Apply Complete\n\n${results.join('\n')}\n\nProject ${projectName} has been successfully deployed!`; } catch (error) { throw new Error(`Failed to apply plan: ${error instanceof Error ? error.message : String(error)}`); } } static async destroyProject(projectName: string): Promise<string> { const resources = await ProjectManager.getProjectResources(projectName); const results: string[] = []; // Stop and remove containers for (const container of resources.containers) { try { await executeDockerCommand(`docker stop ${container.ID || container.id}`); await executeDockerCommand(`docker rm ${container.ID || container.id}`); results.push(`✅ Removed container ${container.Names || container.name}`); } catch (error) { results.push(`❌ Failed to remove container ${container.Names || container.name}`); } } // Remove volumes for (const volume of resources.volumes) { try { await executeDockerCommand(`docker volume rm ${volume.Name || volume.name}`); results.push(`✅ Removed volume ${volume.Name || volume.name}`); } catch (error) { results.push(`❌ Failed to remove volume ${volume.Name || volume.name}`); } } // Remove networks for (const network of resources.networks) { try { await executeDockerCommand(`docker network rm ${network.Name || network.name}`); results.push(`✅ Removed network ${network.Name || network.name}`); } catch (error) { results.push(`❌ Failed to remove network ${network.Name || network.name}`); } } return `## Destroy Complete\n\n${results.join('\n')}\n\nProject ${projectName} has been destroyed.`; } static setProject(projectName: string, project: any): void { this.projects.set(projectName, project); } }
- src/index.ts:40-87 (helper)Helper for project-based resource tracking: labels resources with project name, lists project-specific resources, adds labels to docker commands.class ProjectManager { private static getProjectLabel(projectName: string): string { return `mcp-server-docker.project=${projectName}`; } static async getProjectResources(projectName: string): Promise<DockerProject> { const label = this.getProjectLabel(projectName); try { const [containers, networks, volumes] = await Promise.all([ executeDockerCommand(`docker ps -a --filter "label=${label}" --format "{{json .}}"`).then(r => r.stdout.trim().split('\n').filter(line => line).map(line => JSON.parse(line)) ).catch(() => []), executeDockerCommand(`docker network ls --filter "label=${label}" --format "{{json .}}"`).then(r => r.stdout.trim().split('\n').filter(line => line).map(line => JSON.parse(line)) ).catch(() => []), executeDockerCommand(`docker volume ls --filter "label=${label}" --format "{{json .}}"`).then(r => r.stdout.trim().split('\n').filter(line => line).map(line => JSON.parse(line)) ).catch(() => []) ]); return { name: projectName, containers, networks, volumes }; } catch (error) { return { name: projectName, containers: [], networks: [], volumes: [] }; } } static addProjectLabel(command: string, projectName: string): string { const label = this.getProjectLabel(projectName); // Add label to Docker run commands if (command.includes('docker run') || command.includes('docker create')) { return command.replace(/(docker (?:run|create))/, `$1 --label "${label}"`); } // Add label to Docker network create commands if (command.includes('docker network create')) { return command.replace(/(docker network create)/, `$1 --label "${label}"`); } // Add label to Docker volume create commands if (command.includes('docker volume create')) { return command.replace(/(docker volume create)/, `$1 --label "${label}"`); } return command; } }