docker-mcp-server 2.0.0__tar.gz → 2.0.1__tar.gz
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.
- docker_mcp_server-2.0.1/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/copilot-instructions.md +21 -2
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/canary.yaml +8 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/codeql.yaml +3 -3
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/images.yaml +5 -5
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/publish.yaml +5 -5
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.mcpbignore +2 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/CLAUDE.md +13 -0
- docker_mcp_server-2.0.1/CODE_OF_CONDUCT.md +83 -0
- docker_mcp_server-2.0.1/CONTRIBUTING.md +188 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/PKG-INFO +3 -138
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/README.md +2 -137
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/configs.py +6 -2
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/containers.py +19 -6
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/images.py +10 -3
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/networks.py +23 -7
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/plugins.py +19 -9
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/secrets.py +15 -5
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/services.py +10 -3
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/swarm.py +33 -10
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/volumes.py +17 -6
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/manifest.json +1 -1
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/pyproject.toml +1 -1
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/uv.lock +1 -1
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.claude/commands/docker-sdk.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.claude/settings.json +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.dockerignore +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/CODEOWNERS +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/actions/file-failure-issue/action.yaml +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/dependabot.yaml +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/release.yml +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/premerge.yaml +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/publish-homebrew.yaml +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.gitignore +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.python-version +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/DOCKERHUB.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/Dockerfile +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/LICENSE +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/MIGRATION-2.0.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/PRIVACY.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/SECURITY.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/assets/README.md +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/assets/icon.png +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/__init__.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/__main__.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/_env.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/_hosts.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/server.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/__init__.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_cli.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_labels.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_ssh_proxy.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_utils.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/buildx.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/compose.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/context.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/nodes.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/prompts.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/registry.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/resources.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/scout.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/stack.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/system.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/glama.json +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/mcpb_run.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/scripts/build-mcpb.sh +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/scripts/docker-mcp-server.rb.tpl +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/server.json +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/__init__.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/conftest.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/__init__.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/conftest.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_buildx.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_cli.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_compose.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_containers.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_context.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_file_payloads.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_networks.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_registry.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_scout.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_smoke.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_stack.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_buildx.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_cli.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_compose.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_configs.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_containers.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_context.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_env.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_hosts.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_images.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_labels.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_main.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_naming.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_networks.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_nodes.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_plugins.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_prompts.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_pyproject_pins.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_registry.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_resources.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_scout.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_secrets.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_server.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_services.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_ssh_proxy.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_stack.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_swarm.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_system.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_utils.py +0 -0
- {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_volumes.py +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- What does this change do, and why? -->
|
|
4
|
+
|
|
5
|
+
## Test plan
|
|
6
|
+
|
|
7
|
+
<!-- How did you verify this? e.g. `uv run pytest -v`, `uv run ruff check . && uv run ruff format --check .`, `uv run pyright`, manual steps. -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] `uv run pytest -v`, `uv run ruff check .`, `uv run ruff format --check .`, and `uv run pyright` all pass locally
|
|
12
|
+
- [ ] If this adds or changes a tool: the ["Checklist when adding a new tool module"](https://github.com/GavinLucas/docker-mcp/blob/main/CONTRIBUTING.md#checklist-when-adding-a-new-tool-module) in `CONTRIBUTING.md` has been followed (tests, prompts, resources, README, naming convention)
|
|
13
|
+
- [ ] If this changes project structure, conventions, env vars, or the tool/prompt/resource surface: both `CLAUDE.md` and `.github/copilot-instructions.md` are updated (see the mirror rule at the top of `CLAUDE.md`)
|
|
14
|
+
- [ ] If this changes a dependency: `uv.lock` is updated (`uv lock`) and committed alongside `pyproject.toml`
|
|
@@ -148,15 +148,34 @@ def mcp_example(name: str):
|
|
|
148
148
|
"""
|
|
149
149
|
Say hello to someone by name.
|
|
150
150
|
|
|
151
|
-
args: name
|
|
151
|
+
args: name - The name to say hello to
|
|
152
152
|
returns: str - The greeting
|
|
153
153
|
"""
|
|
154
154
|
return f"Hello, {name}!"
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
- One-line summary sentence, then a blank line
|
|
158
|
-
- `args:` section lists each parameter as `name
|
|
158
|
+
- `args:` section lists each parameter as `name - description`. Do **not** repeat the parameter's
|
|
159
|
+
type — the type annotation already lands in the tool's `inputSchema`, which the client sees
|
|
160
|
+
alongside the description, so a `name: type - ...` form just duplicates it as prose tokens. (The
|
|
161
|
+
`returns:` line keeps its type, since the return shape is not in the input schema.)
|
|
159
162
|
- `returns:` line documents the return type and what it contains
|
|
163
|
+
- Keep descriptions terse: state every functional fact (defaults, accepted formats/values, return
|
|
164
|
+
keys, important caveats) but cut redundancy and verbose phrasing. The docstring is the entire
|
|
165
|
+
tool `description` the client pays tokens for on every session.
|
|
166
|
+
- **A one-line summary + bare `args`/`returns` is not enough for any tool with non-obvious
|
|
167
|
+
behavior.** If the tool has side effects, preconditions, a non-obvious failure mode, or overlaps
|
|
168
|
+
with another tool a caller could reach for instead, add a short paragraph (2-5 sentences,
|
|
169
|
+
between the summary and `args:`) covering: when to use it vs. the alternative, side
|
|
170
|
+
effects/preconditions, and concrete parameter formats/values — not just restating the signature.
|
|
171
|
+
This directly maps to what a low-scoring tool is missing on Glama's per-tool quality rubric
|
|
172
|
+
(`Behavior`, `Usage Guidelines`, `Parameters` sub-scores) — three separate PRs (#97, the 2.0
|
|
173
|
+
rename, #129) have had to chase down thin docstrings written without this paragraph. **Write it
|
|
174
|
+
this way the first time a tool is added or its behavior changes** — don't wait for a future
|
|
175
|
+
Glama pass to catch it. Verify every factual claim in that paragraph against the live docker-py
|
|
176
|
+
docs / Engine API spec per the Docker SDK Policy below — an unverified claim about identifier
|
|
177
|
+
semantics (e.g. "name or id" for a resource actually addressed by name only) is exactly the kind
|
|
178
|
+
of thing PR review catches late.
|
|
160
179
|
|
|
161
180
|
### Bounding rules
|
|
162
181
|
|
|
@@ -42,6 +42,11 @@ jobs:
|
|
|
42
42
|
|
|
43
43
|
- name: Set up uv
|
|
44
44
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
45
|
+
with:
|
|
46
|
+
# Pinned (matching the Dockerfile's uv image tag) so this step never needs the
|
|
47
|
+
# live "resolve latest" fetch to raw.githubusercontent.com — that network call
|
|
48
|
+
# failed transiently on 2026-07-06 and blocked an unrelated PR (#130).
|
|
49
|
+
version: "0.11.21"
|
|
45
50
|
|
|
46
51
|
- name: Resolve runtime deps as wheels-only for each platform
|
|
47
52
|
# --only-binary :all: makes resolution fail if any dependency lacks a wheel for the
|
|
@@ -103,6 +108,9 @@ jobs:
|
|
|
103
108
|
# repo — so disable the repo-keyed cache and the empty-workdir warning it triggers.
|
|
104
109
|
enable-cache: false
|
|
105
110
|
ignore-empty-workdir: true
|
|
111
|
+
# Pinned (matching the Dockerfile's uv image tag) so this step never needs the
|
|
112
|
+
# live "resolve latest" fetch to raw.githubusercontent.com.
|
|
113
|
+
version: "0.11.21"
|
|
106
114
|
|
|
107
115
|
- name: Import smoke (full tool-registration path, no daemon needed)
|
|
108
116
|
run: uv run --no-project --python 3.14 --with docker-mcp-server python -c "import docker_mcp"
|
|
@@ -31,12 +31,12 @@ jobs:
|
|
|
31
31
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
32
32
|
|
|
33
33
|
- name: Initialize CodeQL
|
|
34
|
-
uses: github/codeql-action/init@
|
|
34
|
+
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
|
35
35
|
with:
|
|
36
36
|
languages: ${{ matrix.language }}
|
|
37
37
|
|
|
38
38
|
- name: Autobuild
|
|
39
|
-
uses: github/codeql-action/autobuild@
|
|
39
|
+
uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
|
40
40
|
|
|
41
41
|
- name: Perform CodeQL Analysis
|
|
42
|
-
uses: github/codeql-action/analyze@
|
|
42
|
+
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
|
@@ -50,10 +50,10 @@ jobs:
|
|
|
50
50
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
51
51
|
|
|
52
52
|
- name: Set up Docker Buildx
|
|
53
|
-
uses: docker/setup-buildx-action@
|
|
53
|
+
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
|
54
54
|
|
|
55
55
|
- name: Build image (${{ matrix.variant }})
|
|
56
|
-
uses: docker/build-push-action@
|
|
56
|
+
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
57
57
|
with:
|
|
58
58
|
context: .
|
|
59
59
|
platforms: linux/amd64
|
|
@@ -85,13 +85,13 @@ jobs:
|
|
|
85
85
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
86
86
|
|
|
87
87
|
- name: Set up QEMU
|
|
88
|
-
uses: docker/setup-qemu-action@
|
|
88
|
+
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
|
89
89
|
|
|
90
90
|
- name: Set up Docker Buildx
|
|
91
|
-
uses: docker/setup-buildx-action@
|
|
91
|
+
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
|
92
92
|
|
|
93
93
|
- name: Build full variant for amd64 + arm64
|
|
94
|
-
uses: docker/build-push-action@
|
|
94
|
+
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
95
95
|
with:
|
|
96
96
|
context: .
|
|
97
97
|
platforms: linux/amd64,linux/arm64
|
|
@@ -219,17 +219,17 @@ jobs:
|
|
|
219
219
|
echo "Publishing \`${{ matrix.variant }}\` as: $tags" >> "$GITHUB_STEP_SUMMARY"
|
|
220
220
|
|
|
221
221
|
- name: Set up QEMU
|
|
222
|
-
uses: docker/setup-qemu-action@
|
|
222
|
+
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
|
223
223
|
with:
|
|
224
224
|
# The two matrix jobs race to cache the same binfmt image; skip the cache to avoid the
|
|
225
225
|
# harmless "unable to reserve cache" warning while keeping both builds parallel.
|
|
226
226
|
cache-image: false
|
|
227
227
|
|
|
228
228
|
- name: Set up Docker Buildx
|
|
229
|
-
uses: docker/setup-buildx-action@
|
|
229
|
+
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
|
230
230
|
|
|
231
231
|
- name: Log in to GHCR
|
|
232
|
-
uses: docker/login-action@
|
|
232
|
+
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
|
233
233
|
with:
|
|
234
234
|
registry: ghcr.io
|
|
235
235
|
username: ${{ github.actor }}
|
|
@@ -237,13 +237,13 @@ jobs:
|
|
|
237
237
|
|
|
238
238
|
- name: Log in to Docker Hub
|
|
239
239
|
if: env.DOCKERHUB_USER != '' && env.DOCKERHUB_TOKEN != ''
|
|
240
|
-
uses: docker/login-action@
|
|
240
|
+
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
|
241
241
|
with:
|
|
242
242
|
username: ${{ secrets.DOCKERHUB_USER }}
|
|
243
243
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
244
244
|
|
|
245
245
|
- name: Build and push ${{ matrix.variant }}
|
|
246
|
-
uses: docker/build-push-action@
|
|
246
|
+
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
247
247
|
with:
|
|
248
248
|
context: .
|
|
249
249
|
platforms: linux/amd64,linux/arm64
|
|
@@ -260,6 +260,19 @@ def mcp_example(name: str):
|
|
|
260
260
|
- Keep descriptions terse: state every functional fact (defaults, accepted formats/values, return
|
|
261
261
|
keys, important caveats) but cut redundancy and verbose phrasing. The docstring is the entire
|
|
262
262
|
tool `description` the client pays tokens for on every session.
|
|
263
|
+
- **A one-line summary + bare `args`/`returns` is not enough for any tool with non-obvious
|
|
264
|
+
behavior.** If the tool has side effects, preconditions, a non-obvious failure mode, or overlaps
|
|
265
|
+
with another tool a caller could reach for instead, add a short paragraph (2-5 sentences,
|
|
266
|
+
between the summary and `args:`) covering: when to use it vs. the alternative, side
|
|
267
|
+
effects/preconditions, and concrete parameter formats/values — not just restating the signature.
|
|
268
|
+
This directly maps to what a low-scoring tool is missing on Glama's per-tool quality rubric
|
|
269
|
+
(`Behavior`, `Usage Guidelines`, `Parameters` sub-scores) — see [[project_glama_docstring_quality]]
|
|
270
|
+
in memory for the three rounds of cleanup (#97, the 2.0 rename, #129) this has already taken to
|
|
271
|
+
chase down thin docstrings written without this paragraph. **Write it this way the first time a
|
|
272
|
+
tool is added or its behavior changes** — don't wait for a future Glama pass to catch it. Verify
|
|
273
|
+
every factual claim in that paragraph against the live docker-py docs / Engine API spec per the
|
|
274
|
+
Docker SDK Policy below — an unverified claim about identifier semantics (e.g. "name or id" for a
|
|
275
|
+
resource actually addressed by name only) is exactly the kind of thing PR review catches late.
|
|
263
276
|
|
|
264
277
|
### MCP resources
|
|
265
278
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
|
6
|
+
|
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
|
8
|
+
|
|
9
|
+
## Our Standards
|
|
10
|
+
|
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
|
12
|
+
|
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
|
18
|
+
|
|
19
|
+
Examples of unacceptable behavior include:
|
|
20
|
+
|
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
22
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
23
|
+
* Public or private harassment
|
|
24
|
+
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
|
25
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
26
|
+
|
|
27
|
+
## Enforcement Responsibilities
|
|
28
|
+
|
|
29
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
|
30
|
+
|
|
31
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
|
32
|
+
|
|
33
|
+
## Scope
|
|
34
|
+
|
|
35
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
36
|
+
|
|
37
|
+
## Enforcement
|
|
38
|
+
|
|
39
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Gavin@L337.org](mailto:Gavin@L337.org). All complaints will be reviewed and investigated promptly and fairly.
|
|
40
|
+
|
|
41
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
|
42
|
+
|
|
43
|
+
## Enforcement Guidelines
|
|
44
|
+
|
|
45
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
|
46
|
+
|
|
47
|
+
### 1. Correction
|
|
48
|
+
|
|
49
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
|
50
|
+
|
|
51
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
|
52
|
+
|
|
53
|
+
### 2. Warning
|
|
54
|
+
|
|
55
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
|
56
|
+
|
|
57
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
|
58
|
+
|
|
59
|
+
### 3. Temporary Ban
|
|
60
|
+
|
|
61
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
|
62
|
+
|
|
63
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
|
64
|
+
|
|
65
|
+
### 4. Permanent Ban
|
|
66
|
+
|
|
67
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
|
68
|
+
|
|
69
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
|
70
|
+
|
|
71
|
+
## Attribution
|
|
72
|
+
|
|
73
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
74
|
+
|
|
75
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
76
|
+
|
|
77
|
+
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
|
78
|
+
|
|
79
|
+
[homepage]: https://www.contributor-covenant.org
|
|
80
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
81
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
82
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
83
|
+
[translations]: https://www.contributor-covenant.org/translations
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Contributing to docker-mcp-server
|
|
2
|
+
|
|
3
|
+
Contributions are welcome. The project values a tight mapping between the Docker SDK's public surface and the MCP tools we expose. By participating, you're expected to uphold the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
4
|
+
|
|
5
|
+
## Before you start
|
|
6
|
+
|
|
7
|
+
This is an [MCP](https://modelcontextprotocol.io) server: it exposes Docker operations as `@tool()`-decorated Python functions that an AI agent calls over the Model Context Protocol. If that sentence is new to you, the short version is: each function in `docker_mcp/tools/` becomes one callable "tool" an agent can invoke, with the function's docstring as the description the agent sees and its type-annotated parameters as the input schema.
|
|
8
|
+
|
|
9
|
+
The practical mental model for finding your way around:
|
|
10
|
+
|
|
11
|
+
- One file per Docker feature area (`containers.py`, `images.py`, `swarm.py`, …), each backed by either the [docker-py SDK](https://docker-py.readthedocs.io/en/stable/) or a `docker` CLI shell-out (`compose.py`, `buildx.py`, …). See the tree below for the full map.
|
|
12
|
+
- Every tool function is registered centrally in `docker_mcp/server.py`, which is also where read-only/destructive classification, naming enforcement, and the domain-disable switches live.
|
|
13
|
+
- `CLAUDE.md` (repo root) is the full architecture reference — written to brief Claude Code, but equally the canonical source for humans. This file (`CONTRIBUTING.md`) covers the practical day-to-day: setup, the checklist for adding a tool, testing, and submitting changes. If something isn't covered here, it's almost certainly in `CLAUDE.md`.
|
|
14
|
+
|
|
15
|
+
## Project layout
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
.
|
|
19
|
+
├── docker_mcp/ # the package — `python -m docker_mcp` runs the server
|
|
20
|
+
│ ├── __init__.py # defines `main()`; side-effect-imports `server` and `tools`
|
|
21
|
+
│ ├── __main__.py # calls `main()` so `python -m docker_mcp` works
|
|
22
|
+
│ ├── server.py # creates the FastMCP singleton (`mcp`) shared by every tool module
|
|
23
|
+
│ └── tools/ # one file per Docker SDK domain or CLI/registry feature
|
|
24
|
+
│ ├── _cli.py # cross-platform subprocess helper for docker CLI shell-outs (private)
|
|
25
|
+
│ ├── _utils.py # shared helpers (drop_none, join_bounded, MAX_PAYLOAD_BYTES) (private)
|
|
26
|
+
│ ├── system.py # DockerClient connection + lazy `_get_client()` helper
|
|
27
|
+
│ ├── containers.py
|
|
28
|
+
│ ├── images.py
|
|
29
|
+
│ ├── networks.py
|
|
30
|
+
│ ├── volumes.py
|
|
31
|
+
│ ├── configs.py
|
|
32
|
+
│ ├── secrets.py
|
|
33
|
+
│ ├── nodes.py
|
|
34
|
+
│ ├── services.py
|
|
35
|
+
│ ├── swarm.py
|
|
36
|
+
│ ├── plugins.py
|
|
37
|
+
│ ├── compose.py # `docker compose` CLI plugin (shells out via _cli.py)
|
|
38
|
+
│ ├── stack.py # `docker stack` (Compose-on-Swarm) CLI (shells out via _cli.py)
|
|
39
|
+
│ ├── context.py # `docker context` CLI (shells out via _cli.py)
|
|
40
|
+
│ ├── buildx.py # `docker buildx` CLI plugin (shells out via _cli.py)
|
|
41
|
+
│ ├── scout.py # `docker scout` CLI plugin (shells out via _cli.py)
|
|
42
|
+
│ ├── registry.py # OCI v2 registries + Docker Hub HTTPS APIs (no daemon)
|
|
43
|
+
│ ├── prompts.py # @prompt(...) templates for common docker workflows
|
|
44
|
+
│ └── resources.py # @mcp.resource() endpoints exposing SDK + CLI + registry docs
|
|
45
|
+
├── tests/ # pytest suite, mirrors `docker_mcp/tools/` one-to-one
|
|
46
|
+
│ └── integration/ # tests that hit a real Docker daemon or docker.io
|
|
47
|
+
├── assets/ # bundle assets (e.g. the .mcpb icon) packed into the Desktop Extension
|
|
48
|
+
├── scripts/ # developer convenience scripts (not used by CI) — e.g. build-mcpb.sh
|
|
49
|
+
└── dist/ # build output (git-ignored) — local .mcpb test bundles land here
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Each `docker_mcp/tools/<file>.py` has a matching `tests/test_<file>.py`. New modules must be added to `docker_mcp/tools/__init__.py` and have a corresponding test file. Tool modules that wrap CLI features must funnel every subprocess call through `docker_mcp/tools/_cli.py` so the cross-platform safety concerns (binary discovery, no shell, UTF-8 decoding, output capping, Windows console suppression, env scrubbing) live in one place.
|
|
53
|
+
|
|
54
|
+
New Docker functionality goes in the matching existing file (e.g. a new volume operation goes in `volumes.py`), not a new file — a new `docker_mcp/tools/<domain>.py` is only for a Docker feature area that doesn't exist in the tree above.
|
|
55
|
+
|
|
56
|
+
## Conventions
|
|
57
|
+
|
|
58
|
+
Tool functions are decorated with `@tool()` (the project's wrapper around `@mcp.tool()`, imported from `docker_mcp.server`) and follow this docstring style:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from docker_mcp.server import tool
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@tool()
|
|
65
|
+
def mcp_example(name: str):
|
|
66
|
+
"""
|
|
67
|
+
Say hello to someone by name.
|
|
68
|
+
|
|
69
|
+
args: name - The name to say hello to
|
|
70
|
+
returns: str - The greeting
|
|
71
|
+
"""
|
|
72
|
+
return f"Hello, {name}!"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Tool modules import `tool` from `docker_mcp.server`; prompt modules import `prompt` from `docker_mcp.server`. Only resource modules (`@mcp.resource(...)`) import `mcp` directly — never import `mcp` directly in a tool or prompt module, that creates a circular import.
|
|
76
|
+
- Every tool needs a `TOOL_CATEGORIES` entry in `docker_mcp/server.py` (`READ_ONLY` / `MUTATING` / `DESTRUCTIVE`); the central map drives the tool's `ToolAnnotations` and the read-only env switches, and `tests/test_server.py` fails if it drifts from the registered set. A tool's *domain* (for `DOCKER_MCP_SERVER_DISABLE` and the tool catalog) is derived automatically from its module name, so putting a tool in the right `docker_mcp/tools/<domain>.py` file is all that's needed.
|
|
77
|
+
- Tool names follow `<management-command>_<verb>`, anchored to the docker CLI's own management-command structure (`docker container ls` → `container_list`), with long-form verbs (`list`/`remove`/`inspect`, never `ls`/`rm`/`get`). `tests/test_naming.py` enforces this — a new tool with a short-form verb or an unapproved prefix fails CI. See `CLAUDE.md`'s "Tool naming convention" section for the full rule, including identifier parameter naming (`id_or_name`, `name`/`names`, `repository`).
|
|
78
|
+
- Line length is 120 characters (enforced by ruff).
|
|
79
|
+
- CLI shell-outs must go through `docker_mcp/tools/_cli.py:run_docker` — never call `subprocess.run` directly from a tool module. The helper enforces `shell=False`, resolves the binary via `shutil.which` (cross-platform), decodes output as UTF-8 with replace, caps the captured bytes, scrubs the environment, and suppresses console pop-ups on Windows.
|
|
80
|
+
- A tool with non-obvious behavior (side effects, preconditions, a non-obvious failure mode, or overlap with another tool) needs more than a one-line summary — add a short paragraph covering when to use it vs. the alternative, side effects/preconditions, and concrete parameter formats. Verify any factual claim against the live docker-py docs / Engine API spec rather than assuming; see [`CLAUDE.md`](CLAUDE.md)'s "Tool function format" section for the full convention and why it matters.
|
|
81
|
+
|
|
82
|
+
## Verifying the SDK before writing code
|
|
83
|
+
|
|
84
|
+
To prevent hallucinated method names, the project includes a `/docker-sdk` Claude Code skill that fetches the live Docker SDK for Python documentation, inventories what's already exposed, and produces a gap analysis. Run it before adding new tools:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
/docker-sdk # full gap analysis
|
|
88
|
+
/docker-sdk containers # focus on a single domain
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If you're not using Claude Code, check method names and signatures directly against the [docker-py docs](https://docker-py.readthedocs.io/en/stable/index.html) (or, for SDK gaps, the [low-level `APIClient`](https://docker-py.readthedocs.io/en/stable/api.html)) before writing a call — don't assume a method exists because it sounds plausible.
|
|
92
|
+
|
|
93
|
+
## Testing conventions
|
|
94
|
+
|
|
95
|
+
Unit tests mock the Docker client rather than hitting a real daemon, patching `_get_client` (or, for CLI-backed modules, `run_docker`) at the point the tool module looks it up. A minimal example, from `tests/test_volumes.py`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from unittest.mock import MagicMock, patch
|
|
99
|
+
|
|
100
|
+
from docker_mcp.tools.volumes import volume_inspect
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_volume_inspect():
|
|
104
|
+
volume = MagicMock()
|
|
105
|
+
volume.attrs = {"Name": "myvol"}
|
|
106
|
+
with patch("docker_mcp.tools.volumes._get_client") as mock_client:
|
|
107
|
+
mock_client.return_value.volumes.get.return_value = volume
|
|
108
|
+
assert volume_inspect("myvol") == {"Name": "myvol"}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Integration tests (`tests/integration/`) call the real tool function against an actual Docker daemon and are auto-marked `@pytest.mark.integration` by `tests/integration/conftest.py`, which also provides an autouse `skip_if_no_daemon` fixture — they're skipped automatically (not failed) when no daemon is reachable, so it's safe to run the full suite without Docker running. They're excluded from the default `pytest` invocation (`-m 'not integration'` in `pyproject.toml`); run them explicitly with `uv run pytest -m integration -v`.
|
|
112
|
+
|
|
113
|
+
## Checklist when adding a new tool module
|
|
114
|
+
|
|
115
|
+
When you add a new `docker_mcp/tools/<domain>.py`, also update:
|
|
116
|
+
|
|
117
|
+
1. **`docker_mcp/tools/__init__.py`** — star-import the module (private helpers prefixed with `_` are excluded).
|
|
118
|
+
2. **`tests/test_<domain>.py`** — unit tests using mocks (no real daemon).
|
|
119
|
+
3. **`tests/integration/test_<domain>.py`** — at least one happy-path test against a real daemon (or override the `skip_if_no_daemon` fixture if the module doesn't need one).
|
|
120
|
+
4. **`docker_mcp/tools/prompts.py`** — at least one `@prompt(...)` template that exercises the new tools end-to-end.
|
|
121
|
+
5. **`docker_mcp/tools/resources.py`** — add an entry under `SDK_SECTIONS` or `EXTERNAL_SECTIONS` if the new domain has authoritative docs the agent should be able to read at runtime.
|
|
122
|
+
6. **README.md** — append to the "What the agent can do" list and (if relevant) the "Security considerations" section.
|
|
123
|
+
7. **SECURITY.md** — only if the new module exposes a new class of risk not already covered by the README's Security section.
|
|
124
|
+
|
|
125
|
+
If you're only adding a tool or two to an *existing* domain file rather than a whole new module, items 1 and 7 don't apply, but 2-6 still do wherever relevant (e.g. a new tool still needs unit + integration test cases, even if the test file itself already exists).
|
|
126
|
+
|
|
127
|
+
## Local development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# install dependencies (creates .venv, installs runtime + dev deps)
|
|
131
|
+
uv sync
|
|
132
|
+
|
|
133
|
+
# run the server
|
|
134
|
+
uv run python -m docker_mcp
|
|
135
|
+
# …or via the installed console script
|
|
136
|
+
uv run docker-mcp
|
|
137
|
+
|
|
138
|
+
# unit tests (integration tests are excluded by default)
|
|
139
|
+
uv run pytest -v
|
|
140
|
+
|
|
141
|
+
# integration tests — require a running Docker daemon at $DOCKER_HOST
|
|
142
|
+
uv run pytest -m integration -v
|
|
143
|
+
|
|
144
|
+
# lint, format, type-check
|
|
145
|
+
uv run ruff check .
|
|
146
|
+
uv run ruff format .
|
|
147
|
+
uv run pyright
|
|
148
|
+
|
|
149
|
+
# install the pre-commit hook (one-time, runs ruff on every commit)
|
|
150
|
+
uv run pre-commit install
|
|
151
|
+
|
|
152
|
+
# add a runtime dependency
|
|
153
|
+
uv add <package>
|
|
154
|
+
|
|
155
|
+
# add a development dependency
|
|
156
|
+
uv add --group dev <package>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Submitting your change
|
|
160
|
+
|
|
161
|
+
CI (`.github/workflows/premerge.yaml`) runs on every pull request and push to `main`, and must pass before merging:
|
|
162
|
+
|
|
163
|
+
- `pytest` — two separate jobs: unit tests (`uv run pytest -v`, same as local) and integration tests (`uv run pytest -m integration -v`) against the real Docker Engine the `ubuntu-latest` runner ships with. You don't need a local daemon to contribute — `uv run pytest -v` alone (which excludes integration tests by default) is enough to check your change; the integration job will exercise it in CI.
|
|
164
|
+
- `ruff check` and `ruff format --check` — lint and formatting; run `uv run ruff check . && uv run ruff format .` locally to fix both before committing (the pre-commit hook from `uv run pre-commit install` runs `ruff check --fix` and `ruff format` automatically on each commit, so this is usually already handled for you).
|
|
165
|
+
- `pyright` — type-check.
|
|
166
|
+
- CI installs with `uv sync --locked`, which **fails** if `uv.lock` disagrees with `pyproject.toml` rather than silently re-locking. If you change a dependency, run `uv lock` and commit the updated lockfile alongside `pyproject.toml`.
|
|
167
|
+
|
|
168
|
+
A separate, non-blocking `Check docs mirror` job flags a PR that edits `CLAUDE.md` or `.github/copilot-instructions.md` without the other — the two files intentionally mirror each other (Copilot's own PR review is driven by `copilot-instructions.md`), so a change to project structure, conventions, or the tool surface should update both.
|
|
169
|
+
|
|
170
|
+
Keep PRs focused — one logical change per PR is easier to review than a bundle of unrelated fixes. For anything beyond a small, self-contained fix, consider opening an issue first (see "Reporting issues" below) so the approach can be discussed before you invest time in an implementation.
|
|
171
|
+
|
|
172
|
+
## Building a local Desktop Extension (.mcpb)
|
|
173
|
+
|
|
174
|
+
To smoke-test the Claude Desktop Extension locally, pack a bundle with the developer helper in `scripts/`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# pack dist/docker-mcp-server-<version>.mcpb (auto-increments -1, -2, … if it exists)
|
|
178
|
+
scripts/build-mcpb.sh
|
|
179
|
+
|
|
180
|
+
# …or give it an explicit name (a .mcpb extension is added if missing)
|
|
181
|
+
scripts/build-mcpb.sh my-test-bundle
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
It reads the version from `pyproject.toml`, creates `dist/` if needed, writes a `.sha256` alongside the bundle, and packs via Anthropic's `mcpb` CLI (a global `mcpb`, else `npx @anthropic-ai/mcpb`; see `--help` for the `MCPB=` override). The official release bundle is built separately by the `mcpb` job in `.github/workflows/publish.yaml` — this script is for local testing only and is **not** used by CI.
|
|
185
|
+
|
|
186
|
+
## Reporting issues
|
|
187
|
+
|
|
188
|
+
Bug reports and feature requests have templates that you can choose when you [`create an issue`](https://github.com/GavinLucas/docker-mcp/issues/new/choose). Please select the correct issue type and follow the template.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docker-mcp-server
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: MCP server for managing Docker resources via the Docker SDK for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/GavinLucas/docker-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/GavinLucas/docker-mcp
|
|
@@ -413,6 +413,7 @@ Connecting this server to an AI agent grants it the same level of access as a lo
|
|
|
413
413
|
| Official MCP Registry | [io.github.GavinLucas/docker-mcp-server](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.GavinLucas%2Fdocker-mcp-server/versions) |
|
|
414
414
|
| Glama | [docker-mcp-server](https://glama.ai/mcp/servers/GavinLucas/docker-mcp) |
|
|
415
415
|
| mcp.so | [docker-mcp-server](https://mcp.so/server/docker-mcp-server/GavinLucas) |
|
|
416
|
+
| awesome-mcp-servers | [punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers#cloud-platforms) |
|
|
416
417
|
|
|
417
418
|
## Privacy Policy
|
|
418
419
|
|
|
@@ -422,140 +423,4 @@ the operations you request. The full statement is in [PRIVACY.md](https://github
|
|
|
422
423
|
|
|
423
424
|
## Contributing
|
|
424
425
|
|
|
425
|
-
Contributions are welcome. The project values a tight mapping between the Docker SDK's public surface and the MCP tools we expose.
|
|
426
|
-
|
|
427
|
-
### Project layout
|
|
428
|
-
|
|
429
|
-
```
|
|
430
|
-
.
|
|
431
|
-
├── docker_mcp/ # the package — `python -m docker_mcp` runs the server
|
|
432
|
-
│ ├── __init__.py # defines `main()`; side-effect-imports `server` and `tools`
|
|
433
|
-
│ ├── __main__.py # calls `main()` so `python -m docker_mcp` works
|
|
434
|
-
│ ├── server.py # creates the FastMCP singleton (`mcp`) shared by every tool module
|
|
435
|
-
│ └── tools/ # one file per Docker SDK domain or CLI/registry feature
|
|
436
|
-
│ ├── _cli.py # cross-platform subprocess helper for docker CLI shell-outs (private)
|
|
437
|
-
│ ├── _utils.py # shared helpers (drop_none, join_bounded, MAX_PAYLOAD_BYTES) (private)
|
|
438
|
-
│ ├── system.py # DockerClient connection + lazy `_get_client()` helper
|
|
439
|
-
│ ├── containers.py
|
|
440
|
-
│ ├── images.py
|
|
441
|
-
│ ├── networks.py
|
|
442
|
-
│ ├── volumes.py
|
|
443
|
-
│ ├── configs.py
|
|
444
|
-
│ ├── secrets.py
|
|
445
|
-
│ ├── nodes.py
|
|
446
|
-
│ ├── services.py
|
|
447
|
-
│ ├── swarm.py
|
|
448
|
-
│ ├── plugins.py
|
|
449
|
-
│ ├── compose.py # `docker compose` CLI plugin (shells out via _cli.py)
|
|
450
|
-
│ ├── stack.py # `docker stack` (Compose-on-Swarm) CLI (shells out via _cli.py)
|
|
451
|
-
│ ├── context.py # `docker context` CLI (shells out via _cli.py)
|
|
452
|
-
│ ├── buildx.py # `docker buildx` CLI plugin (shells out via _cli.py)
|
|
453
|
-
│ ├── scout.py # `docker scout` CLI plugin (shells out via _cli.py)
|
|
454
|
-
│ ├── registry.py # OCI v2 registries + Docker Hub HTTPS APIs (no daemon)
|
|
455
|
-
│ ├── prompts.py # @mcp.prompt() templates for common docker workflows
|
|
456
|
-
│ └── resources.py # @mcp.resource() endpoints exposing SDK + CLI + registry docs
|
|
457
|
-
├── tests/ # pytest suite, mirrors `docker_mcp/tools/` one-to-one
|
|
458
|
-
│ └── integration/ # tests that hit a real Docker daemon or docker.io
|
|
459
|
-
├── assets/ # bundle assets (e.g. the .mcpb icon) packed into the Desktop Extension
|
|
460
|
-
├── scripts/ # developer convenience scripts (not used by CI) — e.g. build-mcpb.sh
|
|
461
|
-
└── dist/ # build output (git-ignored) — local .mcpb test bundles land here
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
Each `docker_mcp/tools/<file>.py` has a matching `tests/test_<file>.py`. New modules must be added to `docker_mcp/tools/__init__.py` and have a corresponding test file. Tool modules that wrap CLI features must funnel every subprocess call through `docker_mcp/tools/_cli.py` so the cross-platform safety concerns (binary discovery, no shell, UTF-8 decoding, output capping, Windows console suppression, env scrubbing) live in one place.
|
|
465
|
-
|
|
466
|
-
### Conventions
|
|
467
|
-
|
|
468
|
-
Tool functions are decorated with `@tool()` (the project's wrapper around `@mcp.tool()`, imported from `docker_mcp.server`) and follow this docstring style:
|
|
469
|
-
|
|
470
|
-
```python
|
|
471
|
-
from docker_mcp.server import tool
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
@tool()
|
|
475
|
-
def mcp_example(name: str):
|
|
476
|
-
"""
|
|
477
|
-
Say hello to someone by name.
|
|
478
|
-
|
|
479
|
-
args: name: str - The name to say hello to
|
|
480
|
-
returns: str - The greeting
|
|
481
|
-
"""
|
|
482
|
-
return f"Hello, {name}!"
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
- Import `tool` from `docker_mcp.server` (and, for prompts/resources, `mcp`), never directly from the `mcp` package — that creates a circular import.
|
|
486
|
-
- Every tool needs a `TOOL_CATEGORIES` entry in `docker_mcp/server.py` (`READ_ONLY` / `MUTATING` / `DESTRUCTIVE`); the central map drives the tool's `ToolAnnotations` and the read-only env switches, and `tests/test_server.py` fails if it drifts from the registered set. A tool's *domain* (for `DOCKER_MCP_SERVER_DISABLE` and the tool catalog) is derived automatically from its module name, so putting a tool in the right `docker_mcp/tools/<domain>.py` file is all that's needed.
|
|
487
|
-
- Line length is 120 characters (enforced by ruff).
|
|
488
|
-
- CLI shell-outs must go through `docker_mcp/tools/_cli.py:run_docker` — never call `subprocess.run` directly from a tool module. The helper enforces `shell=False`, resolves the binary via `shutil.which` (cross-platform), decodes output as UTF-8 with replace, caps the captured bytes, scrubs the environment, and suppresses console pop-ups on Windows.
|
|
489
|
-
|
|
490
|
-
### Checklist when adding a new tool module
|
|
491
|
-
|
|
492
|
-
When you add a new `docker_mcp/tools/<domain>.py`, also update:
|
|
493
|
-
|
|
494
|
-
1. **`docker_mcp/tools/__init__.py`** — star-import the module (private helpers prefixed with `_` are excluded).
|
|
495
|
-
2. **`tests/test_<domain>.py`** — unit tests using mocks (no real daemon).
|
|
496
|
-
3. **`tests/integration/test_<domain>.py`** — at least one happy-path test against a real daemon (or override the `skip_if_no_daemon` fixture if the module doesn't need one).
|
|
497
|
-
4. **`docker_mcp/tools/prompts.py`** — at least one `@mcp.prompt(...)` template that exercises the new tools end-to-end.
|
|
498
|
-
5. **`docker_mcp/tools/resources.py`** — add an entry under `SDK_SECTIONS` or `EXTERNAL_SECTIONS` if the new domain has authoritative docs the agent should be able to read at runtime.
|
|
499
|
-
6. **README.md** — append to the "What the agent can do" list and (if relevant) the "Security considerations" section.
|
|
500
|
-
7. **SECURITY.md** — only if the new module exposes a new class of risk not already covered by the README's Security section.
|
|
501
|
-
|
|
502
|
-
### Verifying the SDK before writing code
|
|
503
|
-
|
|
504
|
-
To prevent hallucinated method names, the project includes a `/docker-sdk` Claude Code skill that fetches the live Docker SDK for Python documentation, inventories what's already exposed, and produces a gap analysis. Run it before adding new tools:
|
|
505
|
-
|
|
506
|
-
```
|
|
507
|
-
/docker-sdk # full gap analysis
|
|
508
|
-
/docker-sdk containers # focus on a single domain
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### Local development
|
|
512
|
-
|
|
513
|
-
```bash
|
|
514
|
-
# install dependencies (creates .venv, installs runtime + dev deps)
|
|
515
|
-
uv sync
|
|
516
|
-
|
|
517
|
-
# run the server
|
|
518
|
-
uv run python -m docker_mcp
|
|
519
|
-
# …or via the installed console script
|
|
520
|
-
uv run docker-mcp
|
|
521
|
-
|
|
522
|
-
# unit tests (integration tests are excluded by default)
|
|
523
|
-
uv run pytest -v
|
|
524
|
-
|
|
525
|
-
# integration tests — require a running Docker daemon at $DOCKER_HOST
|
|
526
|
-
uv run pytest -m integration -v
|
|
527
|
-
|
|
528
|
-
# lint, format, type-check
|
|
529
|
-
uv run ruff check .
|
|
530
|
-
uv run ruff format .
|
|
531
|
-
uv run pyright
|
|
532
|
-
|
|
533
|
-
# install the pre-commit hook (one-time, runs ruff on every commit)
|
|
534
|
-
uv run pre-commit install
|
|
535
|
-
|
|
536
|
-
# add a runtime dependency
|
|
537
|
-
uv add <package>
|
|
538
|
-
|
|
539
|
-
# add a development dependency
|
|
540
|
-
uv add --group dev <package>
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
CI runs `pytest` (unit + integration), `ruff` (lint + format check), and `pyright` on every pull request and push to `main` via `.github/workflows/premerge.yaml`.
|
|
544
|
-
|
|
545
|
-
### Building a local Desktop Extension (.mcpb)
|
|
546
|
-
|
|
547
|
-
To smoke-test the Claude Desktop Extension locally, pack a bundle with the developer helper in `scripts/`:
|
|
548
|
-
|
|
549
|
-
```bash
|
|
550
|
-
# pack dist/docker-mcp-server-<version>.mcpb (auto-increments -1, -2, … if it exists)
|
|
551
|
-
scripts/build-mcpb.sh
|
|
552
|
-
|
|
553
|
-
# …or give it an explicit name (a .mcpb extension is added if missing)
|
|
554
|
-
scripts/build-mcpb.sh my-test-bundle
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
It reads the version from `pyproject.toml`, creates `dist/` if needed, writes a `.sha256` alongside the bundle, and packs via Anthropic's `mcpb` CLI (a global `mcpb`, else `npx @anthropic-ai/mcpb`; see `--help` for the `MCPB=` override). The official release bundle is built separately by the `mcpb` job in `.github/workflows/publish.yaml` — this script is for local testing only and is **not** used by CI.
|
|
558
|
-
|
|
559
|
-
### Reporting issues
|
|
560
|
-
|
|
561
|
-
Bug reports and feature requests have templates that you can choose when you [`create an issue`](https://github.com/GavinLucas/docker-mcp/issues/new/choose). Please select the correct issue type and follow the template.
|
|
426
|
+
Contributions are welcome. The project values a tight mapping between the Docker SDK's public surface and the MCP tools we expose. See [CONTRIBUTING.md](https://github.com/GavinLucas/docker-mcp/blob/main/CONTRIBUTING.md) for the project layout, tool conventions, the checklist for adding a new tool module, and local development setup.
|