docker_mcp 0.0.1 → 0.2.5

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