docker-mcp-server 2.2.7__tar.gz → 2.2.8__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.7 → docker_mcp_server-2.2.8}/.claude/commands/docker-sdk.md +6 -0
- docker_mcp_server-2.2.8/.github/workflows/announce-pull-requests.yaml +18 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/canary.yaml +3 -3
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/code-review.yaml +2 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/codeql.yaml +3 -3
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/premerge.yaml +19 -18
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/publish-homebrew.yaml +1 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/publish.yaml +27 -11
- docker_mcp_server-2.2.8/.github/workflows/report-failures.yaml +32 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.gitignore +1 -0
- docker_mcp_server-2.2.8/.pre-commit-config.yaml +18 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/AGENTS.md +25 -14
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/CONTRIBUTING.md +2 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/DOCKERHUB.md +1 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/Dockerfile +10 -4
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/MCP_VS_SKILLS.md +136 -137
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/PKG-INFO +20 -18
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/README.md +17 -17
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/SECURITY.md +2 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/ci.md +32 -12
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/cli-shell-out.md +2 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/distribution.md +12 -6
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/docker-sdk.md +47 -23
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/hosts.md +1 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/server.md +26 -5
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/tool-descriptions.md +2 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/claude-plugin/.claude-plugin/plugin.json +2 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/claude-plugin/pyproject.toml +2 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/server.py +248 -85
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/_cli.py +3 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/_utils.py +20 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/buildx.py +127 -64
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/compose.py +175 -120
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/containers.py +419 -116
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/context.py +5 -14
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/images.py +87 -75
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/networks.py +5 -5
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/nodes.py +68 -27
- docker_mcp_server-2.2.8/docker_mcp/tools/plugins.py +227 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/prompts.py +29 -23
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/registry.py +17 -23
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/resources.py +23 -38
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/scout.py +24 -31
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/secrets.py +2 -8
- docker_mcp_server-2.2.8/docker_mcp/tools/services.py +677 -0
- docker_mcp_server-2.2.8/docker_mcp/tools/stack.py +448 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/swarm.py +7 -191
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/system.py +297 -11
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/volumes.py +6 -6
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/manifest.json +1 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/pyproject.toml +9 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/scripts/claude_plugin_release.py +68 -5
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/scripts/measure-comparison-figures.py +5 -5
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/server.json +4 -4
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/SKILL.md +31 -12
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/buildx.md +28 -5
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/compose.md +10 -6
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/containers.md +26 -10
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/images.md +10 -4
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/networks-volumes.md +5 -2
- docker_mcp_server-2.2.8/skills/l337-docker/reference/observability.md +338 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/registry.md +73 -29
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/scout.md +13 -4
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/swarm.md +58 -26
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/system.md +23 -14
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/workflows/build-publish.md +4 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/workflows/deploy.md +9 -5
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/workflows/maintenance.md +17 -7
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/workflows/security.md +4 -3
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/workflows/troubleshoot.md +3 -2
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/conftest.py +4 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_compose.py +72 -0
- docker_mcp_server-2.2.8/tests/integration/test_containers.py +227 -0
- docker_mcp_server-2.2.8/tests/integration/test_nodes.py +33 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_plugins.py +8 -8
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_registry.py +5 -3
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_services.py +15 -10
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_skill.py +227 -8
- docker_mcp_server-2.2.8/tests/integration/test_stack.py +214 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_swarm.py +32 -29
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_buildx.py +141 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_claude_plugin_release.py +132 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_compose.py +192 -21
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_containers.py +595 -60
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_docs.py +34 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_docstrings.py +21 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_images.py +101 -30
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_naming.py +92 -1
- docker_mcp_server-2.2.8/tests/test_nodes.py +193 -0
- docker_mcp_server-2.2.8/tests/test_plugins.py +200 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_prompts.py +10 -7
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_registry.py +23 -14
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_resources.py +0 -10
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_server.py +310 -5
- docker_mcp_server-2.2.8/tests/test_services.py +743 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_skill.py +30 -0
- docker_mcp_server-2.2.8/tests/test_stack.py +744 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_surface_budget.py +8 -8
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_swarm.py +1 -128
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_system.py +289 -1
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_utils.py +16 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/uv.lock +6 -2
- docker_mcp_server-2.2.7/MIGRATION-2.0.md +0 -219
- docker_mcp_server-2.2.7/docker_mcp/tools/plugins.py +0 -343
- docker_mcp_server-2.2.7/docker_mcp/tools/services.py +0 -470
- docker_mcp_server-2.2.7/docker_mcp/tools/stack.py +0 -272
- docker_mcp_server-2.2.7/skills/l337-docker/reference/observability.md +0 -208
- docker_mcp_server-2.2.7/tests/integration/test_containers.py +0 -105
- docker_mcp_server-2.2.7/tests/integration/test_nodes.py +0 -16
- docker_mcp_server-2.2.7/tests/integration/test_stack.py +0 -65
- docker_mcp_server-2.2.7/tests/test_nodes.py +0 -131
- docker_mcp_server-2.2.7/tests/test_plugins.py +0 -239
- docker_mcp_server-2.2.7/tests/test_services.py +0 -446
- docker_mcp_server-2.2.7/tests/test_stack.py +0 -252
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.agents/policy/architecture.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.agents/policy/review-context.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.agents/policy/testing.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.claude/settings.json +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.dockerignore +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/CODEOWNERS +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/actions/file-failure-issue/action.yaml +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/copilot-instructions.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/dependabot.yaml +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/release.yml +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.github/workflows/images.yaml +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.mcpbignore +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/.python-version +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/CLAUDE.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/CODE_OF_CONDUCT.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/LICENSE +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/PRIVACY.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/architecture/agent-skill.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/assets/README.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/assets/icon.png +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/claude-plugin/README.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/claude-plugin/icon.png +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/__init__.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/__main__.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/_env.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/_hosts.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/exceptions.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/__init__.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/_labels.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/_ssh_proxy.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/docker_mcp/tools/configs.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/glama.json +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/mcpb_run.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/scripts/build-mcpb.sh +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/scripts/docker-mcp-server.rb.tpl +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/LICENSE +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/skills/l337-docker/reference/docs.md +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/__init__.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/conftest.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/__init__.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_buildx.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_cli.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_cli_flag_drift.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_context.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_file_payloads.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_images.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_networks.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_remote_exec.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_scout.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/integration/test_smoke.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_claude_plugin.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_cli.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_configs.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_context.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_env.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_hosts.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_labels.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_main.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_networks.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_pyproject_pins.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_remote_exec.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_scout.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_secrets.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_ssh_proxy.py +0 -0
- {docker_mcp_server-2.2.7 → docker_mcp_server-2.2.8}/tests/test_volumes.py +0 -0
|
@@ -48,6 +48,8 @@ The project structure maps SDK domains to tool files one-to-one:
|
|
|
48
48
|
|
|
49
49
|
Read each `docker_mcp/tools/*.py` file and list every `@mcp.tool` decorated function and the `docker` module methods it calls.
|
|
50
50
|
|
|
51
|
+
The CLI-backed modules shell out to `docker` through `docker_mcp/tools/_cli.py:run_docker`; find them by grepping `docker_mcp/tools/*.py` for `run_docker` rather than from a list (skipping `_cli.py` and `_ssh_proxy.py`, which are the machinery itself), and record the CLI command each tool runs. For each one, check whether a first-party Python API now covers the operation: docker-py, or any other Python package Docker publishes (look in the `docker` GitHub organisation and on PyPI for one covering the features those tools shell out for). Enumerate docker-py's public modules from the source at the release being checked, not from the rendered docs fetched in Step 1, and where the rendered docs have no page for a module, read its source. Read [architecture/cli-shell-out.md](../../architecture/cli-shell-out.md) before judging a candidate: moving a tool off the CLI also moves it off the SSH remote-exec fallback and file staging.
|
|
52
|
+
|
|
51
53
|
### Step 3 - Produce a gap analysis
|
|
52
54
|
|
|
53
55
|
Compare the SDK surface area from Step 1 against the MCP tool inventory from Step 2 and output a structured report:
|
|
@@ -63,6 +65,10 @@ Compare the SDK surface area from Step 1 against the MCP tool inventory from Ste
|
|
|
63
65
|
- <`docker` module method>() - <one-line description> → should go in docker_mcp/tools/<file>.py
|
|
64
66
|
...
|
|
65
67
|
|
|
68
|
+
### CLI Shell-outs With a First-party Python API Equivalent
|
|
69
|
+
- <MCP tool name> (docker_mcp/tools/<file>.py) runs `docker <command>` → could use <package and method>(), verified at <source location>; changes for callers: <remote-exec fallback, staging, credentials, context>
|
|
70
|
+
...
|
|
71
|
+
|
|
66
72
|
### Verification Notes
|
|
67
73
|
For any specific functions the user asked about: confirmed they exist (or do not exist)
|
|
68
74
|
in the live documentation, with the exact signature.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: 'Announce pull requests'
|
|
2
|
+
|
|
3
|
+
# Posts a pull request that needs action to the project's Slack channel through the shared
|
|
4
|
+
# announcer, which decides what to post (architecture/ci.md).
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
types: [opened, reopened, ready_for_review]
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
slack:
|
|
13
|
+
name: Announce to Slack
|
|
14
|
+
uses: L337-org/github-workflows/.github/workflows/slack-on-pull-request.yaml@v2
|
|
15
|
+
with:
|
|
16
|
+
channel: '#docker-mcp'
|
|
17
|
+
secrets:
|
|
18
|
+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
@@ -42,7 +42,7 @@ jobs:
|
|
|
42
42
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
43
43
|
|
|
44
44
|
- name: Set up uv
|
|
45
|
-
uses: astral-sh/setup-uv@
|
|
45
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
46
46
|
with:
|
|
47
47
|
# Pinned (matching the Dockerfile's uv image tag) so this step never needs the
|
|
48
48
|
# live "resolve latest" fetch to raw.githubusercontent.com — that network call
|
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
run: |
|
|
59
59
|
set -euo pipefail
|
|
60
60
|
if [ "$GITHUB_EVENT_NAME" != "pull_request" ]; then
|
|
61
|
-
latest="$(curl -fsSL https://pypi.org/pypi/docker-mcp-server/json | jq -r .info.version)"
|
|
61
|
+
latest="$(curl -fsSL --max-time 30 --retry 3 https://pypi.org/pypi/docker-mcp-server/json | jq -r .info.version)"
|
|
62
62
|
echo "Latest published release: $latest"
|
|
63
63
|
fi
|
|
64
64
|
failed=0
|
|
@@ -104,7 +104,7 @@ jobs:
|
|
|
104
104
|
os: [macos-latest, macos-15-intel, windows-latest]
|
|
105
105
|
steps:
|
|
106
106
|
- name: Set up uv
|
|
107
|
-
uses: astral-sh/setup-uv@
|
|
107
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
108
108
|
with:
|
|
109
109
|
# This job deliberately has no checkout — it tests the *published* package, not the
|
|
110
110
|
# repo — so disable the repo-keyed cache and the empty-workdir warning it triggers.
|
|
@@ -3,7 +3,7 @@ permissions:
|
|
|
3
3
|
contents: read
|
|
4
4
|
|
|
5
5
|
# What this caller decides. The shared workflow decides everything else about the review, and
|
|
6
|
-
# its README describes it at the
|
|
6
|
+
# its README describes it at the version named below.
|
|
7
7
|
#
|
|
8
8
|
# - Opening a pull request, and every push to one, asks for a review at the depth the shared
|
|
9
9
|
# workflow picks. A push is reviewed deliberately: the loop that invites, a reviewing agent and
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
permissions:
|
|
42
42
|
contents: read
|
|
43
43
|
pull-requests: write
|
|
44
|
-
uses: L337-org/github-workflows/.github/workflows/claude-review.yaml@
|
|
44
|
+
uses: L337-org/github-workflows/.github/workflows/claude-review.yaml@v2
|
|
45
45
|
with:
|
|
46
46
|
pr-number: ${{ github.event.pull_request.number || github.event.issue.number }}
|
|
47
47
|
depth: >-
|
|
@@ -33,12 +33,12 @@ jobs:
|
|
|
33
33
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
34
34
|
|
|
35
35
|
- name: Initialize CodeQL
|
|
36
|
-
uses: github/codeql-action/init@
|
|
36
|
+
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
|
|
37
37
|
with:
|
|
38
38
|
languages: ${{ matrix.language }}
|
|
39
39
|
|
|
40
40
|
- name: Autobuild
|
|
41
|
-
uses: github/codeql-action/autobuild@
|
|
41
|
+
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
|
|
42
42
|
|
|
43
43
|
- name: Perform CodeQL Analysis
|
|
44
|
-
uses: github/codeql-action/analyze@
|
|
44
|
+
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
|
|
23
23
|
repo-hygiene:
|
|
24
24
|
# Conventions every repository in this organisation shares, checked by the one copy in the
|
|
25
|
-
# shared workflows repository at the
|
|
25
|
+
# shared workflows repository at the version tag named here.
|
|
26
26
|
runs-on: [ubuntu-latest]
|
|
27
27
|
name: Repository hygiene
|
|
28
28
|
# 9s for the whole job on the first run of the shared action here, uv setup included.
|
|
@@ -32,25 +32,26 @@ jobs:
|
|
|
32
32
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
33
|
|
|
34
34
|
- name: Repository conventions hold
|
|
35
|
-
uses: L337-org/github-workflows/actions/repo-hygiene@
|
|
35
|
+
uses: L337-org/github-workflows/actions/repo-hygiene@v2
|
|
36
36
|
|
|
37
37
|
action-pins:
|
|
38
|
-
# Every `uses:` in .github/workflows and .github/actions names a full commit SHA,
|
|
39
|
-
# the shared
|
|
38
|
+
# Every third-party `uses:` in .github/workflows and .github/actions names a full commit SHA,
|
|
39
|
+
# and the shared workflows repository is named at its major-version tag, checked by the
|
|
40
|
+
# shared copy. Its default directories are this repository's, so the local
|
|
40
41
|
# file-failure-issue action is covered too. Why the pin matters most here: publish.yaml
|
|
41
42
|
# mints an OIDC token for PyPI Trusted Publishing (`id-token: write`), pushes to GHCR
|
|
42
|
-
# (`packages: write`) and uploads release assets (`contents: write`), so a repointed
|
|
43
|
-
# would execute inside the jobs holding this project's strongest credentials.
|
|
43
|
+
# (`packages: write`) and uploads release assets (`contents: write`), so a repointed
|
|
44
|
+
# third-party tag would execute inside the jobs holding this project's strongest credentials.
|
|
44
45
|
runs-on: [ubuntu-latest]
|
|
45
|
-
name: Action
|
|
46
|
+
name: Action pin check
|
|
46
47
|
# 5s for the whole job on the first run of the shared action here - a grep over the workflows.
|
|
47
48
|
timeout-minutes: 5
|
|
48
49
|
steps:
|
|
49
50
|
- name: Check out source repository
|
|
50
51
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
51
52
|
|
|
52
|
-
- name: Every `uses:` must name a 40-hex commit SHA
|
|
53
|
-
uses: L337-org/github-workflows/actions/action-pins@
|
|
53
|
+
- name: Every `uses:` must name a 40-hex commit SHA, or the shared workflows' major-version tag
|
|
54
|
+
uses: L337-org/github-workflows/actions/action-pins@v2
|
|
54
55
|
|
|
55
56
|
fresh-resolve-import:
|
|
56
57
|
# Answers a question `uv sync --locked` (used by every other job here) cannot: would a
|
|
@@ -77,7 +78,7 @@ jobs:
|
|
|
77
78
|
python-version: "3.14"
|
|
78
79
|
|
|
79
80
|
- name: Set up uv
|
|
80
|
-
uses: astral-sh/setup-uv@
|
|
81
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
81
82
|
|
|
82
83
|
- name: Resolve and install from pyproject.toml, ignoring uv.lock
|
|
83
84
|
# A failure here is a resolve/build/install failure (an unsatisfiable specifier, a
|
|
@@ -127,7 +128,7 @@ jobs:
|
|
|
127
128
|
python-version: "3.14"
|
|
128
129
|
|
|
129
130
|
- name: Set up uv
|
|
130
|
-
uses: astral-sh/setup-uv@
|
|
131
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
131
132
|
with:
|
|
132
133
|
# The release jobs' uv, so the rehearsal locks with the same version; a test in
|
|
133
134
|
# tests/test_claude_plugin_release.py keeps them equal.
|
|
@@ -185,7 +186,7 @@ jobs:
|
|
|
185
186
|
python-version: "3.14"
|
|
186
187
|
|
|
187
188
|
- name: Set up uv
|
|
188
|
-
uses: astral-sh/setup-uv@
|
|
189
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
189
190
|
with:
|
|
190
191
|
version: "0.11.21"
|
|
191
192
|
enable-cache: false
|
|
@@ -214,7 +215,7 @@ jobs:
|
|
|
214
215
|
python-version: "3.14"
|
|
215
216
|
|
|
216
217
|
- name: Set up uv
|
|
217
|
-
uses: astral-sh/setup-uv@
|
|
218
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
218
219
|
|
|
219
220
|
- name: Install dependencies
|
|
220
221
|
# --locked fails if uv.lock disagrees with pyproject.toml instead of silently
|
|
@@ -245,7 +246,7 @@ jobs:
|
|
|
245
246
|
python-version: "3.14"
|
|
246
247
|
|
|
247
248
|
- name: Set up uv
|
|
248
|
-
uses: astral-sh/setup-uv@
|
|
249
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
249
250
|
|
|
250
251
|
- name: Install dependencies
|
|
251
252
|
# --locked fails if uv.lock disagrees with pyproject.toml instead of silently
|
|
@@ -278,7 +279,7 @@ jobs:
|
|
|
278
279
|
python-version: "3.14"
|
|
279
280
|
|
|
280
281
|
- name: Set up uv
|
|
281
|
-
uses: astral-sh/setup-uv@
|
|
282
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
282
283
|
|
|
283
284
|
- name: Install dependencies
|
|
284
285
|
run: uv sync --all-groups --locked
|
|
@@ -308,7 +309,7 @@ jobs:
|
|
|
308
309
|
python-version: "3.14"
|
|
309
310
|
|
|
310
311
|
- name: Set up uv
|
|
311
|
-
uses: astral-sh/setup-uv@
|
|
312
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
312
313
|
|
|
313
314
|
- name: Install dependencies
|
|
314
315
|
# --locked fails if uv.lock disagrees with pyproject.toml instead of silently
|
|
@@ -347,7 +348,7 @@ jobs:
|
|
|
347
348
|
python-version: "3.14"
|
|
348
349
|
|
|
349
350
|
- name: Set up uv
|
|
350
|
-
uses: astral-sh/setup-uv@
|
|
351
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
351
352
|
|
|
352
353
|
- name: Install dependencies
|
|
353
354
|
# --locked fails if uv.lock disagrees with pyproject.toml instead of silently
|
|
@@ -366,7 +367,7 @@ jobs:
|
|
|
366
367
|
docker scout version 2>/dev/null || echo "scout not installed; scout integration tests will skip"
|
|
367
368
|
|
|
368
369
|
- name: Initialize a single-node swarm
|
|
369
|
-
# Needed for
|
|
370
|
+
# Needed for system_wait's service / node integration tests (skip_if_no_swarm), which
|
|
370
371
|
# otherwise skip cleanly on a non-swarm daemon. No teardown needed — the runner is
|
|
371
372
|
# ephemeral and torn down after the job.
|
|
372
373
|
run: docker swarm init
|
|
@@ -71,7 +71,7 @@ jobs:
|
|
|
71
71
|
run: git fetch --depth=1 origin main && git checkout origin/main -- scripts/docker-mcp-server.rb.tpl
|
|
72
72
|
|
|
73
73
|
- name: Install uv
|
|
74
|
-
uses: astral-sh/setup-uv@
|
|
74
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
75
75
|
|
|
76
76
|
- name: Resolve version, Python target, and macOS floor
|
|
77
77
|
run: |
|
|
@@ -179,7 +179,7 @@ jobs:
|
|
|
179
179
|
python-version: "3.14"
|
|
180
180
|
|
|
181
181
|
- name: Install uv
|
|
182
|
-
uses: astral-sh/setup-uv@
|
|
182
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
183
183
|
|
|
184
184
|
- name: Build sdist and wheel
|
|
185
185
|
run: uv build
|
|
@@ -202,7 +202,7 @@ jobs:
|
|
|
202
202
|
env:
|
|
203
203
|
VERSION: ${{ needs.preflight.outputs.version }}
|
|
204
204
|
run: |
|
|
205
|
-
code="$(curl -sS --max-time 30 -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/docker-mcp-server/$VERSION/json")"
|
|
205
|
+
code="$(curl -sS --max-time 30 --retry 3 -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/docker-mcp-server/$VERSION/json")"
|
|
206
206
|
case "$code" in
|
|
207
207
|
200) echo "uploaded=false" >> "$GITHUB_OUTPUT"
|
|
208
208
|
echo "::notice::docker-mcp-server $VERSION is already on PyPI; this run uploads nothing (skip-existing)." ;;
|
|
@@ -508,7 +508,7 @@ jobs:
|
|
|
508
508
|
# asset-CDN lag.
|
|
509
509
|
mkdir -p _mcpb
|
|
510
510
|
for i in $(seq 1 3); do
|
|
511
|
-
if gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern '*.mcpb' --dir _mcpb --clobber; then
|
|
511
|
+
if timeout 120 gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern '*.mcpb' --dir _mcpb --clobber; then
|
|
512
512
|
break
|
|
513
513
|
fi
|
|
514
514
|
echo "::notice::.mcpb asset for $TAG not readable yet (attempt $i/3); waiting 10s"
|
|
@@ -544,7 +544,8 @@ jobs:
|
|
|
544
544
|
MCP_PUBLISHER_SHA256: ab128162b0616090b47cf245afe0a23f3ef08936fdce19074f5ba0a4469281ac
|
|
545
545
|
run: |
|
|
546
546
|
set -euo pipefail
|
|
547
|
-
curl -fsSL
|
|
547
|
+
curl -fsSL --connect-timeout 30 --speed-limit 102400 --speed-time 30 --max-time 300 --retry 3 \
|
|
548
|
+
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_linux_amd64.tar.gz" \
|
|
548
549
|
-o mcp-publisher.tar.gz
|
|
549
550
|
echo "${MCP_PUBLISHER_SHA256} mcp-publisher.tar.gz" | sha256sum -c -
|
|
550
551
|
tar xzf mcp-publisher.tar.gz mcp-publisher
|
|
@@ -617,7 +618,7 @@ jobs:
|
|
|
617
618
|
run: |
|
|
618
619
|
set -euo pipefail
|
|
619
620
|
for i in $(seq 1 5); do
|
|
620
|
-
if curl -fsS "https://pypi.org/pypi/docker-mcp-server/${VERSION}/json" >/dev/null; then
|
|
621
|
+
if curl -fsS --max-time 30 "https://pypi.org/pypi/docker-mcp-server/${VERSION}/json" >/dev/null; then
|
|
621
622
|
echo "PyPI OK: docker-mcp-server ${VERSION}"; exit 0
|
|
622
623
|
fi
|
|
623
624
|
echo "::notice::PyPI does not serve ${VERSION} yet (attempt $i/5); waiting 15s"
|
|
@@ -759,7 +760,16 @@ jobs:
|
|
|
759
760
|
run: |
|
|
760
761
|
set -euo pipefail
|
|
761
762
|
mkdir -p _verify
|
|
762
|
-
|
|
763
|
+
# A release asset comes from the same CDN whose 504s these downloads retry elsewhere, and a
|
|
764
|
+
# failure here turns a converged release red; the count check below catches every attempt
|
|
765
|
+
# failing.
|
|
766
|
+
for i in $(seq 1 3); do
|
|
767
|
+
if timeout 120 gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern '*.mcpb*' --dir _verify --clobber; then
|
|
768
|
+
break
|
|
769
|
+
fi
|
|
770
|
+
echo "::notice::.mcpb assets for $TAG not downloadable (attempt $i/3); waiting 10s"
|
|
771
|
+
sleep 10
|
|
772
|
+
done
|
|
763
773
|
shopt -s nullglob
|
|
764
774
|
mcpb=(_verify/*.mcpb); sums=(_verify/*.sha256)
|
|
765
775
|
if [ "${#mcpb[@]}" -ne 1 ] || [ "${#sums[@]}" -ne 1 ]; then
|
|
@@ -775,8 +785,14 @@ jobs:
|
|
|
775
785
|
run: |
|
|
776
786
|
set -euo pipefail
|
|
777
787
|
mkdir -p _skill
|
|
778
|
-
|
|
779
|
-
|
|
788
|
+
for i in $(seq 1 3); do
|
|
789
|
+
if timeout 120 gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
|
|
790
|
+
--pattern 'l337-docker-skill-*' --dir _skill --clobber; then
|
|
791
|
+
break
|
|
792
|
+
fi
|
|
793
|
+
echo "::notice::skill archive for $TAG not downloadable (attempt $i/3); waiting 10s"
|
|
794
|
+
sleep 10
|
|
795
|
+
done
|
|
780
796
|
shopt -s nullglob
|
|
781
797
|
tars=(_skill/*.tar.gz); sums=(_skill/*.sha256)
|
|
782
798
|
if [ "${#tars[@]}" -ne 1 ] || [ "${#sums[@]}" -ne 1 ]; then
|
|
@@ -802,7 +818,7 @@ jobs:
|
|
|
802
818
|
set -euo pipefail
|
|
803
819
|
url="https://registry.modelcontextprotocol.io/v0/servers?search=io.github.L337-org/docker-mcp-server"
|
|
804
820
|
for i in $(seq 1 5); do
|
|
805
|
-
if curl -fsS "$url" | jq -e --arg v "$VERSION" \
|
|
821
|
+
if curl -fsS --max-time 30 "$url" | jq -e --arg v "$VERSION" \
|
|
806
822
|
'.servers[] | select(.server.version == $v)' >/dev/null; then
|
|
807
823
|
echo "MCP Registry OK: ${VERSION} listed"; exit 0
|
|
808
824
|
fi
|
|
@@ -864,7 +880,7 @@ jobs:
|
|
|
864
880
|
|
|
865
881
|
- name: Set up uv
|
|
866
882
|
if: steps.plugin.outputs.has_plugin == 'true'
|
|
867
|
-
uses: astral-sh/setup-uv@
|
|
883
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
868
884
|
with:
|
|
869
885
|
# Pinned, so the lock is written by a known uv; a test in
|
|
870
886
|
# tests/test_claude_plugin_release.py keeps the rehearsal's pins equal to these.
|
|
@@ -938,7 +954,7 @@ jobs:
|
|
|
938
954
|
python-version: "3.14"
|
|
939
955
|
|
|
940
956
|
- name: Set up uv
|
|
941
|
-
uses: astral-sh/setup-uv@
|
|
957
|
+
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
942
958
|
with:
|
|
943
959
|
version: "0.11.21"
|
|
944
960
|
enable-cache: false
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: 'Report failures'
|
|
2
|
+
|
|
3
|
+
# Posts a failed run of a workflow named below, started by one of the events below, to the
|
|
4
|
+
# project's Slack channel through the shared reporter (architecture/ci.md). GitHub reads this
|
|
5
|
+
# trigger only from the default branch.
|
|
6
|
+
on:
|
|
7
|
+
workflow_run:
|
|
8
|
+
workflows:
|
|
9
|
+
- 'Weekly canary'
|
|
10
|
+
- 'CodeQL'
|
|
11
|
+
- 'Code review'
|
|
12
|
+
- 'Build container images'
|
|
13
|
+
- 'Check inbound changes'
|
|
14
|
+
- 'Release'
|
|
15
|
+
- 'Announce pull requests'
|
|
16
|
+
types: [completed]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
# Both read by the shared reporter, which GitHub refuses to start without them: to name the
|
|
20
|
+
# failed jobs and find a run that replaced a cancelled one, and to tell a tag from a branch.
|
|
21
|
+
actions: read
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
slack:
|
|
26
|
+
name: Report to Slack
|
|
27
|
+
uses: L337-org/github-workflows/.github/workflows/slack-on-failure.yaml@v2
|
|
28
|
+
with:
|
|
29
|
+
channel: '#docker-mcp'
|
|
30
|
+
events: '["schedule", "push", "release", "pull_request"]'
|
|
31
|
+
secrets:
|
|
32
|
+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Installed with `uv run pre-commit install` (CONTRIBUTING.md). Local hooks through `uv run`, not the
|
|
2
|
+
# ruff-pre-commit repo, so the hook runs the ruff version uv.lock resolves for the dev group rather than a
|
|
3
|
+
# second pin that can drift from it.
|
|
4
|
+
repos:
|
|
5
|
+
- repo: local
|
|
6
|
+
hooks:
|
|
7
|
+
- id: ruff-check
|
|
8
|
+
name: ruff check --fix
|
|
9
|
+
entry: uv run ruff check --fix
|
|
10
|
+
language: system
|
|
11
|
+
types_or: [python, pyi]
|
|
12
|
+
require_serial: true
|
|
13
|
+
- id: ruff-format
|
|
14
|
+
name: ruff format
|
|
15
|
+
entry: uv run ruff format
|
|
16
|
+
language: system
|
|
17
|
+
types_or: [python, pyi]
|
|
18
|
+
require_serial: true
|
|
@@ -37,7 +37,7 @@ Read the linked file before judging a substantive change to that area.
|
|
|
37
37
|
| code calling `docker` | [architecture/docker-sdk.md](architecture/docker-sdk.md) | an unverified method; a hand-built route that is not in the Engine API spec; a reach-in past the public SDK introduced without recorded sign-off |
|
|
38
38
|
| `Dockerfile`, `manifest.json`, `server.json`, `claude-plugin/`, release workflows | [architecture/distribution.md](architecture/distribution.md) | a version-declaring file missing from the bump checklist there, `preflight`, or the version tests; a registry ownership marker no longer matching `server.json`'s `name` |
|
|
39
39
|
| `skills/l337-docker/` | [architecture/agent-skill.md](architecture/agent-skill.md) | the skill described as equivalent to the server; tool-permission frontmatter added; a hand-edited figure in `MCP_VS_SKILLS.md` |
|
|
40
|
-
| `.github/workflows/` | [architecture/ci.md](architecture/ci.md) | a `uses:` naming a tag or branch; a new job without `timeout-minutes` where the workflow requires one |
|
|
40
|
+
| `.github/workflows/` | [architecture/ci.md](architecture/ci.md) | a third-party `uses:` naming a tag or branch (`L337-org/github-workflows` is named at its major-version tag); a new job without `timeout-minutes` where the workflow requires one |
|
|
41
41
|
|
|
42
42
|
## Project
|
|
43
43
|
|
|
@@ -136,7 +136,10 @@ Any tool wrapping a `docker` CLI feature (Compose, Stack, Buildx, Scout, Context
|
|
|
136
136
|
`docker_mcp/tools/_cli.py:run_docker` — never call `subprocess.run` directly from a tool module. That
|
|
137
137
|
helper is the single place holding binary resolution, `shell=False`, UTF-8 decoding, the output byte
|
|
138
138
|
cap, Windows console suppression and the environment allow-list. Always pass an explicit `timeout=`.
|
|
139
|
-
Never pass `shell=True`, never build paths by string concatenation, never expand
|
|
139
|
+
Never pass `shell=True`, never build paths by string concatenation, and never expand a glob or a
|
|
140
|
+
variable. Expand only a leading `~`, only where the tool documents it (`buildx_build`'s path
|
|
141
|
+
parameters), and only with `os.path.expanduser`: pathlib's `expanduser()` raises a bare
|
|
142
|
+
`RuntimeError` for an unknown `~user`, where `os.path` leaves the value for the CLI to report.
|
|
140
143
|
|
|
141
144
|
**Read [architecture/cli-shell-out.md](architecture/cli-shell-out.md) before touching any of it**, and
|
|
142
145
|
before adding a CLI-backed tool. It covers the SSH remote-exec fallback (which changes *which host* a
|
|
@@ -152,8 +155,9 @@ module method you have not confirmed in the docs. Do not assume a method exists
|
|
|
152
155
|
plausible; if you cannot confirm it, say so and do not use it.
|
|
153
156
|
|
|
154
157
|
Prefer the high-level object API; drop to `_get_client().api` only for documented gaps, verified the
|
|
155
|
-
same way. **"The method exists" and "the method works" are separate claims** —
|
|
156
|
-
standing example of a documented, importable SDK method that 404s against
|
|
158
|
+
same way. **"The method exists" and "the method works" are separate claims** — docker-py's
|
|
159
|
+
`Plugin.push()` is the standing example of a documented, importable SDK method that 404s against
|
|
160
|
+
every daemon.
|
|
157
161
|
|
|
158
162
|
**Reaching past the public SDK is not an agent's call to make.** Where the public path fails, stop,
|
|
159
163
|
write up what you found, and escalate for a human decision rather than implementing a workaround.
|
|
@@ -186,6 +190,17 @@ discovery layers. Four cleanup rounds have chased the same failure.
|
|
|
186
190
|
`timeout_seconds`. `tests/test_naming.py` enforces all of this (approved prefixes, banned short forms and
|
|
187
191
|
1.x spellings, canonical shared-param descriptions) — a violating tool fails CI.
|
|
188
192
|
- New Docker functionality goes in the matching `docker_mcp/tools/<domain>.py` file, not in a new file.
|
|
193
|
+
- **Waiting is one tool, `system_wait`.** A new thing to wait for is a new `kind` or `until` in `_WAIT_KINDS`
|
|
194
|
+
(`system.py`) with its helper in the owning domain's module, never a new `*_wait` tool and never a blocking
|
|
195
|
+
option on an action tool: action tools return once their work is started. The exceptions are
|
|
196
|
+
`container_exec` and `container_run(timeout_seconds=...)`: both serve running something quick and getting
|
|
197
|
+
the answer straight back, which would otherwise take several calls. Both are bounded; see
|
|
198
|
+
[architecture/server.md](architecture/server.md#waiting). `kind`/`until` are built from the
|
|
199
|
+
enabled domains at import, so a disabled domain's waits are not offered. Its `target` parameter is a
|
|
200
|
+
deliberate exception to the identifier spellings above, because one argument names a container, service,
|
|
201
|
+
node, stack, compose project or `repository:tag`. `system` and `containers` cannot be disabled
|
|
202
|
+
(`ALWAYS_ON_DOMAINS` in `server.py`); naming either in `DOCKER_MCP_SERVER_DISABLE` is reported as ignored,
|
|
203
|
+
not silently dropped.
|
|
189
204
|
- Every new `docker_mcp/tools/` file must be imported in `docker_mcp/tools/__init__.py` (private `_*.py` helpers excluded).
|
|
190
205
|
- Every new `docker_mcp/tools/<module>.py` must have a matching `tests/test_<module>.py`.
|
|
191
206
|
- Tool functions are decorated with `@tool()` (imported from `docker_mcp.server`) and must have a `TOOL_CATEGORIES` entry in `docker_mcp/server.py`.
|
|
@@ -237,7 +252,7 @@ discovery layers. Four cleanup rounds have chased the same failure.
|
|
|
237
252
|
|
|
238
253
|
Resources this server **creates** are stamped with `docker-mcp-server.*` provenance labels (`.managed=true`, `.version`, `.tool`, `.created`) via `docker_mcp/tools/_labels.py`, so the agent/operator can later enumerate that footprint — the `managed_only=True` arg on `container_list` / `network_list` / `volume_list` / `service_list`, or `--filter label=docker-mcp-server.managed=true`. The `prune_managed` prompt tears down only the managed footprint. Stamping is **on by default** and additive (a caller-supplied label always wins on a key collision); `DOCKER_MCP_SERVER_NO_LABELS=1` turns it off. The prefix is the bare project name (deliberately not reverse-DNS) and is a single constant in `_labels.py`.
|
|
239
254
|
|
|
240
|
-
When adding a new create tool that accepts a `labels` dict, route it through `_labels.py:with_provenance(labels, "<tool_name>")` (it accepts the dict/list/None shapes the SDK accepts and returns `None` — feed it through `drop_none` — when stamping is off and the caller passed nothing). The seven stamped creators today are `container_run`, `container_create`, `network_create`, `volume_create`, `service_create` (service-level `labels` only, not `container_labels`), `config_create`, `secret_create`. **Image builds are intentionally NOT stamped** — a build label changes the resulting image digest. Compose/stack containers (created via CLI shell-out) are also unstamped
|
|
255
|
+
When adding a new create tool that accepts a `labels` dict, route it through `_labels.py:with_provenance(labels, "<tool_name>")` (it accepts the dict/list/None shapes the SDK accepts and returns `None` — feed it through `drop_none` — when stamping is off and the caller passed nothing). The seven stamped creators today are `container_run`, `container_create`, `network_create`, `volume_create`, `service_create` (service-level `labels` only, not `container_labels`), `config_create`, `secret_create`. **Image builds are intentionally NOT stamped** — a build label changes the resulting image digest. Compose/stack containers (created via CLI shell-out) are also unstamped. The rule is conditional on the tool *accepting a `labels` dict*: a creator with nowhere to put a label is an expected exception, not an oversight, and should say so in its docstring. New `managed_only`-style label filters go through `_labels.py:managed_filter`.
|
|
241
256
|
|
|
242
257
|
## Invariants that fail silently
|
|
243
258
|
|
|
@@ -354,17 +369,14 @@ Removing an entry is a real decision, not a tidy-up.
|
|
|
354
369
|
**SDK surface deliberately not wrapped, or wrapped unobviously.** A recurring audit routine
|
|
355
370
|
re-proposes these; it has no memory of last time.
|
|
356
371
|
|
|
357
|
-
- **`plugin_push`'s hand-built endpoint call.** docker-py's `Plugin.push()` / `APIClient.push_plugin()`
|
|
358
|
-
both POST to `/plugins/{name}/pull`, a route the Engine does not define, so they 404 against every
|
|
359
|
-
daemon. Our call is the working path, not debt. Revisit only if upstream fixes the URL.
|
|
360
372
|
- **`Container.attach` / `attach_socket` / `resize` unwrapped.** An interactive bidirectional stream
|
|
361
373
|
does not fit a request/response tool call. `container_exec` covers scripted execution.
|
|
362
374
|
- **`service_rollback`'s `api.inspect_service` + `api.update_service`.** The high-level
|
|
363
375
|
`Service`/`ServiceCollection` expose no `rollback`. Permanently low-level.
|
|
364
376
|
- **`system_logout`'s `api._auth_configs`.** There is no `logout` anywhere in the SDK and no server-side
|
|
365
377
|
session to end. Permanently low-level.
|
|
366
|
-
- **`
|
|
367
|
-
|
|
378
|
+
- **`service_ps`'s `api.tasks()` with no service, and `service_logs(task=...)`'s hand-built
|
|
379
|
+
`GET /tasks/{id}/logs`.** docker-py has no task collection and no task-logs method at all.
|
|
368
380
|
|
|
369
381
|
**Other settled decisions.**
|
|
370
382
|
|
|
@@ -384,7 +396,7 @@ re-proposes these; it has no memory of last time.
|
|
|
384
396
|
(pyupgrade, 3.14 target) may rewrite to the unparenthesized form.
|
|
385
397
|
|
|
386
398
|
- **Image builds are not provenance-stamped.** A build label changes the resulting image digest.
|
|
387
|
-
Compose and stack containers
|
|
399
|
+
Compose and stack containers are unstamped for their own recorded reasons.
|
|
388
400
|
- **Cross-host redirects are followed on purpose.** Registries answer blob fetches with a redirect to a
|
|
389
401
|
CDN on another host as normal operation, so refusing them breaks `registry_image_config`. httpx
|
|
390
402
|
strips `Authorization` on any cross-origin redirect, and a redirect reaches nothing a tool argument
|
|
@@ -471,9 +483,8 @@ afterthought: at this server's size it is the dominant cost of connecting to it
|
|
|
471
483
|
`tests/test_surface_budget.py` holds the ceilings: per tool, per component, and in total.
|
|
472
484
|
|
|
473
485
|
**What is measured is the wire form, not the docstring.** A tool costs its name, its description
|
|
474
|
-
and its whole input schema,
|
|
475
|
-
|
|
476
|
-
change that added twenty parameters.
|
|
486
|
+
and its whole input schema, so a docstring-only budget would go green on a change that added
|
|
487
|
+
twenty parameters. `tests/test_surface_budget.py`'s header gives the command that measures both.
|
|
477
488
|
|
|
478
489
|
**Bytes, not tokens.** Tokens are what a model pays, but they need a tokenizer pinned to a model
|
|
479
490
|
that changes underneath us, so the figure moves without the surface moving. That is why
|
|
@@ -8,7 +8,7 @@ This is an [MCP](https://modelcontextprotocol.io) server: it exposes Docker oper
|
|
|
8
8
|
|
|
9
9
|
The practical mental model for finding your way around:
|
|
10
10
|
|
|
11
|
-
- One file per Docker feature area (`containers.py`, `images.py`, `swarm.py`, ...), each backed by either the [docker-py SDK](https://docker-py.readthedocs.io/en/stable/) or a `docker` CLI shell-out (`compose.py`, `stack.py
|
|
11
|
+
- One file per Docker feature area (`containers.py`, `images.py`, `swarm.py`, ...), each backed by either the [docker-py SDK](https://docker-py.readthedocs.io/en/stable/) or a `docker` CLI shell-out (`compose.py`, `stack.py` for `stack_deploy` only, `context.py`, `buildx.py`, `scout.py`). See the tree below for the full map.
|
|
12
12
|
- Every tool function is registered centrally in `docker_mcp/server.py`, which is also where read-only/destructive classification, naming enforcement, and the domain-disable switches live.
|
|
13
13
|
- `AGENTS.md` (repo root) is the always-loaded brief for every assistant, and the map of everything else: it carries the conventions and a table pointing at the deeper notes. Those notes live in `architecture/` - one file per area (`server.md`, `hosts.md`, `cli-shell-out.md`, `tool-descriptions.md`, `docker-sdk.md`, `distribution.md`, `agent-skill.md`, `ci.md`), and they are the canonical detail for humans and assistants alike. This file (`CONTRIBUTING.md`) covers the practical day-to-day: setup, the checklist for adding a tool, testing conventions, and submitting changes. If something isn't covered here, look at `AGENTS.md`'s table and follow it into `architecture/`.
|
|
14
14
|
|
|
@@ -35,7 +35,7 @@ The practical mental model for finding your way around:
|
|
|
35
35
|
│ ├── swarm.py
|
|
36
36
|
│ ├── plugins.py
|
|
37
37
|
│ ├── compose.py # `docker compose` CLI plugin (shells out via _cli.py)
|
|
38
|
-
│ ├── stack.py #
|
|
38
|
+
│ ├── stack.py # Compose-on-Swarm: `docker stack deploy` via _cli.py, the rest via docker-py
|
|
39
39
|
│ ├── context.py # `docker context` CLI (shells out via _cli.py)
|
|
40
40
|
│ ├── buildx.py # `docker buildx` CLI plugin (shells out via _cli.py)
|
|
41
41
|
│ ├── scout.py # `docker scout` CLI plugin (shells out via _cli.py)
|
|
@@ -38,7 +38,7 @@ The only host prerequisite is Docker. Point your MCP client at `docker run`:
|
|
|
38
38
|
|
|
39
39
|
## Image variants
|
|
40
40
|
|
|
41
|
-
Two multi-arch (linux/amd64 + linux/arm64) variants are published, both built from one `Dockerfile`. The CLI-backed domains (Compose, Stack, Buildx, Scout, Context) shell out to the `docker` CLI and its plugins.
|
|
41
|
+
Two multi-arch (linux/amd64 + linux/arm64) variants are published, both built from one `Dockerfile`. The CLI-backed domains (Compose, Stack, Buildx, Scout, Context) shell out to the `docker` CLI and its plugins; in Stack, only `stack_deploy` does.
|
|
42
42
|
|
|
43
43
|
| Variant | Tags | Approx. size | Includes |
|
|
44
44
|
|---------|------|-------------|----------|
|
|
@@ -55,13 +55,19 @@ ARG DISABLE_DOMAINS=""
|
|
|
55
55
|
ARG SCOUT_VERSION=1.21.0
|
|
56
56
|
|
|
57
57
|
# Install the docker CLI + compose/buildx plugins (and optionally scout) for the CLI-backed domains.
|
|
58
|
-
# Skipped entirely for the `lite` variant (INSTALL_CLI=0).
|
|
58
|
+
# Skipped entirely for the `lite` variant (INSTALL_CLI=0). Every curl download is bounded and
|
|
59
|
+
# retried, because one 504 from GitHub's release CDN or download.docker.com otherwise fails the
|
|
60
|
+
# whole build; curl's --retry only retries timeouts and HTTP 408, 429, 500, 502, 503 and 504, so a
|
|
61
|
+
# 404 or a bad checksum still fails. --speed-limit/--speed-time abandon a stalled attempt after
|
|
62
|
+
# 30 s, and --max-time caps a slow one, so it must stay large enough for the scout tarball; both
|
|
63
|
+
# apply per attempt, not in total. apt's own fetches follow apt's retry settings, which this does
|
|
64
|
+
# not change.
|
|
59
65
|
RUN if [ "$INSTALL_CLI" = "1" ]; then \
|
|
60
66
|
set -eux; \
|
|
61
67
|
apt-get update; \
|
|
62
68
|
apt-get install -y --no-install-recommends ca-certificates curl gnupg; \
|
|
63
69
|
install -m 0755 -d /etc/apt/keyrings; \
|
|
64
|
-
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc; \
|
|
70
|
+
curl -fsSL --connect-timeout 30 --speed-limit 102400 --speed-time 30 --max-time 300 --retry 3 https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc; \
|
|
65
71
|
chmod a+r /etc/apt/keyrings/docker.asc; \
|
|
66
72
|
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian %s stable\n' \
|
|
67
73
|
"$(dpkg --print-architecture)" "$DEBIAN_RELEASE" > /etc/apt/sources.list.d/docker.list; \
|
|
@@ -72,8 +78,8 @@ RUN if [ "$INSTALL_CLI" = "1" ]; then \
|
|
|
72
78
|
scout_tar="docker-scout_${SCOUT_VERSION}_linux_${scout_arch}.tar.gz"; \
|
|
73
79
|
scout_base="https://github.com/docker/scout-cli/releases/download/v${SCOUT_VERSION}"; \
|
|
74
80
|
mkdir -p /usr/libexec/docker/cli-plugins; \
|
|
75
|
-
curl -fsSL "${scout_base}/${scout_tar}" -o "/tmp/${scout_tar}"; \
|
|
76
|
-
curl -fsSL "${scout_base}/docker-scout_${SCOUT_VERSION}_checksums.txt" -o /tmp/scout_checksums.txt; \
|
|
81
|
+
curl -fsSL --connect-timeout 30 --speed-limit 102400 --speed-time 30 --max-time 300 --retry 3 "${scout_base}/${scout_tar}" -o "/tmp/${scout_tar}"; \
|
|
82
|
+
curl -fsSL --connect-timeout 30 --speed-limit 102400 --speed-time 30 --max-time 300 --retry 3 "${scout_base}/docker-scout_${SCOUT_VERSION}_checksums.txt" -o /tmp/scout_checksums.txt; \
|
|
77
83
|
(cd /tmp && grep -F "${scout_tar}" scout_checksums.txt | sha256sum -c -); \
|
|
78
84
|
tar -xzf "/tmp/${scout_tar}" -C /usr/libexec/docker/cli-plugins docker-scout; \
|
|
79
85
|
chmod +x /usr/libexec/docker/cli-plugins/docker-scout; \
|