[add] preview_deploy for MCP - #116
Merged
Merged
Conversation
What a deploy would change, without deploying: services created, recreated with a different image, or running but no longer in the file. Compares against the CONTAINERS actually running rather than a stored record of the last deploy. A record says what someone last asked for; the containers say what is there, and those differ precisely when it matters — after a manual removal, or a deploy that half-failed. Two claims it declines to make. A service that builds locally has no image in the resolved config until it is built, so comparing that against a running image would report a change on every preview. And an orphan is described as LEFT RUNNING, not removed: the app does not pass --remove-orphans, and the alarming version would also be false. Gated as a read. A preview has to be cheaper to reach than the deploy it protects, so a read-only token can look even though it cannot leap; mutation-tested in both directions. Remote-host projects are refused here as they are for deploy, since previewing one means listing that host's containers. Lifting that is one change for both and belongs with it.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new MCP tool (preview_deploy) that reports what a managed project deploy would change by comparing resolved Compose services to currently running containers, including returning invalid-compose as a structured result.
Changes:
- Introduces
preview_deployMCP tool (read-gated) plus handler wiring and tests. - Adds daemon-independent comparison logic in
internal/docker(parse resolved services, diff vs running, stable ordering) with unit tests. - Wires the API implementation to resolve compose config, list stacks, and produce a
ProjectPreview, plus docs/changelog updates.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/mcp/tools_parity.go | Adds preview_deploy tool registration + handler. |
| internal/mcp/tools_parity_test.go | Adds unit tests covering read-only access and input validation for preview_deploy. |
| internal/mcp/server.go | Introduces ProjectPreview type and wires tool registration into server setup. |
| internal/docker/preview.go | New comparison/parsing logic to build a deploy preview (changes + unchanged count). |
| internal/docker/preview_test.go | Unit tests for preview diff classification, ordering, and deduplication. |
| internal/api/server.go | Wires PreviewProject dependency into MCP deps. |
| internal/api/mcp_projects.go | Implements mcpPreviewProject to produce a ProjectPreview from compose+running containers. |
| docs/mcp.md | Documents preview_deploy tool behavior and read-only gating rationale. |
| CHANGELOG.md | Announces preview_deploy feature and key behavioral guarantees. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+237
to
+240
| if in.ProjectID <= 0 { | ||
| return nil, ProjectPreview{}, errors.New("project_id is required") | ||
| } | ||
| out, err := h.deps.PreviewProject(ctx, in.ProjectID) |
Comment on lines
+133
to
+141
| var running []docker.ServiceSpec | ||
| if stacks, serr := s.docker.ListStacks(ctx, p.HostID); serr == nil { | ||
| for i := range stacks { | ||
| if stacks[i].Project == p.Slug { | ||
| running = docker.RunningServices(&stacks[i]) | ||
| break | ||
| } | ||
| } | ||
| } |
Comment on lines
143
to
148
| h.registerReadTools(srv) | ||
| h.registerAlertTools(srv) | ||
| h.registerDiagnosticTools(srv) | ||
| h.registerParityTools(srv) | ||
| h.registerPreviewTool(srv) | ||
| h.registerControlTools(srv) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docker compose upis the moment a change becomes real, and until now the onlyway to learn what it would do was to do it.
preview_deployturns that into aquestion you can ask first: which services would be created, which would be
recreated with a different image, and which are running but no longer in the
compose file.
An invalid compose file comes back as a result rather than an error — it is the
single most useful thing a preview can report.
Type of change
Checklist
go test -short ./...andgo vet ./...passgofmtgate is clean (gofmt -l $(git ls-files '*.go')after staging)web/dist— N/Adocs/and added aCHANGELOG.mdentryNotes for reviewers
It compares against the containers that are actually running, not against a
stored record of the last deploy. A record tells you what somebody last asked
for; the containers tell you what is there — and the two diverge exactly when a
preview earns its keep, after a manual
docker rmor a deploy that half-failed.Two claims it deliberately declines to make, both of which would make it cry
wolf and get ignored:
has been built. Comparing
""against the running image would report an imagechange on every single preview.
not pass
--remove-orphans(a deliberate choice from the stack-redeploy work),so the alarming phrasing would also be the false one. There is a test asserting
the wording does not claim deletion.
Gated as a read, and that is a decision rather than an oversight: a preview
must be cheaper to reach than the deploy it protects. A read-only token can look
even though it cannot leap; gating it as a write would leave that principal
guessing, which is the opposite of the point. Mutation-tested in both directions —
flipping it to a write fails
TestPreviewDeployIsAReadNotAWrite, and the sectiongate is still enforced (
TestPreviewDeployStillNeedsTheProjectsSection).The comparison logic lives in
internal/dockerand is tested without a daemon,so the interesting cases — added, removed, image-changed, unchanged-and-therefore-
not-listed, build-only, scaled services collapsing to one row — are all covered by
fast tests rather than needing Docker.
Remote-host projects are refused here, exactly as they are for
deploy_project,because previewing one means listing that host's containers and MCP tokens carry no
per-host authorization yet. That is the next item in the agreed batch, and it lifts
both restrictions in one change.