feat(dockhand): support build-time dependency constraints via build_with - #827
feat(dockhand): support build-time dependency constraints via build_with#827danbarr wants to merge 4 commits into
Conversation
Exposes ToolHive's --build-with mechanism (PEP 508 constraints on uvx:// transitive dependencies, e.g. "mcp<2") through spec.yaml, so Dockyard can pin dependencies that upstream packages leave unbounded without waiting on an upstream release.
adb-mysql-mcp-server depends on mcp[cli]>=1.8.0 with no upper bound. mcp 2.0.0 removed the mcp.server.fastmcp module this server imports at startup, breaking the smoke test canary in build-containers.yml.
🛡️ Skill Security Scan Results |
The security scan invokes uvx directly against the raw package, bypassing the constrained container image entirely, so pinning build_with in spec.yaml didn't help it. Pass the same PEP 508 constraints through to uvx's own --with flag so the scanner exercises the same dependency set as the built image. Also fixes --stdio-arg tokens that start with "-" (like "--with") being misread as a new flag by argparse, the same problem npx's --yes already worked around.
🔒 MCP Security Scan Results✅ adb-mysql-mcp-server
Summary: Scanned 1 MCP server(s), all passed security checks. ✅ |
|
Superseded by #669, closing.
#669 uses the real override mechanisms ( |
Summary
build_withfield tospec.yaml, wired throughdockhand's Dockerfile generation into ToolHive's existingtemplates.RuntimeConfig.BuildWithsupport (--build-within thethvCLI). This lets auvx://server's spec pin PEP 508 constraints on transitive dependencies (e.g.mcp<2) that the upstream package itself leaves unbounded, without waiting on an upstream release.adb-mysql-mcp-server(the smoke test canary inbuild-containers.yml) has been failing to build withModuleNotFoundError: No module named 'mcp.server.fastmcp'. Root cause is upstream: it depends onmcp[cli]>=1.8.0with no upper bound, andmcp2.0.0 (just released) removed thefastmcpmodule it imports at startup. This PR fixes it by pinningbuild_with: ["mcp<2"]in itsspec.yaml.build_withis uvx-only, matching ToolHive's own restriction: ToolHive rejects (rather than silently ignores) a non-emptyBuildWithfornpx:///go://builds, and this rejection fires throughdockhandtoo since it happens before dry-run Dockerfile generation. Verified with a throwaway npx spec.mcp-security-scanCI job scans the package independently of the built container image, by invokinguvxdirectly against the raw package. Pinningbuild_withalone didn't fix that job, since it never went throughdockhand. Extendedscripts/mcp-scan/generate_mcp_config.pyto pass the same constraints viauvx --with, and fixedrun_scan.py's argparse handling for--stdio-argvalues that themselves start with-(same class of issue already worked around for npx's--yes).build_withindocs/adding-servers.md, including a Troubleshooting entry and a worked example based on theadb-mysql-mcp-serverfix.Note on overlap: #669 (branch
feat/dep-overrides, refs #668) already implements a similar mechanism, a hand-rolledspec.constraints/spec.overridesfield patched into the Dockerfile by content-matching. #741 carries a rebased copy of that same commit plus a follow-up that uses it fornext-devtools-mcp. Both PRs are open and unmerged. This PR takes a different approach, wiring through ToolHive's own native--build-withflag instead of maintaining our own patching/escaping logic, which seems the more correct long-term direction — flagging the overlap so it's visible during review.Test plan
go build ./cmd/dockhand/...succeedsdockhand build -c uvx/adb-mysql-mcp-server/spec.yamlgenerates a Dockerfile containinguv tool install --with 'mcp<2' ...docker buildof the generated Dockerfile installsmcp==1.29.0(not 2.0.0)docker run --rm adb-test --helpstarts cleanly and activates tools/resources with noModuleNotFoundErrorbuild_withon a non-uvx spec errors clearly rather than being silently ignoredgenerate_mcp_config.pyforadb-mysql-mcp-serveremitsuvx --with mcp<2 adb-mysql-mcp-server@2.0.0run_scan.pyagainst that config locally: scan completes (exit 0), all tools reported SAFE, noModuleNotFoundError--stdio-arg=--yeshandling is unaffected by therun_scan.pychange🤖 Generated with Claude Code