Containerize truecopy-mcp for Glama / image-based MCP hosts#72
Conversation
truecopy-mcp is a gate, not a standalone server: with nothing pinned it correctly drops every tool, so a bare container introspects an empty tools/list and fails hosts (e.g. Glama) that require a working server. Ship a Dockerfile that wraps the MCP reference server (@modelcontextprotocol/server-everything) and pins its tools at build time — docker/pin-everything.mjs captures the live tools/list and feeds it to `truecopy add`, so at runtime the same server advertises byte-identical tools and the gate serves all 13 as `vetted`. Both versions are pinned via build args for a reproducible image and lock. Verified: image builds and `docker run -i` answers initialize + tools/list with 13 vetted tools over stdio. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sprayberry-reviewer
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Verdict: No blocking issues found — approving.
What I checked
gh pr diff 72in full (5 files:.dockerignore,CHANGELOG.md,Dockerfile,README.md,docker/pin-everything.mjs).- Cross-referenced the new
Dockerfile/pin-everything.mjsagainst the existing CLI entrypoints (src/cli.mjsrunAdd,src/mcp-cli.mjs) andsrc/skill.mjs's MCP-manifest loader to confirm thetruecopy add everything.json --lock .../truecopy-mcp --lock ... --name ... -- mcp-server-everything stdioinvocations match the real CLI argument parsing. - Verified
mcp-server-everything's actual bin entry and CLI usage (dist/index.js [stdio|sse|streamableHttp], defaultstdio) against npm registry metadata for@modelcontextprotocol/server-everything@2026.7.4— matches the pinnedARG EVERYTHING_VERSIONand thestdioarg passed in both theRUNpin step and theENTRYPOINT. - Verified
@askalf/truecopy@0.9.0's registrybinmap (truecopy,truecopy-mcp) matches what the Dockerfile/README invoke. - CI check-rollup: all 9 checks green (CI ×6 matrix jobs, CodeQL ×2, "verify pinned skills" gate) as of the latest commit.
Findings
None blocking. docker/pin-everything.mjs's JSON-RPC handshake (send initialize id:1 → on ack send notifications/initialized + tools/list id:2 → capture result.tools) is correctly ordered, guards non-JSON banner lines, fails closed on an empty/missing tool array, and has a 60s timeout for a hung downstream. The Dockerfile's arg parsing (everything -- mcp-server-everything stdio) is consumed correctly by the script's argv.indexOf('--') split.
Minor (non-blocking): pin-everything.mjs only wires srv.on('error', ...) for a spawn failure (e.g. ENOENT). If the downstream process starts but crashes immediately after (exits without ever writing a tools/list response), the script has no exit/close handler and relies on the 60s timeout to fail closed rather than surfacing the crash immediately. Not a correctness bug — it does still fail closed — just a slower failure mode than necessary for a build-time script.
What's good
- The Dockerfile and script are well-commented and self-explanatory about why (gate-needs-something-to-gate), which matches the PR description precisely.
- Version pins (
TRUECOPY_VERSION,EVERYTHING_VERSION) verified against the actual published npm versions — reproducible as claimed. .dockerignorecorrectly scopes the build context to just the one script the image needs, consistent with installingtruecopy/server-everythingfrom npm rather than copying the working tree.- README/CHANGELOG additions accurately describe the new container path and don't overstate what it does.
…context — scope it to the MCP image (#76) * ci(fuzz): scope the docker ignore to the MCP image — root .dockerignore blanked ClusterFuzzLite's build context * ci(fuzz): drop root .dockerignore (moved to Dockerfile.dockerignore)
Why
The awesome-mcp-servers listing (punkpeye/awesome-mcp-servers#10332) is gated by Glama's check, which builds an image and sends an MCP introspection (
initialize+tools/list). truecopy had no Dockerfile, and a naive one wouldn't pass anyway:truecopy-mcpis a gate, not a server — it filters a downstream server's tools down to the pinned set. With nothing pinned, every tool isunpinned→ dropped, sotools/listcomes back empty and the check hollows out.What
A
Dockerfilethat wraps the MCP reference server (@modelcontextprotocol/server-everything) and pins its tools at build time:docker/pin-everything.mjslaunches the downstream, captures its livetools/list, and emits a truecopy manifest.truecopy addpins those tools into/app/truecopy.lock. SincepartsOfand the runtime gate hash tools with the samesha256(canonicalJson(t)), the same server at runtime advertises byte-identical tools → all served asvetted.ARGfor a reproducible image + lock.This doubles as a live demo of the gate: a real server, passed through untouched, with drift/poison dropped.
Verified locally
Files
Dockerfile,.dockerignoredocker/pin-everything.mjs— build-time tool-capture helper🤖 Generated with Claude Code