@supernova123/docker-mcp-server 0.3.3 → 0.3.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,23 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ node-version: [18, 20, 22]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-node@v4
18
+ with:
19
+ node-version: ${{ matrix.node-version }}
20
+ cache: npm
21
+ - run: npm ci
22
+ - run: npm test
23
+ - run: npx tsc --noEmit
package/CHANGELOG.md CHANGED
@@ -1,7 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.5] - 2026-06-14
4
+
5
+ ### Added
6
+ - `prune_containers` — Remove all stopped Docker containers with optional label filters
7
+ - `prune_images` — Remove unused Docker images (dangling and unreferenced) with optional filters
8
+ - `update_container` — Update container resource limits (CPU, memory, CPU shares)
9
+
10
+ ### Changed
11
+ - Improved tool descriptions for Glama Quality optimization (commit 255cf60)
12
+ - Added CI workflow (GitHub Actions, Node 18/20/22)
13
+ - Added `relatedServers` to glama.json
14
+
15
+
3
16
  All notable changes to @supernova123/docker-mcp-server will be documented in this file.
4
17
 
18
+ ## [0.3.4] - 2026-06-14
19
+
20
+ ### Added
21
+ - **copy_from_container** tool — copy files from a container to the host filesystem
22
+ - **copy_to_container** tool — copy files from the host to a container filesystem
23
+ - Glama "Try it now" link in README for zero-install tool testing
24
+
25
+ ## [0.3.3] - 2026-06-13
26
+
27
+ ### Added
28
+ - **docker_info** tool — Docker daemon system information (version, OS, kernel, CPU, memory, storage driver, container/image counts)
29
+ - **disk_usage** tool — Disk usage breakdown by images, containers, volumes, and build cache with human-readable sizes
30
+
5
31
  ## [0.3.2] - 2026-06-13
6
32
 
7
33
  ### Added
@@ -95,4 +121,4 @@ All notable changes to @supernova123/docker-mcp-server will be documented in thi
95
121
  ## [0.1.0] - 2026-06-10
96
122
 
97
123
  ### Added
98
- - Initial release: 25 tools across container, compose, exec, health, logs, image, and network modules
124
+ - Initial release: 25 tools across container, compose, exec, health, logs, image, and network modules
package/README.md CHANGED
@@ -55,6 +55,8 @@ Here's what an agent actually does with this server during a deployment:
55
55
 
56
56
  If the health check fails at step 2, your agent catches it immediately — no 3am alerts, no user complaints. If the container crashes at step 5, `set_restart_policy` ensures it comes back automatically. The agent doesn't just deploy containers — it keeps them running.
57
57
 
58
+ **[▶ Try it now on Glama](https://glama.ai/mcp/servers/friendlygeorge/docker-mcp-server)** — test all 31 tools in your browser, no install required.
59
+
58
60
  ## Quick Start
59
61
 
60
62
  One command to run:
package/dist/server.js CHANGED
@@ -9,10 +9,11 @@ import { registerNetworkTools } from "./tools/network.js";
9
9
  import { registerVolumeTools } from "./tools/volume.js";
10
10
  import { registerMonitoringTools } from "./tools/monitoring.js";
11
11
  import { registerSystemTools } from "./tools/system.js";
12
+ import { registerTransferTools } from "./tools/transfer.js";
12
13
  export function createServer(docker, options) {
13
14
  const server = new McpServer({
14
15
  name: "docker-mcp-server",
15
- version: "0.3.3",
16
+ version: "0.3.4",
16
17
  });
17
18
  // Register all tool categories
18
19
  registerContainerTools(server, docker);
@@ -25,6 +26,7 @@ export function createServer(docker, options) {
25
26
  registerVolumeTools(server, docker);
26
27
  registerMonitoringTools(server, docker);
27
28
  registerSystemTools(server, docker);
29
+ registerTransferTools(server, docker);
28
30
  return server;
29
31
  }
30
32
  //# sourceMappingURL=server.js.map
@@ -38,7 +38,7 @@ function runCompose(path, args) {
38
38
  }
39
39
  }
40
40
  export function registerComposeTools(server) {
41
- server.tool("compose_up", "Bring up Docker Compose services from a docker-compose.yml file. Optionally build images first.", ComposeUpSchema.shape, { idempotentHint: true, openWorldHint: false }, async (params) => {
41
+ server.tool("compose_up", "Bring up Docker Compose services from a docker-compose.yml file at path. Use compose_ps to check service states after bringing them up; use compose_logs to inspect output. Optionally rebuild images before starting (build=true). Returns a confirmation string listing which services were started. Idempotent: already-running services are left untouched. Returns an error string if the Compose file is missing or invalid.", ComposeUpSchema.shape, { idempotentHint: true, openWorldHint: false }, async (params) => {
42
42
  try {
43
43
  const args = ["up", "-d"];
44
44
  if (params.build)
@@ -66,7 +66,7 @@ export function registerComposeTools(server) {
66
66
  return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
67
67
  }
68
68
  });
69
- server.tool("compose_ps", "List service states across a Docker Compose stack.", ComposePsSchema.shape, { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, async (params) => {
69
+ server.tool("compose_ps", "List service states across a Docker Compose stack defined by docker-compose.yml at path. Returns an array of services with name, state (running, exited, etc.), health status, and port mappings. Use compose_up to start services; use compose_logs to inspect output. Read-only and safe to call repeatedly. Returns an error string if the Compose file is missing.", ComposePsSchema.shape, { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, async (params) => {
70
70
  try {
71
71
  const output = runCompose(params.path, ["ps", "--format", "json"]);
72
72
  const lines = output.split("\n").filter(Boolean);
@@ -1,4 +1,4 @@
1
- import { ListContainersSchema, InspectContainerSchema, StartContainerSchema, StopContainerSchema, RestartContainerSchema, RemoveContainerSchema, RecreateContainerSchema, RunContainerSchema, } from "../types.js";
1
+ import { ListContainersSchema, InspectContainerSchema, StartContainerSchema, StopContainerSchema, RestartContainerSchema, RemoveContainerSchema, RecreateContainerSchema, RunContainerSchema, PruneContainersSchema, UpdateContainerSchema, } from "../types.js";
2
2
  import { formatContainer, formatError, withRetry } from "../docker.js";
3
3
  export function registerContainerTools(server, docker) {
4
4
  server.tool("list_containers", "List Docker containers with optional filters (state, label, name). Returns container IDs, names, images, states, ports, and labels.", ListContainersSchema.shape, { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, async (params) => {
@@ -161,5 +161,90 @@ export function registerContainerTools(server, docker) {
161
161
  return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
162
162
  }
163
163
  });
164
+ // prune_containers — remove stopped containers
165
+ server.tool("prune_containers", "Remove all stopped Docker containers. Returns the number of containers removed and reclaimed disk space. This is a destructive operation — stopped containers and their non-persisted data will be deleted. Use list_containers first to see what will be removed. Useful for cleanup after deployments or when disk space is low.", PruneContainersSchema.shape, { readOnlyHint: false, idempotentHint: false, openWorldHint: false }, async (params) => {
166
+ try {
167
+ const filterObj = {};
168
+ if (params.filter) {
169
+ const parts = params.filter.split('=');
170
+ if (parts.length === 2) {
171
+ filterObj[parts[0]] = [parts[1]];
172
+ }
173
+ }
174
+ const result = await withRetry(() => docker.pruneContainers({ filters: filterObj }), { label: "prune_containers" });
175
+ return {
176
+ content: [{
177
+ type: "text",
178
+ text: JSON.stringify({
179
+ containers_deleted: (result.ContainersDeleted || []).length,
180
+ space_reclaimed: result.SpaceReclaimed || 0,
181
+ space_reclaimed_human: formatBytes(result.SpaceReclaimed || 0),
182
+ deleted_ids: (result.ContainersDeleted || []).map((id) => id.substring(0, 12)),
183
+ }, null, 2),
184
+ }],
185
+ };
186
+ }
187
+ catch (error) {
188
+ return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
189
+ }
190
+ });
191
+ // update_container — update container resource limits
192
+ server.tool("update_container", "Update a Docker container's resource limits (CPU, memory, CPU shares). Requires the container to be stopped first. Returns the updated resource limits. Use this to right-size containers based on actual usage — set CPU limits to prevent runaway processes and memory limits to prevent OOM kills.", UpdateContainerSchema.shape, { readOnlyHint: false, idempotentHint: false, openWorldHint: false }, async (params) => {
193
+ try {
194
+ const updateConfig = {};
195
+ if (params.cpu_limit !== undefined) {
196
+ updateConfig.NanoCpus = Math.round(params.cpu_limit * 1e9);
197
+ }
198
+ if (params.memory_limit !== undefined) {
199
+ updateConfig.Memory = parseMemory(params.memory_limit);
200
+ }
201
+ if (params.cpu_shares !== undefined) {
202
+ updateConfig.CpuShares = params.cpu_shares;
203
+ }
204
+ if (Object.keys(updateConfig).length === 0) {
205
+ return { content: [{ type: "text", text: "Error: No resource limits specified. Provide at least one of: cpu_limit, memory_limit, cpu_shares." }], isError: true };
206
+ }
207
+ const container = docker.getContainer(params.container_id);
208
+ await withRetry(() => container.update(updateConfig), { label: "update_container" });
209
+ // Inspect to return current state
210
+ const info = await withRetry(() => container.inspect(), { label: "update_container_inspect" });
211
+ const hostConfig = info.HostConfig || {};
212
+ return {
213
+ content: [{
214
+ type: "text",
215
+ text: JSON.stringify({
216
+ container: params.container_id,
217
+ state: info.State?.Status,
218
+ resource_limits: {
219
+ cpu_limit_cores: hostConfig.NanoCpus ? hostConfig.NanoCpus / 1e9 : null,
220
+ memory_limit: hostConfig.Memory || null,
221
+ memory_limit_human: hostConfig.Memory ? formatBytes(hostConfig.Memory) : null,
222
+ cpu_shares: hostConfig.CpuShares || null,
223
+ },
224
+ }, null, 2),
225
+ }],
226
+ };
227
+ }
228
+ catch (error) {
229
+ return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
230
+ }
231
+ });
232
+ }
233
+ function formatBytes(bytes) {
234
+ if (bytes === 0)
235
+ return '0 B';
236
+ const k = 1024;
237
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
238
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
239
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
240
+ }
241
+ function parseMemory(mem) {
242
+ const match = mem.match(/^(\d+)(b|k|m|g|t)?$/i);
243
+ if (!match)
244
+ throw new Error(`Invalid memory format: ${mem}`);
245
+ const value = parseInt(match[1]);
246
+ const unit = (match[2] || 'b').toLowerCase();
247
+ const multipliers = { b: 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
248
+ return value * (multipliers[unit] || 1);
164
249
  }
165
250
  //# sourceMappingURL=container.js.map
@@ -108,7 +108,7 @@ export function registerHealthTools(server, docker) {
108
108
  return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
109
109
  }
110
110
  });
111
- server.tool("set_restart_policy", "Change the restart policy of a running container without recreating it.", SetRestartPolicySchema.shape, { idempotentHint: true, openWorldHint: false }, async (params) => {
111
+ server.tool("set_restart_policy", "Change the restart policy of a running container without recreating it. Use restart_container for an immediate restart; use this tool to change the policy (always, unless-stopped, on-failure, no) for future restarts. Returns a confirmation string on success. Idempotent: setting the same policy is a no-op. Returns an error string if the container does not exist.", SetRestartPolicySchema.shape, { idempotentHint: true, openWorldHint: false }, async (params) => {
112
112
  try {
113
113
  const container = docker.getContainer(params.container_id);
114
114
  await container.update({
@@ -1,4 +1,4 @@
1
- import { ListImagesSchema, PullImageSchema, BuildImageSchema, RemoveImageSchema, } from "../types.js";
1
+ import { ListImagesSchema, PullImageSchema, BuildImageSchema, RemoveImageSchema, PruneImagesSchema, } from "../types.js";
2
2
  import { formatImage, formatError, withRetry } from "../docker.js";
3
3
  export function registerImageTools(server, docker) {
4
4
  server.tool("list_images", "List Docker images with optional filters. Returns image IDs, tags, sizes, and creation dates.", ListImagesSchema.shape, { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, async (params) => {
@@ -14,7 +14,7 @@ export function registerImageTools(server, docker) {
14
14
  return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
15
15
  }
16
16
  });
17
- server.tool("pull_image", "Pull a Docker image from a registry. Returns pull progress events.", PullImageSchema.shape, { idempotentHint: true, openWorldHint: false }, async (params) => {
17
+ server.tool("pull_image", "Pull a Docker image from a registry by image name (e.g. nginx:latest). Use list_images to see locally available images after pulling. Returns pull progress events as text. Idempotent: pulling an already-up-to-date image is a no-op. Returns an error string if the image does not exist on the registry or the pull fails.", PullImageSchema.shape, { idempotentHint: true, openWorldHint: false }, async (params) => {
18
18
  try {
19
19
  const imageRef = params.tag ? `${params.image}:${params.tag}` : params.image;
20
20
  const stream = await docker.pull(imageRef);
@@ -63,5 +63,47 @@ export function registerImageTools(server, docker) {
63
63
  return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
64
64
  }
65
65
  });
66
+ // prune_images — remove unused Docker images
67
+ server.tool("prune_images", "Remove unused Docker images (dangling and unreferenced). Returns the number of images deleted and reclaimed disk space. Only removes images not used by any container. Use list_images first to see what will be removed. Useful for reclaiming disk space after builds or when switching base images frequently.", PruneImagesSchema.shape, { readOnlyHint: false, idempotentHint: false, openWorldHint: false }, async (params) => {
68
+ try {
69
+ const filterObj = {};
70
+ if (params.filter) {
71
+ try {
72
+ const parsed = JSON.parse(params.filter);
73
+ Object.assign(filterObj, parsed);
74
+ }
75
+ catch {
76
+ // If not JSON, try key=value format
77
+ const parts = params.filter.split('=');
78
+ if (parts.length === 2) {
79
+ filterObj[parts[0]] = [parts[1]];
80
+ }
81
+ }
82
+ }
83
+ const result = await withRetry(() => docker.pruneImages({ filters: filterObj }), { label: "prune_images" });
84
+ return {
85
+ content: [{
86
+ type: "text",
87
+ text: JSON.stringify({
88
+ images_deleted: (result.ImagesDeleted || []).length,
89
+ space_reclaimed: result.SpaceReclaimed || 0,
90
+ space_reclaimed_human: formatBytes(result.SpaceReclaimed || 0),
91
+ deleted_ids: (result.ImagesDeleted || []).map((img) => typeof img === 'string' ? img.substring(0, 19) : img.Deleted?.substring(0, 19) || 'unknown'),
92
+ }, null, 2),
93
+ }],
94
+ };
95
+ }
96
+ catch (error) {
97
+ return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
98
+ }
99
+ });
100
+ }
101
+ function formatBytes(bytes) {
102
+ if (bytes === 0)
103
+ return '0 B';
104
+ const k = 1024;
105
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
106
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
107
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
66
108
  }
67
109
  //# sourceMappingURL=image.js.map
@@ -0,0 +1,4 @@
1
+ import Dockerode from "dockerode";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare function registerTransferTools(server: McpServer, docker: Dockerode): void;
4
+ //# sourceMappingURL=transfer.d.ts.map
@@ -0,0 +1,175 @@
1
+ import { Readable } from "stream";
2
+ import { CopyFromContainerSchema, CopyToContainerSchema } from "../types.js";
3
+ import { formatError, withRetry } from "../docker.js";
4
+ /**
5
+ * Read a file from a container using docker exec (cat).
6
+ * Much simpler and more reliable than parsing getArchive tar streams.
7
+ */
8
+ async function readFileViaExec(docker, containerId, filePath) {
9
+ const container = docker.getContainer(containerId);
10
+ // Create exec to cat the file
11
+ const exec = await container.exec({
12
+ Cmd: ["cat", filePath],
13
+ AttachStdout: true,
14
+ AttachStderr: true,
15
+ });
16
+ // Start exec and collect output
17
+ const stream = await exec.start({ Detach: false });
18
+ const chunks = [];
19
+ return new Promise((resolve, reject) => {
20
+ stream.on("data", (chunk) => {
21
+ // Docker exec streams have 8-byte headers per frame
22
+ // Skip the header bytes (first 8 bytes of each frame)
23
+ if (chunk.length > 8) {
24
+ chunks.push(chunk.slice(8));
25
+ }
26
+ });
27
+ stream.on("end", () => {
28
+ const content = Buffer.concat(chunks).toString("utf-8");
29
+ resolve({ content, size: content.length });
30
+ });
31
+ stream.on("error", reject);
32
+ });
33
+ }
34
+ /**
35
+ * Get file metadata (size, permissions) via stat command.
36
+ */
37
+ async function getFileStat(docker, containerId, filePath) {
38
+ const container = docker.getContainer(containerId);
39
+ const exec = await container.exec({
40
+ Cmd: ["stat", "-c", "%s %a %f", filePath],
41
+ AttachStdout: true,
42
+ AttachStderr: true,
43
+ });
44
+ const stream = await exec.start({ Detach: false });
45
+ const chunks = [];
46
+ return new Promise((resolve, reject) => {
47
+ stream.on("data", (chunk) => {
48
+ if (chunk.length > 8)
49
+ chunks.push(chunk.slice(8));
50
+ });
51
+ stream.on("end", () => {
52
+ const output = Buffer.concat(chunks).toString("utf-8").trim();
53
+ const [sizeStr, modeStr, typeStr] = output.split(" ");
54
+ const size = parseInt(sizeStr, 10) || 0;
55
+ const mode = modeStr || "644";
56
+ const isFile = typeStr?.startsWith("81") ?? true;
57
+ resolve({ size, mode: `0${mode}`, isFile });
58
+ });
59
+ stream.on("error", reject);
60
+ });
61
+ }
62
+ /**
63
+ * Create a minimal tar archive buffer containing a single file.
64
+ * Used for putArchive to inject files into containers.
65
+ */
66
+ function createSingleFileTar(filePath, content, mode) {
67
+ const contentBuffer = Buffer.from(content, "utf-8");
68
+ const contentBlocks = Math.ceil(contentBuffer.length / 512);
69
+ const totalSize = 512 + contentBlocks * 512;
70
+ const tar = Buffer.alloc(totalSize, 0);
71
+ // File name (100 bytes, null-terminated)
72
+ const nameBytes = Buffer.from(filePath, "utf-8");
73
+ nameBytes.copy(tar, 0, 0, Math.min(nameBytes.length, 100));
74
+ // File mode (8 bytes, octal, null-padded)
75
+ const modeStr = mode.toString(8).padStart(7, "0") + "\0";
76
+ Buffer.from(modeStr).copy(tar, 100);
77
+ // Owner ID (8 bytes) - 0
78
+ Buffer.from("0000000\0").copy(tar, 108);
79
+ // Group ID (8 bytes) - 0
80
+ Buffer.from("0000000\0").copy(tar, 116);
81
+ // File size (12 bytes, octal)
82
+ const sizeStr = contentBuffer.length.toString(8).padStart(11, "0") + "\0";
83
+ Buffer.from(sizeStr).copy(tar, 124);
84
+ // Modification time (12 bytes, octal)
85
+ const mtime = Math.floor(Date.now() / 1000);
86
+ const mtimeStr = mtime.toString(8).padStart(11, "0") + "\0";
87
+ Buffer.from(mtimeStr).copy(tar, 136);
88
+ // Type flag (1 byte) - '0' = regular file
89
+ tar[156] = 0x30; // '0'
90
+ // Checksum placeholder (8 bytes)
91
+ tar.fill(" ", 148, 156);
92
+ // Compute checksum
93
+ let checksum = 0;
94
+ for (let i = 0; i < 512; i++) {
95
+ checksum += tar[i];
96
+ }
97
+ const chkStr = checksum.toString(8).padStart(7, "0") + "\0";
98
+ Buffer.from(chkStr).copy(tar, 148);
99
+ // Copy content
100
+ contentBuffer.copy(tar, 512);
101
+ return tar;
102
+ }
103
+ export function registerTransferTools(server, docker) {
104
+ server.tool("copy_from_container", "Copy a file from a Docker container to read its contents. Returns the file content as text along with metadata (size, permissions). Useful for inspecting config files, logs, or application state inside running containers.", CopyFromContainerSchema.shape, { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, async (params) => {
105
+ try {
106
+ const { content, size } = await withRetry(() => readFileViaExec(docker, params.container_id, params.container_path), { label: "copy_from_container" });
107
+ // Get metadata (stat)
108
+ let mode = "0644";
109
+ try {
110
+ const stat = await getFileStat(docker, params.container_id, params.container_path);
111
+ mode = stat.mode;
112
+ }
113
+ catch {
114
+ // stat might fail if file doesn't exist, exec already validated it
115
+ }
116
+ const result = {
117
+ path: params.container_path,
118
+ content,
119
+ size,
120
+ mode,
121
+ truncated: content.length > 50000,
122
+ };
123
+ return {
124
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
125
+ };
126
+ }
127
+ catch (error) {
128
+ return {
129
+ content: [{ type: "text", text: `Error: ${formatError(error)}` }],
130
+ isError: true,
131
+ };
132
+ }
133
+ });
134
+ server.tool("copy_to_container", "Write file content into a Docker container at the specified path. Overwrites existing files. Useful for injecting configuration files, scripts, or environment files into running or stopped containers.", CopyToContainerSchema.shape, { readOnlyHint: false, idempotentHint: false, openWorldHint: false }, async (params) => {
135
+ try {
136
+ const container = docker.getContainer(params.container_id);
137
+ const mode = params.mode ?? 0o644;
138
+ // Create tar archive with the file
139
+ const tarBuffer = createSingleFileTar(params.container_path, params.content, mode);
140
+ // putArchive expects the path to be the PARENT directory
141
+ const parts = params.container_path.split("/");
142
+ parts.pop(); // remove filename
143
+ const dirPath = parts.join("/") || "/";
144
+ const readable = Readable.from(tarBuffer);
145
+ // Use putArchive with promise API
146
+ await withRetry(() => new Promise((resolve, reject) => {
147
+ container
148
+ .putArchive(readable, { path: dirPath })
149
+ .then(() => resolve())
150
+ .catch(reject);
151
+ }), { label: "copy_to_container" });
152
+ return {
153
+ content: [
154
+ {
155
+ type: "text",
156
+ text: JSON.stringify({
157
+ success: true,
158
+ path: params.container_path,
159
+ size: Buffer.byteLength(params.content, "utf-8"),
160
+ mode: `0${(mode & 0o777).toString(8)}`,
161
+ message: `File written to ${params.container_path} in container ${params.container_id}`,
162
+ }),
163
+ },
164
+ ],
165
+ };
166
+ }
167
+ catch (error) {
168
+ return {
169
+ content: [{ type: "text", text: `Error: ${formatError(error)}` }],
170
+ isError: true,
171
+ };
172
+ }
173
+ });
174
+ }
175
+ //# sourceMappingURL=transfer.js.map
package/dist/types.d.ts CHANGED
@@ -346,6 +346,36 @@ export declare const PruneVolumesSchema: z.ZodObject<{
346
346
  }, {
347
347
  filter?: string | undefined;
348
348
  }>;
349
+ export declare const PruneContainersSchema: z.ZodObject<{
350
+ filter: z.ZodOptional<z.ZodString>;
351
+ }, "strip", z.ZodTypeAny, {
352
+ filter?: string | undefined;
353
+ }, {
354
+ filter?: string | undefined;
355
+ }>;
356
+ export declare const PruneImagesSchema: z.ZodObject<{
357
+ filter: z.ZodOptional<z.ZodString>;
358
+ }, "strip", z.ZodTypeAny, {
359
+ filter?: string | undefined;
360
+ }, {
361
+ filter?: string | undefined;
362
+ }>;
363
+ export declare const UpdateContainerSchema: z.ZodObject<{
364
+ container_id: z.ZodString;
365
+ cpu_limit: z.ZodOptional<z.ZodNumber>;
366
+ memory_limit: z.ZodOptional<z.ZodString>;
367
+ cpu_shares: z.ZodOptional<z.ZodNumber>;
368
+ }, "strip", z.ZodTypeAny, {
369
+ container_id: string;
370
+ cpu_limit?: number | undefined;
371
+ memory_limit?: string | undefined;
372
+ cpu_shares?: number | undefined;
373
+ }, {
374
+ container_id: string;
375
+ cpu_limit?: number | undefined;
376
+ memory_limit?: string | undefined;
377
+ cpu_shares?: number | undefined;
378
+ }>;
349
379
  export declare const ContainerHealthStatusSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
350
380
  export declare const ContainerResourceUsageSchema: z.ZodObject<{
351
381
  sort_by: z.ZodOptional<z.ZodEnum<["cpu", "memory", "network"]>>;
@@ -405,4 +435,30 @@ export declare const ResourceAlertCheckSchema: z.ZodObject<{
405
435
  export declare const MonitorDashboardSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
406
436
  export declare const DockerInfoSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
407
437
  export declare const DiskUsageSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
438
+ export declare const CopyFromContainerSchema: z.ZodObject<{
439
+ container_id: z.ZodString;
440
+ container_path: z.ZodString;
441
+ }, "strip", z.ZodTypeAny, {
442
+ container_id: string;
443
+ container_path: string;
444
+ }, {
445
+ container_id: string;
446
+ container_path: string;
447
+ }>;
448
+ export declare const CopyToContainerSchema: z.ZodObject<{
449
+ container_id: z.ZodString;
450
+ container_path: z.ZodString;
451
+ content: z.ZodString;
452
+ mode: z.ZodOptional<z.ZodNumber>;
453
+ }, "strip", z.ZodTypeAny, {
454
+ container_id: string;
455
+ container_path: string;
456
+ content: string;
457
+ mode?: number | undefined;
458
+ }, {
459
+ container_id: string;
460
+ container_path: string;
461
+ content: string;
462
+ mode?: number | undefined;
463
+ }>;
408
464
  //# sourceMappingURL=types.d.ts.map
package/dist/types.js CHANGED
@@ -149,6 +149,18 @@ export const RemoveVolumeSchema = z.object({
149
149
  export const PruneVolumesSchema = z.object({
150
150
  filter: z.string().optional().describe("Filter by label (e.g., 'label=key=value')"),
151
151
  });
152
+ export const PruneContainersSchema = z.object({
153
+ filter: z.string().optional().describe("Filter by label (e.g., 'label=key=value')"),
154
+ });
155
+ export const PruneImagesSchema = z.object({
156
+ filter: z.string().optional().describe('Docker filters JSON (e.g. "dangling=true")'),
157
+ });
158
+ export const UpdateContainerSchema = z.object({
159
+ container_id: z.string().describe('Container ID or name'),
160
+ cpu_limit: z.number().optional().describe('CPU limit in cores (e.g. 1.5 for 1.5 CPUs)'),
161
+ memory_limit: z.string().optional().describe('Memory limit (e.g. "512m", "1g", "2048m")'),
162
+ cpu_shares: z.number().optional().describe('CPU shares (relative weight, 0-1024)'),
163
+ });
152
164
  // Monitoring schemas (v0.2.0)
153
165
  export const ContainerHealthStatusSchema = z.object({});
154
166
  export const ContainerResourceUsageSchema = z.object({
@@ -176,4 +188,15 @@ export const MonitorDashboardSchema = z.object({});
176
188
  // System info schemas (v0.3.3)
177
189
  export const DockerInfoSchema = z.object({});
178
190
  export const DiskUsageSchema = z.object({});
191
+ // File transfer schemas (v0.3.4)
192
+ export const CopyFromContainerSchema = z.object({
193
+ container_id: z.string().describe("Container ID or name"),
194
+ container_path: z.string().describe("Path inside container to copy from (e.g., '/etc/nginx/nginx.conf')"),
195
+ });
196
+ export const CopyToContainerSchema = z.object({
197
+ container_id: z.string().describe("Container ID or name"),
198
+ container_path: z.string().describe("Destination path inside container (e.g., '/app/config.json')"),
199
+ content: z.string().describe("File content to write (plain text)"),
200
+ mode: z.number().optional().describe("File permissions in octal (e.g., 0o644 = 420). Default: 0o644"),
201
+ });
179
202
  //# sourceMappingURL=types.js.map
package/glama.json CHANGED
@@ -1,12 +1,35 @@
1
1
  {
2
2
  "$schema": "https://glama.ai/schemas/glama.json",
3
- "maintainers": ["friendlygeorge"],
3
+ "maintainers": [
4
+ "friendlygeorge"
5
+ ],
4
6
  "title": "Docker MCP Server",
5
- "description": "31 tools for AI agent Docker management container lifecycle, Compose stack operations, health checks, log streaming, and fleet monitoring through the Model Context Protocol.",
6
- "tags": ["docker", "containers", "mcp", "compose", "health-checks", "monitoring", "devops", "ai-agents", "typescript", "self-healing"],
7
+ "description": "31 tools for AI agent Docker management \u2014 container lifecycle, Compose stack operations, health checks, log streaming, and fleet monitoring through the Model Context Protocol.",
8
+ "tags": [
9
+ "docker",
10
+ "containers",
11
+ "mcp",
12
+ "compose",
13
+ "health-checks",
14
+ "monitoring",
15
+ "devops",
16
+ "ai-agents",
17
+ "typescript",
18
+ "self-healing"
19
+ ],
7
20
  "repository": "https://github.com/friendlygeorge/docker-mcp-server",
8
21
  "license": "MIT",
9
- "categories": ["virtualization", "developer-tools"],
22
+ "categories": [
23
+ "virtualization",
24
+ "developer-tools"
25
+ ],
10
26
  "language": "TypeScript",
11
- "runtime": "node"
27
+ "runtime": "node",
28
+ "relatedServers": [
29
+ "https://github.com/friendlygeorge/resend-mcp-server",
30
+ "https://github.com/friendlygeorge/defillama-mcp-server",
31
+ "https://github.com/friendlygeorge/coingecko-mcp-server",
32
+ "https://github.com/friendlygeorge/etherscan-mcp-server",
33
+ "https://github.com/friendlygeorge/jobber-mcp-server"
34
+ ]
12
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supernova123/docker-mcp-server",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "mcpName": "io.github.friendlygeorge/docker-mcp-server",
5
5
  "description": "MCP server for Docker — container management, health checks, auto-restart, Compose lifecycle, and log streaming for Claude, Cursor, and AI agents",
6
6
  "type": "module",
@@ -13,7 +13,10 @@
13
13
  "start": "node dist/index.js",
14
14
  "dev": "tsc && node dist/index.js",
15
15
  "test": "vitest run",
16
- "prepublishOnly": "npm run build"
16
+ "prepublishOnly": "npm run build",
17
+ "cf:dev": "wrangler dev src/cf-worker/index.ts",
18
+ "cf:deploy": "wrangler deploy src/cf-worker/index.ts",
19
+ "cf:typecheck": "tsc --noEmit src/cf-worker/*.ts"
17
20
  },
18
21
  "keywords": [
19
22
  "mcp",
@@ -60,9 +63,11 @@
60
63
  "zod": "^3.24.0"
61
64
  },
62
65
  "devDependencies": {
66
+ "@cloudflare/workers-types": "^4.20260613.1",
63
67
  "@types/dockerode": "^3.3.31",
64
68
  "@types/node": "^22.0.0",
65
69
  "typescript": "^5.7.0",
66
- "vitest": "^3.1.0"
70
+ "vitest": "^3.1.0",
71
+ "wrangler": "^4.100.0"
67
72
  }
68
- }
73
+ }