fix(serve,mcp,cli): serve correctness + MCP helper surface + secure-by-default auth#38
Merged
Merged
Conversation
…y-default auth
Consolidates a four-agent review of `contentrain serve` and the
MCP helpers it consumes. Fixes drift, ships the UI affordances that
make the fixes visible, hardens auth, and eliminates ~300 lines of
duplicated merge/diff logic by delegating to MCP.
### MCP — public helpers + empty-repo init
- New `branchDiff(projectRoot, { branch, base? })` exported from
`@contentrain/mcp/git/branch-lifecycle`. Defaults `base` to
`CONTENTRAIN_BRANCH` — the singleton content-tracking branch every
feature branch forks from. Replaces the CLI's duplicated
`git.diff([${defaultBranch}...${branch}])` calls, which surfaced
unrelated historical content changes once contentrain advanced
past the repo's default branch.
- `tools/setup.ts` `contentrain_init` now handles greenfield
directories: seeds an `--allow-empty` initial commit when the repo
has zero commits so `ensureContentBranch` has a base ref to
anchor on. Previously the CLI `init` command created this commit
manually while the MCP tool skipped the step — the tool failed
on an empty directory the CLI handled fine.
- Tests: new `tests/git/branch-lifecycle.test.ts` (3 cases) +
new setup case (`initialises a greenfield directory with no .git
and no commits`).
### Serve server — correctness + new routes + auth
- Three duplicated merge-via-worktree implementations
(`/api/branches/approve`, `/api/normalize/approve`, CLI `diff`)
replaced with calls to MCP's `mergeBranch()` helper. Runs the
worktree transaction with selective sync + dirty-file protection.
Skipped files are cached and surfaced to the UI via a new
`sync:warning` WebSocket event and a new
`/api/branches/:name/sync-status` route. Merge conflicts now
broadcast `branch:merge-conflict` instead of silently succeeding.
- `/api/branches/diff` delegates to the new `branchDiff()` helper
with CONTENTRAIN_BRANCH as the default base.
- History filter tolerates BOTH legacy `Merge branch 'contentrain/'`
and current `Merge branch 'cr/'` commit patterns so post-Phase-7
history doesn't drop merges.
- `.catch(() => {})` error-swallowing at three sites removed.
Merge conflicts and cleanup failures no longer pretend to
succeed.
- Normalize plan approve broadcasts `branch:created` on the
returned `git.branch` metadata (parity with content save route).
- New `/api/capabilities` route — provider type, transport,
capability manifest, branch health, repo info in one call.
Dashboard consumes this to render the capability badge.
- New `/api/branches/:name/sync-status` — on-demand sync warning
fetch for the branch detail page; 1h TTL cache in memory.
- New WebSocket events: `branch:rejected`, `branch:merge-conflict`,
`sync:warning`.
- Zod input validation on every write route via new
`serve/schemas.ts`. Catches malformed bodies with structured 400
errors before they reach the MCP tool layer. Adds `zod` as a
direct CLI dependency.
- `serve` command — secure-by-default auth. Binding to a
non-localhost interface now HARD ERRORS when no `--authToken` is
set. No `--allow-unsafe` opt-out flag (OWASP Secure-by-Default;
matches Postgres, helm, kubectl port-forward conventions).
### Serve UI — level-ups that make the fixes visible
- `useWatch.ts` — WSEvent union widened for the new event types
(branch:rejected, branch:merge-conflict, sync:warning, connected).
- `project` store — `capabilities` state + `branchHealthAlarm`
computed + `fetchCapabilities()` action.
- `AppLayout` — global branch-health banner (warning / blocked
thresholds from MCP), sync-warning toasts with "View details"
action deep-linking to the branch detail page, merge-conflict
toasts with the failure message.
- `DashboardPage` — capability badge (provider type · transport)
next to the workflow + stack badges.
- `BranchDetailPage` — sync warnings panel listing every file the
selective sync skipped and why, rendered only when the last
mergeBranch() call produced warnings.
- `ValidatePage` — issues are now clickable when a `model` is
present; deep-links to the content list filtered to the issue's
`locale` / `id` / `slug`. Fallback to the model list for
aggregate issues (e.g. i18n parity).
### CLI — delegation to MCP helpers
- `commands/diff.ts` — both the per-branch summary line and the
merge confirmation call `branchDiff()` / `mergeBranch()` from
MCP. Surfaces `sync.skipped[]` warnings to the user with the
list of preserved files. Removes the duplicated
`contentrain` branch + worktree + update-ref + checkout dance
(~45 lines).
- `commands/doctor.ts` — branch health check delegates to MCP's
`checkBranchHealth()`. Previously filtered `contentrain/*`
directly after the Phase 7 naming migration to `cr/*`, so the
check silently reported "None" no matter how many branches had
accumulated. The delegation uses MCP's canonical thresholds
(warn at 50, block at 80).
- `commands/validate.ts` non-interactive path — captures
`tx.complete()` result and surfaces the branch name + workflow
action in review mode. Previously this metadata was dropped; the
user ran `contentrain validate --fix`, saw "fixed 3 issues", and
had no indication that the fix landed on a `cr/fix/validate/...`
branch that needed manual review.
### Verification
- `pnpm -r typecheck` → 0 errors across 8 packages.
- `oxlint` monorepo → 0 warnings across 399 files.
- `vue-tsc --noEmit` serve-ui → 0 errors.
- `pnpm --filter @contentrain/mcp build` +
`pnpm --filter contentrain build:cli-only` → clean.
- MCP fast suite → 443/443 green, 2 skipped.
### Tool surface
No changes. Same 16 MCP tools, same arg schemas, same JSON response
shapes. Stdio + LocalProvider flows behave identically to the
previous release.
### Changeset
`.changeset/phase-13-serve-correctness-levelup.md` — minor bump for
both `@contentrain/mcp` (new `branchDiff` export, empty-repo init
fix) and `contentrain` (new serve capabilities + secure auth + zod
dependency).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation The Phase 13 commit delegated CLI diff/merge paths to MCP helpers and hardened serve auth, but three CLI test files locked in the pre-refactor behavior and failed CI: - `tests/commands/diff.test.ts` — asserted diffs against `main` (default branch) and legacy `contentrain/review/hero` branch naming. Rewritten to mock `@contentrain/mcp/git/branch-lifecycle` branchDiff + `@contentrain/mcp/git/transaction` mergeBranch, assert the delegation, and use `cr/*` branch naming throughout. - `tests/commands/serve.test.ts` — mocked consola without `.error`, so the new "refuse to start on 0.0.0.0 without --authToken" path crashed at `consola.error(...)`. Mock now includes `error`; the existing `--host 0.0.0.0` test passes `authToken` to exercise the success path; a new test asserts the hard-error path. - `tests/integration/serve.integration.test.ts` — asserted the old merge-via-worktree dance (worktree add + update-ref + checkout). Rewritten to mock the MCP helpers instead, verify the delegation call shapes, and assert `sync:warning` + `branch:merge-conflict` WS broadcasts land on the fake client. CI target: `contentrain` package vitest run → 119/119 green (previously 111/117 with 6 failures; 8 new assertions added for the secure-auth + merge-conflict + sync-warning paths). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Consolidates a four-agent review of
contentrain serveand the MCP helpers it consumes. Fixes drift from the MCP refactor, ships the UI affordances that make the fixes visible, hardens auth to secure-by-default, and eliminates ~300 lines of duplicated merge/diff logic by delegating to MCP.Ships as a single PR because the drift fixes are invisible without the UI that surfaces them — sync warnings, capability badge, branch-health banner all light up the backend work.
What's in the box
MCP — new public helpers
branchDiff(projectRoot, { branch, base? })in@contentrain/mcp/git/branch-lifecycle. DefaultsbasetoCONTENTRAIN_BRANCHso diffs don't pick up unrelated historical content oncecontentrainhas advanced past the repo's default branch.contentrain_initempty-repo fix — seeds--allow-emptyinitial commit when the directory has zero commits, soensureContentBranchcan fork. CLI handled this manually before; tool failed on greenfield.Serve server correctness
mergeBranch()from MCP. Dirty-file protection + selective sync come back;sync.skipped[]surfaces via newsync:warningWS event +/api/branches/:name/sync-statusroute.branchDiff()helper, base =CONTENTRAIN_BRANCH.contentrain/and currentcr/merge commit patterns..catch(() => {})error-swallowing removed at 3 sites; merge-conflict broadcasts replace silent success.branch:createdlike content save does./api/capabilities— provider / transport / capabilities / branch health / repo in one call.branch:rejected,branch:merge-conflict,sync:warning.serve/schemas.ts.Secure-by-default auth
contentrain serveon a non-localhost interface HARD ERRORS when--authTokenisn't set. No opt-out flag (OWASP Secure-by-Default — matches Postgres, helm, kubectl port-forward conventions).Serve UI level-ups
AppLayout— global branch-health banner + sync-warning toasts with "View details" deep-link + merge-conflict toasts with failure messageDashboardPage— capability badge (provider type · transport)BranchDetailPage— sync warnings panel listing skipped files + reasonValidatePage— issues clickable, deep-link to content entry (locale/id/slug)projectstore — capabilities state +branchHealthAlarmcomputedCLI delegation
commands/diff.ts—branchDiff()+mergeBranch(). Removes ~45 lines of duplicated worktree dance. Surfaces skipped files.commands/doctor.ts— delegates tocheckBranchHealth()MCP helper. Previously filteredcontentrain/*(legacy, no matches post-Phase-7), silently reported "None" regardless of actual branch count.commands/validate.ts— non-interactive path capturestx.complete()metadata and surfaces branch name + workflow action in review mode. Previously dropped silently.Verification
pnpm -r typecheck→ 0 errors (8 packages)oxlintmonorepo → 0 warnings (399 files)vue-tsc --noEmitserve-ui → 0 errorspnpm --filter @contentrain/mcp build+pnpm --filter contentrain build:cli-only→ cleanbranch-lifecycle.test.ts(3 cases) + new setup empty-repo case.Changeset
.changeset/phase-13-serve-correctness-levelup.md—@contentrain/mcpminor (newbranchDiff+ empty-repo fix) +contentrainminor (serve correctness + level-ups + secure auth +zoddependency).Tool surface
No changes. Same 16 MCP tools, same arg schemas, same response JSON.
🤖 Generated with Claude Code