Skip to content

[add] preview_deploy for MCP - #116

Merged
malickyeu merged 1 commit into
mainfrom
feat/mcp-preview-deploy
Aug 3, 2026
Merged

[add] preview_deploy for MCP#116
malickyeu merged 1 commit into
mainfrom
feat/mcp-preview-deploy

Conversation

@malickyeu

Copy link
Copy Markdown
Contributor

Summary

docker compose up is the moment a change becomes real, and until now the only
way to learn what it would do was to do it. preview_deploy turns that into a
question 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

  • Bug fix
  • New feature
  • Docs only
  • Refactor / chore

Checklist

  • go test -short ./... and go vet ./... pass
  • gofmt gate is clean (gofmt -l $(git ls-files '*.go') after staging)
  • Frontend type-checks — N/A (no UI changed)
  • Rebuilt and committed web/dist — N/A
  • Added/updated tests for the change
  • Updated docs/ and added a CHANGELOG.md entry

Notes 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 rm or a deploy that half-failed.

Two claims it deliberately declines to make, both of which would make it cry
wolf and get ignored:

  • A service that builds locally has no image in the resolved config until it
    has been built. Comparing "" against the running image would report an image
    change on every single preview.
  • An orphaned service is described as left running, not removed. The app does
    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 section
gate is still enforced (TestPreviewDeployStillNeedsTheProjectsSection).

The comparison logic lives in internal/docker and 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.

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.
Copilot AI review requested due to automatic review settings August 3, 2026 09:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_deploy MCP 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 thread internal/mcp/server.go
Comment on lines 143 to 148
h.registerReadTools(srv)
h.registerAlertTools(srv)
h.registerDiagnosticTools(srv)
h.registerParityTools(srv)
h.registerPreviewTool(srv)
h.registerControlTools(srv)
@malickyeu
malickyeu merged commit db3065f into main Aug 3, 2026
4 checks passed
@malickyeu
malickyeu deleted the feat/mcp-preview-deploy branch August 3, 2026 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants