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.
- 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 +174 -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 +219 -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 +132 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efb62c57572aa0d7838b21fb503cc82d4d29b1b7a4b67a56a73403abf95569cd
|
4
|
+
data.tar.gz: 659cee10a2d4bb77b2a4dabe5acf47c63415e17e67b34f3046eb5bbb149ca9a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f24eb8a997aa33b2a4389170fd3d938d9ac0e00fec6f437074497ddfc260d81a8390b61b883caf35901d19f34af271b1d8d32f3b5ff24ca51dea9c885cb9e7c7
|
7
|
+
data.tar.gz: 68672e9b0c1432fa2575aea0034d4d4d74e872d83da97456d2fc0cc698249fd643d9325cfc9ed25223f3832b980926212fc98d228d135a10180c7cdd8ea8e0d5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Aaron F Stanton
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,35 +1,271 @@
|
|
1
|
-
#
|
1
|
+
# DockerMCP
|
2
2
|
|
3
|
-
|
3
|
+
A Model Context Protocol (MCP) server that provides comprehensive Docker management capabilities through a standardized interface. This tool enables AI assistants and other MCP clients to interact with Docker containers, images, networks, and volumes programmatically.
|
4
4
|
|
5
|
-
|
5
|
+
## ⚠️ Security Warning
|
6
6
|
|
7
|
-
|
7
|
+
**This tool is inherently unsafe and should be used with extreme caution.**
|
8
|
+
|
9
|
+
- **Arbitrary Code Execution**: The `exec_container` tool allows execution of arbitrary commands inside Docker containers
|
10
|
+
- **File System Access**: The `copy_to_container` tool can copy files from the host system into containers
|
11
|
+
- **Container Management**: Full container lifecycle management including creation, modification, and deletion
|
12
|
+
- **Network & Volume Control**: Complete control over Docker networks and volumes
|
8
13
|
|
9
|
-
|
14
|
+
**Recommendations:**
|
15
|
+
- Only use in trusted environments
|
16
|
+
- Ensure proper Docker daemon security configuration
|
17
|
+
- Consider running with restricted Docker permissions
|
18
|
+
- Monitor and audit all container operations
|
19
|
+
- Be cautious when exposing this tool to external or untrusted MCP clients
|
20
|
+
|
21
|
+
## Installation
|
10
22
|
|
11
23
|
Install the gem and add to the application's Gemfile by executing:
|
12
24
|
|
13
25
|
```bash
|
14
|
-
bundle add
|
26
|
+
bundle add docker_mcp
|
15
27
|
```
|
16
28
|
|
17
29
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
18
30
|
|
19
31
|
```bash
|
20
|
-
gem install
|
32
|
+
gem install docker_mcp
|
21
33
|
```
|
22
34
|
|
35
|
+
## Prerequisites
|
36
|
+
|
37
|
+
- Docker Engine installed and running
|
38
|
+
- Ruby 3.2+
|
39
|
+
- Docker permissions for the user running the MCP server
|
40
|
+
|
23
41
|
## Usage
|
24
42
|
|
25
|
-
|
43
|
+
### MCP Client Configuration
|
44
|
+
|
45
|
+
Add this to your MCP client configuration after installing the gem:
|
46
|
+
|
47
|
+
```json
|
48
|
+
{
|
49
|
+
"docker_mcp": {
|
50
|
+
"command": "bash",
|
51
|
+
"args": [
|
52
|
+
"-l",
|
53
|
+
"-c",
|
54
|
+
"docker_mcp"
|
55
|
+
]
|
56
|
+
}
|
57
|
+
}
|
58
|
+
```
|
59
|
+
|
60
|
+
### Example Usage
|
61
|
+
|
62
|
+
Once configured, you can use the tools through your MCP client:
|
63
|
+
|
64
|
+
```
|
65
|
+
# List all containers
|
66
|
+
list_containers
|
67
|
+
|
68
|
+
# Create and run a new container
|
69
|
+
run_container image="nginx:latest" name="my-web-server"
|
70
|
+
|
71
|
+
# Execute commands in a container
|
72
|
+
exec_container id="my-web-server" cmd="nginx -v"
|
73
|
+
|
74
|
+
# Copy files to a container
|
75
|
+
copy_to_container id="my-web-server" source_path="/local/file.txt" destination_path="/var/www/html/"
|
76
|
+
|
77
|
+
# View container logs
|
78
|
+
fetch_container_logs id="my-web-server"
|
79
|
+
```
|
80
|
+
|
81
|
+
## 🔨 Tools
|
82
|
+
|
83
|
+
This MCP server provides 22 comprehensive Docker management tools organized by functionality:
|
84
|
+
|
85
|
+
### Container Management
|
86
|
+
|
87
|
+
- **`list_containers`** - List all Docker containers (running and stopped) with detailed information
|
88
|
+
- **`create_container`** - Create a new container from an image without starting it
|
89
|
+
- **`run_container`** - Create and immediately start a container from an image
|
90
|
+
- **`start_container`** - Start an existing stopped container
|
91
|
+
- **`stop_container`** - Stop a running container gracefully
|
92
|
+
- **`remove_container`** - Delete a container (must be stopped first unless forced)
|
93
|
+
- **`recreate_container`** - Stop, remove, and recreate a container with the same configuration
|
94
|
+
- **`exec_container`** ⚠️ - Execute arbitrary commands inside a running container
|
95
|
+
- **`fetch_container_logs`** - Retrieve stdout/stderr logs from a container
|
96
|
+
- **`copy_to_container`** ⚠️ - Copy files or directories from host to container
|
97
|
+
|
98
|
+
### Image Management
|
99
|
+
|
100
|
+
- **`list_images`** - List all Docker images available locally
|
101
|
+
- **`pull_image`** - Download an image from a Docker registry
|
102
|
+
- **`push_image`** - Upload an image to a Docker registry
|
103
|
+
- **`build_image`** - Build a new image from a Dockerfile
|
104
|
+
- **`tag_image`** - Create a new tag for an existing image
|
105
|
+
- **`remove_image`** - Delete an image from local storage
|
106
|
+
|
107
|
+
### Network Management
|
108
|
+
|
109
|
+
- **`list_networks`** - List all Docker networks
|
110
|
+
- **`create_network`** - Create a new Docker network
|
111
|
+
- **`remove_network`** - Delete a Docker network
|
112
|
+
|
113
|
+
### Volume Management
|
114
|
+
|
115
|
+
- **`list_volumes`** - List all Docker volumes
|
116
|
+
- **`create_volume`** - Create a new Docker volume for persistent data
|
117
|
+
- **`remove_volume`** - Delete a Docker volume
|
118
|
+
|
119
|
+
### Tool Parameters
|
120
|
+
|
121
|
+
Most tools accept standard Docker parameters:
|
122
|
+
- **Container ID/Name**: Can use either the full container ID, short ID, or container name
|
123
|
+
- **Image**: Specify images using `name:tag` format (e.g., `nginx:latest`, `ubuntu:22.04`)
|
124
|
+
- **Ports**: Use Docker port mapping syntax (e.g., `"8080:80"`)
|
125
|
+
- **Volumes**: Use Docker volume mount syntax (e.g., `"/host/path:/container/path"`)
|
126
|
+
- **Environment**: Set environment variables as `KEY=VALUE` pairs
|
127
|
+
|
128
|
+
## Common Use Cases
|
129
|
+
|
130
|
+
### Development Environment Setup
|
131
|
+
```bash
|
132
|
+
# Pull development image
|
133
|
+
pull_image from_image="node:18-alpine"
|
134
|
+
|
135
|
+
# Create development container with volume mounts
|
136
|
+
run_container image="node:18-alpine" name="dev-env" \
|
137
|
+
host_config='{"PortBindings":{"3000/tcp":[{"HostPort":"3000"}]},"Binds":["/local/project:/app"]}'
|
138
|
+
|
139
|
+
# Execute development commands
|
140
|
+
exec_container id="dev-env" cmd="npm install"
|
141
|
+
exec_container id="dev-env" cmd="npm start"
|
142
|
+
```
|
143
|
+
|
144
|
+
### Container Debugging
|
145
|
+
```bash
|
146
|
+
# Check container status
|
147
|
+
list_containers
|
148
|
+
|
149
|
+
# View container logs
|
150
|
+
fetch_container_logs id="problematic-container"
|
151
|
+
|
152
|
+
# Execute diagnostic commands
|
153
|
+
exec_container id="problematic-container" cmd="ps aux"
|
154
|
+
exec_container id="problematic-container" cmd="df -h"
|
155
|
+
exec_container id="problematic-container" cmd="netstat -tlnp"
|
156
|
+
```
|
157
|
+
|
158
|
+
### File Management
|
159
|
+
```bash
|
160
|
+
# Copy configuration files to container
|
161
|
+
copy_to_container id="web-server" \
|
162
|
+
source_path="/local/nginx.conf" \
|
163
|
+
destination_path="/etc/nginx/"
|
164
|
+
|
165
|
+
# Copy application code
|
166
|
+
copy_to_container id="app-container" \
|
167
|
+
source_path="/local/src" \
|
168
|
+
destination_path="/app/"
|
169
|
+
```
|
170
|
+
|
171
|
+
## Error Handling
|
172
|
+
|
173
|
+
The server provides detailed error messages for common issues:
|
174
|
+
|
175
|
+
- **Container Not Found**: When referencing non-existent containers
|
176
|
+
- **Image Not Available**: When trying to use images that aren't pulled locally
|
177
|
+
- **Permission Denied**: When Docker daemon access is restricted
|
178
|
+
- **Network Conflicts**: When creating networks with conflicting configurations
|
179
|
+
- **Volume Mount Issues**: When specified paths don't exist or lack permissions
|
180
|
+
|
181
|
+
All errors include descriptive messages to help diagnose and resolve issues.
|
182
|
+
|
183
|
+
## Troubleshooting
|
184
|
+
|
185
|
+
### Docker Daemon Connection Issues
|
186
|
+
```bash
|
187
|
+
# Check if Docker daemon is running
|
188
|
+
docker info
|
189
|
+
|
190
|
+
# Verify Docker permissions
|
191
|
+
docker ps
|
192
|
+
|
193
|
+
# Check MCP server logs for connection errors
|
194
|
+
```
|
195
|
+
|
196
|
+
### Container Operation Failures
|
197
|
+
- Ensure container IDs/names are correct (use `list_containers` to verify)
|
198
|
+
- Check if containers are in the expected state (running/stopped)
|
199
|
+
- Verify image availability with `list_images`
|
200
|
+
|
201
|
+
### Permission Issues
|
202
|
+
- Ensure the user running the MCP server has Docker permissions
|
203
|
+
- Consider adding user to the `docker` group: `sudo usermod -aG docker $USER`
|
204
|
+
- Verify Docker socket permissions: `ls -la /var/run/docker.sock`
|
205
|
+
|
206
|
+
## Limitations
|
207
|
+
|
208
|
+
- **Platform Specific**: Some container operations may behave differently across operating systems
|
209
|
+
- **Docker API Version**: Requires compatible Docker Engine API version
|
210
|
+
- **Resource Limits**: Large file copies and image operations may timeout
|
211
|
+
- **Concurrent Operations**: Heavy concurrent usage may impact performance
|
212
|
+
|
213
|
+
## Contributing
|
214
|
+
|
215
|
+
We welcome contributions! Areas for improvement:
|
216
|
+
|
217
|
+
- **Enhanced Security**: Additional safety checks and permission validation
|
218
|
+
- **Better Error Handling**: More specific error messages and recovery suggestions
|
219
|
+
- **Performance Optimization**: Streaming for large file operations
|
220
|
+
- **Extended Functionality**: Support for Docker Compose, Swarm, etc.
|
221
|
+
- **Testing**: Comprehensive test coverage for all tools
|
26
222
|
|
27
223
|
## Development
|
28
224
|
|
29
225
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
226
|
|
227
|
+
### Running Tests
|
228
|
+
```bash
|
229
|
+
# Install dependencies
|
230
|
+
bundle install
|
231
|
+
|
232
|
+
# Run the test suite
|
233
|
+
bundle exec rake spec
|
234
|
+
|
235
|
+
# Run tests with coverage
|
236
|
+
bundle exec rake spec COVERAGE=true
|
237
|
+
```
|
238
|
+
|
239
|
+
### Local Development Setup
|
240
|
+
```bash
|
241
|
+
# Clone the repository
|
242
|
+
git clone https://github.com/afstanton/docker_mcp.git
|
243
|
+
cd docker_mcp
|
244
|
+
|
245
|
+
# Install dependencies
|
246
|
+
bin/setup
|
247
|
+
|
248
|
+
# Start development console
|
249
|
+
bin/console
|
250
|
+
|
251
|
+
# Build the gem locally
|
252
|
+
bundle exec rake build
|
253
|
+
|
254
|
+
# Install locally built gem
|
255
|
+
bundle exec rake install
|
256
|
+
```
|
257
|
+
|
258
|
+
### Testing with MCP Client
|
259
|
+
```bash
|
260
|
+
# Start the MCP server locally
|
261
|
+
bundle exec exe/docker_mcp
|
262
|
+
|
263
|
+
# Configure your MCP client to use local development server
|
264
|
+
# Use file path instead of installed gem command
|
265
|
+
```
|
266
|
+
|
31
267
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
268
|
|
33
|
-
##
|
269
|
+
## License
|
34
270
|
|
35
|
-
|
271
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
5
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
|
-
require
|
8
|
+
require 'rubocop/rake_task'
|
9
9
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
data/examples/.keep
ADDED
File without changes
|
data/exe/docker_mcp
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DockerMCP
|
4
|
+
# MCP tool for building Docker images from Dockerfile content.
|
5
|
+
#
|
6
|
+
# This tool provides the ability to build Docker images by providing Dockerfile
|
7
|
+
# content as a string. It supports optional tagging of the resulting image for
|
8
|
+
# easy identification and reuse.
|
9
|
+
#
|
10
|
+
# == Security Considerations
|
11
|
+
#
|
12
|
+
# Building Docker images can be potentially dangerous:
|
13
|
+
# - Dockerfile commands execute with Docker daemon privileges
|
14
|
+
# - Images can contain malicious software or backdoors
|
15
|
+
# - Build process can access host resources (network, files)
|
16
|
+
# - Base images may contain vulnerabilities
|
17
|
+
# - Build context may expose sensitive information
|
18
|
+
#
|
19
|
+
# Security recommendations:
|
20
|
+
# - Review Dockerfile content carefully before building
|
21
|
+
# - Use trusted base images from official repositories
|
22
|
+
# - Scan built images for vulnerabilities
|
23
|
+
# - Limit network access during builds
|
24
|
+
# - Avoid including secrets in Dockerfile instructions
|
25
|
+
# - Use multi-stage builds to minimize final image size
|
26
|
+
#
|
27
|
+
# == Features
|
28
|
+
#
|
29
|
+
# - Build images from Dockerfile content strings
|
30
|
+
# - Optional image tagging during build
|
31
|
+
# - Comprehensive error handling
|
32
|
+
# - Support for all standard Dockerfile instructions
|
33
|
+
# - Returns image ID and build status
|
34
|
+
#
|
35
|
+
# == Example Usage
|
36
|
+
#
|
37
|
+
# # Simple image build
|
38
|
+
# BuildImage.call(
|
39
|
+
# server_context: context,
|
40
|
+
# dockerfile: "FROM alpine:latest\nRUN apk add --no-cache curl"
|
41
|
+
# )
|
42
|
+
#
|
43
|
+
# # Build with custom tag
|
44
|
+
# BuildImage.call(
|
45
|
+
# server_context: context,
|
46
|
+
# dockerfile: dockerfile_content,
|
47
|
+
# tag: "myapp:v1.0"
|
48
|
+
# )
|
49
|
+
#
|
50
|
+
# @see Docker::Image.build
|
51
|
+
# @see TagImage
|
52
|
+
# @since 0.1.0
|
53
|
+
class BuildImage < MCP::Tool
|
54
|
+
description 'Build a Docker image'
|
55
|
+
|
56
|
+
input_schema(
|
57
|
+
properties: {
|
58
|
+
dockerfile: {
|
59
|
+
type: 'string',
|
60
|
+
description: 'Dockerfile content as a string'
|
61
|
+
},
|
62
|
+
tag: {
|
63
|
+
type: 'string',
|
64
|
+
description: 'Tag for the built image (e.g., "myimage:latest")'
|
65
|
+
}
|
66
|
+
},
|
67
|
+
required: ['dockerfile']
|
68
|
+
)
|
69
|
+
|
70
|
+
# Build a Docker image from Dockerfile content.
|
71
|
+
#
|
72
|
+
# This method creates a Docker image by building from the provided Dockerfile
|
73
|
+
# content string. The Dockerfile is processed by the Docker daemon and can
|
74
|
+
# include any valid Dockerfile instructions. Optionally, the resulting image
|
75
|
+
# can be tagged with a custom name for easy reference.
|
76
|
+
#
|
77
|
+
# @param dockerfile [String] the complete Dockerfile content as a string
|
78
|
+
# @param server_context [Object] MCP server context (unused but required)
|
79
|
+
# @param tag [String, nil] optional tag to apply to the built image
|
80
|
+
#
|
81
|
+
# @return [MCP::Tool::Response] build results including image ID and tag info
|
82
|
+
#
|
83
|
+
# @raise [Docker::Error] for Docker daemon communication errors
|
84
|
+
# @raise [StandardError] for build failures or other errors
|
85
|
+
#
|
86
|
+
# @example Build simple image
|
87
|
+
# dockerfile = <<~DOCKERFILE
|
88
|
+
# FROM alpine:latest
|
89
|
+
# RUN apk add --no-cache nginx
|
90
|
+
# EXPOSE 80
|
91
|
+
# CMD ["nginx", "-g", "daemon off;"]
|
92
|
+
# DOCKERFILE
|
93
|
+
#
|
94
|
+
# response = BuildImage.call(
|
95
|
+
# server_context: context,
|
96
|
+
# dockerfile: dockerfile,
|
97
|
+
# tag: "my-nginx:latest"
|
98
|
+
# )
|
99
|
+
#
|
100
|
+
# @see Docker::Image.build
|
101
|
+
def self.call(dockerfile:, server_context:, tag: nil)
|
102
|
+
# Build the image
|
103
|
+
image = Docker::Image.build(dockerfile)
|
104
|
+
|
105
|
+
# If a tag was specified, tag the image
|
106
|
+
if tag
|
107
|
+
# Split tag into repo and tag parts
|
108
|
+
repo, image_tag = tag.split(':', 2)
|
109
|
+
image_tag ||= 'latest'
|
110
|
+
image.tag('repo' => repo, 'tag' => image_tag, 'force' => true)
|
111
|
+
end
|
112
|
+
|
113
|
+
response_text = "Image built successfully. ID: #{image.id}"
|
114
|
+
response_text += ", Tag: #{tag}" if tag
|
115
|
+
|
116
|
+
MCP::Tool::Response.new([{
|
117
|
+
type: 'text',
|
118
|
+
text: response_text
|
119
|
+
}])
|
120
|
+
rescue StandardError => e
|
121
|
+
MCP::Tool::Response.new([{
|
122
|
+
type: 'text',
|
123
|
+
text: "Error building image: #{e.message}"
|
124
|
+
}])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DockerMCP
|
4
|
+
# MCP tool for copying files and directories from host to Docker containers.
|
5
|
+
#
|
6
|
+
# This tool provides the ability to copy files or entire directory trees from
|
7
|
+
# the host filesystem into running Docker containers. It uses Docker's archive
|
8
|
+
# streaming API to efficiently transfer files while preserving permissions and
|
9
|
+
# directory structure.
|
10
|
+
#
|
11
|
+
# == ⚠️ SECURITY WARNING ⚠️
|
12
|
+
#
|
13
|
+
# This tool can be dangerous as it allows:
|
14
|
+
# - Reading arbitrary files from the host filesystem
|
15
|
+
# - Writing files into container filesystems
|
16
|
+
# - Potentially overwriting critical container files
|
17
|
+
# - Escalating privileges if used with setuid/setgid files
|
18
|
+
# - Exposing sensitive host data to containers
|
19
|
+
#
|
20
|
+
# Security recommendations:
|
21
|
+
# - Validate source paths to prevent directory traversal
|
22
|
+
# - Ensure containers run with minimal privileges
|
23
|
+
# - Monitor file copy operations for sensitive paths
|
24
|
+
# - Use read-only filesystems where possible
|
25
|
+
# - Implement proper access controls on source files
|
26
|
+
#
|
27
|
+
# == Features
|
28
|
+
#
|
29
|
+
# - Copy individual files or entire directories
|
30
|
+
# - Preserve file permissions and directory structure
|
31
|
+
# - Optional ownership changes after copy
|
32
|
+
# - Comprehensive error handling
|
33
|
+
# - Support for both absolute and relative paths
|
34
|
+
#
|
35
|
+
# == Example Usage
|
36
|
+
#
|
37
|
+
# # Copy a configuration file
|
38
|
+
# CopyToContainer.call(
|
39
|
+
# server_context: context,
|
40
|
+
# id: "web-server",
|
41
|
+
# source_path: "/host/config/nginx.conf",
|
42
|
+
# destination_path: "/etc/nginx/"
|
43
|
+
# )
|
44
|
+
#
|
45
|
+
# # Copy directory with ownership change
|
46
|
+
# CopyToContainer.call(
|
47
|
+
# server_context: context,
|
48
|
+
# id: "app-container",
|
49
|
+
# source_path: "/host/app/src",
|
50
|
+
# destination_path: "/app/",
|
51
|
+
# owner: "appuser:appgroup"
|
52
|
+
# )
|
53
|
+
#
|
54
|
+
# @see Docker::Container#archive_in_stream
|
55
|
+
# @since 0.1.0
|
56
|
+
class CopyToContainer < MCP::Tool
|
57
|
+
description 'Copy a file or directory from the local filesystem into a running Docker container. ' \
|
58
|
+
'The source path is on the local machine, and the destination path is inside the container.'
|
59
|
+
|
60
|
+
input_schema(
|
61
|
+
properties: {
|
62
|
+
id: {
|
63
|
+
type: 'string',
|
64
|
+
description: 'Container ID or name'
|
65
|
+
},
|
66
|
+
source_path: {
|
67
|
+
type: 'string',
|
68
|
+
description: 'Path to the file or directory on the local filesystem to copy'
|
69
|
+
},
|
70
|
+
destination_path: {
|
71
|
+
type: 'string',
|
72
|
+
description: 'Path inside the container where the file/directory should be copied'
|
73
|
+
},
|
74
|
+
owner: {
|
75
|
+
type: 'string',
|
76
|
+
description: 'Owner for the copied files (optional, e.g., "1000:1000" or "username:group")'
|
77
|
+
}
|
78
|
+
},
|
79
|
+
required: %w[id source_path destination_path]
|
80
|
+
)
|
81
|
+
|
82
|
+
# Copy files or directories from host filesystem to a Docker container.
|
83
|
+
#
|
84
|
+
# This method creates a tar archive of the source path and streams it into
|
85
|
+
# the specified container using Docker's archive API. The operation preserves
|
86
|
+
# file permissions and directory structure. Optionally, ownership can be
|
87
|
+
# changed after the copy operation completes.
|
88
|
+
#
|
89
|
+
# The source path must exist on the host filesystem and be readable by the
|
90
|
+
# process running the MCP server. The destination path must be a valid path
|
91
|
+
# within the container.
|
92
|
+
#
|
93
|
+
# @param id [String] container ID or name to copy files into
|
94
|
+
# @param source_path [String] path to file/directory on host filesystem
|
95
|
+
# @param destination_path [String] destination path inside container
|
96
|
+
# @param server_context [Object] MCP server context (unused but required)
|
97
|
+
# @param owner [String, nil] ownership specification (e.g., "user:group", "1000:1000")
|
98
|
+
#
|
99
|
+
# @return [MCP::Tool::Response] success/failure message with operation details
|
100
|
+
#
|
101
|
+
# @raise [Docker::Error::NotFoundError] if container doesn't exist
|
102
|
+
# @raise [StandardError] for file system or Docker API errors
|
103
|
+
#
|
104
|
+
# @example Copy configuration file
|
105
|
+
# response = CopyToContainer.call(
|
106
|
+
# server_context: context,
|
107
|
+
# id: "nginx-container",
|
108
|
+
# source_path: "/etc/nginx/sites-available/default",
|
109
|
+
# destination_path: "/etc/nginx/sites-enabled/"
|
110
|
+
# )
|
111
|
+
#
|
112
|
+
# @example Copy directory with ownership
|
113
|
+
# response = CopyToContainer.call(
|
114
|
+
# server_context: context,
|
115
|
+
# id: "app-container",
|
116
|
+
# source_path: "/local/project",
|
117
|
+
# destination_path: "/app/",
|
118
|
+
# owner: "www-data:www-data"
|
119
|
+
# )
|
120
|
+
#
|
121
|
+
# @see Docker::Container#archive_in_stream
|
122
|
+
# @see #add_to_tar
|
123
|
+
def self.call(id:, source_path:, destination_path:, server_context:, owner: nil)
|
124
|
+
container = Docker::Container.get(id)
|
125
|
+
|
126
|
+
# Verify source path exists
|
127
|
+
unless File.exist?(source_path)
|
128
|
+
return MCP::Tool::Response.new([{
|
129
|
+
type: 'text',
|
130
|
+
text: "Source path not found: #{source_path}"
|
131
|
+
}])
|
132
|
+
end
|
133
|
+
|
134
|
+
# Create a tar archive of the source
|
135
|
+
tar_io = StringIO.new
|
136
|
+
tar_io.set_encoding('ASCII-8BIT')
|
137
|
+
|
138
|
+
Gem::Package::TarWriter.new(tar_io) do |tar|
|
139
|
+
add_to_tar(tar, source_path, File.basename(source_path))
|
140
|
+
end
|
141
|
+
|
142
|
+
tar_io.rewind
|
143
|
+
|
144
|
+
# Copy to container
|
145
|
+
container.archive_in_stream(destination_path) do
|
146
|
+
tar_io.read
|
147
|
+
end
|
148
|
+
|
149
|
+
# Optionally change ownership
|
150
|
+
if owner
|
151
|
+
chown_path = File.join(destination_path, File.basename(source_path))
|
152
|
+
container.exec(['chown', '-R', owner, chown_path])
|
153
|
+
end
|
154
|
+
|
155
|
+
file_type = File.directory?(source_path) ? 'directory' : 'file'
|
156
|
+
response_text = "Successfully copied #{file_type} from #{source_path} to #{id}:#{destination_path}"
|
157
|
+
response_text += "\nOwnership changed to #{owner}" if owner
|
158
|
+
|
159
|
+
MCP::Tool::Response.new([{
|
160
|
+
type: 'text',
|
161
|
+
text: response_text
|
162
|
+
}])
|
163
|
+
rescue Docker::Error::NotFoundError
|
164
|
+
MCP::Tool::Response.new([{
|
165
|
+
type: 'text',
|
166
|
+
text: "Container #{id} not found"
|
167
|
+
}])
|
168
|
+
rescue StandardError => e
|
169
|
+
MCP::Tool::Response.new([{
|
170
|
+
type: 'text',
|
171
|
+
text: "Error copying to container: #{e.message}"
|
172
|
+
}])
|
173
|
+
end
|
174
|
+
|
175
|
+
# Recursively add files and directories to a tar archive.
|
176
|
+
#
|
177
|
+
# This helper method builds a tar archive by recursively traversing
|
178
|
+
# the filesystem starting from the given path. It preserves file
|
179
|
+
# permissions and handles both files and directories appropriately.
|
180
|
+
#
|
181
|
+
# For directories, it creates directory entries in the tar and then
|
182
|
+
# recursively processes all contained files and subdirectories.
|
183
|
+
# For files, it reads the content and adds it to the tar with
|
184
|
+
# preserved permissions.
|
185
|
+
#
|
186
|
+
# @param tar [Gem::Package::TarWriter] the tar writer instance
|
187
|
+
# @param path [String] the filesystem path to add to the archive
|
188
|
+
# @param archive_path [String] the path within the tar archive
|
189
|
+
#
|
190
|
+
# @return [void]
|
191
|
+
#
|
192
|
+
# @example Add single file
|
193
|
+
# add_to_tar(tar_writer, "/host/file.txt", "file.txt")
|
194
|
+
#
|
195
|
+
# @example Add directory tree
|
196
|
+
# add_to_tar(tar_writer, "/host/mydir", "mydir")
|
197
|
+
#
|
198
|
+
# @see Gem::Package::TarWriter#mkdir
|
199
|
+
# @see Gem::Package::TarWriter#add_file_simple
|
200
|
+
def self.add_to_tar(tar, path, archive_path)
|
201
|
+
if File.directory?(path)
|
202
|
+
# Add directory entry
|
203
|
+
tar.mkdir(archive_path, File.stat(path).mode)
|
204
|
+
|
205
|
+
# Add directory contents
|
206
|
+
Dir.entries(path).each do |entry|
|
207
|
+
next if ['.', '..'].include?(entry)
|
208
|
+
|
209
|
+
full_path = File.join(path, entry)
|
210
|
+
archive_entry_path = File.join(archive_path, entry)
|
211
|
+
add_to_tar(tar, full_path, archive_entry_path)
|
212
|
+
end
|
213
|
+
else
|
214
|
+
# Add file
|
215
|
+
File.open(path, 'rb') do |file|
|
216
|
+
tar.add_file_simple(archive_path, File.stat(path).mode, file.size) do |tar_file|
|
217
|
+
IO.copy_stream(file, tar_file)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|