docker_mcp 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +246 -10
- data/Rakefile +3 -3
- data/examples/.keep +0 -0
- data/exe/docker_mcp +8 -0
- data/lib/docker_mcp/build_image.rb +127 -0
- data/lib/docker_mcp/copy_to_container.rb +223 -0
- data/lib/docker_mcp/create_container.rb +171 -0
- data/lib/docker_mcp/create_network.rb +152 -0
- data/lib/docker_mcp/create_volume.rb +148 -0
- data/lib/docker_mcp/exec_container.rb +216 -0
- data/lib/docker_mcp/fetch_container_logs.rb +161 -0
- data/lib/docker_mcp/list_containers.rb +66 -0
- data/lib/docker_mcp/list_images.rb +80 -0
- data/lib/docker_mcp/list_networks.rb +84 -0
- data/lib/docker_mcp/list_volumes.rb +91 -0
- data/lib/docker_mcp/pull_image.rb +145 -0
- data/lib/docker_mcp/push_image.rb +179 -0
- data/lib/docker_mcp/recreate_container.rb +168 -0
- data/lib/docker_mcp/remove_container.rb +142 -0
- data/lib/docker_mcp/remove_image.rb +164 -0
- data/lib/docker_mcp/remove_network.rb +136 -0
- data/lib/docker_mcp/remove_volume.rb +146 -0
- data/lib/docker_mcp/run_container.rb +129 -0
- data/lib/docker_mcp/server.rb +108 -0
- data/lib/docker_mcp/start_container.rb +112 -0
- data/lib/docker_mcp/stop_container.rb +126 -0
- data/lib/docker_mcp/tag_image.rb +163 -0
- data/lib/docker_mcp/version.rb +11 -2
- data/lib/docker_mcp.rb +64 -3
- data/sig/docker_mcp.rbs +1 -1
- metadata +63 -6
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module DockerMCP
|
7
|
+
# MCP tool for pushing Docker images to registries.
|
8
|
+
#
|
9
|
+
# This tool provides the ability to upload Docker images to Docker registries
|
10
|
+
# such as Docker Hub, private registries, or cloud-based container registries.
|
11
|
+
# It uses the Docker CLI to leverage native credential handling and push
|
12
|
+
# capabilities.
|
13
|
+
#
|
14
|
+
# == Features
|
15
|
+
#
|
16
|
+
# - Push images to any accessible Docker registry
|
17
|
+
# - Flexible image and tag specification
|
18
|
+
# - Native Docker credential handling
|
19
|
+
# - Support for private registries
|
20
|
+
# - Comprehensive error handling
|
21
|
+
# - Validation of registry-compatible names
|
22
|
+
#
|
23
|
+
# == ⚠️ Security Considerations ⚠️
|
24
|
+
#
|
25
|
+
# Pushing images involves significant security risks:
|
26
|
+
# - **Credential Exposure**: Registry credentials may be exposed
|
27
|
+
# - **Data Exfiltration**: Images may contain sensitive application data
|
28
|
+
# - **Intellectual Property**: Source code and binaries may be exposed
|
29
|
+
# - **Supply Chain Risk**: Malicious actors could access pushed images
|
30
|
+
# - **Registry Access**: Unauthorized access to registry accounts
|
31
|
+
#
|
32
|
+
# Critical security measures:
|
33
|
+
# - Verify registry authentication and authorization
|
34
|
+
# - Scan images for secrets before pushing
|
35
|
+
# - Use private registries for sensitive applications
|
36
|
+
# - Implement image signing and verification
|
37
|
+
# - Monitor registry access and downloads
|
38
|
+
# - Regularly audit pushed image contents
|
39
|
+
#
|
40
|
+
# == Registry Requirements
|
41
|
+
#
|
42
|
+
# Images must be properly tagged for registry compatibility:
|
43
|
+
# - Include registry hostname for private registries
|
44
|
+
# - Include username/organization for Docker Hub
|
45
|
+
# - Examples: `username/myapp`, `registry.company.com/team/app`
|
46
|
+
# - Local image names (without `/`) cannot be pushed
|
47
|
+
#
|
48
|
+
# == Example Usage
|
49
|
+
#
|
50
|
+
# # Push to Docker Hub
|
51
|
+
# PushImage.call(
|
52
|
+
# server_context: context,
|
53
|
+
# name: "myusername/myapp",
|
54
|
+
# tag: "v1.0"
|
55
|
+
# )
|
56
|
+
#
|
57
|
+
# # Push to private registry
|
58
|
+
# PushImage.call(
|
59
|
+
# server_context: context,
|
60
|
+
# name: "registry.company.com/team/app",
|
61
|
+
# tag: "latest"
|
62
|
+
# )
|
63
|
+
#
|
64
|
+
# # Push with full repo specification
|
65
|
+
# PushImage.call(
|
66
|
+
# server_context: context,
|
67
|
+
# name: "myapp",
|
68
|
+
# repo_tag: "myregistry.com/myuser/myapp:v2.0"
|
69
|
+
# )
|
70
|
+
#
|
71
|
+
# @see PullImage
|
72
|
+
# @see TagImage
|
73
|
+
# @see BuildImage
|
74
|
+
# @since 0.1.0
|
75
|
+
class PushImage < MCP::Tool
|
76
|
+
description 'Push a Docker image'
|
77
|
+
|
78
|
+
input_schema(
|
79
|
+
properties: {
|
80
|
+
name: {
|
81
|
+
type: 'string',
|
82
|
+
description: 'Image name or ID to push'
|
83
|
+
},
|
84
|
+
tag: {
|
85
|
+
type: 'string',
|
86
|
+
description: 'Tag to push (optional, pushes all tags if not specified)'
|
87
|
+
},
|
88
|
+
repo_tag: {
|
89
|
+
type: 'string',
|
90
|
+
description: 'Full repo:tag to push (e.g., "registry/repo:tag") (optional)'
|
91
|
+
}
|
92
|
+
},
|
93
|
+
required: ['name']
|
94
|
+
)
|
95
|
+
|
96
|
+
# Push a Docker image to a registry.
|
97
|
+
#
|
98
|
+
# This method uploads the specified image to a Docker registry using
|
99
|
+
# the Docker CLI for native credential handling. The image must be
|
100
|
+
# properly tagged for registry compatibility.
|
101
|
+
#
|
102
|
+
# @param name [String] image name or ID to push
|
103
|
+
# @param server_context [Object] MCP server context (unused but required)
|
104
|
+
# @param tag [String, nil] specific tag to push
|
105
|
+
# @param repo_tag [String, nil] complete repository:tag specification
|
106
|
+
#
|
107
|
+
# @return [MCP::Tool::Response] push operation results
|
108
|
+
#
|
109
|
+
# @raise [StandardError] for push failures or authentication issues
|
110
|
+
#
|
111
|
+
# @example Push tagged image to Docker Hub
|
112
|
+
# response = PushImage.call(
|
113
|
+
# server_context: context,
|
114
|
+
# name: "myuser/webapp",
|
115
|
+
# tag: "v1.2.3"
|
116
|
+
# )
|
117
|
+
#
|
118
|
+
# @example Push to private registry
|
119
|
+
# response = PushImage.call(
|
120
|
+
# server_context: context,
|
121
|
+
# name: "internal-registry.com/team/service",
|
122
|
+
# tag: "latest"
|
123
|
+
# )
|
124
|
+
#
|
125
|
+
# @see Docker::Image.get
|
126
|
+
def self.call(name:, server_context:, tag: nil, repo_tag: nil)
|
127
|
+
# Construct the full image identifier
|
128
|
+
image_identifier = tag ? "#{name}:#{tag}" : name
|
129
|
+
|
130
|
+
# Validate that the image name includes a registry/username
|
131
|
+
# Images without a registry prefix will fail to push to Docker Hub
|
132
|
+
unless name.include?('/') || repo_tag&.include?('/')
|
133
|
+
error_msg = 'Error: Image name must include registry/username ' \
|
134
|
+
"(e.g., 'username/#{name}'). Local images cannot be " \
|
135
|
+
'pushed without a registry prefix.'
|
136
|
+
return MCP::Tool::Response.new([{
|
137
|
+
type: 'text',
|
138
|
+
text: error_msg
|
139
|
+
}])
|
140
|
+
end
|
141
|
+
|
142
|
+
# Verify the image exists
|
143
|
+
begin
|
144
|
+
Docker::Image.get(image_identifier)
|
145
|
+
rescue Docker::Error::NotFoundError
|
146
|
+
return MCP::Tool::Response.new([{
|
147
|
+
type: 'text',
|
148
|
+
text: "Image #{image_identifier} not found"
|
149
|
+
}])
|
150
|
+
end
|
151
|
+
|
152
|
+
# Use the Docker CLI to push the image
|
153
|
+
# This way we leverage Docker's native credential handling
|
154
|
+
push_target = repo_tag || image_identifier
|
155
|
+
_, stderr, status = Open3.capture3('docker', 'push', push_target)
|
156
|
+
|
157
|
+
if status.success?
|
158
|
+
MCP::Tool::Response.new([{
|
159
|
+
type: 'text',
|
160
|
+
text: "Image #{push_target} pushed successfully"
|
161
|
+
}])
|
162
|
+
else
|
163
|
+
# Extract the error message from stderr
|
164
|
+
error_msg = stderr.strip
|
165
|
+
error_msg = 'Failed to push image' if error_msg.empty?
|
166
|
+
|
167
|
+
MCP::Tool::Response.new([{
|
168
|
+
type: 'text',
|
169
|
+
text: "Error pushing image: #{error_msg}"
|
170
|
+
}])
|
171
|
+
end
|
172
|
+
rescue StandardError => e
|
173
|
+
MCP::Tool::Response.new([{
|
174
|
+
type: 'text',
|
175
|
+
text: "Error pushing image: #{e.message}"
|
176
|
+
}])
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DockerMCP
|
4
|
+
# MCP tool for recreating Docker containers with the same configuration.
|
5
|
+
#
|
6
|
+
# This tool provides a convenient way to recreate containers while preserving
|
7
|
+
# their original configuration. It stops and removes the existing container,
|
8
|
+
# then creates a new one with identical settings. This is useful for applying
|
9
|
+
# image updates, clearing container state, or resolving container issues.
|
10
|
+
#
|
11
|
+
# == Features
|
12
|
+
#
|
13
|
+
# - Preserves complete container configuration
|
14
|
+
# - Maintains original name and settings
|
15
|
+
# - Handles running containers gracefully
|
16
|
+
# - Restores running state after recreation
|
17
|
+
# - Configurable stop timeout
|
18
|
+
# - Comprehensive error handling
|
19
|
+
#
|
20
|
+
# == Process Overview
|
21
|
+
#
|
22
|
+
# 1. Retrieve existing container configuration
|
23
|
+
# 2. Stop container gracefully (if running)
|
24
|
+
# 3. Remove the old container
|
25
|
+
# 4. Create new container with identical config
|
26
|
+
# 5. Start new container (if original was running)
|
27
|
+
#
|
28
|
+
# == ⚠️ Data Loss Warning ⚠️
|
29
|
+
#
|
30
|
+
# **DESTRUCTIVE OPERATION - DATA LOSS POSSIBLE**
|
31
|
+
#
|
32
|
+
# This operation can cause permanent data loss:
|
33
|
+
# - Container filesystem changes are lost
|
34
|
+
# - Temporary data and logs are deleted
|
35
|
+
# - Container state is reset completely
|
36
|
+
# - Network connections are interrupted
|
37
|
+
# - Anonymous volumes may be recreated empty
|
38
|
+
#
|
39
|
+
# == Security Considerations
|
40
|
+
#
|
41
|
+
# - Original configuration is preserved exactly
|
42
|
+
# - Sensitive environment variables are maintained
|
43
|
+
# - Port mappings and volume mounts are restored
|
44
|
+
# - Network access patterns remain the same
|
45
|
+
#
|
46
|
+
# Ensure original configuration is still secure:
|
47
|
+
# - Review exposed ports and volumes
|
48
|
+
# - Validate environment variables
|
49
|
+
# - Check image security updates
|
50
|
+
# - Verify network policies
|
51
|
+
#
|
52
|
+
# == Example Usage
|
53
|
+
#
|
54
|
+
# # Recreate with default timeout
|
55
|
+
# RecreateContainer.call(
|
56
|
+
# server_context: context,
|
57
|
+
# id: "web-server"
|
58
|
+
# )
|
59
|
+
#
|
60
|
+
# # Recreate with longer stop timeout
|
61
|
+
# RecreateContainer.call(
|
62
|
+
# server_context: context,
|
63
|
+
# id: "database",
|
64
|
+
# timeout: 30
|
65
|
+
# )
|
66
|
+
#
|
67
|
+
# @see CreateContainer
|
68
|
+
# @see StopContainer
|
69
|
+
# @see RemoveContainer
|
70
|
+
# @see Docker::Container.create
|
71
|
+
# @since 0.1.0
|
72
|
+
class RecreateContainer < MCP::Tool
|
73
|
+
description 'Recreate a Docker container (stops, removes, and recreates with same configuration)'
|
74
|
+
|
75
|
+
input_schema(
|
76
|
+
properties: {
|
77
|
+
id: {
|
78
|
+
type: 'string',
|
79
|
+
description: 'Container ID or name to recreate'
|
80
|
+
},
|
81
|
+
timeout: {
|
82
|
+
type: 'integer',
|
83
|
+
description: 'Seconds to wait before killing the container when stopping (default: 10)'
|
84
|
+
}
|
85
|
+
},
|
86
|
+
required: ['id']
|
87
|
+
)
|
88
|
+
|
89
|
+
# Recreate a Docker container with identical configuration.
|
90
|
+
#
|
91
|
+
# This method performs a complete container recreation cycle while
|
92
|
+
# preserving all configuration settings. The new container will have
|
93
|
+
# the same name, environment, port mappings, volumes, and other
|
94
|
+
# settings as the original.
|
95
|
+
#
|
96
|
+
# @param id [String] container ID (full or short) or container name
|
97
|
+
# @param server_context [Object] MCP server context (unused but required)
|
98
|
+
# @param timeout [Integer] seconds to wait before force killing during stop (default: 10)
|
99
|
+
#
|
100
|
+
# @return [MCP::Tool::Response] recreation results with new container ID
|
101
|
+
#
|
102
|
+
# @raise [Docker::Error::NotFoundError] if container doesn't exist
|
103
|
+
# @raise [StandardError] for recreation failures
|
104
|
+
#
|
105
|
+
# @example Recreate application container
|
106
|
+
# response = RecreateContainer.call(
|
107
|
+
# server_context: context,
|
108
|
+
# id: "my-app"
|
109
|
+
# )
|
110
|
+
#
|
111
|
+
# @example Recreate database with extended timeout
|
112
|
+
# response = RecreateContainer.call(
|
113
|
+
# server_context: context,
|
114
|
+
# id: "postgres-main",
|
115
|
+
# timeout: 60 # Allow time for DB shutdown
|
116
|
+
# )
|
117
|
+
#
|
118
|
+
# @see Docker::Container.get
|
119
|
+
# @see Docker::Container.create
|
120
|
+
def self.call(id:, server_context:, timeout: 10)
|
121
|
+
# Get the existing container
|
122
|
+
old_container = Docker::Container.get(id)
|
123
|
+
config = old_container.json
|
124
|
+
|
125
|
+
# Extract configuration we need to preserve
|
126
|
+
image = config['Config']['Image']
|
127
|
+
name = config['Name']&.delete_prefix('/')
|
128
|
+
cmd = config['Config']['Cmd']
|
129
|
+
env = config['Config']['Env']
|
130
|
+
exposed_ports = config['Config']['ExposedPorts']
|
131
|
+
host_config = config['HostConfig']
|
132
|
+
|
133
|
+
# Stop and remove the old container
|
134
|
+
old_container.stop('timeout' => timeout) if config['State']['Running']
|
135
|
+
old_container.delete
|
136
|
+
|
137
|
+
# Create new container with same config
|
138
|
+
new_config = {
|
139
|
+
'Image' => image,
|
140
|
+
'Cmd' => cmd,
|
141
|
+
'Env' => env,
|
142
|
+
'ExposedPorts' => exposed_ports,
|
143
|
+
'HostConfig' => host_config
|
144
|
+
}
|
145
|
+
new_config['name'] = name if name
|
146
|
+
|
147
|
+
new_container = Docker::Container.create(new_config)
|
148
|
+
|
149
|
+
# Start if the old one was running
|
150
|
+
new_container.start if config['State']['Running']
|
151
|
+
|
152
|
+
MCP::Tool::Response.new([{
|
153
|
+
type: 'text',
|
154
|
+
text: "Container #{id} recreated successfully. New ID: #{new_container.id}"
|
155
|
+
}])
|
156
|
+
rescue Docker::Error::NotFoundError
|
157
|
+
MCP::Tool::Response.new([{
|
158
|
+
type: 'text',
|
159
|
+
text: "Container #{id} not found"
|
160
|
+
}])
|
161
|
+
rescue StandardError => e
|
162
|
+
MCP::Tool::Response.new([{
|
163
|
+
type: 'text',
|
164
|
+
text: "Error recreating container: #{e.message}"
|
165
|
+
}])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DockerMCP
|
4
|
+
# MCP tool for removing Docker containers.
|
5
|
+
#
|
6
|
+
# This tool provides the ability to permanently delete Docker containers
|
7
|
+
# from the system. It supports both graceful removal of stopped containers
|
8
|
+
# and forced removal of running containers. Optionally, it can also remove
|
9
|
+
# associated anonymous volumes.
|
10
|
+
#
|
11
|
+
# == Features
|
12
|
+
#
|
13
|
+
# - Remove stopped containers safely
|
14
|
+
# - Force removal of running containers
|
15
|
+
# - Optional removal of associated volumes
|
16
|
+
# - Comprehensive error handling
|
17
|
+
# - Works with containers by ID or name
|
18
|
+
#
|
19
|
+
# == Data Loss Warning
|
20
|
+
#
|
21
|
+
# ⚠️ **DESTRUCTIVE OPERATION** ⚠️
|
22
|
+
#
|
23
|
+
# This operation permanently deletes containers and potentially data:
|
24
|
+
# - Container filesystem changes are lost forever
|
25
|
+
# - Running processes are killed immediately (with force)
|
26
|
+
# - Associated volumes may be removed if specified
|
27
|
+
# - Container logs and metadata are deleted
|
28
|
+
# - Operation cannot be undone
|
29
|
+
#
|
30
|
+
# == Security Considerations
|
31
|
+
#
|
32
|
+
# - Forced removal can cause data corruption
|
33
|
+
# - Volume removal may affect other containers
|
34
|
+
# - Running container removal terminates services abruptly
|
35
|
+
# - Sensitive data in container memory is not securely wiped
|
36
|
+
#
|
37
|
+
# Best practices:
|
38
|
+
# - Stop containers gracefully before removal
|
39
|
+
# - Backup important data before removing
|
40
|
+
# - Verify volume dependencies before volume removal
|
41
|
+
# - Use force removal only when necessary
|
42
|
+
#
|
43
|
+
# == Example Usage
|
44
|
+
#
|
45
|
+
# # Remove stopped container
|
46
|
+
# RemoveContainer.call(
|
47
|
+
# server_context: context,
|
48
|
+
# id: "old-container"
|
49
|
+
# )
|
50
|
+
#
|
51
|
+
# # Force remove running container with volumes
|
52
|
+
# RemoveContainer.call(
|
53
|
+
# server_context: context,
|
54
|
+
# id: "problematic-container",
|
55
|
+
# force: true,
|
56
|
+
# volumes: true
|
57
|
+
# )
|
58
|
+
#
|
59
|
+
# @see StopContainer
|
60
|
+
# @see CreateContainer
|
61
|
+
# @see Docker::Container#delete
|
62
|
+
# @since 0.1.0
|
63
|
+
class RemoveContainer < MCP::Tool
|
64
|
+
description 'Remove a Docker container'
|
65
|
+
|
66
|
+
input_schema(
|
67
|
+
properties: {
|
68
|
+
id: {
|
69
|
+
type: 'string',
|
70
|
+
description: 'Container ID or name'
|
71
|
+
},
|
72
|
+
force: {
|
73
|
+
type: 'boolean',
|
74
|
+
description: 'Force removal of running container (default: false)'
|
75
|
+
},
|
76
|
+
volumes: {
|
77
|
+
type: 'boolean',
|
78
|
+
description: 'Remove associated volumes (default: false)'
|
79
|
+
}
|
80
|
+
},
|
81
|
+
required: ['id']
|
82
|
+
)
|
83
|
+
|
84
|
+
# Remove a Docker container permanently.
|
85
|
+
#
|
86
|
+
# This method deletes a container from the Docker system. By default,
|
87
|
+
# it only removes stopped containers. The force option allows removal
|
88
|
+
# of running containers, and the volumes option removes associated
|
89
|
+
# anonymous volumes.
|
90
|
+
#
|
91
|
+
# @param id [String] container ID (full or short) or container name
|
92
|
+
# @param server_context [Object] MCP server context (unused but required)
|
93
|
+
# @param force [Boolean] whether to force remove running containers (default: false)
|
94
|
+
# @param volumes [Boolean] whether to remove associated volumes (default: false)
|
95
|
+
#
|
96
|
+
# @return [MCP::Tool::Response] removal operation results
|
97
|
+
#
|
98
|
+
# @raise [Docker::Error::NotFoundError] if container doesn't exist
|
99
|
+
# @raise [StandardError] for other removal failures
|
100
|
+
#
|
101
|
+
# @example Remove stopped container
|
102
|
+
# response = RemoveContainer.call(
|
103
|
+
# server_context: context,
|
104
|
+
# id: "finished-job"
|
105
|
+
# )
|
106
|
+
#
|
107
|
+
# @example Force remove running container
|
108
|
+
# response = RemoveContainer.call(
|
109
|
+
# server_context: context,
|
110
|
+
# id: "stuck-container",
|
111
|
+
# force: true
|
112
|
+
# )
|
113
|
+
#
|
114
|
+
# @example Remove container and its volumes
|
115
|
+
# response = RemoveContainer.call(
|
116
|
+
# server_context: context,
|
117
|
+
# id: "temp-container",
|
118
|
+
# volumes: true
|
119
|
+
# )
|
120
|
+
#
|
121
|
+
# @see Docker::Container#delete
|
122
|
+
def self.call(id:, server_context:, force: false, volumes: false)
|
123
|
+
container = Docker::Container.get(id)
|
124
|
+
container.delete(force: force, v: volumes)
|
125
|
+
|
126
|
+
MCP::Tool::Response.new([{
|
127
|
+
type: 'text',
|
128
|
+
text: "Container #{id} removed successfully"
|
129
|
+
}])
|
130
|
+
rescue Docker::Error::NotFoundError
|
131
|
+
MCP::Tool::Response.new([{
|
132
|
+
type: 'text',
|
133
|
+
text: "Container #{id} not found"
|
134
|
+
}])
|
135
|
+
rescue StandardError => e
|
136
|
+
MCP::Tool::Response.new([{
|
137
|
+
type: 'text',
|
138
|
+
text: "Error removing container: #{e.message}"
|
139
|
+
}])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DockerMCP
|
4
|
+
# MCP tool for removing Docker images.
|
5
|
+
#
|
6
|
+
# This tool provides the ability to permanently delete Docker images from
|
7
|
+
# the local system to free up disk space and remove unused images. It
|
8
|
+
# supports both regular and forced removal with options for parent image
|
9
|
+
# handling.
|
10
|
+
#
|
11
|
+
# == Features
|
12
|
+
#
|
13
|
+
# - Remove images by ID, name, or name:tag
|
14
|
+
# - Force removal of images in use
|
15
|
+
# - Control parent image cleanup
|
16
|
+
# - Comprehensive error handling
|
17
|
+
# - Safe removal with dependency checking
|
18
|
+
#
|
19
|
+
# == ⚠️ Data Loss Warning ⚠️
|
20
|
+
#
|
21
|
+
# **DESTRUCTIVE OPERATION - PERMANENT DATA LOSS**
|
22
|
+
#
|
23
|
+
# This operation permanently deletes images and associated data:
|
24
|
+
# - Image layers are deleted from disk
|
25
|
+
# - All tags pointing to the image are removed
|
26
|
+
# - Operation cannot be undone
|
27
|
+
# - Dependent containers may become unusable
|
28
|
+
# - Custom modifications to images are lost
|
29
|
+
#
|
30
|
+
# == Dependency Management
|
31
|
+
#
|
32
|
+
# Docker images have complex dependency relationships:
|
33
|
+
# - **Child Images**: Images built FROM this image
|
34
|
+
# - **Parent Images**: Base images this image depends on
|
35
|
+
# - **Running Containers**: Containers using this image
|
36
|
+
# - **Stopped Containers**: Containers that could be restarted
|
37
|
+
#
|
38
|
+
# == Security Considerations
|
39
|
+
#
|
40
|
+
# Image removal affects system security posture:
|
41
|
+
# - Removes potentially vulnerable software
|
42
|
+
# - May break running applications
|
43
|
+
# - Could remove security patches
|
44
|
+
# - Affects rollback capabilities
|
45
|
+
#
|
46
|
+
# Best practices:
|
47
|
+
# - Verify no critical containers depend on the image
|
48
|
+
# - Backup important images before removal
|
49
|
+
# - Use force removal judiciously
|
50
|
+
# - Monitor disk space after removal
|
51
|
+
# - Maintain image inventory documentation
|
52
|
+
#
|
53
|
+
# == Removal Options
|
54
|
+
#
|
55
|
+
# - **Normal Removal**: Only removes if no containers use the image
|
56
|
+
# - **Force Removal**: Removes even if containers depend on it
|
57
|
+
# - **Prune Parents**: Automatically removes unused parent images
|
58
|
+
# - **No Prune**: Keeps parent images even if unused
|
59
|
+
#
|
60
|
+
# == Example Usage
|
61
|
+
#
|
62
|
+
# # Safe removal of unused image
|
63
|
+
# RemoveImage.call(
|
64
|
+
# server_context: context,
|
65
|
+
# id: "old-app:v1.0"
|
66
|
+
# )
|
67
|
+
#
|
68
|
+
# # Force removal of image in use
|
69
|
+
# RemoveImage.call(
|
70
|
+
# server_context: context,
|
71
|
+
# id: "broken-image:latest",
|
72
|
+
# force: true
|
73
|
+
# )
|
74
|
+
#
|
75
|
+
# # Remove image but keep parent layers
|
76
|
+
# RemoveImage.call(
|
77
|
+
# server_context: context,
|
78
|
+
# id: "temp-build:abc123",
|
79
|
+
# noprune: true
|
80
|
+
# )
|
81
|
+
#
|
82
|
+
# @see ListImages
|
83
|
+
# @see BuildImage
|
84
|
+
# @see Docker::Image#remove
|
85
|
+
# @since 0.1.0
|
86
|
+
class RemoveImage < MCP::Tool
|
87
|
+
description 'Remove a Docker image'
|
88
|
+
|
89
|
+
input_schema(
|
90
|
+
properties: {
|
91
|
+
id: {
|
92
|
+
type: 'string',
|
93
|
+
description: 'Image ID, name, or name:tag'
|
94
|
+
},
|
95
|
+
force: {
|
96
|
+
type: 'boolean',
|
97
|
+
description: 'Force removal of the image (default: false)'
|
98
|
+
},
|
99
|
+
noprune: {
|
100
|
+
type: 'boolean',
|
101
|
+
description: 'Do not delete untagged parents (default: false)'
|
102
|
+
}
|
103
|
+
},
|
104
|
+
required: ['id']
|
105
|
+
)
|
106
|
+
|
107
|
+
# Remove a Docker image from the local system.
|
108
|
+
#
|
109
|
+
# This method permanently deletes the specified image from local storage.
|
110
|
+
# By default, it performs safety checks to prevent removal of images with
|
111
|
+
# dependent containers. Force removal bypasses these checks.
|
112
|
+
#
|
113
|
+
# @param id [String] image ID, name, or name:tag to remove
|
114
|
+
# @param server_context [Object] MCP server context (unused but required)
|
115
|
+
# @param force [Boolean] whether to force removal despite dependencies (default: false)
|
116
|
+
# @param noprune [Boolean] whether to preserve parent images (default: false)
|
117
|
+
#
|
118
|
+
# @return [MCP::Tool::Response] removal operation results
|
119
|
+
#
|
120
|
+
# @raise [Docker::Error::NotFoundError] if image doesn't exist
|
121
|
+
# @raise [StandardError] for removal failures or dependency conflicts
|
122
|
+
#
|
123
|
+
# @example Remove unused image
|
124
|
+
# response = RemoveImage.call(
|
125
|
+
# server_context: context,
|
126
|
+
# id: "old-version:1.0"
|
127
|
+
# )
|
128
|
+
#
|
129
|
+
# @example Force remove problematic image
|
130
|
+
# response = RemoveImage.call(
|
131
|
+
# server_context: context,
|
132
|
+
# id: "corrupted-image",
|
133
|
+
# force: true
|
134
|
+
# )
|
135
|
+
#
|
136
|
+
# @example Remove while preserving layers
|
137
|
+
# response = RemoveImage.call(
|
138
|
+
# server_context: context,
|
139
|
+
# id: "temp-image:build-123",
|
140
|
+
# noprune: true
|
141
|
+
# )
|
142
|
+
#
|
143
|
+
# @see Docker::Image#remove
|
144
|
+
def self.call(id:, server_context:, force: false, noprune: false)
|
145
|
+
image = Docker::Image.get(id)
|
146
|
+
image.remove(force: force, noprune: noprune)
|
147
|
+
|
148
|
+
MCP::Tool::Response.new([{
|
149
|
+
type: 'text',
|
150
|
+
text: "Image #{id} removed successfully"
|
151
|
+
}])
|
152
|
+
rescue Docker::Error::NotFoundError
|
153
|
+
MCP::Tool::Response.new([{
|
154
|
+
type: 'text',
|
155
|
+
text: "Image #{id} not found"
|
156
|
+
}])
|
157
|
+
rescue StandardError => e
|
158
|
+
MCP::Tool::Response.new([{
|
159
|
+
type: 'text',
|
160
|
+
text: "Error removing image: #{e.message}"
|
161
|
+
}])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|