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.
Files changed (114) hide show
  1. docker_mcp_server-2.0.1/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  2. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/copilot-instructions.md +21 -2
  3. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/canary.yaml +8 -0
  4. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/codeql.yaml +3 -3
  5. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/images.yaml +5 -5
  6. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/publish.yaml +5 -5
  7. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.mcpbignore +2 -0
  8. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/CLAUDE.md +13 -0
  9. docker_mcp_server-2.0.1/CODE_OF_CONDUCT.md +83 -0
  10. docker_mcp_server-2.0.1/CONTRIBUTING.md +188 -0
  11. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/PKG-INFO +3 -138
  12. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/README.md +2 -137
  13. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/configs.py +6 -2
  14. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/containers.py +19 -6
  15. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/images.py +10 -3
  16. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/networks.py +23 -7
  17. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/plugins.py +19 -9
  18. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/secrets.py +15 -5
  19. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/services.py +10 -3
  20. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/swarm.py +33 -10
  21. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/volumes.py +17 -6
  22. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/manifest.json +1 -1
  23. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/pyproject.toml +1 -1
  24. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/uv.lock +1 -1
  25. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.claude/commands/docker-sdk.md +0 -0
  26. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.claude/settings.json +0 -0
  27. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.dockerignore +0 -0
  28. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/CODEOWNERS +0 -0
  29. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  30. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  31. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/actions/file-failure-issue/action.yaml +0 -0
  32. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/dependabot.yaml +0 -0
  33. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/release.yml +0 -0
  34. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/premerge.yaml +0 -0
  35. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.github/workflows/publish-homebrew.yaml +0 -0
  36. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.gitignore +0 -0
  37. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/.python-version +0 -0
  38. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/DOCKERHUB.md +0 -0
  39. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/Dockerfile +0 -0
  40. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/LICENSE +0 -0
  41. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/MIGRATION-2.0.md +0 -0
  42. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/PRIVACY.md +0 -0
  43. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/SECURITY.md +0 -0
  44. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/assets/README.md +0 -0
  45. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/assets/icon.png +0 -0
  46. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/__init__.py +0 -0
  47. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/__main__.py +0 -0
  48. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/_env.py +0 -0
  49. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/_hosts.py +0 -0
  50. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/server.py +0 -0
  51. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/__init__.py +0 -0
  52. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_cli.py +0 -0
  53. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_labels.py +0 -0
  54. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_ssh_proxy.py +0 -0
  55. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/_utils.py +0 -0
  56. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/buildx.py +0 -0
  57. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/compose.py +0 -0
  58. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/context.py +0 -0
  59. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/nodes.py +0 -0
  60. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/prompts.py +0 -0
  61. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/registry.py +0 -0
  62. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/resources.py +0 -0
  63. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/scout.py +0 -0
  64. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/stack.py +0 -0
  65. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/docker_mcp/tools/system.py +0 -0
  66. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/glama.json +0 -0
  67. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/mcpb_run.py +0 -0
  68. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/scripts/build-mcpb.sh +0 -0
  69. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/scripts/docker-mcp-server.rb.tpl +0 -0
  70. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/server.json +0 -0
  71. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/__init__.py +0 -0
  72. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/conftest.py +0 -0
  73. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/__init__.py +0 -0
  74. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/conftest.py +0 -0
  75. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_buildx.py +0 -0
  76. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_cli.py +0 -0
  77. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_compose.py +0 -0
  78. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_containers.py +0 -0
  79. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_context.py +0 -0
  80. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_file_payloads.py +0 -0
  81. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_networks.py +0 -0
  82. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_registry.py +0 -0
  83. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_scout.py +0 -0
  84. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_smoke.py +0 -0
  85. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/integration/test_stack.py +0 -0
  86. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_buildx.py +0 -0
  87. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_cli.py +0 -0
  88. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_compose.py +0 -0
  89. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_configs.py +0 -0
  90. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_containers.py +0 -0
  91. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_context.py +0 -0
  92. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_env.py +0 -0
  93. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_hosts.py +0 -0
  94. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_images.py +0 -0
  95. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_labels.py +0 -0
  96. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_main.py +0 -0
  97. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_naming.py +0 -0
  98. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_networks.py +0 -0
  99. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_nodes.py +0 -0
  100. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_plugins.py +0 -0
  101. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_prompts.py +0 -0
  102. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_pyproject_pins.py +0 -0
  103. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_registry.py +0 -0
  104. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_resources.py +0 -0
  105. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_scout.py +0 -0
  106. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_secrets.py +0 -0
  107. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_server.py +0 -0
  108. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_services.py +0 -0
  109. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_ssh_proxy.py +0 -0
  110. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_stack.py +0 -0
  111. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_swarm.py +0 -0
  112. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_system.py +0 -0
  113. {docker_mcp_server-2.0.0 → docker_mcp_server-2.0.1}/tests/test_utils.py +0 -0
  114. {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: str - The name to say hello to
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: type - description`
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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
39
+ uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
40
40
 
41
41
  - name: Perform CodeQL Analysis
42
- uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
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@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
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@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
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@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
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@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
229
+ uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
230
230
 
231
231
  - name: Log in to GHCR
232
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
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@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
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@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
246
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
247
247
  with:
248
248
  context: .
249
249
  platforms: linux/amd64,linux/arm64
@@ -27,6 +27,8 @@ DOCKERHUB.md
27
27
  # Agent / project docs not needed inside the bundle.
28
28
  CLAUDE.md
29
29
  SECURITY.md
30
+ CONTRIBUTING.md
31
+ CODE_OF_CONDUCT.md
30
32
 
31
33
  # Caches and VCS.
32
34
  __pycache__/
@@ -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.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.