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.
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerMCP
4
+ # MCP tool for creating Docker containers without starting them.
5
+ #
6
+ # This tool provides the ability to create Docker containers from images with
7
+ # comprehensive configuration options. Unlike RunContainer, this tool only
8
+ # creates the container but does not start it, allowing for additional
9
+ # configuration or manual startup control.
10
+ #
11
+ # == Features
12
+ #
13
+ # - Create containers from any available Docker image
14
+ # - Full container configuration support
15
+ # - Port exposure and binding configuration
16
+ # - Volume mounting capabilities
17
+ # - Environment variable configuration
18
+ # - Custom command specification
19
+ # - Comprehensive error handling with specific error types
20
+ #
21
+ # == Security Considerations
22
+ #
23
+ # - Containers inherit Docker daemon security context
24
+ # - Host configuration can expose host resources
25
+ # - Volume mounts provide filesystem access
26
+ # - Port bindings expose services to networks
27
+ # - Environment variables may contain sensitive data
28
+ #
29
+ # Use appropriate security measures:
30
+ # - Validate image sources and integrity
31
+ # - Limit host resource exposure
32
+ # - Use read-only volumes where appropriate
33
+ # - Restrict network access through host configuration
34
+ # - Sanitize environment variables
35
+ #
36
+ # == Example Usage
37
+ #
38
+ # # Simple container creation
39
+ # CreateContainer.call(
40
+ # server_context: context,
41
+ # image: "nginx:latest",
42
+ # name: "web-server"
43
+ # )
44
+ #
45
+ # # Advanced container with configuration
46
+ # CreateContainer.call(
47
+ # server_context: context,
48
+ # image: "postgres:13",
49
+ # name: "database",
50
+ # env: ["POSTGRES_PASSWORD=secret", "POSTGRES_DB=myapp"],
51
+ # exposed_ports: {"5432/tcp" => {}},
52
+ # host_config: {
53
+ # "PortBindings" => {"5432/tcp" => [{"HostPort" => "5432"}]},
54
+ # "Binds" => ["/host/data:/var/lib/postgresql/data:rw"]
55
+ # }
56
+ # )
57
+ #
58
+ # @see RunContainer
59
+ # @see StartContainer
60
+ # @see Docker::Container.create
61
+ # @since 0.1.0
62
+ class CreateContainer < MCP::Tool
63
+ description 'Create a Docker container'
64
+
65
+ input_schema(
66
+ properties: {
67
+ image: {
68
+ type: 'string',
69
+ description: 'Image name to use (e.g., "ubuntu:22.04")'
70
+ },
71
+ name: {
72
+ type: 'string',
73
+ description: 'Container name (optional)'
74
+ },
75
+ cmd: {
76
+ type: 'array',
77
+ items: { type: 'string' },
78
+ description: 'Command to run (optional)'
79
+ },
80
+ env: {
81
+ type: 'array',
82
+ items: { type: 'string' },
83
+ description: 'Environment variables as KEY=VALUE (optional)'
84
+ },
85
+ exposed_ports: {
86
+ type: 'object',
87
+ description: 'Exposed ports as {"port/protocol": {}} (optional)'
88
+ },
89
+ host_config: {
90
+ type: 'object',
91
+ description: 'Host configuration including port bindings, volumes, etc. (optional)'
92
+ }
93
+ },
94
+ required: ['image']
95
+ )
96
+
97
+ # Create a new Docker container from an image.
98
+ #
99
+ # This method creates a container with the specified configuration but does
100
+ # not start it. The container can be started later using StartContainer or
101
+ # other Docker commands. All configuration parameters are optional except
102
+ # for the base image.
103
+ #
104
+ # @param image [String] Docker image name with optional tag (e.g., "nginx:latest")
105
+ # @param server_context [Object] MCP server context (unused but required)
106
+ # @param name [String, nil] custom name for the container
107
+ # @param cmd [Array<String>, nil] command to execute in the container
108
+ # @param env [Array<String>, nil] environment variables in KEY=VALUE format
109
+ # @param exposed_ports [Hash, nil] ports to expose in {"port/protocol" => {}} format
110
+ # @param host_config [Hash, nil] Docker host configuration including bindings and volumes
111
+ #
112
+ # @return [MCP::Tool::Response] creation results with container ID and name
113
+ #
114
+ # @raise [Docker::Error::NotFoundError] if the specified image doesn't exist
115
+ # @raise [Docker::Error::ConflictError] if container name already exists
116
+ # @raise [StandardError] for other creation failures
117
+ #
118
+ # @example Create simple container
119
+ # response = CreateContainer.call(
120
+ # server_context: context,
121
+ # image: "alpine:latest",
122
+ # name: "test-container"
123
+ # )
124
+ #
125
+ # @example Create container with full configuration
126
+ # response = CreateContainer.call(
127
+ # server_context: context,
128
+ # image: "redis:7-alpine",
129
+ # name: "redis-cache",
130
+ # env: ["REDIS_PASSWORD=secret"],
131
+ # exposed_ports: {"6379/tcp" => {}},
132
+ # host_config: {
133
+ # "PortBindings" => {"6379/tcp" => [{"HostPort" => "6379"}]},
134
+ # "RestartPolicy" => {"Name" => "unless-stopped"}
135
+ # }
136
+ # )
137
+ #
138
+ # @see Docker::Container.create
139
+ def self.call(image:, server_context:, name: nil, cmd: nil, env: nil, exposed_ports: nil, host_config: nil)
140
+ config = { 'Image' => image }
141
+ config['name'] = name if name
142
+ config['Cmd'] = cmd if cmd
143
+ config['Env'] = env if env
144
+ config['ExposedPorts'] = exposed_ports if exposed_ports
145
+ config['HostConfig'] = host_config if host_config
146
+
147
+ container = Docker::Container.create(config)
148
+ container_name = container.info['Names']&.first&.delete_prefix('/')
149
+
150
+ MCP::Tool::Response.new([{
151
+ type: 'text',
152
+ text: "Container created successfully. ID: #{container.id}, Name: #{container_name}"
153
+ }])
154
+ rescue Docker::Error::NotFoundError
155
+ MCP::Tool::Response.new([{
156
+ type: 'text',
157
+ text: "Image #{image} not found"
158
+ }])
159
+ rescue Docker::Error::ConflictError
160
+ MCP::Tool::Response.new([{
161
+ type: 'text',
162
+ text: "Container with name #{name} already exists"
163
+ }])
164
+ rescue StandardError => e
165
+ MCP::Tool::Response.new([{
166
+ type: 'text',
167
+ text: "Error creating container: #{e.message}"
168
+ }])
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerMCP
4
+ # MCP tool for creating Docker networks.
5
+ #
6
+ # This tool provides the ability to create custom Docker networks for
7
+ # container communication and isolation. Networks enable containers to
8
+ # communicate securely while providing isolation from other network
9
+ # segments.
10
+ #
11
+ # == Features
12
+ #
13
+ # - Create custom Docker networks
14
+ # - Support for different network drivers
15
+ # - Duplicate name detection
16
+ # - Comprehensive error handling
17
+ # - Flexible network configuration
18
+ #
19
+ # == Network Drivers
20
+ #
21
+ # Docker supports various network drivers:
22
+ # - **bridge**: Default isolated network for single-host networking
23
+ # - **host**: Remove network isolation, use host networking directly
24
+ # - **overlay**: Multi-host networking for swarm services
25
+ # - **macvlan**: Assign MAC addresses to containers
26
+ # - **none**: Disable networking completely
27
+ # - **Custom**: Third-party network drivers
28
+ #
29
+ # == Security Considerations
30
+ #
31
+ # Network creation affects system security:
32
+ # - **Network Isolation**: Networks provide security boundaries
33
+ # - **Traffic Control**: Custom networks enable traffic filtering
34
+ # - **Access Control**: Networks control container communication
35
+ # - **DNS Resolution**: Networks provide internal DNS services
36
+ #
37
+ # Security implications:
38
+ # - Bridge networks isolate containers from host network
39
+ # - Host networks expose containers to all host traffic
40
+ # - Custom networks should follow least-privilege principles
41
+ # - Network names may reveal infrastructure details
42
+ #
43
+ # Best practices:
44
+ # - Use descriptive but not sensitive network names
45
+ # - Implement network segmentation strategies
46
+ # - Limit container access to necessary networks only
47
+ # - Monitor network traffic and connections
48
+ # - Regular audit of network configurations
49
+ #
50
+ # == Example Usage
51
+ #
52
+ # # Create basic bridge network
53
+ # CreateNetwork.call(
54
+ # server_context: context,
55
+ # name: "app-network"
56
+ # )
57
+ #
58
+ # # Create network with specific driver
59
+ # CreateNetwork.call(
60
+ # server_context: context,
61
+ # name: "frontend-net",
62
+ # driver: "bridge"
63
+ # )
64
+ #
65
+ # # Create without duplicate checking
66
+ # CreateNetwork.call(
67
+ # server_context: context,
68
+ # name: "temp-network",
69
+ # check_duplicate: false
70
+ # )
71
+ #
72
+ # @see ListNetworks
73
+ # @see RemoveNetwork
74
+ # @see Docker::Network.create
75
+ # @since 0.1.0
76
+ class CreateNetwork < MCP::Tool
77
+ description 'Create a Docker network'
78
+
79
+ input_schema(
80
+ properties: {
81
+ name: {
82
+ type: 'string',
83
+ description: 'Name of the network'
84
+ },
85
+ driver: {
86
+ type: 'string',
87
+ description: 'Driver to use (default: bridge)'
88
+ },
89
+ check_duplicate: {
90
+ type: 'boolean',
91
+ description: 'Check for networks with duplicate names (default: true)'
92
+ }
93
+ },
94
+ required: ['name']
95
+ )
96
+
97
+ # Create a new Docker network.
98
+ #
99
+ # This method creates a custom Docker network with the specified name
100
+ # and driver. The network can then be used by containers for isolated
101
+ # communication.
102
+ #
103
+ # @param name [String] name for the new network
104
+ # @param server_context [Object] MCP server context (unused but required)
105
+ # @param driver [String] network driver to use (default: "bridge")
106
+ # @param check_duplicate [Boolean] whether to check for duplicate names (default: true)
107
+ #
108
+ # @return [MCP::Tool::Response] network creation results with network ID
109
+ #
110
+ # @raise [Docker::Error::ConflictError] if network name already exists
111
+ # @raise [StandardError] for other network creation failures
112
+ #
113
+ # @example Create application network
114
+ # response = CreateNetwork.call(
115
+ # server_context: context,
116
+ # name: "webapp-network"
117
+ # )
118
+ #
119
+ # @example Create host network
120
+ # response = CreateNetwork.call(
121
+ # server_context: context,
122
+ # name: "high-performance-net",
123
+ # driver: "host"
124
+ # )
125
+ #
126
+ # @see Docker::Network.create
127
+ def self.call(name:, server_context:, driver: 'bridge', check_duplicate: true)
128
+ options = {
129
+ 'Name' => name,
130
+ 'Driver' => driver,
131
+ 'CheckDuplicate' => check_duplicate
132
+ }
133
+
134
+ network = Docker::Network.create(name, options)
135
+
136
+ MCP::Tool::Response.new([{
137
+ type: 'text',
138
+ text: "Network #{name} created successfully. ID: #{network.id}"
139
+ }])
140
+ rescue Docker::Error::ConflictError
141
+ MCP::Tool::Response.new([{
142
+ type: 'text',
143
+ text: "Network #{name} already exists"
144
+ }])
145
+ rescue StandardError => e
146
+ MCP::Tool::Response.new([{
147
+ type: 'text',
148
+ text: "Error creating network: #{e.message}"
149
+ }])
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerMCP
4
+ # MCP tool for creating Docker volumes.
5
+ #
6
+ # This tool provides the ability to create persistent Docker volumes for
7
+ # data storage that survives container lifecycle events. Volumes are the
8
+ # preferred mechanism for persisting data generated and used by Docker
9
+ # containers.
10
+ #
11
+ # == Features
12
+ #
13
+ # - Create named persistent volumes
14
+ # - Support for different volume drivers
15
+ # - Comprehensive error handling
16
+ # - Duplicate name detection
17
+ # - Flexible storage configuration
18
+ #
19
+ # == Volume Drivers
20
+ #
21
+ # Docker supports various volume drivers:
22
+ # - **local**: Default driver using host filesystem
23
+ # - **nfs**: Network File System for shared storage
24
+ # - **cifs**: Common Internet File System
25
+ # - **rexray**: External storage orchestration
26
+ # - **Custom**: Third-party volume drivers
27
+ #
28
+ # == Persistence Benefits
29
+ #
30
+ # Docker volumes provide several advantages:
31
+ # - **Data Persistence**: Survive container deletion and recreation
32
+ # - **Performance**: Better performance than bind mounts on Docker Desktop
33
+ # - **Portability**: Can be moved between containers and hosts
34
+ # - **Backup**: Easier to backup and restore than container filesystems
35
+ # - **Sharing**: Can be shared between multiple containers
36
+ # - **Management**: Managed by Docker daemon with proper lifecycle
37
+ #
38
+ # == Security Considerations
39
+ #
40
+ # Volume creation affects data security:
41
+ # - **Data Isolation**: Volumes provide isolation between containers
42
+ # - **Access Control**: Volume permissions affect data access
43
+ # - **Storage Location**: Local volumes stored on host filesystem
44
+ # - **Shared Access**: Multiple containers can access the same volume
45
+ #
46
+ # Security implications:
47
+ # - Volumes persist data beyond container lifecycle
48
+ # - Volume names may reveal application details
49
+ # - Shared volumes can leak data between containers
50
+ # - Storage driver choice affects security properties
51
+ #
52
+ # Best practices:
53
+ # - Use descriptive but not sensitive volume names
54
+ # - Implement proper volume access controls
55
+ # - Regular backup of critical volume data
56
+ # - Monitor volume usage and access patterns
57
+ # - Choose appropriate drivers for security requirements
58
+ #
59
+ # == Example Usage
60
+ #
61
+ # # Create basic local volume
62
+ # CreateVolume.call(
63
+ # server_context: context,
64
+ # name: "app-data"
65
+ # )
66
+ #
67
+ # # Create volume with specific driver
68
+ # CreateVolume.call(
69
+ # server_context: context,
70
+ # name: "shared-storage",
71
+ # driver: "nfs"
72
+ # )
73
+ #
74
+ # @see ListVolumes
75
+ # @see RemoveVolume
76
+ # @see Docker::Volume.create
77
+ # @since 0.1.0
78
+ class CreateVolume < MCP::Tool
79
+ description 'Create a Docker volume'
80
+
81
+ input_schema(
82
+ properties: {
83
+ name: {
84
+ type: 'string',
85
+ description: 'Name of the volume'
86
+ },
87
+ driver: {
88
+ type: 'string',
89
+ description: 'Driver to use (default: local)'
90
+ }
91
+ },
92
+ required: ['name']
93
+ )
94
+
95
+ # Create a new Docker volume for persistent data storage.
96
+ #
97
+ # This method creates a named Docker volume that can be used by containers
98
+ # for persistent data storage. The volume will survive container deletion
99
+ # and can be shared between multiple containers.
100
+ #
101
+ # @param name [String] name for the new volume
102
+ # @param server_context [Object] MCP server context (unused but required)
103
+ # @param driver [String] volume driver to use (default: "local")
104
+ #
105
+ # @return [MCP::Tool::Response] volume creation results
106
+ #
107
+ # @raise [Docker::Error::ConflictError] if volume name already exists
108
+ # @raise [StandardError] for other volume creation failures
109
+ #
110
+ # @example Create application data volume
111
+ # response = CreateVolume.call(
112
+ # server_context: context,
113
+ # name: "webapp-data"
114
+ # )
115
+ #
116
+ # @example Create NFS volume
117
+ # response = CreateVolume.call(
118
+ # server_context: context,
119
+ # name: "shared-files",
120
+ # driver: "nfs"
121
+ # )
122
+ #
123
+ # @see Docker::Volume.create
124
+ def self.call(name:, server_context:, driver: 'local')
125
+ options = {
126
+ 'Name' => name,
127
+ 'Driver' => driver
128
+ }
129
+
130
+ Docker::Volume.create(name, options)
131
+
132
+ MCP::Tool::Response.new([{
133
+ type: 'text',
134
+ text: "Volume #{name} created successfully"
135
+ }])
136
+ rescue Docker::Error::ConflictError
137
+ MCP::Tool::Response.new([{
138
+ type: 'text',
139
+ text: "Volume #{name} already exists"
140
+ }])
141
+ rescue StandardError => e
142
+ MCP::Tool::Response.new([{
143
+ type: 'text',
144
+ text: "Error creating volume: #{e.message}"
145
+ }])
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerMCP
4
+ # MCP tool for executing commands inside running Docker containers.
5
+ #
6
+ # This tool provides the ability to execute arbitrary commands inside running
7
+ # Docker containers, with full control over execution environment including
8
+ # working directory, user context, environment variables, and stdin input.
9
+ #
10
+ # == ⚠️ CRITICAL SECURITY WARNING ⚠️
11
+ #
12
+ # This tool is EXTREMELY DANGEROUS as it allows arbitrary command execution
13
+ # within Docker containers. This can be used to:
14
+ # - Execute malicious code inside containers
15
+ # - Access sensitive data within container filesystems
16
+ # - Escalate privileges if container is poorly configured
17
+ # - Perform lateral movement within containerized environments
18
+ # - Exfiltrate data from applications
19
+ #
20
+ # ONLY use this tool in trusted environments with proper security controls:
21
+ # - Ensure containers run with minimal privileges
22
+ # - Use read-only filesystems where possible
23
+ # - Implement proper network segmentation
24
+ # - Monitor and audit all command executions
25
+ # - Never expose this tool to untrusted clients
26
+ #
27
+ # == Features
28
+ #
29
+ # - Execute commands with custom working directory
30
+ # - Run commands as specific users
31
+ # - Set custom environment variables
32
+ # - Provide stdin input to commands
33
+ # - Configurable timeout protection
34
+ # - Comprehensive error handling
35
+ # - Separate stdout/stderr capture
36
+ #
37
+ # == Example Usage
38
+ #
39
+ # # Simple command execution
40
+ # ExecContainer.call(
41
+ # server_context: context,
42
+ # id: "my-container",
43
+ # cmd: "ls -la /app"
44
+ # )
45
+ #
46
+ # # Advanced execution with custom environment
47
+ # ExecContainer.call(
48
+ # server_context: context,
49
+ # id: "web-server",
50
+ # cmd: "python manage.py migrate",
51
+ # working_dir: "/app",
52
+ # user: "appuser",
53
+ # env: ["DJANGO_ENV=production", "DEBUG=false"],
54
+ # timeout: 120
55
+ # )
56
+ #
57
+ # @see Docker::Container#exec
58
+ # @since 0.1.0
59
+ class ExecContainer < MCP::Tool
60
+ description 'Execute a command inside a running Docker container. ' \
61
+ 'WARNING: This provides arbitrary command execution within the container. ' \
62
+ 'Ensure proper security measures are in place.'
63
+
64
+ input_schema(
65
+ properties: {
66
+ id: {
67
+ type: 'string',
68
+ description: 'Container ID or name'
69
+ },
70
+ cmd: {
71
+ type: 'string',
72
+ description: 'Command to execute (e.g., "ls -la /app" or "python script.py")'
73
+ },
74
+ working_dir: {
75
+ type: 'string',
76
+ description: 'Working directory for the command (optional)'
77
+ },
78
+ user: {
79
+ type: 'string',
80
+ description: 'User to run the command as (optional, e.g., "1000" or "username")'
81
+ },
82
+ env: {
83
+ type: 'array',
84
+ items: { type: 'string' },
85
+ description: 'Environment variables as KEY=VALUE (optional)'
86
+ },
87
+ stdin: {
88
+ type: 'string',
89
+ description: 'Input to send to the command via stdin (optional)'
90
+ },
91
+ timeout: {
92
+ type: 'integer',
93
+ description: 'Timeout in seconds (optional, default: 60)'
94
+ }
95
+ },
96
+ required: %w[id cmd]
97
+ )
98
+
99
+ # Execute a command inside a running Docker container.
100
+ #
101
+ # This method provides comprehensive command execution capabilities within
102
+ # Docker containers, including advanced features like custom user context,
103
+ # environment variables, working directory, and stdin input.
104
+ #
105
+ # The command is parsed using shell-like syntax with support for quoted
106
+ # arguments. Output is captured separately for stdout and stderr, and
107
+ # the exit code is reported.
108
+ #
109
+ # @param id [String] container ID or name to execute command in
110
+ # @param cmd [String] command to execute (shell-parsed into arguments)
111
+ # @param server_context [Object] MCP server context (unused but required)
112
+ # @param working_dir [String, nil] working directory for command execution
113
+ # @param user [String, nil] user to run command as (username or UID)
114
+ # @param env [Array<String>, nil] environment variables in KEY=VALUE format
115
+ # @param stdin [String, nil] input to send to command via stdin
116
+ # @param timeout [Integer] maximum execution time in seconds (default: 60)
117
+ #
118
+ # @return [MCP::Tool::Response] execution results including stdout, stderr, and exit code
119
+ #
120
+ # @raise [Docker::Error::NotFoundError] if container doesn't exist
121
+ # @raise [Docker::Error::TimeoutError] if execution exceeds timeout
122
+ # @raise [StandardError] for other execution failures
123
+ #
124
+ # @example Basic command execution
125
+ # response = ExecContainer.call(
126
+ # server_context: context,
127
+ # id: "web-container",
128
+ # cmd: "nginx -t"
129
+ # )
130
+ #
131
+ # @example Advanced execution with environment
132
+ # response = ExecContainer.call(
133
+ # server_context: context,
134
+ # id: "app-container",
135
+ # cmd: "bundle exec rails console",
136
+ # working_dir: "/app",
137
+ # user: "rails",
138
+ # env: ["RAILS_ENV=production"],
139
+ # timeout: 300
140
+ # )
141
+ #
142
+ # @see Docker::Container#exec
143
+ def self.call(id:, cmd:, server_context:, working_dir: nil, user: nil,
144
+ env: nil, stdin: nil, timeout: 60)
145
+ container = Docker::Container.get(id)
146
+
147
+ # Parse command string into array
148
+ # Simple shell-like parsing: split on spaces but respect quoted strings
149
+
150
+ cmd_array = Shellwords.split(cmd)
151
+
152
+ # Build exec options
153
+ exec_options = {
154
+ 'Cmd' => cmd_array,
155
+ 'AttachStdout' => true,
156
+ 'AttachStderr' => true
157
+ }
158
+ exec_options['WorkingDir'] = working_dir if working_dir
159
+ exec_options['User'] = user if user
160
+ exec_options['Env'] = env if env
161
+ exec_options['AttachStdin'] = true if stdin
162
+
163
+ # Execute the command
164
+ stdout_data = []
165
+ stderr_data = []
166
+ exit_code = nil
167
+
168
+ begin
169
+ # Use container.exec which returns [stdout, stderr, exit_code]
170
+ result = if stdin
171
+ container.exec(cmd_array, stdin: StringIO.new(stdin), wait: timeout)
172
+ else
173
+ container.exec(cmd_array, wait: timeout)
174
+ end
175
+
176
+ stdout_data = result[0]
177
+ stderr_data = result[1]
178
+ exit_code = result[2]
179
+ rescue Docker::Error::TimeoutError
180
+ return MCP::Tool::Response.new([{
181
+ type: 'text',
182
+ text: "Command execution timed out after #{timeout} seconds"
183
+ }])
184
+ end
185
+
186
+ # Format response
187
+ response_text = "Command executed in container #{id}\n"
188
+ response_text += "Exit code: #{exit_code}\n\n"
189
+
190
+ if stdout_data && !stdout_data.empty?
191
+ stdout_str = stdout_data.join
192
+ response_text += "STDOUT:\n#{stdout_str}\n" unless stdout_str.strip.empty?
193
+ end
194
+
195
+ if stderr_data && !stderr_data.empty?
196
+ stderr_str = stderr_data.join
197
+ response_text += "\nSTDERR:\n#{stderr_str}\n" unless stderr_str.strip.empty?
198
+ end
199
+
200
+ MCP::Tool::Response.new([{
201
+ type: 'text',
202
+ text: response_text.strip
203
+ }])
204
+ rescue Docker::Error::NotFoundError
205
+ MCP::Tool::Response.new([{
206
+ type: 'text',
207
+ text: "Container #{id} not found"
208
+ }])
209
+ rescue StandardError => e
210
+ MCP::Tool::Response.new([{
211
+ type: 'text',
212
+ text: "Error executing command: #{e.message}"
213
+ }])
214
+ end
215
+ end
216
+ end