@supernova123/docker-mcp-server 0.1.4 → 0.1.6
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.
- package/.dockerignore +9 -0
- package/Dockerfile +29 -0
- package/dist/tools/container.js +27 -2
- package/package.json +7 -7
- package/src/tools/container.ts +25 -3
package/.dockerignore
ADDED
package/Dockerfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
FROM node:22-slim AS builder
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Copy package files first for better layer caching
|
|
6
|
+
COPY package.json package-lock.json ./
|
|
7
|
+
RUN npm ci --ignore-scripts
|
|
8
|
+
|
|
9
|
+
# Copy source and build
|
|
10
|
+
COPY tsconfig.json ./
|
|
11
|
+
COPY src/ ./src/
|
|
12
|
+
RUN npm run build
|
|
13
|
+
|
|
14
|
+
# Production stage
|
|
15
|
+
FROM node:22-slim
|
|
16
|
+
|
|
17
|
+
WORKDIR /app
|
|
18
|
+
|
|
19
|
+
# Copy package files and install production deps only
|
|
20
|
+
COPY package.json package-lock.json ./
|
|
21
|
+
RUN npm ci --omit=dev --ignore-scripts
|
|
22
|
+
|
|
23
|
+
# Copy built output
|
|
24
|
+
COPY --from=builder /app/dist/ ./dist/
|
|
25
|
+
|
|
26
|
+
# The server communicates via stdio (MCP transport)
|
|
27
|
+
# No EXPOSE needed — stdio transport doesn't listen on a port
|
|
28
|
+
|
|
29
|
+
ENTRYPOINT ["node", "dist/index.js"]
|
package/dist/tools/container.js
CHANGED
|
@@ -45,6 +45,10 @@ export function registerContainerTools(server, docker) {
|
|
|
45
45
|
return { content: [{ type: "text", text: `Container ${params.container_id} stopped.` }] };
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
|
+
// 304 means container is already stopped — treat as success
|
|
49
|
+
if (error?.statusCode === 304) {
|
|
50
|
+
return { content: [{ type: "text", text: `Container ${params.container_id} was already stopped.` }] };
|
|
51
|
+
}
|
|
48
52
|
return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
|
|
49
53
|
}
|
|
50
54
|
});
|
|
@@ -103,7 +107,7 @@ export function registerContainerTools(server, docker) {
|
|
|
103
107
|
return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
|
|
104
108
|
}
|
|
105
109
|
});
|
|
106
|
-
server.tool("run_container", "Create and start a new Docker container with one command. Supports image, env, ports, volumes, restart policy, and command override.", RunContainerSchema.shape, async (params) => {
|
|
110
|
+
server.tool("run_container", "Create and start a new Docker container with one command. Supports image, env, ports, volumes, restart policy, and command override. Auto-pulls missing images.", RunContainerSchema.shape, async (params) => {
|
|
107
111
|
try {
|
|
108
112
|
const createOpts = {
|
|
109
113
|
Image: params.image,
|
|
@@ -126,7 +130,28 @@ export function registerContainerTools(server, docker) {
|
|
|
126
130
|
: undefined,
|
|
127
131
|
},
|
|
128
132
|
};
|
|
129
|
-
|
|
133
|
+
let container;
|
|
134
|
+
try {
|
|
135
|
+
container = await docker.createContainer(createOpts);
|
|
136
|
+
}
|
|
137
|
+
catch (createError) {
|
|
138
|
+
// Auto-pull if image not found (HTTP 404)
|
|
139
|
+
if (createError?.statusCode === 404 && /no such image|No such image/i.test(createError.message || "")) {
|
|
140
|
+
const stream = await docker.pull(params.image);
|
|
141
|
+
await new Promise((resolve, reject) => {
|
|
142
|
+
docker.modem.followProgress(stream, (err) => {
|
|
143
|
+
if (err)
|
|
144
|
+
reject(err);
|
|
145
|
+
else
|
|
146
|
+
resolve();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
container = await docker.createContainer(createOpts);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw createError;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
130
155
|
await container.start();
|
|
131
156
|
return {
|
|
132
157
|
content: [{ type: "text", text: `Container created and started. ID: ${container.id.substring(0, 12)}` }],
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supernova123/docker-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"mcpName": "io.github.friendlygeorge/docker-mcp-server",
|
|
5
|
-
"description": "MCP server for Docker
|
|
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",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"bin": {
|
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"mcp",
|
|
20
|
+
"mcp-server",
|
|
20
21
|
"docker",
|
|
22
|
+
"docker-mcp",
|
|
21
23
|
"container",
|
|
22
24
|
"compose",
|
|
23
25
|
"health-check",
|
|
24
26
|
"devops",
|
|
25
27
|
"ai-agent",
|
|
26
28
|
"model-context-protocol",
|
|
27
|
-
"mcp-server",
|
|
28
|
-
"modelcontextprotocol",
|
|
29
29
|
"claude",
|
|
30
30
|
"cursor",
|
|
31
31
|
"chatgpt",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"claude-desktop",
|
|
33
|
+
"ai"
|
|
34
34
|
],
|
|
35
35
|
"author": "Nova",
|
|
36
36
|
"license": "MIT",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"typescript": "^5.7.0",
|
|
54
54
|
"vitest": "^3.1.0"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
package/src/tools/container.ts
CHANGED
|
@@ -74,7 +74,11 @@ export function registerContainerTools(server: McpServer, docker: Dockerode): vo
|
|
|
74
74
|
const container = docker.getContainer(params.container_id);
|
|
75
75
|
await container.stop({ t: params.timeout ?? 10 });
|
|
76
76
|
return { content: [{ type: "text", text: `Container ${params.container_id} stopped.` }] };
|
|
77
|
-
} catch (error) {
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
// 304 means container is already stopped — treat as success
|
|
79
|
+
if (error?.statusCode === 304) {
|
|
80
|
+
return { content: [{ type: "text", text: `Container ${params.container_id} was already stopped.` }] };
|
|
81
|
+
}
|
|
78
82
|
return { content: [{ type: "text", text: `Error: ${formatError(error)}` }], isError: true };
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -152,7 +156,7 @@ export function registerContainerTools(server: McpServer, docker: Dockerode): vo
|
|
|
152
156
|
|
|
153
157
|
server.tool(
|
|
154
158
|
"run_container",
|
|
155
|
-
"Create and start a new Docker container with one command. Supports image, env, ports, volumes, restart policy, and command override.",
|
|
159
|
+
"Create and start a new Docker container with one command. Supports image, env, ports, volumes, restart policy, and command override. Auto-pulls missing images.",
|
|
156
160
|
RunContainerSchema.shape,
|
|
157
161
|
async (params) => {
|
|
158
162
|
try {
|
|
@@ -180,7 +184,25 @@ export function registerContainerTools(server: McpServer, docker: Dockerode): vo
|
|
|
180
184
|
},
|
|
181
185
|
};
|
|
182
186
|
|
|
183
|
-
|
|
187
|
+
let container;
|
|
188
|
+
try {
|
|
189
|
+
container = await docker.createContainer(createOpts);
|
|
190
|
+
} catch (createError: any) {
|
|
191
|
+
// Auto-pull if image not found (HTTP 404)
|
|
192
|
+
if (createError?.statusCode === 404 && /no such image|No such image/i.test(createError.message || "")) {
|
|
193
|
+
const stream = await docker.pull(params.image);
|
|
194
|
+
await new Promise<void>((resolve, reject) => {
|
|
195
|
+
docker.modem.followProgress(stream, (err: Error | null) => {
|
|
196
|
+
if (err) reject(err);
|
|
197
|
+
else resolve();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
container = await docker.createContainer(createOpts);
|
|
201
|
+
} else {
|
|
202
|
+
throw createError;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
184
206
|
await container.start();
|
|
185
207
|
return {
|
|
186
208
|
content: [{ type: "text", text: `Container created and started. ID: ${container.id.substring(0, 12)}` }],
|