docker-mcp-server 2.2.4__tar.gz → 2.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/copilot-instructions.md +1 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.mcpbignore +4 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/CLAUDE.md +4 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/MCP_VS_SKILLS.md +11 -3
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/PKG-INFO +5 -5
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/README.md +4 -4
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/server.py +5 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/images.py +105 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/plugins.py +26 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/swarm.py +61 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/manifest.json +1 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/pyproject.toml +1 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/server.json +4 -4
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/images.md +17 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/swarm.md +19 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/system.md +66 -0
- docker_mcp_server-2.2.5/tests/integration/test_images.py +61 -0
- docker_mcp_server-2.2.5/tests/integration/test_plugins.py +35 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_services.py +6 -1
- docker_mcp_server-2.2.5/tests/integration/test_swarm.py +69 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_images.py +125 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_plugins.py +20 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_swarm.py +27 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/uv.lock +1 -1
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.claude/commands/docker-sdk.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.claude/settings.json +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.dockerignore +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/CODEOWNERS +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/actions/file-failure-issue/action.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/dependabot.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/release.yml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/workflows/canary.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/workflows/codeql.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/workflows/images.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/workflows/premerge.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/workflows/publish-homebrew.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.github/workflows/publish.yaml +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.gitignore +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/.python-version +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/CODE_OF_CONDUCT.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/CONTRIBUTING.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/DOCKERHUB.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/Dockerfile +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/LICENSE +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/MIGRATION-2.0.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/PRIVACY.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/SECURITY.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/assets/README.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/assets/icon.png +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/__init__.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/__main__.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/_env.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/_hosts.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/__init__.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/_cli.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/_labels.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/_ssh_proxy.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/_utils.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/buildx.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/compose.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/configs.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/containers.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/context.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/networks.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/nodes.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/prompts.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/registry.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/resources.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/scout.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/secrets.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/services.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/stack.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/system.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/docker_mcp/tools/volumes.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/glama.json +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/mcpb_run.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/scripts/build-mcpb.sh +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/scripts/docker-mcp-server.rb.tpl +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/scripts/measure-comparison-figures.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/LICENSE +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/SKILL.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/buildx.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/compose.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/containers.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/docs.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/networks-volumes.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/observability.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/registry.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/reference/scout.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/workflows/build-publish.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/workflows/deploy.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/workflows/maintenance.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/workflows/security.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/skills/l337-docker/workflows/troubleshoot.md +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/__init__.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/conftest.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/__init__.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/conftest.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_buildx.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_cli.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_cli_flag_drift.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_compose.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_containers.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_context.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_file_payloads.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_networks.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_nodes.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_registry.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_remote_exec.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_scout.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_skill.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_smoke.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/integration/test_stack.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_buildx.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_cli.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_compose.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_configs.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_containers.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_context.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_docs.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_env.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_hosts.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_labels.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_main.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_naming.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_networks.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_nodes.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_prompts.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_pyproject_pins.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_registry.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_remote_exec.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_resources.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_scout.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_secrets.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_server.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_services.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_skill.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_ssh_proxy.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_stack.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_system.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_utils.py +0 -0
- {docker_mcp_server-2.2.4 → docker_mcp_server-2.2.5}/tests/test_volumes.py +0 -0
|
@@ -304,7 +304,7 @@ When the high-level SDK lacks a method (e.g. swarm node removal, service rollbac
|
|
|
304
304
|
|
|
305
305
|
Treat "the method exists" as separate from "the method works": the rendered docs show a docstring, not the URL a method builds. `plugin_push` is the standing example — docker-py's `Plugin.push()` / `APIClient.push_plugin()` POST to `/plugins/{name}/pull`, which the Engine does not define (push is `POST /plugins/{name}/push`), so they 404 everywhere; broken since 2017 and still in `main`, because upstream has no test for it. Where a documented method is provably broken, the ladder is: another public SDK path, then the correct endpoint via docker-py's private request helpers (`_url`/`_post`/`_raise_for_status`/`_stream_helper`) resolved through `getattr` and guarded to raise an actionable message rather than `AttributeError` (as `system_logout` does with `api._auth_configs`), then a CLI shell-out if the domain is already CLI-backed. Anything below the first rung leaves the supported surface and is **a human's call, not an agent's**: a PR that adds a *new* private-helper reach-in should be flagged as needing explicit maintainer sign-off, even when it is well built — say so rather than approving it on the strength of the guards. Do flag one that is unguarded, undocumented, or that hits an endpoint absent from the Engine API spec (no compatibility promise, no deprecation cycle — a categorically worse bet than `plugin_push`'s, which uses a spec'd endpoint the CLI itself calls and reaches it through unofficial plumbing). Do **not** re-litigate the reach-ins already blessed above (`plugin_push`, `system_logout`, `stage_build_context`): being present on `main` *is* the sign-off, so a PR that merely touches or refactors one needs no fresh approval — only a genuinely new reach-in does. If a diff both adds and blesses one, that has already been decided; say nothing. And do not treat an audit or routine that *declined* to implement something and documented why as an unfinished job — that write-up is the intended escalation path, and is how `plugin_push` reached a human decision in the first place.
|
|
306
306
|
|
|
307
|
-
**SDK audit exclusions.** A recurring audit routine looks for uncovered SDK surface and for low-level `client.api.*` calls a high-level method could replace. These were decided on the merits and must not be re-proposed (in review or by the routine): `Plugin.push()`/`APIClient.push_plugin()` — broken upstream, so `plugin_push`'s hand-built endpoint call is the working path, not debt to tidy away; `Container.attach`/`attach_socket`/`resize` — deliberately unwrapped, as an interactive bidirectional stream doesn't fit a request/response tool call (`container_exec` covers scripted exec); `service_rollback`'s `inspect_service`/`update_service` and `system_logout`'s `_auth_configs` — permanently low-level, as the high-level SDK has no `rollback` and no `logout` at all. Anything deliberately not wrapped, or wrapped unobviously, belongs on that list in `CLAUDE.md`.
|
|
307
|
+
**SDK audit exclusions.** A recurring audit routine looks for uncovered SDK surface and for low-level `client.api.*` calls a high-level method could replace. These were decided on the merits and must not be re-proposed (in review or by the routine): `Plugin.push()`/`APIClient.push_plugin()` — broken upstream, so `plugin_push`'s hand-built endpoint call is the working path, not debt to tidy away; `Container.attach`/`attach_socket`/`resize` — deliberately unwrapped, as an interactive bidirectional stream doesn't fit a request/response tool call (`container_exec` covers scripted exec); `service_rollback`'s `inspect_service`/`update_service` and `system_logout`'s `_auth_configs` — permanently low-level, as the high-level SDK has no `rollback` and no `logout` at all; `swarm_task_list`/`swarm_task_inspect`'s `api.tasks()`/`api.inspect_task()` — permanently low-level, as docker-py has no task collection at all. Anything deliberately not wrapped, or wrapped unobviously, belongs on that list in `CLAUDE.md`.
|
|
308
308
|
|
|
309
309
|
Docker SDK docs: https://docker-py.readthedocs.io/en/stable/index.html
|
|
310
310
|
Docker SDK low-level API: https://docker-py.readthedocs.io/en/stable/api.html
|
|
@@ -13,6 +13,10 @@ scripts/
|
|
|
13
13
|
# server, not part of it — packing it here would ship ~140 KB of markdown the bundle never reads.
|
|
14
14
|
skills/
|
|
15
15
|
.github/
|
|
16
|
+
# Claude Code project config: the mirror-rule PostToolUse hook and the /docker-sdk command. Both are
|
|
17
|
+
# development tooling for this repo, meaningless to an installed bundle, and 2.2.4 shipped them by
|
|
18
|
+
# omission. `.dockerignore` already excludes this directory.
|
|
19
|
+
.claude/
|
|
16
20
|
.idea/
|
|
17
21
|
.vscode/
|
|
18
22
|
.pytest_cache/
|
|
@@ -516,6 +516,10 @@ say why. **Anything deliberately not wrapped, or wrapped in an unobvious way, be
|
|
|
516
516
|
permanently. The high-level `Service`/`ServiceCollection` expose no `rollback`.
|
|
517
517
|
- **`system_logout`'s `api._auth_configs`** — stays low-level permanently. There is no `logout`
|
|
518
518
|
anywhere in the SDK and no server-side session to end, so there is nothing to migrate to.
|
|
519
|
+
- **`swarm_task_list` / `swarm_task_inspect`'s `api.tasks()` / `api.inspect_task()`** — stay
|
|
520
|
+
low-level permanently. docker-py has no task collection at all (there is no `client.tasks`, and
|
|
521
|
+
`docker/models/` has no `tasks.py`), so these documented `APIClient` methods are the only public
|
|
522
|
+
path. Nothing to migrate to; do not propose one.
|
|
519
523
|
|
|
520
524
|
The audit must also **check the latest published docker-py, not the pinned one**: `uv.lock` is
|
|
521
525
|
routinely behind what `pyproject.toml`'s floor lets a fresh `uvx`/`pip install` resolve, so auditing
|
|
@@ -419,7 +419,7 @@ Legend: **✓** direct CLI equivalent; **≈** covered by a documented recipe (l
|
|
|
419
419
|
| container_wait (exit) ✓ | `docker wait` |
|
|
420
420
|
| container_wait (healthy) ≈ | `wait_healthy` loop, `reference/observability.md` |
|
|
421
421
|
|
|
422
|
-
### images (
|
|
422
|
+
### images (15) - `reference/images.md`
|
|
423
423
|
|
|
424
424
|
| Tool | CLI |
|
|
425
425
|
|---|---|
|
|
@@ -431,6 +431,7 @@ Legend: **✓** direct CLI equivalent; **≈** covered by a documented recipe (l
|
|
|
431
431
|
| image_history ✓ | `docker history` |
|
|
432
432
|
| image_inspect ✓ | `docker image inspect` |
|
|
433
433
|
| image_save / load ✓ | `docker save -o` / `load -i` |
|
|
434
|
+
| image_import ✓ | `docker import` |
|
|
434
435
|
| image_prune ✓ | `docker image prune` |
|
|
435
436
|
| image_prune_builds ✓ | `docker builder prune` (`--reserved-space`, ex-`--keep-storage`) |
|
|
436
437
|
| image_search ✓ | `docker search` (Hub only) |
|
|
@@ -447,7 +448,7 @@ All ✓ - each maps to the identically-named `docker compose <sub>`: `up`, `down
|
|
|
447
448
|
`logs`, `build`, `pull`, `config`, `cp`, `exec`, `run`, `start`, `stop`, `restart`, `kill`,
|
|
448
449
|
`pause`, `unpause`, `port`, `top`, `images`, `wait`. (`compose_list` → `docker compose ls`.)
|
|
449
450
|
|
|
450
|
-
### swarm (
|
|
451
|
+
### swarm (10) / services (10) / nodes (5) / secrets (4) / configs (4) / stack (5) - `reference/swarm.md`
|
|
451
452
|
|
|
452
453
|
| Tool | CLI |
|
|
453
454
|
|---|---|
|
|
@@ -455,6 +456,8 @@ All ✓ - each maps to the identically-named `docker compose <sub>`: `up`, `down
|
|
|
455
456
|
| swarm_join_tokens ✓ | `docker swarm join-token <worker\|manager>` |
|
|
456
457
|
| swarm_unlock / unlock_key ✓ | `docker swarm unlock` / `unlock-key` |
|
|
457
458
|
| swarm_inspect ✓ | `docker info --format '{{json .Swarm}}'` |
|
|
459
|
+
| swarm_task_list ≈ | `docker service ps $(docker service ls -q)`, `reference/swarm.md` - no CLI command lists the cluster's tasks in one shot |
|
|
460
|
+
| swarm_task_inspect ✓ | `docker inspect --type task` |
|
|
458
461
|
| service_create / update / remove / logs / ps / inspect / list ✓ | `docker service <sub>` |
|
|
459
462
|
| service_scale ✓ | `docker service scale` |
|
|
460
463
|
| service_rollback ✓ | `docker service rollback` |
|
|
@@ -477,10 +480,15 @@ All ✓: `docker scout cves/quickview/compare/recommendations/sbom`.
|
|
|
477
480
|
|
|
478
481
|
All ✓: `docker context create/ls/inspect/rm/use`.
|
|
479
482
|
|
|
480
|
-
### plugins (
|
|
483
|
+
### plugins (11) - `reference/system.md`
|
|
481
484
|
|
|
482
485
|
All ✓: `docker plugin install/ls/inspect/enable/disable/set/upgrade/rm/push/create`.
|
|
483
486
|
`plugin_configure` → `docker plugin set`.
|
|
487
|
+
`plugin_privileges` ≈ no `docker plugin` subcommand prints them, and `docker plugin inspect` needs
|
|
488
|
+
the plugin installed first; the skill reads the plugin's registry config blob over `curl` instead
|
|
489
|
+
(`reference/system.md`), reproducing the install prompt's privilege list without installing
|
|
490
|
+
anything. The recipe mirrors the daemon's own `computePrivileges`, so it reports whichever of the
|
|
491
|
+
seven privilege kinds a given plugin declares rather than a fixed subset.
|
|
484
492
|
|
|
485
493
|
### registry (7) - `reference/registry.md`
|
|
486
494
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: docker-mcp-server
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
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
|
|
@@ -265,10 +265,10 @@ Everything above targets one daemon. To manage **several in a single session** -
|
|
|
265
265
|
Once loaded, the agent gets MCP tools grouped by Docker domain. A few examples:
|
|
266
266
|
|
|
267
267
|
- **Containers** - `container_run`, `container_list` (`managed_only=True` to list only what this server created - see [Provenance labels](#provenance-labels)), `container_exec`, `container_logs`, `container_stop`, `container_commit`, `container_wait` (block until exit, `until="healthy"` to poll a healthcheck, or `until="log-match"` to poll for a log line containing `pattern`), `container_export` / `container_archive_get_to_file` / `container_archive_put` (stream tar archives to/from a host path)
|
|
268
|
-
- **Images** - `image_build`, `image_pull`, `image_push`, `image_tag`, `image_prune`, `image_prune_builds` (clear the daemon's build cache - a separate resource from dangling images), `image_save` / `image_load` (stream image tarballs to/from a host path via `dest_path` / `from_file`)
|
|
269
|
-
- **Plugins** - `plugin_install` (pull from a registry), `plugin_create` (build one from a local `config.json` + `rootfs`), `plugin_push` (publish it back), `plugin_enable` / `plugin_disable`, `plugin_configure`, `plugin_upgrade`, `plugin_list` / `plugin_inspect`, `plugin_remove` *(managed engine plugins - volume/network/logging drivers - not the CLI plugins that extend the `docker` command itself)*
|
|
268
|
+
- **Images** - `image_build`, `image_pull`, `image_push`, `image_tag`, `image_prune`, `image_prune_builds` (clear the daemon's build cache - a separate resource from dangling images), `image_save` / `image_load` (stream image tarballs to/from a host path via `dest_path` / `from_file`), `image_import` (build a single-layer image from a flat rootfs tarball, URL, or existing image - `docker import`, not to be confused with `image_load`)
|
|
269
|
+
- **Plugins** - `plugin_install` (pull from a registry), `plugin_privileges` (read what host access a plugin demands *before* installing it - the daemon grants them non-interactively), `plugin_create` (build one from a local `config.json` + `rootfs`), `plugin_push` (publish it back), `plugin_enable` / `plugin_disable`, `plugin_configure`, `plugin_upgrade`, `plugin_list` / `plugin_inspect`, `plugin_remove` *(managed engine plugins - volume/network/logging drivers - not the CLI plugins that extend the `docker` command itself)*
|
|
270
270
|
- **Networks / Volumes** - `network_create`, `network_connect`, `volume_create`, `volume_prune`
|
|
271
|
-
- **Swarm** - `swarm_init`, `swarm_join_tokens` (close the init → join loop), `swarm_update` (rotate join tokens / unlock key), `service_create`, `service_scale`, `service_rollback` (re-apply the previous service spec), `service_wait` (block until tasks converge, or a rolling update completes), `node_list`, `node_wait` (block until a node reaches a target state - e.g. `ready` after joining), `node_remove`, `secret_create`, `config_create`
|
|
271
|
+
- **Swarm** - `swarm_init`, `swarm_join_tokens` (close the init → join loop), `swarm_update` (rotate join tokens / unlock key), `service_create`, `service_scale`, `service_rollback` (re-apply the previous service spec), `service_wait` (block until tasks converge, or a rolling update completes), `swarm_task_list` / `swarm_task_inspect` (every task in the cluster, filterable by node or desired state - no single CLI command does this), `node_list`, `node_wait` (block until a node reaches a target state - e.g. `ready` after joining), `node_remove`, `secret_create`, `config_create`
|
|
272
272
|
- **System** - `system_ping`, `system_info`, `system_version`, `system_df`, `system_events`, `host_list` (the configured daemons and which is the default - see [Managing several daemons](#managing-several-daemons)), `system_login` / `system_logout` (cache or clear registry credentials), `system_reconnect` (rebuild a host's SDK client to recover a wedged connection)
|
|
273
273
|
- **Compose** - `compose_up`, `compose_down`, `compose_stop`, `compose_start`, `compose_restart`, `compose_pause` / `compose_unpause`, `compose_kill`, `compose_ps`, `compose_list`, `compose_images`, `compose_top`, `compose_port`, `compose_logs`, `compose_config`, `compose_build`, `compose_pull`, `compose_run`, `compose_exec`, `compose_cp`, `compose_wait` *(wraps the `docker compose` CLI plugin)*
|
|
274
274
|
- **Stacks** - `stack_deploy`, `stack_list`, `stack_ps`, `stack_services`, `stack_remove` *(deploy a Compose file to a swarm as a stack; wraps the `docker stack` CLI - requires a swarm manager)*
|
|
@@ -460,7 +460,7 @@ Connecting this server to an AI agent grants it the same level of access as a lo
|
|
|
460
460
|
- **Swarm secret material transits tool calls too.** Beyond registry credentials, several swarm tools carry secret material through arguments or return values that MCP clients may log: `secret_create(data=...)` and `config_create(data=...)` take the payload as an argument, `secret_inspect` / `config_inspect` return the stored object, `swarm_join(join_token=...)` and `swarm_unlock(key=...)` take cluster join/unlock secrets, and `swarm_unlock_key` and `swarm_join_tokens` *return* cluster credentials (rotation via `swarm_update` invalidates old tokens) - a manager join token lets its holder join the swarm as a manager (root-equivalent on the cluster). Treat all of these as exposed in any client that records tool traffic, and prefer provisioning swarm secrets and reading join tokens out-of-band on the host rather than through the agent. If an agent never needs to admit nodes, drop the whole surface with `DOCKER_MCP_SERVER_DISABLE=swarm` (see [Configuration](#configuration)).
|
|
461
461
|
- **`container_exec`, `compose_exec`, and `compose_run` run arbitrary commands.** When any part of the command is derived from agent-controlled input, use an exec-form argv list that does not invoke a shell (e.g. `["python", "-V"]`). A list like `["sh", "-c", template]` that invokes a shell will interpret shell metacharacters in the untrusted substrings.
|
|
462
462
|
- **Container archive paths.** `container_archive_get` and `container_archive_put` forward the supplied path verbatim to the daemon. The container is the trust boundary - if you do not trust its filesystem, do not assume `..` traversal will be rejected.
|
|
463
|
-
- **File-path payload tools read and write the server host's filesystem.** `image_save`, `container_export` (with `dest_path`), and `container_archive_get_to_file` write to a `dest_path` on the host running this MCP server (refusing to overwrite an existing file unless `overwrite=True`); `image_load` and `container_archive_put` (with `from_file`) read a host path; `compose_cp` copies between a service container and a host path in either direction. These run as the server's user, so the agent can write any file that user can write and read any file it can read. Prefer the in-band byte tools (capped at 32 MiB) when you don't trust the agent with host filesystem access. `DOCKER_MCP_SERVER_READONLY` also drops the host-writing tools - but note it is not targeted at them: it registers *only* read-only tools, so `image_load` and `container_archive_put` (and every other mutating/destructive tool) go too. There is no switch that drops just the file-writers.
|
|
463
|
+
- **File-path payload tools read and write the server host's filesystem.** `image_save`, `container_export` (with `dest_path`), and `container_archive_get_to_file` write to a `dest_path` on the host running this MCP server (refusing to overwrite an existing file unless `overwrite=True`); `image_load`, `image_import`, and `container_archive_put` (with `from_file`) read a host path; `compose_cp` copies between a service container and a host path in either direction. `image_import(from_url=...)` is the one that leaves the host entirely: the *daemon* fetches the URL the caller named, in the daemon's own network namespace, so it can reach anything that daemon can reach (the same caller-chooses-the-destination footing as `image_pull`'s registry argument). These run as the server's user, so the agent can write any file that user can write and read any file it can read. Prefer the in-band byte tools (capped at 32 MiB) when you don't trust the agent with host filesystem access. `DOCKER_MCP_SERVER_READONLY` also drops the host-writing tools - but note it is not targeted at them: it registers *only* read-only tools, so `image_load` and `container_archive_put` (and every other mutating/destructive tool) go too. There is no switch that drops just the file-writers.
|
|
464
464
|
- **Destructive operations have no built-in confirmation.** `prune_*`, `remove_*`, `container_kill`, `swarm_leave`, `compose_down(volumes=True)`, `compose_kill`, `stack_remove` (tears down every service in a stack), `buildx_prune` (always runs with `--force`), and `buildx_remove` execute immediately. These tools carry the `destructiveHint` annotation, so a client like Claude Code can gate them, and the shipped `clean_environment` prompt asks the agent to confirm before pruning volumes - but tool calls themselves are not gated by the server. For a hard guarantee, run with `DOCKER_MCP_SERVER_NO_DESTRUCTIVE=1` (drops them entirely) or `DOCKER_MCP_SERVER_READONLY=1` (see [Configuration](#configuration)); for an approval step, configure it at the MCP client.
|
|
465
465
|
- **CLI shell-out attack surface.** Compose, Stack, Buildx, Scout, and Context tools spawn `docker` subprocesses on the host running this MCP server. Every invocation passes arguments as a list (no shell, no metacharacter interpretation), resolves the binary via `shutil.which`, and runs against a scrubbed environment (DOCKER_HOST and related vars only). Positional values (image refs, service / context / builder names, build contexts, bake targets) are additionally rejected if they start with `-`, so an argument can't be smuggled in as a CLI flag (e.g. a service named `--output=...`); the one deliberate exception is the trailing command in `compose_exec` / `compose_run`, which is meant to be an arbitrary argv. Filesystem paths supplied to `compose_*` (project_dir, files) are read by the docker CLI on the server host - passing an unfamiliar path can expose any compose file the server's user can read. **With no local docker CLI and an `ssh://` target** those subprocesses run on the remote host instead ([above](#talking-to-a-remote-daemon)), which shifts two things: the command executes as the remote SSH user with *its* registry credentials, and the files a command reads are copied to that host's temp directory first - so a `buildx_build` `--secret src=` file, or anything else in a staged directory, exists briefly on the remote disk (mode `0700`, removed when the call returns; only a dropped connection can leave it behind). Point staging-backed tools at directories you would be content to copy.
|
|
466
466
|
- **The daemon set is fixed at startup; pick it deliberately.** When `DOCKER_HOST` / `DOCKER_MCP_SERVER_HOSTS` are unset, the server's *initial* SDK connection follows your active Docker context (`DOCKER_CONTEXT` / `currentContext`) - the same daemon your `docker` CLI targets - so if that context points at a remote or production daemon, the agent connects there too. Set `DOCKER_MCP_SERVER_HOSTS` (or `DOCKER_HOST`, or select a scoped context) before starting the server to pin the target(s) deliberately; with `DOCKER_MCP_SERVER_HOSTS` the `auto`/`local` endpoints are resolved and **pinned at startup**, so they can't drift if a context changes later. After startup, `context_use` only changes the CLI default for subsequent CLI-backed tools; SDK-backed tools keep using the daemon their pooled client connected to. **There is no runtime way to introduce or retarget a daemon at an arbitrary endpoint** - `system_reconnect` only *rebuilds* an already-configured host's client (to recover a wedged connection), it can't point it elsewhere; to add or change a daemon, edit `DOCKER_MCP_SERVER_HOSTS` and restart. This deliberately closes a trust-expansion vector (an agent can't move the root-equivalent boundary to an unvetted endpoint mid-session). `context_create(skip_tls_verify=True)` disables TLS verification for a context; use only against trusted local daemons.
|
|
@@ -238,10 +238,10 @@ Everything above targets one daemon. To manage **several in a single session** -
|
|
|
238
238
|
Once loaded, the agent gets MCP tools grouped by Docker domain. A few examples:
|
|
239
239
|
|
|
240
240
|
- **Containers** - `container_run`, `container_list` (`managed_only=True` to list only what this server created - see [Provenance labels](#provenance-labels)), `container_exec`, `container_logs`, `container_stop`, `container_commit`, `container_wait` (block until exit, `until="healthy"` to poll a healthcheck, or `until="log-match"` to poll for a log line containing `pattern`), `container_export` / `container_archive_get_to_file` / `container_archive_put` (stream tar archives to/from a host path)
|
|
241
|
-
- **Images** - `image_build`, `image_pull`, `image_push`, `image_tag`, `image_prune`, `image_prune_builds` (clear the daemon's build cache - a separate resource from dangling images), `image_save` / `image_load` (stream image tarballs to/from a host path via `dest_path` / `from_file`)
|
|
242
|
-
- **Plugins** - `plugin_install` (pull from a registry), `plugin_create` (build one from a local `config.json` + `rootfs`), `plugin_push` (publish it back), `plugin_enable` / `plugin_disable`, `plugin_configure`, `plugin_upgrade`, `plugin_list` / `plugin_inspect`, `plugin_remove` *(managed engine plugins - volume/network/logging drivers - not the CLI plugins that extend the `docker` command itself)*
|
|
241
|
+
- **Images** - `image_build`, `image_pull`, `image_push`, `image_tag`, `image_prune`, `image_prune_builds` (clear the daemon's build cache - a separate resource from dangling images), `image_save` / `image_load` (stream image tarballs to/from a host path via `dest_path` / `from_file`), `image_import` (build a single-layer image from a flat rootfs tarball, URL, or existing image - `docker import`, not to be confused with `image_load`)
|
|
242
|
+
- **Plugins** - `plugin_install` (pull from a registry), `plugin_privileges` (read what host access a plugin demands *before* installing it - the daemon grants them non-interactively), `plugin_create` (build one from a local `config.json` + `rootfs`), `plugin_push` (publish it back), `plugin_enable` / `plugin_disable`, `plugin_configure`, `plugin_upgrade`, `plugin_list` / `plugin_inspect`, `plugin_remove` *(managed engine plugins - volume/network/logging drivers - not the CLI plugins that extend the `docker` command itself)*
|
|
243
243
|
- **Networks / Volumes** - `network_create`, `network_connect`, `volume_create`, `volume_prune`
|
|
244
|
-
- **Swarm** - `swarm_init`, `swarm_join_tokens` (close the init → join loop), `swarm_update` (rotate join tokens / unlock key), `service_create`, `service_scale`, `service_rollback` (re-apply the previous service spec), `service_wait` (block until tasks converge, or a rolling update completes), `node_list`, `node_wait` (block until a node reaches a target state - e.g. `ready` after joining), `node_remove`, `secret_create`, `config_create`
|
|
244
|
+
- **Swarm** - `swarm_init`, `swarm_join_tokens` (close the init → join loop), `swarm_update` (rotate join tokens / unlock key), `service_create`, `service_scale`, `service_rollback` (re-apply the previous service spec), `service_wait` (block until tasks converge, or a rolling update completes), `swarm_task_list` / `swarm_task_inspect` (every task in the cluster, filterable by node or desired state - no single CLI command does this), `node_list`, `node_wait` (block until a node reaches a target state - e.g. `ready` after joining), `node_remove`, `secret_create`, `config_create`
|
|
245
245
|
- **System** - `system_ping`, `system_info`, `system_version`, `system_df`, `system_events`, `host_list` (the configured daemons and which is the default - see [Managing several daemons](#managing-several-daemons)), `system_login` / `system_logout` (cache or clear registry credentials), `system_reconnect` (rebuild a host's SDK client to recover a wedged connection)
|
|
246
246
|
- **Compose** - `compose_up`, `compose_down`, `compose_stop`, `compose_start`, `compose_restart`, `compose_pause` / `compose_unpause`, `compose_kill`, `compose_ps`, `compose_list`, `compose_images`, `compose_top`, `compose_port`, `compose_logs`, `compose_config`, `compose_build`, `compose_pull`, `compose_run`, `compose_exec`, `compose_cp`, `compose_wait` *(wraps the `docker compose` CLI plugin)*
|
|
247
247
|
- **Stacks** - `stack_deploy`, `stack_list`, `stack_ps`, `stack_services`, `stack_remove` *(deploy a Compose file to a swarm as a stack; wraps the `docker stack` CLI - requires a swarm manager)*
|
|
@@ -433,7 +433,7 @@ Connecting this server to an AI agent grants it the same level of access as a lo
|
|
|
433
433
|
- **Swarm secret material transits tool calls too.** Beyond registry credentials, several swarm tools carry secret material through arguments or return values that MCP clients may log: `secret_create(data=...)` and `config_create(data=...)` take the payload as an argument, `secret_inspect` / `config_inspect` return the stored object, `swarm_join(join_token=...)` and `swarm_unlock(key=...)` take cluster join/unlock secrets, and `swarm_unlock_key` and `swarm_join_tokens` *return* cluster credentials (rotation via `swarm_update` invalidates old tokens) - a manager join token lets its holder join the swarm as a manager (root-equivalent on the cluster). Treat all of these as exposed in any client that records tool traffic, and prefer provisioning swarm secrets and reading join tokens out-of-band on the host rather than through the agent. If an agent never needs to admit nodes, drop the whole surface with `DOCKER_MCP_SERVER_DISABLE=swarm` (see [Configuration](#configuration)).
|
|
434
434
|
- **`container_exec`, `compose_exec`, and `compose_run` run arbitrary commands.** When any part of the command is derived from agent-controlled input, use an exec-form argv list that does not invoke a shell (e.g. `["python", "-V"]`). A list like `["sh", "-c", template]` that invokes a shell will interpret shell metacharacters in the untrusted substrings.
|
|
435
435
|
- **Container archive paths.** `container_archive_get` and `container_archive_put` forward the supplied path verbatim to the daemon. The container is the trust boundary - if you do not trust its filesystem, do not assume `..` traversal will be rejected.
|
|
436
|
-
- **File-path payload tools read and write the server host's filesystem.** `image_save`, `container_export` (with `dest_path`), and `container_archive_get_to_file` write to a `dest_path` on the host running this MCP server (refusing to overwrite an existing file unless `overwrite=True`); `image_load` and `container_archive_put` (with `from_file`) read a host path; `compose_cp` copies between a service container and a host path in either direction. These run as the server's user, so the agent can write any file that user can write and read any file it can read. Prefer the in-band byte tools (capped at 32 MiB) when you don't trust the agent with host filesystem access. `DOCKER_MCP_SERVER_READONLY` also drops the host-writing tools - but note it is not targeted at them: it registers *only* read-only tools, so `image_load` and `container_archive_put` (and every other mutating/destructive tool) go too. There is no switch that drops just the file-writers.
|
|
436
|
+
- **File-path payload tools read and write the server host's filesystem.** `image_save`, `container_export` (with `dest_path`), and `container_archive_get_to_file` write to a `dest_path` on the host running this MCP server (refusing to overwrite an existing file unless `overwrite=True`); `image_load`, `image_import`, and `container_archive_put` (with `from_file`) read a host path; `compose_cp` copies between a service container and a host path in either direction. `image_import(from_url=...)` is the one that leaves the host entirely: the *daemon* fetches the URL the caller named, in the daemon's own network namespace, so it can reach anything that daemon can reach (the same caller-chooses-the-destination footing as `image_pull`'s registry argument). These run as the server's user, so the agent can write any file that user can write and read any file it can read. Prefer the in-band byte tools (capped at 32 MiB) when you don't trust the agent with host filesystem access. `DOCKER_MCP_SERVER_READONLY` also drops the host-writing tools - but note it is not targeted at them: it registers *only* read-only tools, so `image_load` and `container_archive_put` (and every other mutating/destructive tool) go too. There is no switch that drops just the file-writers.
|
|
437
437
|
- **Destructive operations have no built-in confirmation.** `prune_*`, `remove_*`, `container_kill`, `swarm_leave`, `compose_down(volumes=True)`, `compose_kill`, `stack_remove` (tears down every service in a stack), `buildx_prune` (always runs with `--force`), and `buildx_remove` execute immediately. These tools carry the `destructiveHint` annotation, so a client like Claude Code can gate them, and the shipped `clean_environment` prompt asks the agent to confirm before pruning volumes - but tool calls themselves are not gated by the server. For a hard guarantee, run with `DOCKER_MCP_SERVER_NO_DESTRUCTIVE=1` (drops them entirely) or `DOCKER_MCP_SERVER_READONLY=1` (see [Configuration](#configuration)); for an approval step, configure it at the MCP client.
|
|
438
438
|
- **CLI shell-out attack surface.** Compose, Stack, Buildx, Scout, and Context tools spawn `docker` subprocesses on the host running this MCP server. Every invocation passes arguments as a list (no shell, no metacharacter interpretation), resolves the binary via `shutil.which`, and runs against a scrubbed environment (DOCKER_HOST and related vars only). Positional values (image refs, service / context / builder names, build contexts, bake targets) are additionally rejected if they start with `-`, so an argument can't be smuggled in as a CLI flag (e.g. a service named `--output=...`); the one deliberate exception is the trailing command in `compose_exec` / `compose_run`, which is meant to be an arbitrary argv. Filesystem paths supplied to `compose_*` (project_dir, files) are read by the docker CLI on the server host - passing an unfamiliar path can expose any compose file the server's user can read. **With no local docker CLI and an `ssh://` target** those subprocesses run on the remote host instead ([above](#talking-to-a-remote-daemon)), which shifts two things: the command executes as the remote SSH user with *its* registry credentials, and the files a command reads are copied to that host's temp directory first - so a `buildx_build` `--secret src=` file, or anything else in a staged directory, exists briefly on the remote disk (mode `0700`, removed when the call returns; only a dropped connection can leave it behind). Point staging-backed tools at directories you would be content to copy.
|
|
439
439
|
- **The daemon set is fixed at startup; pick it deliberately.** When `DOCKER_HOST` / `DOCKER_MCP_SERVER_HOSTS` are unset, the server's *initial* SDK connection follows your active Docker context (`DOCKER_CONTEXT` / `currentContext`) - the same daemon your `docker` CLI targets - so if that context points at a remote or production daemon, the agent connects there too. Set `DOCKER_MCP_SERVER_HOSTS` (or `DOCKER_HOST`, or select a scoped context) before starting the server to pin the target(s) deliberately; with `DOCKER_MCP_SERVER_HOSTS` the `auto`/`local` endpoints are resolved and **pinned at startup**, so they can't drift if a context changes later. After startup, `context_use` only changes the CLI default for subsequent CLI-backed tools; SDK-backed tools keep using the daemon their pooled client connected to. **There is no runtime way to introduce or retarget a daemon at an arbitrary endpoint** - `system_reconnect` only *rebuilds* an already-configured host's client (to recover a wedged connection), it can't point it elsewhere; to add or change a daemon, edit `DOCKER_MCP_SERVER_HOSTS` and restart. This deliberately closes a trust-expansion vector (an agent can't move the root-equivalent boundary to an unvetted endpoint mid-session). `context_create(skip_tls_verify=True)` disables TLS verification for a context; use only against trusted local daemons.
|
|
@@ -83,6 +83,7 @@ TOOL_CATEGORIES: dict[str, ToolCategory] = {
|
|
|
83
83
|
"image_prune": ToolCategory.DESTRUCTIVE,
|
|
84
84
|
"image_prune_builds": ToolCategory.DESTRUCTIVE,
|
|
85
85
|
"image_load": ToolCategory.MUTATING,
|
|
86
|
+
"image_import": ToolCategory.MUTATING,
|
|
86
87
|
"image_save": ToolCategory.MUTATING, # can write a file on the server host (dest_path)
|
|
87
88
|
"image_tag": ToolCategory.MUTATING,
|
|
88
89
|
"image_history": ToolCategory.READ_ONLY,
|
|
@@ -136,10 +137,13 @@ TOOL_CATEGORIES: dict[str, ToolCategory] = {
|
|
|
136
137
|
"swarm_unlock": ToolCategory.MUTATING,
|
|
137
138
|
"swarm_unlock_key": ToolCategory.READ_ONLY,
|
|
138
139
|
"swarm_join_tokens": ToolCategory.READ_ONLY,
|
|
140
|
+
"swarm_task_list": ToolCategory.READ_ONLY,
|
|
141
|
+
"swarm_task_inspect": ToolCategory.READ_ONLY,
|
|
139
142
|
# plugins
|
|
140
143
|
"plugin_create": ToolCategory.MUTATING,
|
|
141
144
|
"plugin_inspect": ToolCategory.READ_ONLY,
|
|
142
145
|
"plugin_install": ToolCategory.MUTATING,
|
|
146
|
+
"plugin_privileges": ToolCategory.READ_ONLY,
|
|
143
147
|
"plugin_push": ToolCategory.MUTATING,
|
|
144
148
|
"plugin_list": ToolCategory.READ_ONLY,
|
|
145
149
|
"plugin_configure": ToolCategory.MUTATING,
|
|
@@ -486,7 +490,7 @@ _DOMAIN_BLURBS: dict[str, str] = {
|
|
|
486
490
|
"volumes": "create/list/inspect/remove",
|
|
487
491
|
"compose": "Docker Compose v2 (up/down/ps/logs/build/run/exec/...); CLI-backed",
|
|
488
492
|
"stack": "Compose-on-Swarm (deploy/ls/ps/rm/services); CLI-backed",
|
|
489
|
-
"swarm": "swarm init/join/leave/unlock, join-tokens; manager node only",
|
|
493
|
+
"swarm": "swarm init/join/leave/unlock, join-tokens, cluster-wide task list/inspect; manager node only",
|
|
490
494
|
"services": "Swarm services (create/scale/update/rollback/logs/tasks); manager node only",
|
|
491
495
|
"nodes": "Swarm nodes (list/inspect/update/remove); manager node only",
|
|
492
496
|
"secrets": "Swarm secrets; manager node only",
|
|
@@ -315,7 +315,8 @@ def image_load(data: bytes | None = None, from_file: str | None = None, host: st
|
|
|
315
315
|
Load an image from a tarball produced by `image_save`, from in-band bytes or a file on the server host.
|
|
316
316
|
|
|
317
317
|
Counterpart of `image_save`; when the image lives in a registry, `image_pull` is the normal
|
|
318
|
-
route
|
|
318
|
+
route, and for a flat rootfs archive that is not a `docker save` bundle use `image_import`.
|
|
319
|
+
Pass exactly one of `data` (tarball bytes in band) or `from_file` (a path on the server host,
|
|
319
320
|
streamed straight to the daemon — preferred for anything but small images, since in-band bytes are
|
|
320
321
|
base64-encoded by MCP). `from_file` is read by the server's user; `~` is expanded.
|
|
321
322
|
|
|
@@ -333,6 +334,109 @@ def image_load(data: bytes | None = None, from_file: str | None = None, host: st
|
|
|
333
334
|
return [i.attrs for i in _get_client(host).images.load(handle)]
|
|
334
335
|
|
|
335
336
|
|
|
337
|
+
@tool()
|
|
338
|
+
def image_import(
|
|
339
|
+
repository: str | None = None,
|
|
340
|
+
tag: str | None = None,
|
|
341
|
+
from_file: str | None = None,
|
|
342
|
+
data: bytes | None = None,
|
|
343
|
+
from_url: str | None = None,
|
|
344
|
+
from_image: str | None = None,
|
|
345
|
+
changes: list | None = None,
|
|
346
|
+
host: str | None = None,
|
|
347
|
+
) -> str:
|
|
348
|
+
"""
|
|
349
|
+
Create an image from a flat root-filesystem tarball, like `docker import`.
|
|
350
|
+
|
|
351
|
+
Imports a *filesystem* archive as a new single-layer image with no build history — not the same
|
|
352
|
+
thing as `image_load`, which restores a `docker save` archive complete with its layers, tags and
|
|
353
|
+
history, so prefer `image_load` for anything `image_save` produced. Use this for a rootfs that
|
|
354
|
+
came from somewhere else: a `container_export` archive, a distro base tarball, a VM image dump.
|
|
355
|
+
The result has an empty config — no `CMD`/`ENTRYPOINT`/`ENV` — unless you supply `changes`, so an
|
|
356
|
+
imported image is usually not runnable until you set at least a command. Pass exactly one source
|
|
357
|
+
(`from_file`, `data`, `from_url` or `from_image`); ValueError otherwise. `from_url` and
|
|
358
|
+
`from_image` are fetched by the *daemon*, `from_file`/`data` are read here and uploaded; a
|
|
359
|
+
`from_file` path that is not a readable file raises rather than being retried as a URL. Unlike
|
|
360
|
+
the other image-creating tools this stamps no provenance labels: the Engine's import call accepts
|
|
361
|
+
no labels field, and `changes` does not cover `LABEL`.
|
|
362
|
+
|
|
363
|
+
args:
|
|
364
|
+
repository - Repository name to give the new image, e.g. "myorg/rootfs"; may include a tag
|
|
365
|
+
(`myorg/rootfs:v1`), and defaults to `:latest` when it does not. Omit to import untagged,
|
|
366
|
+
addressable only by the id in the returned progress (omit it entirely -- a blank string
|
|
367
|
+
is a ValueError, not a shorthand for untagged). A digest reference is refused by the
|
|
368
|
+
daemon. Required if `tag` is given
|
|
369
|
+
tag - Tag to apply, e.g. "v1". **Overrides** a tag already in `repository` rather than being
|
|
370
|
+
ignored, so passing `repository="myorg/rootfs:v1"` with `tag="v2"` yields `:v2`. Requires
|
|
371
|
+
`repository` (ValueError without it — the daemon would otherwise silently drop the tag
|
|
372
|
+
and import untagged). Blank is also a ValueError, not a shorthand for the default: the
|
|
373
|
+
daemon would substitute `latest` without saying so
|
|
374
|
+
from_file - Path to a rootfs tarball on the server host (`~` expanded), read by the server's
|
|
375
|
+
user; FileNotFoundError if it is not an existing regular file; exactly one source
|
|
376
|
+
data - Rootfs tarball contents in band (base64-encoded by MCP, so prefer `from_file` for
|
|
377
|
+
anything but small archives); exactly one source
|
|
378
|
+
from_url - URL the daemon fetches the tarball from; exactly one source
|
|
379
|
+
from_image - Name of an existing image to import from, like a Dockerfile `FROM`; exactly one
|
|
380
|
+
source
|
|
381
|
+
changes - Dockerfile instructions applied to the new image, e.g. ['CMD ["/bin/sh"]']; only
|
|
382
|
+
CMD, ENTRYPOINT, ENV, EXPOSE, ONBUILD, USER, VOLUME and WORKDIR are supported. Parsed
|
|
383
|
+
as real Dockerfile syntax, so shell form is wrapped exactly as a Dockerfile would wrap
|
|
384
|
+
it (`CMD /bin/sh` is stored as `["/bin/sh","-c","/bin/sh"]`) — use the exec form
|
|
385
|
+
`CMD ["/bin/sh"]` to store a bare argv
|
|
386
|
+
returns: str - The daemon's raw newline-delimited JSON progress records; the final record carries
|
|
387
|
+
the new image id as its `status`
|
|
388
|
+
"""
|
|
389
|
+
sources = {"from_file": from_file, "data": data, "from_url": from_url, "from_image": from_image}
|
|
390
|
+
supplied = [name for name, value in sources.items() if value is not None]
|
|
391
|
+
if len(supplied) != 1:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
"Pass exactly one of `from_file`, `data`, `from_url` or `from_image` "
|
|
394
|
+
f"(got {', '.join(supplied) if supplied else 'none'})."
|
|
395
|
+
)
|
|
396
|
+
# A bare `tag` is refused rather than forwarded: the Engine returns early when `repo` is empty
|
|
397
|
+
# (moby's httputils.RepoTagReference), so the tag is silently dropped and the image lands
|
|
398
|
+
# untagged -- a caller asking for `:v1` would get no error and no tag. A blank `repository`
|
|
399
|
+
# reaches that same early return, verified against a live daemon (repository="", tag=... imports
|
|
400
|
+
# with RepoTags []), so it is refused rather than read as "no repository": passing one is never
|
|
401
|
+
# meaningful, and treating it as absent would reopen the silent drop through the back door.
|
|
402
|
+
if repository is not None and not repository.strip():
|
|
403
|
+
raise ValueError("`repository` cannot be blank; omit it entirely to import untagged.")
|
|
404
|
+
# A blank `tag` is the same silent substitution one step further on: `RepoTagReference` tests
|
|
405
|
+
# `tag != ""`, so an empty tag skips the WithTag path and falls through to `TagNameOnly`, which
|
|
406
|
+
# supplies `:latest`. The caller asked for one tag and would get a different one, with no error --
|
|
407
|
+
# so it is refused alongside a blank `repository` rather than left as the asymmetric case.
|
|
408
|
+
if tag is not None and not tag.strip():
|
|
409
|
+
raise ValueError("`tag` cannot be blank; omit it to accept the daemon's default of `latest`.")
|
|
410
|
+
if tag is not None and repository is None:
|
|
411
|
+
raise ValueError(
|
|
412
|
+
"`tag` needs a `repository` to attach to; pass `repository`, or omit `tag` to import untagged."
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# The high-level ImageCollection has no import; these four are the documented low-level calls.
|
|
416
|
+
api = _get_client(host).api
|
|
417
|
+
common = drop_none(repository=repository, tag=tag, changes=changes)
|
|
418
|
+
if from_file is not None:
|
|
419
|
+
# `import_image_from_file` is a one-line delegation to `import_image(src=...)`, which sends
|
|
420
|
+
# the path as `fromSrc` whenever `docker.utils.is_file(src)` is false -- so a missing path
|
|
421
|
+
# (or a directory) is not an error but an instruction to the *daemon* to fetch that string
|
|
422
|
+
# over HTTP, in the daemon's network namespace. Verified against a live daemon:
|
|
423
|
+
# `from_file="127.0.0.1:9/rootfs.tar"` produced `Get "http://127.0.0.1:9/rootfs.tar"`.
|
|
424
|
+
# `docker import` itself opens the file and reports ENOENT, so this guard restores CLI
|
|
425
|
+
# parity and keeps `from_url` the only source that leaves the host.
|
|
426
|
+
path = host_read_path(from_file)
|
|
427
|
+
if not path.is_file():
|
|
428
|
+
raise FileNotFoundError(
|
|
429
|
+
f"No such rootfs tarball: {path}. Pass `from_url` to have the daemon fetch a URL, "
|
|
430
|
+
"or `data` to send the archive in band."
|
|
431
|
+
)
|
|
432
|
+
return api.import_image_from_file(str(path), **common)
|
|
433
|
+
if data is not None:
|
|
434
|
+
return api.import_image_from_data(data, **common)
|
|
435
|
+
if from_url is not None:
|
|
436
|
+
return api.import_image_from_url(from_url, **common)
|
|
437
|
+
return api.import_image_from_image(cast(str, from_image), **common)
|
|
438
|
+
|
|
439
|
+
|
|
336
440
|
@tool()
|
|
337
441
|
def image_save(
|
|
338
442
|
id_or_name: str,
|
|
@@ -63,7 +63,8 @@ def plugin_install(remote: str, local_name: str | None = None, host: str | None
|
|
|
63
63
|
Install a plugin from Docker Hub.
|
|
64
64
|
|
|
65
65
|
`remote` is a Docker Hub reference in `author/name:tag` form, e.g.
|
|
66
|
-
`vieux/sshfs:latest`. The daemon handles permission grants non-interactively
|
|
66
|
+
`vieux/sshfs:latest`. The daemon handles permission grants non-interactively — call
|
|
67
|
+
`plugin_privileges` first to see what host access the plugin is asking for.
|
|
67
68
|
After installation use `plugin_inspect` to confirm the plugin's enabled state, then call
|
|
68
69
|
`plugin_enable` to activate it if needed, and optionally `plugin_configure` first if
|
|
69
70
|
it requires settings. Use `plugin_list` to list all plugins, or `plugin_remove` to
|
|
@@ -77,6 +78,30 @@ def plugin_install(remote: str, local_name: str | None = None, host: str | None
|
|
|
77
78
|
return _get_client(host).plugins.install(remote, local_name=local_name).attrs
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
@tool()
|
|
82
|
+
def plugin_privileges(remote: str, host: str | None = None) -> list:
|
|
83
|
+
"""
|
|
84
|
+
Ask the registry which host privileges a not-yet-installed plugin demands.
|
|
85
|
+
|
|
86
|
+
The review step before `plugin_install`, which grants these privileges non-interactively (the
|
|
87
|
+
daemon never prompts) — so this is the only chance to see what a plugin wants before it has it.
|
|
88
|
+
Worth checking for anything not already trusted: plugins routinely request host mounts, devices,
|
|
89
|
+
and elevated capabilities, and a granted privilege is host-level access, not container-scoped.
|
|
90
|
+
Reads the *remote* plugin from its registry and installs nothing; for the privileges of a plugin
|
|
91
|
+
already installed, read `Config` from `plugin_inspect` instead. Credentials come from
|
|
92
|
+
`system_login`, or from `~/.docker/config.json` if the host ran `docker login`. Raises if the
|
|
93
|
+
reference cannot be resolved in the registry.
|
|
94
|
+
|
|
95
|
+
args: remote - Registry plugin reference, `author/name:tag`; the `:latest` tag is optional and
|
|
96
|
+
is the default if omitted
|
|
97
|
+
returns: list - One dict per requested privilege ({"Name", "Description", "Value"}), e.g. Name
|
|
98
|
+
"mount" with Value ["/data"], or "capabilities" with Value ["CAP_SYS_ADMIN"]; empty if the
|
|
99
|
+
plugin requests none
|
|
100
|
+
"""
|
|
101
|
+
# The high-level PluginCollection exposes no privileges call; this is the documented low-level one.
|
|
102
|
+
return _get_client(host).api.plugin_privileges(remote)
|
|
103
|
+
|
|
104
|
+
|
|
80
105
|
@tool()
|
|
81
106
|
def plugin_push(name: str, timeout_seconds: float = 300.0, host: str | None = None) -> dict:
|
|
82
107
|
"""
|
|
@@ -217,3 +217,64 @@ def swarm_join_tokens(host: str | None = None) -> dict:
|
|
|
217
217
|
swarm = _get_client(host).swarm
|
|
218
218
|
swarm.reload()
|
|
219
219
|
return _read_join_tokens(swarm)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# --- cluster-wide task queries ---
|
|
223
|
+
#
|
|
224
|
+
# Tasks are swarm objects, so these live here and share the `swarm` domain gate: a user who sets
|
|
225
|
+
# DOCKER_MCP_SERVER_DISABLE=swarm expects everything named `swarm_*` to go with it. They are the
|
|
226
|
+
# one part of this module that is not cluster lifecycle, and they read what `services.py` writes --
|
|
227
|
+
# `service_ps` is the per-service view of the same task documents.
|
|
228
|
+
#
|
|
229
|
+
# Both stay on the low-level `client.api`: docker-py has no task collection at all (there is no
|
|
230
|
+
# `client.tasks`), so these documented `APIClient` methods are the only public path.
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@tool()
|
|
234
|
+
def swarm_task_list(filters: dict | None = None, host: str | None = None) -> list:
|
|
235
|
+
"""
|
|
236
|
+
List tasks across the whole swarm, like `docker service ps` with no service to scope it.
|
|
237
|
+
|
|
238
|
+
The cluster-wide view of what is actually scheduled. `service_ps` covers one service and
|
|
239
|
+
`stack_ps` one stack, so answering "what is failing anywhere" or "what is running on this node"
|
|
240
|
+
through those means looping over every service; this is one call. Filter by `node` for a node's
|
|
241
|
+
workload (the CLI's `docker node ps`), `desired-state` to separate what should be running from
|
|
242
|
+
what is shutting down, or `service` for a single service -- for which `service_ps` is the
|
|
243
|
+
simpler call. Each task carries its full `Spec`, including the `ContainerSpec` (image, command,
|
|
244
|
+
env), so this returns much more per task than the `service-tasks://{id_or_name}` resource's
|
|
245
|
+
computed rollout summary. Read-only. Requires a swarm manager: any other node raises
|
|
246
|
+
`docker.errors.APIError`.
|
|
247
|
+
|
|
248
|
+
args:
|
|
249
|
+
filters - Filter dict; keys: id, name, service, node, label, desired-state
|
|
250
|
+
(running|shutdown|accepted); omit for every task in the cluster
|
|
251
|
+
returns: list - One full task document per task (ID, ServiceID, NodeID, Slot, Spec, Status,
|
|
252
|
+
DesiredState), the same shape `service_ps` returns
|
|
253
|
+
"""
|
|
254
|
+
return _get_client(host).api.tasks(filters=filters)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@tool()
|
|
258
|
+
def swarm_task_inspect(id_or_name: str, host: str | None = None) -> dict:
|
|
259
|
+
"""
|
|
260
|
+
Inspect a single swarm task, like `docker inspect --type task`.
|
|
261
|
+
|
|
262
|
+
For when you already hold a task reference -- from a `swarm_task_list` or `service_ps` row, a
|
|
263
|
+
service event, or an error message -- and want just that task. `swarm_task_list` returns the
|
|
264
|
+
same document for every task, so prefer it when scanning; this is the single-object fetch.
|
|
265
|
+
To reach the container behind a running task, read `Status.ContainerStatus.ContainerID` and pass
|
|
266
|
+
it to `container_inspect` / `container_logs` -- but note the container may be on another node,
|
|
267
|
+
where those tools cannot see it, and `service_logs` aggregates across tasks instead. Read-only.
|
|
268
|
+
Requires a swarm manager; raises `docker.errors.APIError` if the task does not exist, if a
|
|
269
|
+
prefix matches more than one task, or if this node is not a manager.
|
|
270
|
+
|
|
271
|
+
args:
|
|
272
|
+
id_or_name - The task id, an unambiguous id prefix, or the task's full name -- which is the
|
|
273
|
+
container-name form `<service>.<slot>.<taskid>` (`<service>.<nodeid>.<taskid>` for a
|
|
274
|
+
global service), NOT the shorter `<service>.<slot>` that `docker service ps` prints in
|
|
275
|
+
its NAME column, which does not resolve. The daemon tries full id, then full name, then
|
|
276
|
+
prefix, and rejects an ambiguous prefix rather than picking a match
|
|
277
|
+
returns: dict - Full task inspect payload, as `docker inspect --type task`. Carries no name
|
|
278
|
+
field of its own; compose one from `ServiceID`/`Slot` if you need it
|
|
279
|
+
"""
|
|
280
|
+
return _get_client(host).api.inspect_task(id_or_name)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": "0.4",
|
|
3
3
|
"name": "docker-mcp-server",
|
|
4
4
|
"display_name": "Docker MCP Server",
|
|
5
|
-
"version": "2.2.
|
|
5
|
+
"version": "2.2.5",
|
|
6
6
|
"description": "Manage Docker through the Docker SDK for Python and the docker CLI: containers, images, networks, volumes, Compose, Swarm, Buildx, Scout, and OCI registries, exposed as MCP tools.",
|
|
7
7
|
"long_description": "Manage one or more Docker daemons through your AI agent - containers, images, networks, volumes, Compose, Swarm, Buildx, Scout, and OCI registries, exposed as MCP tools.\n\nIt gives you much more control and flexibility than calling the Docker CLI directly: each operation is exposed as its own typed tool, marked read-only or not, with destructive actions separately flagged. This means a client can auto-approve reads while always confirming anything destructive, and the whole server can also be switched into a read-only or no-destructive mode as a blanket safeguard. Output is bounded rather than left to grow unboundedly - capped with a truncated flag instead of silently overflowing the agent's context.\n\nDiscovery is automatic for your local socket with no configuration required. Or point it at additional remote daemons over TCP, TLS, or SSH (and mark any of them read-only to monitor without the risk of accidental changes). It also surfaces logs and stats as resources for triage. Read-only / no-destructive / disabled-domain switches let you register a restricted tool surface.\n\nDocumentation is built for the agent, not just the person configuring it: an MCP resource exposes the Docker SDK reference in-session (with a tool-callable fallback for clients that can't read resources), and a live tool-catalog resource reports exactly what's registered under the current configuration. Each tool's own description names its nearest siblings and when to prefer each, states preconditions and side effects in plain language, and is honest about when it can still fail - so an agent can pick the right tool on the first try among 150+ options, not guess.\n\nAnswer questions like: 'why did my local container crash?', 'is my production system under memory pressure?', 'what differences are there between my staging and production systems?'\n\nRuns entirely on your machine and sends no telemetry - see the Privacy Policy.",
|
|
8
8
|
"author": {
|
|
@@ -7,22 +7,22 @@
|
|
|
7
7
|
"url": "https://github.com/L337-org/docker-mcp",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.2.
|
|
10
|
+
"version": "2.2.5",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "docker-mcp-server",
|
|
15
|
-
"version": "2.2.
|
|
15
|
+
"version": "2.2.5",
|
|
16
16
|
"transport": { "type": "stdio" }
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"registryType": "oci",
|
|
20
|
-
"identifier": "ghcr.io/l337-org/docker-mcp-server:2.2.
|
|
20
|
+
"identifier": "ghcr.io/l337-org/docker-mcp-server:2.2.5",
|
|
21
21
|
"transport": { "type": "stdio" }
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"registryType": "mcpb",
|
|
25
|
-
"identifier": "https://github.com/L337-org/docker-mcp/releases/download/v2.2.
|
|
25
|
+
"identifier": "https://github.com/L337-org/docker-mcp/releases/download/v2.2.5/docker-mcp-server-2.2.5.mcpb",
|
|
26
26
|
"fileSha256": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
27
27
|
"transport": { "type": "stdio" }
|
|
28
28
|
}
|
|
@@ -103,6 +103,23 @@ Always use `-o`/`-i`. A bare `docker save` writes a multi-hundred-megabyte tar t
|
|
|
103
103
|
`export`/`import` (see `reference/containers.md`) flatten and lose all of it; they are not
|
|
104
104
|
interchangeable.
|
|
105
105
|
|
|
106
|
+
## Importing a flat rootfs
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
docker import rootfs.tar myorg/rootfs:v1 # tar of a filesystem -> 1-layer image
|
|
110
|
+
docker import --change 'CMD /bin/sh' rootfs.tar myorg/rootfs:v1
|
|
111
|
+
docker import https://example.com/rootfs.tar myorg/rootfs:v1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- `import` takes a **filesystem** tar, `load` takes a `docker save` bundle. Feeding a save bundle to
|
|
115
|
+
`import` "works" and produces a useless image whose root is the bundle's own metadata files.
|
|
116
|
+
- An imported image has an empty config - no `CMD`, `ENTRYPOINT` or `ENV` - so it will not run until
|
|
117
|
+
you set one. `--change` accepts only `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`, `ONBUILD`, `USER`,
|
|
118
|
+
`VOLUME` and `WORKDIR`; `LABEL` is not among them, so an imported image cannot be
|
|
119
|
+
provenance-labelled at import time.
|
|
120
|
+
- The URL form is fetched by the **daemon**, not by the shell - so it resolves in the daemon's
|
|
121
|
+
network namespace, which matters against a remote host.
|
|
122
|
+
|
|
106
123
|
## Removing and pruning
|
|
107
124
|
|
|
108
125
|
```bash
|
|
@@ -82,6 +82,25 @@ docker service ps <svc> --no-trunc --format json | jq -rs \
|
|
|
82
82
|
`--no-trunc` matters here - the `Error` column is where the real reason lives and it is truncated
|
|
83
83
|
by default.
|
|
84
84
|
|
|
85
|
+
### Tasks across the whole cluster
|
|
86
|
+
|
|
87
|
+
There is no CLI command that lists every task in the swarm; `docker service ps` always needs a
|
|
88
|
+
service. Fan out over the service list instead:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
for svc in $(docker service ls -q); do
|
|
92
|
+
docker service ps "$svc" --no-trunc --format json
|
|
93
|
+
done | jq -rs '.[] | select(.CurrentState | startswith("Running") | not)
|
|
94
|
+
| [.Name, .Node, .CurrentState, .Error] | @tsv'
|
|
95
|
+
docker node ps <node> --no-trunc # the other axis: every task on one node
|
|
96
|
+
docker inspect --type task <task-id> # one task, full document
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`docker service ps` prints `NAME` as `<service>.<slot>`, but `docker inspect --type task` will not
|
|
100
|
+
resolve that form - pass the task `ID` from the same row instead. (The MCP server exposes the
|
|
101
|
+
cluster-wide read directly as `swarm_task_list`, with `node`, `service` and `desired-state`
|
|
102
|
+
filters.)
|
|
103
|
+
|
|
85
104
|
### Creating and updating
|
|
86
105
|
|
|
87
106
|
```bash
|