Skip to content

fix(serve,mcp,cli): serve correctness + MCP helper surface + secure-by-default auth#38

Merged
ABB65 merged 2 commits into
next-mcpfrom
fix/serve-correctness-and-levelup
Apr 17, 2026
Merged

fix(serve,mcp,cli): serve correctness + MCP helper surface + secure-by-default auth#38
ABB65 merged 2 commits into
next-mcpfrom
fix/serve-correctness-and-levelup

Conversation

@ABB65

@ABB65 ABB65 commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

Consolidates a four-agent review of contentrain serve and 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. Defaults base to CONTENTRAIN_BRANCH so diffs don't pick up unrelated historical content once contentrain has advanced past the repo's default branch.
  • contentrain_init empty-repo fix — seeds --allow-empty initial commit when the directory has zero commits, so ensureContentBranch can fork. CLI handled this manually before; tool failed on greenfield.

Serve server correctness

  • Merge flow — 3 duplicated merge-via-worktree implementations replaced with mergeBranch() from MCP. Dirty-file protection + selective sync come back; sync.skipped[] surfaces via new sync:warning WS event + /api/branches/:name/sync-status route.
  • Branch diffbranchDiff() helper, base = CONTENTRAIN_BRANCH.
  • History filter tolerant of both legacy contentrain/ and current cr/ merge commit patterns.
  • .catch(() => {}) error-swallowing removed at 3 sites; merge-conflict broadcasts replace silent success.
  • Normalize plan approve now broadcasts branch:created like content save does.
  • New /api/capabilities — provider / transport / capabilities / branch health / repo in one call.
  • New WS events: branch:rejected, branch:merge-conflict, sync:warning.
  • Zod input validation on every write route via serve/schemas.ts.

Secure-by-default auth

contentrain serve on a non-localhost interface HARD ERRORS when --authToken isn'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 message
  • DashboardPage — capability badge (provider type · transport)
  • BranchDetailPage — sync warnings panel listing skipped files + reason
  • ValidatePage — issues clickable, deep-link to content entry (locale/id/slug)
  • project store — capabilities state + branchHealthAlarm computed

CLI delegation

  • commands/diff.tsbranchDiff() + mergeBranch(). Removes ~45 lines of duplicated worktree dance. Surfaces skipped files.
  • commands/doctor.ts — delegates to checkBranchHealth() MCP helper. Previously filtered contentrain/* (legacy, no matches post-Phase-7), silently reported "None" regardless of actual branch count.
  • commands/validate.ts — non-interactive path captures tx.complete() metadata and surfaces branch name + workflow action in review mode. Previously dropped silently.

Verification

  • pnpm -r typecheck → 0 errors (8 packages)
  • oxlint monorepo → 0 warnings (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. Includes new branch-lifecycle.test.ts (3 cases) + new setup empty-repo case.

Changeset

.changeset/phase-13-serve-correctness-levelup.md@contentrain/mcp minor (new branchDiff + empty-repo fix) + contentrain minor (serve correctness + level-ups + secure auth + zod dependency).

Tool surface

No changes. Same 16 MCP tools, same arg schemas, same response JSON.

🤖 Generated with Claude Code

Contentrain and others added 2 commits April 17, 2026 21:46
…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>
@ABB65 ABB65 merged commit 66b1dd6 into next-mcp Apr 17, 2026
1 check passed
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.

1 participant