docker-mcp-server 2.1.2__tar.gz → 2.1.4__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 (116) hide show
  1. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/copilot-instructions.md +71 -18
  2. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/workflows/codeql.yaml +3 -3
  3. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/workflows/publish-homebrew.yaml +7 -4
  4. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/CLAUDE.md +79 -18
  5. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/PKG-INFO +4 -4
  6. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/README.md +3 -3
  7. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/buildx.py +36 -8
  8. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/compose.py +59 -8
  9. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/configs.py +12 -5
  10. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/containers.py +102 -41
  11. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/context.py +17 -2
  12. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/images.py +36 -15
  13. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/networks.py +22 -11
  14. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/nodes.py +13 -5
  15. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/plugins.py +15 -6
  16. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/registry.py +5 -2
  17. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/scout.py +25 -8
  18. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/secrets.py +11 -5
  19. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/services.py +26 -7
  20. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/stack.py +21 -6
  21. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/swarm.py +7 -0
  22. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/system.py +31 -10
  23. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/volumes.py +16 -6
  24. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/manifest.json +1 -1
  25. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/pyproject.toml +1 -1
  26. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/scripts/build-mcpb.sh +71 -9
  27. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/uv.lock +1 -1
  28. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.claude/commands/docker-sdk.md +0 -0
  29. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.claude/settings.json +0 -0
  30. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.dockerignore +0 -0
  31. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/CODEOWNERS +0 -0
  32. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  33. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  34. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  35. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/actions/file-failure-issue/action.yaml +0 -0
  36. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/dependabot.yaml +0 -0
  37. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/release.yml +0 -0
  38. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/workflows/canary.yaml +0 -0
  39. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/workflows/images.yaml +0 -0
  40. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/workflows/premerge.yaml +0 -0
  41. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.github/workflows/publish.yaml +0 -0
  42. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.gitignore +0 -0
  43. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.mcpbignore +0 -0
  44. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/.python-version +0 -0
  45. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/CODE_OF_CONDUCT.md +0 -0
  46. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/CONTRIBUTING.md +0 -0
  47. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/DOCKERHUB.md +0 -0
  48. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/Dockerfile +0 -0
  49. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/LICENSE +0 -0
  50. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/MIGRATION-2.0.md +0 -0
  51. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/PRIVACY.md +0 -0
  52. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/SECURITY.md +0 -0
  53. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/assets/README.md +0 -0
  54. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/assets/icon.png +0 -0
  55. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/__init__.py +0 -0
  56. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/__main__.py +0 -0
  57. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/_env.py +0 -0
  58. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/_hosts.py +0 -0
  59. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/server.py +0 -0
  60. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/__init__.py +0 -0
  61. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/_cli.py +0 -0
  62. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/_labels.py +0 -0
  63. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/_ssh_proxy.py +0 -0
  64. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/_utils.py +0 -0
  65. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/prompts.py +0 -0
  66. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/docker_mcp/tools/resources.py +0 -0
  67. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/glama.json +0 -0
  68. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/mcpb_run.py +0 -0
  69. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/scripts/docker-mcp-server.rb.tpl +0 -0
  70. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/server.json +0 -0
  71. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/__init__.py +0 -0
  72. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/conftest.py +0 -0
  73. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/__init__.py +0 -0
  74. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/conftest.py +0 -0
  75. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_buildx.py +0 -0
  76. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_cli.py +0 -0
  77. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_compose.py +0 -0
  78. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_containers.py +0 -0
  79. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_context.py +0 -0
  80. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_file_payloads.py +0 -0
  81. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_networks.py +0 -0
  82. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_nodes.py +0 -0
  83. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_registry.py +0 -0
  84. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_scout.py +0 -0
  85. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_services.py +0 -0
  86. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_smoke.py +0 -0
  87. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/integration/test_stack.py +0 -0
  88. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_buildx.py +0 -0
  89. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_cli.py +0 -0
  90. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_compose.py +0 -0
  91. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_configs.py +0 -0
  92. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_containers.py +0 -0
  93. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_context.py +0 -0
  94. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_env.py +0 -0
  95. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_hosts.py +0 -0
  96. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_images.py +0 -0
  97. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_labels.py +0 -0
  98. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_main.py +0 -0
  99. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_naming.py +0 -0
  100. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_networks.py +0 -0
  101. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_nodes.py +0 -0
  102. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_plugins.py +0 -0
  103. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_prompts.py +0 -0
  104. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_pyproject_pins.py +0 -0
  105. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_registry.py +0 -0
  106. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_resources.py +0 -0
  107. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_scout.py +0 -0
  108. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_secrets.py +0 -0
  109. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_server.py +0 -0
  110. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_services.py +0 -0
  111. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_ssh_proxy.py +0 -0
  112. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_stack.py +0 -0
  113. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_swarm.py +0 -0
  114. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_system.py +0 -0
  115. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_utils.py +0 -0
  116. {docker_mcp_server-2.1.2 → docker_mcp_server-2.1.4}/tests/test_volumes.py +0 -0
@@ -4,7 +4,7 @@ This file provides guidance to GitHub Copilot when working with code in this rep
4
4
 
5
5
  ## Project
6
6
 
7
- `docker-mcp` is a Python MCP (Model Context Protocol) server that exposes the Docker SDK for Python — plus selected docker CLI features (Compose, Context, Buildx, Scout) and direct OCI-registry HTTPS access — as MCP tools. It requires Python >=3.14 and is managed with `uv`. It is published to PyPI as **`docker-mcp-server`** and as a container image to GHCR (`ghcr.io/l337-org/docker-mcp-server`), mirrored to Docker Hub (`gavinlucas/docker-mcp-server`) when the opt-in `DOCKERHUB_*` release secrets are configured (the import package stays `docker_mcp`, the repo stays `…/docker-mcp`); two console scripts — `docker-mcp` and `docker-mcp-server` — both target `docker_mcp:main`. A third distribution channel packages the server as a **Claude Desktop Extension (`.mcpb`)** attached to each GitHub Release (see "Desktop Extension (MCPB)" below). A fourth channel (**Homebrew tap**) exists in `gavinlucas/homebrew-tap` but is currently **paused** — see "Homebrew tap" below.
7
+ `docker-mcp` is a Python MCP (Model Context Protocol) server that exposes the Docker SDK for Python — plus selected docker CLI features (Compose, Context, Buildx, Scout) and direct OCI-registry HTTPS access — as MCP tools. It requires Python >=3.14 and is managed with `uv`. It is published to PyPI as **`docker-mcp-server`** and as a container image to GHCR (`ghcr.io/l337-org/docker-mcp-server`), mirrored to Docker Hub (`gavinlucas/docker-mcp-server`) when the opt-in `DOCKERHUB_*` release secrets are configured (the import package stays `docker_mcp`, the repo stays `…/docker-mcp`); two console scripts — `docker-mcp` and `docker-mcp-server` — both target `docker_mcp:main`. A third distribution channel packages the server as a **Claude Desktop Extension (`.mcpb`)** attached to each GitHub Release (see "Desktop Extension (MCPB)" below). A fourth channel (**Homebrew tap**) exists in `L337-org/homebrew-tap` but is currently **paused** — see "Homebrew tap" below.
8
8
 
9
9
  The `docker` dep uses the `[ssh]` extra (paramiko), so `DOCKER_HOST=ssh://…` works via a pure-Python transport (no system `ssh` binary; works in the container images). docker-py auto-selects paramiko for `ssh://`, so there's no transport code — only the `ssh://` branch in `system._connection_help`. CLI-backed tools (Compose, Buildx, Context, Scout) shell out to `docker`, which would otherwise need the system `ssh` client — instead, `_cli.py:run_docker` detects `DOCKER_HOST=ssh://…` and routes the subprocess through a per-call local TCP proxy (`docker_mcp/tools/_ssh_proxy.py`) that opens its own paramiko connection and runs `docker system dial-stdio` over it, so the CLI authenticates the same way the docker-py-backed tools do, with no system `ssh` binary involved (except a `ProxyCommand` in `~/.ssh/config` for bastion/jump-host setups, which paramiko runs as an external command — commonly `ssh -W %h:%p ...` — for both tool families alike).
10
10
 
@@ -89,11 +89,11 @@ An additional distribution channel alongside the uvx-from-git install (unchanged
89
89
 
90
90
  ### Desktop Extension (MCPB)
91
91
 
92
- A third distribution channel for one-click install in Claude Desktop. Repo-root sources: `manifest.json` (MCPB manifest, `manifest_version` 0.4, `server.type: "uv"`), `mcpb_run.py` (bundle entry point — `from docker_mcp import main; main()`, at the root so `import docker_mcp` resolves under the host's managed `uv`), `.mcpbignore` (trims the packed bundle to source + `pyproject.toml` + `uv.lock` + `README.md`/`LICENSE` + manifest/entry-point + `assets/`), and `assets/icon.png` (512×512). It's a `uv`-type bundle: the host resolves deps from `pyproject.toml` at install time (no vendored venv). `manifest.json`'s `user_config` maps install-dialog fields to env — `DOCKER_MCP_SERVER_HOSTS` (the single host field; `DOCKER_HOST` is **not** exposed — the bare-value shorthand keeps the one-daemon case a one-liner) and the `DOCKER_MCP_SERVER_READONLY` / `_NO_DESTRUCTIVE` / `_DISABLE` switches (the container-only `_ALLOW_SELF_TERMINATE` is intentionally omitted — the bundle never runs containerized). The `mcpb` job in `.github/workflows/publish.yaml` rewrites the manifest `version` from the release tag, packs the `.mcpb` via `npx @anthropic-ai/mcpb`, and attaches it (plus a `.sha256` recording the bare filename, so `sha256sum -c` works on downloads) to each GitHub Release. `scripts/build-mcpb.sh` is the **developer-only** local equivalent of that pack step (version from `pyproject.toml`, auto-incrementing output in `dist/`, never mutates `manifest.json`) for smoke-testing in Claude Desktop — it is deliberately **not** used by CI. `PRIVACY.md` (no-telemetry statement) is referenced by the manifest's `privacy_policies`. Keep the manifest `version` aligned with `pyproject.toml`; update this section when the manifest's tool/env surface or the bundled file set changes.
92
+ A third distribution channel for one-click install in Claude Desktop. Repo-root sources: `manifest.json` (MCPB manifest, `manifest_version` 0.4, `server.type: "uv"`), `mcpb_run.py` (bundle entry point — `from docker_mcp import main; main()`, at the root so `import docker_mcp` resolves under the host's managed `uv`), `.mcpbignore` (trims the packed bundle to source + `pyproject.toml` + `uv.lock` + `README.md`/`LICENSE` + manifest/entry-point + `assets/`), and `assets/icon.png` (512×512). It's a `uv`-type bundle: the host resolves deps from `pyproject.toml` at install time (no vendored venv). `manifest.json`'s `user_config` maps install-dialog fields to env — `DOCKER_MCP_SERVER_HOSTS` (the single host field; `DOCKER_HOST` is **not** exposed — the bare-value shorthand keeps the one-daemon case a one-liner) and the `DOCKER_MCP_SERVER_READONLY` / `_NO_DESTRUCTIVE` / `_DISABLE` switches (the container-only `_ALLOW_SELF_TERMINATE` is intentionally omitted — the bundle never runs containerized). The `mcpb` job in `.github/workflows/publish.yaml` rewrites the manifest `version` from the release tag, packs the `.mcpb` via `npx @anthropic-ai/mcpb`, and attaches it (plus a `.sha256` recording the bare filename, so `sha256sum -c` works on downloads) to each GitHub Release. `scripts/build-mcpb.sh` is the **developer-only** local equivalent of that pack step (auto-incrementing output in `dist/`) for smoke-testing in Claude Desktop — it is deliberately **not** used by CI. It stamps a **dev version** from `pyproject.toml` + git HEAD — `<version>-dev.<short-commit>[.dirty]` — so a local bundle can't be mistaken for a release and is traceable to its commit; the stamp goes into `manifest.json` only for the pack and is restored by an `EXIT` trap. `PRIVACY.md` (no-telemetry statement) is referenced by the manifest's `privacy_policies`. Keep the manifest `version` aligned with `pyproject.toml`; update this section when the manifest's tool/env surface or the bundled file set changes.
93
93
 
94
- ### Homebrew tap (`gavinlucas/homebrew-tap`) — PAUSED
94
+ ### Homebrew tap (`L337-org/homebrew-tap`) — PAUSED
95
95
 
96
- The infrastructure exists (`scripts/docker-mcp-server.rb.tpl`, `.github/workflows/publish-homebrew.yaml`, `GavinLucas/homebrew-tap`) but the **release trigger is disabled** pending resolution of a Homebrew dylib linkage issue: pre-built PyPI wheels for `pydantic_core` lack `-headerpad_max_install_names`, so Homebrew's post-install relocation step fails to rewrite the `@rpath` ID (the binary's Mach-O header has no room for the longer absolute path). The workflow is `workflow_dispatch`-only until a fix is found. The channel is not advertised in the README. To re-enable: add `release: types: [published]` back to `publish-homebrew.yaml`'s `on:` block and re-add the Homebrew section to the README.
96
+ The infrastructure exists (`scripts/docker-mcp-server.rb.tpl`, `.github/workflows/publish-homebrew.yaml`, `L337-org/homebrew-tap`) but the **release trigger is disabled** pending resolution of a Homebrew dylib linkage issue: pre-built PyPI wheels for `pydantic_core` lack `-headerpad_max_install_names`, so Homebrew's post-install relocation step fails to rewrite the `@rpath` ID (the binary's Mach-O header has no room for the longer absolute path). The workflow is `workflow_dispatch`-only until a fix is found. The channel is not advertised in the README. To re-enable: add `release: types: [published]` back to `publish-homebrew.yaml`'s `on:` block and re-add the Homebrew section to the README.
97
97
 
98
98
  ### MCP Registry (`server.json`)
99
99
 
@@ -148,12 +148,18 @@ def mcp_example(name: str):
148
148
  """
149
149
  Say hello to someone by name.
150
150
 
151
- args: name - The name to say hello to
151
+ Use it for a single greeting; use `mcp_example_bulk` to greet many names in one call.
152
+ Read-only, no side effects.
153
+
154
+ args: name - The name to say hello to (any non-empty string)
152
155
  returns: str - The greeting
153
156
  """
154
157
  return f"Hello, {name}!"
155
158
  ```
156
159
 
160
+ (`mcp_example` and `mcp_example_bulk` are illustrative only and exist nowhere in the repo — in a
161
+ real docstring the discriminator must name an actually-registered sibling tool.)
162
+
157
163
  - One-line summary sentence, then a blank line
158
164
  - `args:` section lists each parameter as `name - description`. Do **not** repeat the parameter's
159
165
  type — the type annotation already lands in the tool's `inputSchema`, which the client sees
@@ -163,19 +169,66 @@ def mcp_example(name: str):
163
169
  - Keep descriptions terse: state every functional fact (defaults, accepted formats/values, return
164
170
  keys, important caveats) but cut redundancy and verbose phrasing. The docstring is the entire
165
171
  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.
172
+
173
+ #### Docstring quality standard (review checklist push back on non-compliant docstrings)
174
+
175
+ Tool descriptions are scored externally on Glama's six-dimension Tool Definition Quality rubric
176
+ (<https://glama.ai/mcp/servers/L337-org/docker-mcp/score>): Purpose Clarity 25%, Usage Guidelines
177
+ 20%, Behavioral Transparency 20%, Parameter Semantics 15%, Conciseness & Structure 10%, Contextual
178
+ Completeness 10%. Repeated cleanup PRs (#97, the 2.0 rename, #129, the 2026-07 bottom-20 pass) all
179
+ chased the same failure: docstrings that state *what* the tool does but never *when to use it over
180
+ its neighbors*, plus `args:`/`returns:` lines that merely restate the schema. **When a PR adds or
181
+ modifies an `@tool()` docstring, check it against every numbered item below and request changes
182
+ naming the specific item it fails.** The standard is a ratchet: it applies to docstrings the PR
183
+ touches do not demand rewrites of untouched legacy docstrings.
184
+
185
+ 1. **Summary = specific verb + resource**, with the distinguishing trait up front when a sibling
186
+ could be confused ("Send a signal to a running container (default SIGKILL — immediate, no
187
+ graceful shutdown)").
188
+ 2. **A usage-guidance paragraph (1–5 sentences between the summary and `args:`) is required for
189
+ every tool, not just complex ones.** Flag a new/edited tool whose docstring jumps straight from
190
+ summary to `args:`. The paragraph must carry:
191
+ - at least one *discriminator* naming the sibling tool(s) an agent could reach for instead and
192
+ when to prefer which (`container_stop` vs `container_kill` vs `container_restart`;
193
+ `service_ps` vs `stack_ps` vs the `service-tasks://` resource);
194
+ - preconditions in prose (swarm manager only, plugin required, container must be
195
+ running/paused);
196
+ - side effects and destructive/irreversible behavior in prose — the scorer explicitly discounts
197
+ `readOnlyHint`/`destructiveHint` annotations as a substitute for description text;
198
+ - for CLI-backed tools, the error style ("does not raise on a non-zero CLI exit — inspect
199
+ `returncode`/`stderr`" vs "raises `RuntimeError` on CLI failure"). Flag an absolute "never
200
+ raises" claim — a missing binary/plugin or a subprocess timeout still raises even in action
201
+ tools.
202
+ Scale to the tool: a trivial read-only tool needs one discriminator sentence, not five.
203
+ 3. **Every `args:` line adds semantics the schema cannot carry**: format, accepted values/ranges,
204
+ defaults, units, parameter interactions. Flag a line that merely echoes the parameter name
205
+ ("name - The volume name") — it scores 2/5 on the rubric. Canonical shared-param prefixes in
206
+ `tests/test_naming.py` still apply — tool-specific detail is appended after the canonical
207
+ prefix, not reworded.
208
+ 4. **`returns:` names the shape, not just the type.** For computed or partial returns the
209
+ load-bearing keys must be listed (`{"Titles", "Processes"}`). For a full engine inspect
210
+ document, key enumeration is NOT wanted — an arbitrary subset of hundreds of keys is noise; the
211
+ line should identify the document ("full inspect payload, as `docker inspect`"), optionally
212
+ plus one or two keys a caller typically wants. Flag the shapeless "dict - The X's attrs" on a
213
+ new/modified tool, which identifies neither form.
214
+ 5. **Front-loaded and terse** — the description is paid for in every session's context.
215
+ 6. **Every factual claim must be verifiable** against the docker-py docs / Engine API spec (Docker
216
+ SDK Policy below). Flag claims that contradict them — especially identifier semantics ("name or
217
+ id" for a resource actually addressed by name only), default values, and signal/timeout
218
+ behavior.
219
+
220
+ **Division of labor across the three discovery layers.** For a lazy-loading client (e.g. Claude
221
+ Code), tool schemas load on demand; always in context are only (1) the **tool names** and (2) the
222
+ **`instructions` router** (`_DOMAIN_BLURBS` / `build_instructions()` in `server.py`). Docstrings
223
+ are layer (3): a deferred tool cannot be invoked without fetching its definition, so it is read at
224
+ the moment of choice, side by side with sibling definitions — where the item-2 discriminators do
225
+ their work. When reviewing, apply the boundary: a *cross-domain* selection caveat that must be
226
+ visible before any schema is fetched (e.g. "prefer `dest_path` for large output") belongs in the
227
+ router's caveat list, not buried in one docstring; *sibling-level* discriminators belong in
228
+ docstrings and should not be duplicated into the router; flag docstrings padded with search
229
+ keywords (discoverability is the naming convention's and the router's job); and flag sibling
230
+ references that don't use the exact tool name (`container_kill`, never "the kill tool") — lazy
231
+ clients keyword-search descriptions, so exact names double as retrieval anchors.
179
232
 
180
233
  ### Bounding rules
181
234
 
@@ -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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
34
+ uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4
35
35
  with:
36
36
  languages: ${{ matrix.language }}
37
37
 
38
38
  - name: Autobuild
39
- uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
39
+ uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # v4
40
40
 
41
41
  - name: Perform CodeQL Analysis
42
- uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
42
+ uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4
@@ -2,7 +2,7 @@ name: 'Publish Homebrew formula'
2
2
 
3
3
  # On each GitHub Release: downloads pre-built macOS wheels for arm64 and x86_64 from PyPI (using
4
4
  # the frozen lockfile so versions are pinned), packages each set into a wheelhouse tarball, attaches
5
- # both to the release, then pushes an updated Formula/docker-mcp-server.rb to GavinLucas/homebrew-tap.
5
+ # both to the release, then pushes an updated Formula/docker-mcp-server.rb to L337-org/homebrew-tap.
6
6
  #
7
7
  # Runs on ubuntu-latest. Both wheelhouses are built via cross-platform pip downloads:
8
8
  # arm64: --platform macosx_<floor>_arm64 + --platform macosx_<floor>_universal2
@@ -23,8 +23,11 @@ name: 'Publish Homebrew formula'
23
23
  # guardrail; do not remove it.
24
24
  #
25
25
  # Requires the repository secret TAP_GITHUB_TOKEN: a fine-grained PAT with Contents read+write on
26
- # GavinLucas/homebrew-tap. Create it at github.com/settings/tokens and add it as a secret on this
27
- # repo under Settings Secrets Actions.
26
+ # L337-org/homebrew-tap, issued with **L337-org as the resource owner** (a fine-grained PAT scoped
27
+ # to a personal account cannot access org repos, and org settings must allow fine-grained PAT
28
+ # access). Create it at github.com/settings/tokens and add it as a secret on this repo under
29
+ # Settings → Secrets → Actions. The pre-transfer token (resource owner GavinLucas) is invalid for
30
+ # the moved tap — re-issue before re-enabling this channel.
28
31
  #
29
32
  # workflow_dispatch lets you backfill a release that predates this workflow (supply the tag, e.g.
30
33
  # v1.9.2). The release must already exist and docker-mcp-server must be live on PyPI for that version.
@@ -206,7 +209,7 @@ jobs:
206
209
  GH_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
207
210
  run: |
208
211
  set -euo pipefail
209
- git clone "https://x-access-token:${GH_TOKEN}@github.com/GavinLucas/homebrew-tap.git" tap
212
+ git clone "https://x-access-token:${GH_TOKEN}@github.com/L337-org/homebrew-tap.git" tap
210
213
  cd tap
211
214
  git config user.name "github-actions[bot]"
212
215
  git config user.email "github-actions[bot]@users.noreply.github.com"
@@ -11,7 +11,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
11
11
 
12
12
  ## Project
13
13
 
14
- `docker-mcp` is a Python MCP server (requires Python >=3.14) managed with `uv` that exposes the Docker SDK for Python as MCP tools. The entry point is the `docker_mcp` package, run with `python -m docker_mcp` or via the installed console script. It is **published to PyPI as `docker-mcp-server`** (the `docker-mcp` name was already taken) and as a container image to GHCR (`ghcr.io/l337-org/docker-mcp-server`), mirrored to Docker Hub (`gavinlucas/docker-mcp-server`) when the opt-in `DOCKERHUB_*` release secrets are configured; the import package stays `docker_mcp` and the repo stays `…/docker-mcp`. Two console scripts are installed — `docker-mcp` and `docker-mcp-server` — both targeting `docker_mcp:main`. A third channel packages the server as a **Claude Desktop Extension (`.mcpb`)** attached to each GitHub Release — see "Desktop Extension (MCPB bundle)" below. A fourth channel (**Homebrew tap**) exists in `gavinlucas/homebrew-tap` but is currently **paused** — see "Homebrew tap" below.
14
+ `docker-mcp` is a Python MCP server (requires Python >=3.14) managed with `uv` that exposes the Docker SDK for Python as MCP tools. The entry point is the `docker_mcp` package, run with `python -m docker_mcp` or via the installed console script. It is **published to PyPI as `docker-mcp-server`** (the `docker-mcp` name was already taken) and as a container image to GHCR (`ghcr.io/l337-org/docker-mcp-server`), mirrored to Docker Hub (`gavinlucas/docker-mcp-server`) when the opt-in `DOCKERHUB_*` release secrets are configured; the import package stays `docker_mcp` and the repo stays `…/docker-mcp`. Two console scripts are installed — `docker-mcp` and `docker-mcp-server` — both targeting `docker_mcp:main`. A third channel packages the server as a **Claude Desktop Extension (`.mcpb`)** attached to each GitHub Release — see "Desktop Extension (MCPB bundle)" below. A fourth channel (**Homebrew tap**) exists in `L337-org/homebrew-tap` but is currently **paused** — see "Homebrew tap" below.
15
15
 
16
16
  The `docker` dependency is pulled with its `[ssh]` extra (paramiko), so `DOCKER_HOST=ssh://…` works through a pure-Python transport — no system `ssh` binary, identical on the host and in the container images. docker-py auto-selects paramiko for `ssh://` when present, so there is no transport code to maintain (just the `ssh://` branch in `system._connection_help`). CLI-backed tools (Compose, Buildx, Context, Scout) shell out to `docker`, which would otherwise use the *system* `ssh` — instead, `_cli.py:run_docker` detects `DOCKER_HOST=ssh://…` and routes the subprocess through a per-call local TCP proxy (`docker_mcp/tools/_ssh_proxy.py`) that opens its own paramiko connection (mirroring docker-py's `SSHHTTPAdapter` defaults) and runs `docker system dial-stdio` over it, so the CLI authenticates identically to the docker-py-backed tools with no system `ssh` binary involved (the one exception being a `ProxyCommand` in `~/.ssh/config` for bastion/jump-host setups, which paramiko runs as an external command — commonly `ssh -W %h:%p ...` — same as it would for the docker-py-backed tools).
17
17
 
@@ -155,11 +155,11 @@ An additional distribution channel alongside uvx-from-git (which is unchanged).
155
155
 
156
156
  ### Desktop Extension (MCPB bundle)
157
157
 
158
- A third distribution channel (alongside uvx/PyPI and the container images) for one-click install in Claude Desktop. The repo root carries the bundle sources: `manifest.json` (the MCPB manifest, `manifest_version` 0.4, `server.type: "uv"`), `mcpb_run.py` (the bundle entry point — `from docker_mcp import main; main()`, kept at the root so `import docker_mcp` resolves however the host's managed `uv` lays out `sys.path`), `.mcpbignore` (trims the packed bundle to source + `pyproject.toml` + `uv.lock` + `README.md`/`LICENSE` + `manifest.json`/`mcpb_run.py` + `assets/`), and `assets/icon.png` (512×512). Because it's a `uv`-type bundle, the host resolves dependencies from `pyproject.toml` at install time — there's no vendored venv, so the bundle stays ~250 KB and cross-platform. `manifest.json`'s `user_config` block renders the install-dialog fields and maps them to env: `DOCKER_MCP_SERVER_HOSTS` (the single host field — `DOCKER_HOST` is deliberately **not** exposed; the bare-value shorthand keeps the simple one-daemon case a one-liner) plus the `DOCKER_MCP_SERVER_READONLY` / `_NO_DESTRUCTIVE` / `_DISABLE` switches (the container-only `_ALLOW_SELF_TERMINATE` is deliberately **not** exposed — the bundle never runs containerized). The `manifest.json` `version` is kept in step with `pyproject.toml` (`tests/test_pyproject_pins.py` asserts it), but the `mcpb` job in `.github/workflows/publish.yaml` rewrites it from the release tag at pack time so it can't drift; that job packs the `.mcpb` with `npx @anthropic-ai/mcpb` and attaches it (plus a `.sha256` recording the bare filename, so `sha256sum -c` works on downloads) to each GitHub Release. `scripts/build-mcpb.sh` is the **developer-only** local equivalent of that pack step (reads the version from `pyproject.toml`, packs to `dist/` with an auto-incrementing name, never mutates `manifest.json`) — for smoke-testing a bundle in Claude Desktop; it is deliberately **not** wired into CI, which uses the workflow above. `PRIVACY.md` (a no-telemetry/no-backend statement) is referenced by the manifest's `privacy_policies` and summarized in the README. **When the manifest's tool/env surface or the bundled file set changes, update this section.**
158
+ A third distribution channel (alongside uvx/PyPI and the container images) for one-click install in Claude Desktop. The repo root carries the bundle sources: `manifest.json` (the MCPB manifest, `manifest_version` 0.4, `server.type: "uv"`), `mcpb_run.py` (the bundle entry point — `from docker_mcp import main; main()`, kept at the root so `import docker_mcp` resolves however the host's managed `uv` lays out `sys.path`), `.mcpbignore` (trims the packed bundle to source + `pyproject.toml` + `uv.lock` + `README.md`/`LICENSE` + `manifest.json`/`mcpb_run.py` + `assets/`), and `assets/icon.png` (512×512). Because it's a `uv`-type bundle, the host resolves dependencies from `pyproject.toml` at install time — there's no vendored venv, so the bundle stays ~250 KB and cross-platform. `manifest.json`'s `user_config` block renders the install-dialog fields and maps them to env: `DOCKER_MCP_SERVER_HOSTS` (the single host field — `DOCKER_HOST` is deliberately **not** exposed; the bare-value shorthand keeps the simple one-daemon case a one-liner) plus the `DOCKER_MCP_SERVER_READONLY` / `_NO_DESTRUCTIVE` / `_DISABLE` switches (the container-only `_ALLOW_SELF_TERMINATE` is deliberately **not** exposed — the bundle never runs containerized). The `manifest.json` `version` is kept in step with `pyproject.toml` (`tests/test_pyproject_pins.py` asserts it), but the `mcpb` job in `.github/workflows/publish.yaml` rewrites it from the release tag at pack time so it can't drift; that job packs the `.mcpb` with `npx @anthropic-ai/mcpb` and attaches it (plus a `.sha256` recording the bare filename, so `sha256sum -c` works on downloads) to each GitHub Release. `scripts/build-mcpb.sh` is the **developer-only** local equivalent of that pack step (packs to `dist/` with an auto-incrementing name) for smoke-testing a bundle in Claude Desktop. It stamps a **dev version** derived from `pyproject.toml` plus the git HEAD — `<version>-dev.<short-commit>[.dirty]` (`.dirty` = uncommitted changes at pack time) — so a local bundle is never mistaken for a release in Claude Desktop's extension list and is traceable to the commit it was built from despite having no tag; the stamp is written into `manifest.json` only for the duration of the pack and restored by an `EXIT` trap, so the working tree is left unmodified. It is deliberately **not** wired into CI, which uses the workflow above. `PRIVACY.md` (a no-telemetry/no-backend statement) is referenced by the manifest's `privacy_policies` and summarized in the README. **When the manifest's tool/env surface or the bundled file set changes, update this section.**
159
159
 
160
- ### Homebrew tap (`gavinlucas/homebrew-tap`) — PAUSED
160
+ ### Homebrew tap (`L337-org/homebrew-tap`) — PAUSED
161
161
 
162
- The infrastructure exists (`scripts/docker-mcp-server.rb.tpl`, `.github/workflows/publish-homebrew.yaml`, `GavinLucas/homebrew-tap`) but the **release trigger is disabled** pending resolution of a Homebrew dylib linkage issue: pre-built PyPI wheels for `pydantic_core` lack `-headerpad_max_install_names`, so Homebrew's post-install relocation step fails to rewrite the `@rpath` ID (the binary's Mach-O header has no room for the longer absolute path). The workflow is `workflow_dispatch`-only until a fix is found. The channel is not advertised in the README. To re-enable: add `release: types: [published]` back to `publish-homebrew.yaml`'s `on:` block and re-add the Homebrew section to the README.
162
+ The infrastructure exists (`scripts/docker-mcp-server.rb.tpl`, `.github/workflows/publish-homebrew.yaml`, `L337-org/homebrew-tap`) but the **release trigger is disabled** pending resolution of a Homebrew dylib linkage issue: pre-built PyPI wheels for `pydantic_core` lack `-headerpad_max_install_names`, so Homebrew's post-install relocation step fails to rewrite the `@rpath` ID (the binary's Mach-O header has no room for the longer absolute path). The workflow is `workflow_dispatch`-only until a fix is found. The channel is not advertised in the README. To re-enable: add `release: types: [published]` back to `publish-homebrew.yaml`'s `on:` block and re-add the Homebrew section to the README.
163
163
 
164
164
  ### MCP Registry (`server.json`)
165
165
 
@@ -247,12 +247,18 @@ def mcp_example(name: str):
247
247
  """
248
248
  Say hello to someone by name.
249
249
 
250
- args: name - The name to say hello to
250
+ Use it for a single greeting; use `mcp_example_bulk` to greet many names in one call.
251
+ Read-only, no side effects.
252
+
253
+ args: name - The name to say hello to (any non-empty string)
251
254
  returns: str - The greeting
252
255
  """
253
256
  return f"Hello, {name}!"
254
257
  ```
255
258
 
259
+ (`mcp_example` and `mcp_example_bulk` are illustrative only and exist nowhere in the repo — in a
260
+ real docstring the discriminator must name an actually-registered sibling tool.)
261
+
256
262
  - One-line summary sentence, then a blank line
257
263
  - `args:` section lists each parameter as `name - description`. Do **not** repeat the parameter's
258
264
  type — the type annotation already lands in the tool's `inputSchema`, which the client sees
@@ -262,19 +268,74 @@ def mcp_example(name: str):
262
268
  - Keep descriptions terse: state every functional fact (defaults, accepted formats/values, return
263
269
  keys, important caveats) but cut redundancy and verbose phrasing. The docstring is the entire
264
270
  tool `description` the client pays tokens for on every session.
265
- - **A one-line summary + bare `args`/`returns` is not enough for any tool with non-obvious
266
- behavior.** If the tool has side effects, preconditions, a non-obvious failure mode, or overlaps
267
- with another tool a caller could reach for instead, add a short paragraph (2-5 sentences,
268
- between the summary and `args:`) covering: when to use it vs. the alternative, side
269
- effects/preconditions, and concrete parameter formats/values not just restating the signature.
270
- This directly maps to what a low-scoring tool is missing on Glama's per-tool quality rubric
271
- (`Behavior`, `Usage Guidelines`, `Parameters` sub-scores) see [[project_glama_docstring_quality]]
272
- in memory for the three rounds of cleanup (#97, the 2.0 rename, #129) this has already taken to
273
- chase down thin docstrings written without this paragraph. **Write it this way the first time a
274
- tool is added or its behavior changes** don't wait for a future Glama pass to catch it. Verify
275
- every factual claim in that paragraph against the live docker-py docs / Engine API spec per the
276
- Docker SDK Policy below an unverified claim about identifier semantics (e.g. "name or id" for a
277
- resource actually addressed by name only) is exactly the kind of thing PR review catches late.
271
+
272
+ #### Docstring quality standard
273
+
274
+ Tool descriptions are scored externally on Glama's six-dimension Tool Definition Quality rubric
275
+ (<https://glama.ai/mcp/servers/L337-org/docker-mcp/score>): Purpose Clarity 25%, Usage Guidelines
276
+ 20%, Behavioral Transparency 20%, Parameter Semantics 15%, Conciseness & Structure 10%, Contextual
277
+ Completeness 10%. Four rounds of cleanup (#97, the 2.0 rename, #129, and the 2026-07 bottom-20
278
+ pass see [[project_glama_docstring_quality]] in memory) all chased the same failure: docstrings
279
+ that state *what* the tool does but never *when to use it over its neighbors*, plus `args:` /
280
+ `returns:` lines that merely restate the schema. The standard below exists to prevent a fifth
281
+ round. It applies to **every `@tool()` docstring added or modified in a PR** (a ratchet untouched
282
+ legacy docstrings are cleaned opportunistically, not churned):
283
+
284
+ 1. **Summary = specific verb + resource**, with the distinguishing trait up front when a sibling
285
+ could be confused ("Send a signal to a running container (default SIGKILL — immediate, no
286
+ graceful shutdown)").
287
+ 2. **A usage-guidance paragraph (1–5 sentences between the summary and `args:`) is required for
288
+ every tool, not just complex ones.** Every tool in a 150+-tool server has neighbors. It must
289
+ carry:
290
+ - at least one *discriminator* naming the sibling tool(s) an agent could reach for instead and
291
+ when to prefer which (`container_stop` vs `container_kill` vs `container_restart`;
292
+ `service_ps` vs `stack_ps` vs the `service-tasks://` resource);
293
+ - preconditions in prose (swarm manager only, plugin required, container must be
294
+ running/paused);
295
+ - side effects and destructive/irreversible behavior in prose — the scorer explicitly discounts
296
+ `readOnlyHint`/`destructiveHint` annotations as a substitute for description text;
297
+ - for CLI-backed tools, the error style ("does not raise on a non-zero CLI exit — inspect
298
+ `returncode`/`stderr`" vs "raises `RuntimeError` on CLI failure"). Don't overpromise "never
299
+ raises" — a missing binary/plugin or a subprocess timeout still raises even in action tools.
300
+ Scale it to the tool: a trivial read-only tool needs one discriminator sentence, not five.
301
+ 3. **Every `args:` line adds semantics the schema cannot carry**: format, accepted values/ranges,
302
+ defaults, units, and interactions with other parameters. A line that echoes the parameter name
303
+ ("name - The volume name") scores 2/5 on the rubric — say what makes a value valid or how it
304
+ behaves ("name - The volume name (volumes have no separate id)"). Canonical shared-param
305
+ prefixes in `tests/test_naming.py` still apply — append tool-specific detail after the
306
+ canonical prefix rather than rewording it.
307
+ 4. **`returns:` names the shape, not just the type.** There is no output schema, so this line is
308
+ all an agent gets. For computed or partial returns, name the load-bearing keys (`{"Titles",
309
+ "Processes"}`; `{"LayersSize", "Images", "Containers", "Volumes", "BuildCache"}`). For a full
310
+ engine inspect document, do NOT enumerate an arbitrary subset of its hundreds of keys — say
311
+ what document it is ("full inspect payload, as `docker inspect`"), optionally plus the one or
312
+ two keys a caller typically wants from it. What stays banned is the shapeless "dict - The X's
313
+ attrs", which identifies neither form.
314
+ 5. **Front-load and stay terse** — the description is paid for in every session's context; every
315
+ sentence must earn its place.
316
+ 6. **Verify every factual claim** against the live docker-py docs / Engine API spec per the Docker
317
+ SDK Policy below — an unverified claim about identifier semantics (e.g. "name or id" for a
318
+ resource actually addressed by name only) is exactly the kind of thing PR review catches late.
319
+
320
+ **Division of labor across the three discovery layers.** For a lazy-loading client (e.g. Claude
321
+ Code), tool schemas load on demand; what is always in context is only (1) the **tool names** and
322
+ (2) the **`instructions` router**. Docstrings are layer (3): a deferred tool cannot be invoked
323
+ without fetching its definition, so the docstring is guaranteed to be read at the moment of
324
+ choice — typically side by side with the sibling definitions the same search returned, which is
325
+ where the item-2 discriminators do their work. Consequences: pre-fetch discoverability belongs to
326
+ the naming convention and the router, not the docstring (don't pad docstrings with search
327
+ keywords); a *cross-domain* selection caveat that must be visible before any schema is fetched
328
+ (e.g. "prefer `dest_path` for large output") goes in the router's caveat list (`_DOMAIN_BLURBS` /
329
+ `build_instructions()` — see "Server singleton" above), while *sibling-level* discriminators stay
330
+ in docstrings and are never duplicated into the router; and sibling references must use the exact
331
+ tool name (`container_kill`, never "the kill tool") — lazy clients keyword-search descriptions, so
332
+ exact names double as retrieval anchors that surface the right alternative even when the agent
333
+ searched for the wrong one.
334
+
335
+ Self-check before opening the PR: read the docstring as an agent holding 150+ tool names and
336
+ nothing else — could you pick this tool over its neighbors and call it correctly on the first try?
337
+ **Write it this way the first time a tool is added or its behavior changes** — don't wait for a
338
+ future Glama pass to catch it.
278
339
 
279
340
  ### MCP resources
280
341
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docker-mcp-server
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: MCP server for managing Docker resources via the Docker SDK for Python
5
5
  Project-URL: Homepage, https://github.com/L337-org/docker-mcp
6
6
  Project-URL: Repository, https://github.com/L337-org/docker-mcp
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  # docker-mcp-server
32
32
 
33
- [![docker-mcp MCP server](https://glama.ai/mcp/servers/GavinLucas/docker-mcp/badges/score.svg)](https://glama.ai/mcp/servers/GavinLucas/docker-mcp)
33
+ [![docker-mcp MCP server](https://glama.ai/mcp/servers/L337-org/docker-mcp/badges/score.svg)](https://glama.ai/mcp/servers/L337-org/docker-mcp)
34
34
 
35
35
  <!-- mcp-name: io.github.L337-org/docker-mcp-server -->
36
36
 
@@ -411,8 +411,8 @@ Connecting this server to an AI agent grants it the same level of access as a lo
411
411
  | Docker Hub (container) | [gavinlucas/docker-mcp-server](https://hub.docker.com/r/gavinlucas/docker-mcp-server) |
412
412
  | Desktop Extension (.mcpb) | [GitHub Releases](https://github.com/L337-org/docker-mcp/releases) |
413
413
  | Official MCP Registry | [io.github.L337-org/docker-mcp-server](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.L337-org%2Fdocker-mcp-server/versions) |
414
- | Glama | [docker-mcp-server](https://glama.ai/mcp/servers/GavinLucas/docker-mcp) |
415
- | mcp.so | [docker-mcp-server](https://mcp.so/server/docker-mcp-server/GavinLucas) |
414
+ | Glama | [docker-mcp-server](https://glama.ai/mcp/servers/L337-org/docker-mcp) |
415
+ | mcp.so | [docker-mcp-server](https://mcp.so/servers/docker-1ae67d) |
416
416
  | awesome-mcp-servers | [punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers#cloud-platforms) |
417
417
 
418
418
  ## Privacy Policy
@@ -2,7 +2,7 @@
2
2
 
3
3
  # docker-mcp-server
4
4
 
5
- [![docker-mcp MCP server](https://glama.ai/mcp/servers/GavinLucas/docker-mcp/badges/score.svg)](https://glama.ai/mcp/servers/GavinLucas/docker-mcp)
5
+ [![docker-mcp MCP server](https://glama.ai/mcp/servers/L337-org/docker-mcp/badges/score.svg)](https://glama.ai/mcp/servers/L337-org/docker-mcp)
6
6
 
7
7
  <!-- mcp-name: io.github.L337-org/docker-mcp-server -->
8
8
 
@@ -383,8 +383,8 @@ Connecting this server to an AI agent grants it the same level of access as a lo
383
383
  | Docker Hub (container) | [gavinlucas/docker-mcp-server](https://hub.docker.com/r/gavinlucas/docker-mcp-server) |
384
384
  | Desktop Extension (.mcpb) | [GitHub Releases](https://github.com/L337-org/docker-mcp/releases) |
385
385
  | Official MCP Registry | [io.github.L337-org/docker-mcp-server](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.L337-org%2Fdocker-mcp-server/versions) |
386
- | Glama | [docker-mcp-server](https://glama.ai/mcp/servers/GavinLucas/docker-mcp) |
387
- | mcp.so | [docker-mcp-server](https://mcp.so/server/docker-mcp-server/GavinLucas) |
386
+ | Glama | [docker-mcp-server](https://glama.ai/mcp/servers/L337-org/docker-mcp) |
387
+ | mcp.so | [docker-mcp-server](https://mcp.so/servers/docker-1ae67d) |
388
388
  | awesome-mcp-servers | [punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers#cloud-platforms) |
389
389
 
390
390
  ## Privacy Policy
@@ -178,6 +178,10 @@ def buildx_bake(
178
178
  """
179
179
  Build multiple targets defined in a bake file (HCL, JSON, or compose).
180
180
 
181
+ Use it for multi-target builds declared in `docker-bake.hcl`/compose files; for a single
182
+ Dockerfile target use `buildx_build`.
183
+ Does not raise on a non-zero CLI exit — inspect `returncode`/`stderr` in the result.
184
+
181
185
  args:
182
186
  targets - Bake targets to build (default: the `default` group)
183
187
  files - Bake file paths (`-f`, repeatable)
@@ -273,7 +277,9 @@ def buildx_imagetools_create(
273
277
  Create a manifest list / OCI image index from existing per-platform tags.
274
278
 
275
279
  Replaces `docker manifest create` + `docker manifest push` — builds the index and pushes it in
276
- one operation. Source tags must already be pushed; this only stitches them together.
280
+ one operation. Source tags must already be pushed; this only stitches them together. Verify
281
+ the result with `buildx_imagetools_inspect`.
282
+ Does not raise on a non-zero CLI exit — inspect `returncode`/`stderr` in the result.
277
283
 
278
284
  args:
279
285
  target - Tag for the new manifest list (`-t`)
@@ -311,6 +317,10 @@ def buildx_list(host: str | None = None) -> list:
311
317
  """
312
318
  List builder instances.
313
319
 
320
+ Machine-parsed view of every builder; use `buildx_inspect` for one builder's human-readable
321
+ detail and `buildx_use` to switch the default.
322
+ Raises RuntimeError if the CLI call fails.
323
+
314
324
  returns: list - One dict per builder (parsed from `--format '{{json .}}'`).
315
325
  If the captured stdout was truncated by MAX_CLI_OUTPUT_BYTES the
316
326
  last (likely partial) record is dropped before parsing.
@@ -346,8 +356,10 @@ def buildx_history_inspect(ref: str = "", builder: str | None = None, host: str
346
356
  """
347
357
  Inspect a single build record by ref, parsed from `--format json`.
348
358
 
349
- Returns the full record for one build — duration, materials, attestations, error (if any) — for
350
- debugging a failed or slow build found via `buildx_history_list`. Requires buildx >= v0.13.
359
+ Returns the full record for one build — duration, materials, attestations, error (if any) —
360
+ for debugging a failed or slow build found via `buildx_history_list`. Requires buildx >=
361
+ v0.13.
362
+ Raises RuntimeError if the CLI call fails.
351
363
 
352
364
  args:
353
365
  ref - Build record ref. Pass the `ref` field from `buildx_history_list` directly — it
@@ -386,6 +398,10 @@ def buildx_inspect(name: str | None = None, bootstrap: bool = False, host: str |
386
398
  """
387
399
  Inspect a builder instance.
388
400
 
401
+ Human-readable detail (driver, status, supported platforms) for one builder; `buildx_list`
402
+ returns machine-parsed JSON for all builders.
403
+ Does not raise on a non-zero CLI exit — inspect `returncode`/`stderr` in the result.
404
+
389
405
  args:
390
406
  name - Builder name (defaults to the active builder)
391
407
  bootstrap - Boot the builder if it isn't already running
@@ -405,10 +421,12 @@ def buildx_du(builder: str | None = None, host: str | None = None) -> list:
405
421
  """
406
422
  Report BuildKit cache disk usage as a list of records.
407
423
 
408
- A large cache can easily generate more output than MAX_CLI_OUTPUT_BYTES; if that
409
- happens the captured stdout is truncated and this tool drops the final (partial)
410
- record before parsing. For an exhaustive accounting on a busy builder, run
411
- `docker buildx du --format '{{json .}}'` on the host directly.
424
+ A large cache can easily generate more output than MAX_CLI_OUTPUT_BYTES; if that happens the
425
+ captured stdout is truncated and this tool drops the final (partial) record before parsing.
426
+ For an exhaustive accounting on a busy builder, run `docker buildx du --format '{{json .}}'`
427
+ on the host directly. Reclaim the cache with `buildx_prune` (`system_df` covers daemon-side
428
+ disk, not builder cache).
429
+ Raises RuntimeError if the CLI call fails.
412
430
 
413
431
  args: builder - Override the active builder
414
432
  returns: list - One dict per cache record (parsed from `--format '{{json .}}'`)
@@ -477,7 +495,13 @@ def buildx_create(
477
495
  host: str | None = None,
478
496
  ) -> dict:
479
497
  """
480
- Create a new builder instance.
498
+ Create a new BuildKit builder instance.
499
+
500
+ Needed when the default `docker` driver falls short: multi-platform builds and cache export
501
+ require a `docker-container` (or `kubernetes`/`remote`) builder. Pass `use=True` to make it
502
+ the default for later `buildx_build` calls (else switch with `buildx_use`); `bootstrap=True`
503
+ starts the builder now rather than on first build.
504
+ Does not raise on a non-zero CLI exit — inspect `returncode`/`stderr` in the result.
481
505
 
482
506
  args:
483
507
  name - Name for the new builder (defaults to a generated name)
@@ -551,6 +575,10 @@ def buildx_remove(
551
575
  """
552
576
  Remove a builder instance.
553
577
 
578
+ Deletes a builder made by `buildx_create`, including its build cache unless keep_state=True;
579
+ use `buildx_prune` to reclaim cache while keeping the builder.
580
+ Does not raise on a non-zero CLI exit — inspect `returncode`/`stderr` in the result.
581
+
554
582
  args:
555
583
  name - Builder name to remove (mutually exclusive with `all_inactive`)
556
584
  all_inactive - Remove every inactive builder