Skip to content

feat(mcp,docs): studio handoff + embedding guide + two P2 fixes#37

Merged
ABB65 merged 2 commits into
next-mcpfrom
docs/studio-mcp-integration
Apr 17, 2026
Merged

feat(mcp,docs): studio handoff + embedding guide + two P2 fixes#37
ABB65 merged 2 commits into
next-mcpfrom
docs/studio-mcp-integration

Conversation

@ABB65

@ABB65 ABB65 commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

Phase 11 — closes the integration gap between the finished MCP refactor and the consumers that want to embed it. Studio-specific internal handoff is updated to match today's API, a public embedding guide lands under docs/guides/, and two P2 bugs surfaced while writing the handoff are fixed.

What's in the box

Studio handoff updated (internal)

.internal/refactor/02-studio-handoff.md was written at Phase 2 and had drifted through phases 3–10. Rewritten to the finished-refactor state:

  • Preconditions reflect the actual shipped subpath exports (core/ops, core/overlay-reader, core/contracts, providers/local)
  • Contracts canonical source is @contentrain/types, not @contentrain/mcp/core/contracts (re-export kept for back-compat)
  • S2 code samples use today's buildContextChange(reader, operation) + OverlayReader wrapping, provider.applyPlan({ base: CONTENTRAIN_BRANCH }) invariant
  • New "Phase 10 tuzakları" section listing the three traps (fork from contentrain, OverlayReader before context/validation, isNotFoundError is internal)
  • S6 (MCP Cloud Endpoint) reframed around reusing startHttpMcpServerWith with a Bearer/quota/metering Nitro middleware instead of a hand-rolled JSON-RPC layer
  • Provider selection matrix: Studio never uses LocalProvider, so normalize/scan/apply/init/scaffold/submit/merge/validate-fix/bulk always capability_required — capability gate handles it, Studio doesn't filter client-side

Public embedding guide (docs)

New docs/guides/embedding-mcp.md — consumer-agnostic integration guide sitting under the existing Providers / HTTP Transport pages. Four construction recipes, three critical primitives, auth model, extension point for custom providers, troubleshooting. Studio called out as reference integration alongside CI and scripted automation. Sidebar + top nav updated.

P2 — ApplyPlanInput.base contract alignment

Contract docstring said "defaults to provider's content-tracking branch", but GitHubProvider / GitLabProvider defaulted to the repo's default branch (main / master / trunk). Tests locked in the bug. MCP's own write path bypassed the issue by passing base: CONTENTRAIN_BRANCH explicitly, but any Studio-style direct provider.applyPlan call without base silently forked from main.

Both implementations now default to CONTENTRAIN_BRANCH. Tests rewritten to assert:

  • GitHub: repos.get NOT called, getRef resolves heads/contentrain
  • GitLab: Projects.show NOT called, startBranch: 'contentrain'

Docstring tightened in packages/types/src/provider.ts.

P2 — contentrain_status context on remote

Remote contentrain_status returned context: null unconditionally — Studio never saw lastOperation or stats despite .contentrain/context.json being committed by remote writes. readContext gains a reader overload; tools/context.ts uses it for remote sessions. HTTP E2E now seeds .contentrain/context.json and asserts the context surface through.

New public subpath exports

  • @contentrain/mcp/core/ops — plan helpers (planContentSave, planContentDelete, planModelSave, planModelDelete) + path helpers (contentDirPath, contentFilePath, documentFilePath, metaFilePath). Embedders need these to compose custom write paths.
  • @contentrain/mcp/core/overlay-reader — the OverlayReader primitive. Required for any non-local write path that needs buildContextChange / validateProject to see post-commit state.

Without these, a from-scratch Studio-style integration hits ERR_PACKAGE_PATH_NOT_EXPORTED on the first useful import.

Test plan

  • pnpm -r typecheck (8 packages) → 0 errors
  • oxlint (397 files) → 0 warnings
  • pnpm --filter @contentrain/mcp build → clean; dist includes new subpaths
  • node -e "import('@contentrain/mcp/core/ops')" + overlay-reader + contracts + providers/local → all four resolve
  • Fast suite → 440/440 green, 2 skipped
  • Provider + HTTP suites (tests/providers/github tests/providers/gitlab tests/server/http.test.ts) → 60/60 green (includes rewritten base-default + augmented remote-context E2E)

Changesets

.changeset/mcp-phase-11-studio-handoff.md@contentrain/mcp minor. Stacks with existing Phase 10 changesets into a single minor release.

Scope notes

  • Studio handoff lives in .internal/ which is a symlink to studio/.internal/ — the Studio repo picks it up automatically.
  • .changeset/types-provider-alignment.md from Phase 10 still carries the types minor; the docstring clarification in this PR doesn't warrant a separate changeset entry.

🤖 Generated with Claude Code

Contentrain and others added 2 commits April 17, 2026 18:56
…docs parity

Single PR that closes the gaps a four-agent review surfaced after
phases 0-9 landed. "Hiçbir şeyi atlamadan" — every finding is
addressed in this change, from cohesion polish to the docs drift the
refactor left in its wake.

### Provider contracts move to @contentrain/types

- packages/types/src/provider.ts (new) — RepoReader, RepoWriter,
  RepoProvider, ProviderCapabilities, LOCAL_CAPABILITIES, FileChange,
  Branch, Commit, CommitAuthor, FileDiff, MergeResult, ApplyPlanInput.
  Third-party tools can now implement a custom RepoProvider without
  a runtime dependency on @contentrain/mcp.
- packages/types/src/index.ts — repository.provider widened from
  `'github'` to `'github' | 'gitlab'`; all provider contracts
  re-exported from the root.
- packages/mcp/src/core/contracts/index.ts — now a thin barrel that
  re-exports from @contentrain/types. The per-file contract modules
  (branch.ts / capabilities.ts / file-change.ts / provider.ts /
  repo-reader.ts / repo-writer.ts) are deleted; existing MCP imports
  through `core/contracts` continue to work unchanged.

### P1 — remote write base branch invariant

- packages/mcp/src/tools/commit-plan.ts (new) extracts the
  LocalProvider vs remote RepoProvider dispatch from four repeated
  blocks in content.ts / model.ts into `commitThroughProvider`.
- Remote writes now always fork from CONTENTRAIN_BRANCH, matching the
  local transaction flow. Previously they used
  `config.repository.default_branch ?? 'contentrain'` — in any
  project that set `repository.default_branch: 'main'`, feature
  branches were silently forked from `main`, breaking the
  `contentrain` singleton invariant.

### P1 — stale remote context.json + validation

- packages/mcp/src/core/overlay-reader.ts (new) — OverlayReader wraps
  a RepoReader and layers pending FileChanges on top. Semantics:
  pending adds → visible; pending deletes → missing; base falls
  through for everything else. listDirectory merges additions into
  the base listing, removes deletes, and surfaces nested
  subdirectories for deeper pending paths.
- commit-plan.ts: `buildContextChange` is now called against an
  OverlayReader over the pending plan, so `stats.entries` /
  `stats.models` reflect the state the new commit produces instead
  of the pre-change base branch.
- tools/content.ts: post-save validateProject runs against the same
  overlay on remote, so the validation envelope matches the new
  content state. Local flow still uses projectRoot-based validation
  — the transaction layer has already written the worktree.

### P2 — read-only tools work over remote providers

- tools/context.ts (`contentrain_status`, `contentrain_describe`)
  and tools/content.ts (`contentrain_content_list`) no longer gate
  on `!projectRoot`. They route through `provider` and degrade
  gracefully when no project root is available: stack detection,
  branch health, and `content_list --resolve` stay local-only and
  are skipped / rejected with a descriptive error on remote.
- core/content-manager.ts: listContent gains a reader overload
  (`listContentViaReader`) that handles all four kinds (singleton,
  collection, document, dictionary). Relation hydration
  (`opts.resolve`) still requires local filesystem access because
  the walk touches other models.

### P2 — public export surface unbroken

- packages/mcp/package.json: adds `./core/contracts` and
  `./providers/local` to `exports` and the build targets. Both paths
  were referenced in package docstrings since phase 5 but were never
  publishable — external imports failed with
  `ERR_PACKAGE_PATH_NOT_EXPORTED`. Now they resolve.

### Cohesion — provider cleanup

- packages/mcp/src/providers/shared/ (new) consolidates helpers that
  were duplicated across GitHub + GitLab:
  - `errors.ts:isNotFoundError` — unified strict-status-based check.
    Removes four per-provider `isNotFound` helpers and GitLab's
    lenient description-match fallback that risked masking non-404
    failures silently.
  - `paths.ts:normaliseContentRoot + resolveRepoPath` — one copy
    instead of two identical files under github/ and gitlab/.
- tools/workflow.ts: `contentrain_submit` and `contentrain_merge`
  now gate on explicit `provider.capabilities.X` (pushRemote /
  localWorktree) instead of the `!projectRoot` proxy. Matches the
  pattern `tools/normalize.ts` adopted in phase 6.

### Tool surface

No changes. Same 16 tools, same parameters, same response JSON.

### Docs

Tool-count drift (15 → 16) fixed in root README, CLAUDE.md,
docs/packages/mcp.md frontmatter, docs/concepts.md. Tool lists
updated to include `contentrain_merge`.

"Local-first only / no GitHub API" framing rewritten in four spots
(packages/mcp/README.md opening + design constraints,
docs/packages/mcp.md frontmatter + body section). New framing:
"provider-agnostic, local-first by default, optional GitHub and
GitLab backends".

New VitePress pages (sidebar + nav updated):
- docs/guides/providers.md — Local / GitHub / GitLab overview and
  capability matrix
- docs/guides/http-transport.md — HTTP deployment, Bearer auth,
  Studio / CI / remote-agent patterns
- docs/reference/providers.md — RepoProvider contract reference

Existing pages updated: getting-started (HTTP transport tip),
concepts (provider concept + capability gates), studio (content
engine is MCP over HTTP + RepoProvider), normalize (LocalProvider
warning), config reference (widened provider type).

CLI:
- packages/cli/src/commands/serve.ts description now says
  "stdio + HTTP"
- packages/cli/README.md adds a "MCP HTTP (Studio, CI, remote
  drivers)" subsection under `serve` modes and updates branch
  references from `contentrain/*` to `cr/*`.

Rules and skills:
- packages/rules/shared/mcp-usage.md — new "Transport and Provider
  Capabilities" section with the capability matrix;
  `contentrain_merge` added to the tool catalogue; branch naming
  corrected to `cr/*`; `capability_required` entry added to the
  common-errors table.
- packages/rules/shared/{workflow,normalize}-rules.md — branch
  examples migrated to `cr/*`.
- packages/skills/skills/contentrain-normalize/SKILL.md — new
  "Transport Requirements" section documenting the LocalProvider
  constraint.
- packages/skills/workflows/{contentrain-normalize,translate}.md —
  branch references migrated to `cr/*`.

### Tests

- packages/mcp/tests/providers/local/reader.test.ts (new, 11 cases)
  — LocalReader was previously untested at the unit level.
- packages/mcp/tests/core/overlay-reader.test.ts (new, 11 cases)
  — exercises the primitive behind both P1 fixes.
- packages/mcp/tests/server/fixtures/github-mock.ts (new) — shared
  mock for GitHubProvider HTTP E2Es; previously inlined in each
  case.
- packages/mcp/tests/server/http.test.ts — five new E2Es
  (content_delete, model_save, model_delete, validate read-only,
  status read-only) plus an updated capability-error test that
  exercises `contentrain_submit` (genuinely local-only) instead of
  the now-remote-capable `contentrain_status`.

### Changesets

- `.changeset/types-provider-alignment.md` — @contentrain/types minor
  (provider widened + contracts exposed).
- `.changeset/mcp-phase-10-alignment.md` — @contentrain/mcp minor
  (new subpath exports + P1 bug fixes + read-only-over-remote
  feature additions).

### Verification

- pnpm -r typecheck (8 packages) → 0 errors
- npx oxlint (monorepo, 397 files) → 0 warnings
- pnpm --filter @contentrain/mcp build → clean, 130 files packed
- vitest run tests/core tests/conformance tests/serialization-parity
      tests/git tests/providers tests/server tests/util
  → 440/440 green, 2 skipped (22 new tests land in this pass: 11
  LocalReader + 11 OverlayReader, plus 5 new HTTP E2Es replacing
  one rewritten case).
- vitest run tests/tools → 90/91 on parallel; 1 pre-existing flaky
  timeout on `content.test.ts > blocks new writes when 80 active
  contentrain branches exist` (80 sequential git checkouts inside a
  120s budget, unrelated to phase 10). Re-run in isolation: 17/17
  green in 411s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the integration gap between the finished MCP refactor and the
consumers that want to embed it (Studio first, but the public guide
extends to any host). Also fixes two bugs surfaced while writing
the handoff.

### Studio handoff (internal)

`.internal/refactor/02-studio-handoff.md` was written at the start
of the refactor (Phase 2) and had drifted on every subsequent phase.
Updated to the finished-refactor state:

- Preconditions now list the actual shipped subpath exports
  (`core/ops`, `core/overlay-reader`, `core/contracts`,
  `providers/local`, etc.), not the Phase-2-era stubs.
- Contracts canonical source is `@contentrain/types`, not
  `@contentrain/mcp/core/contracts` (which re-exports for back-compat).
- S2 (Content Engine Sökümü) code samples updated for today's APIs:
  `buildContextChange(reader, operation)` with `OverlayReader`
  wrapping, `provider.applyPlan({..., base: CONTENTRAIN_BRANCH})`
  invariant, no `.reader()` accessor (didn't exist then, doesn't
  now).
- New "Phase 10 tuzakları" section spelling out the three traps
  Studio should avoid: fork from contentrain, OverlayReader before
  context / validation, `isNotFoundError` is internal.
- S6 (MCP Cloud Endpoint) reframed around `startHttpMcpServerWith`
  rather than a hand-rolled JSON-RPC layer — Studio can reuse the
  MCP package's HTTP primitive and put Bearer / quota / metering in
  a Nitro middleware in front of it.
- Removed the hypothetical "core branch-policy module" placeholder
  (Phase 3.5 that never shipped separately — the helper logic lives
  inline in `providers/local/migration.ts` now).
- Provider selection matrix clarifying that Studio never uses
  LocalProvider, so normalize / scan / apply / init / scaffold /
  submit / merge / validate --fix / bulk are always
  `capability_required` on Studio's Cloud endpoint — not something
  Studio has to filter client-side, the capability gate handles it.

### Public embedding guide (public docs)

New `docs/guides/embedding-mcp.md` — a consumer-agnostic guide that
sits under the existing Providers / HTTP Transport guides. Documents
four construction recipes (stdio+Local, HTTP+Local, HTTP+Remote,
programmatic no-transport), the three primitives every integrator
must understand (`CONTENTRAIN_BRANCH` as fork point, `OverlayReader`
for post-commit consistency, `capability_required` as a structured
error), auth model, and an extension point for custom providers.
Studio is called out as a reference integration alongside CI and
scripted automation. Sidebar + top nav updated.

### P2 — `ApplyPlanInput.base` contract vs implementation

The `@contentrain/types` docstring said "defaults to provider's
content-tracking branch", but both `GitHubProvider` and
`GitLabProvider` defaulted to the repository's default branch
(main / master / trunk). Tests locked in that wrong behaviour. MCP's
own tool-level write path (`commit-plan.ts`) bypassed the issue by
always passing `base: CONTENTRAIN_BRANCH` explicitly, but any
Studio-style direct `provider.applyPlan` call with `base` omitted
would silently fork from `main`.

Both implementations now default to `CONTENTRAIN_BRANCH`, matching
the documented contract and the local-transaction behaviour. Tests
rewritten to assert:

- `github.apply-plan.test.ts > defaults to the contentrain branch
  when no base is provided` — `repos.get` is NOT called, `getRef`
  resolves `heads/contentrain`.
- `gitlab.apply-plan.test.ts > defaults to the contentrain branch
  when input.base is absent` — `Projects.show` is NOT called,
  `startBranch` is `'contentrain'`.

Docstring tightened in `packages/types/src/provider.ts` to leave no
room for a split-brain reinterpretation.

`commit-plan.ts` can now omit `base` entirely (provider default is
correct), but keeps it explicit for readability.

### P2 — `contentrain_status` context field on remote

Remote `contentrain_status` returned `context: null` unconditionally,
even though remote writes commit `.contentrain/context.json` through
the overlay reader. Studio's surface reported "no last operation"
and "stats.entries: null" forever.

`readContext` gains a reader overload in `core/context.ts`.
`tools/context.ts` uses the reader form for remote sessions. The
HTTP E2E now seeds `.contentrain/context.json` and asserts the
`lastOperation` + `stats` surface from the remote provider.

### New public subpath exports

- `@contentrain/mcp/core/ops` — plan helpers + path helpers, exactly
  what an embedder needs to compose custom write paths against any
  `RepoProvider`.
- `@contentrain/mcp/core/overlay-reader` — the `OverlayReader`
  primitive. Required by any integrator writing to a remote
  provider so `buildContextChange` / `validateProject` see
  post-commit state.

Both paths are referenced from the embedding guide and the Studio
handoff; without them, a from-scratch Studio-style integration would
require reaching into `dist/` which is explicitly blocked by
`package.json#exports`.

### Verification

- `pnpm -r typecheck` → 0 errors across 8 packages.
- `oxlint` → 0 warnings on 397 files.
- `pnpm --filter @contentrain/mcp build` → clean; dist contains
  `core/ops/`, `core/overlay-reader.{d.mts,mjs}`, `core/contracts/`,
  `providers/local/`.
- `vitest run tests/core tests/conformance tests/serialization-parity
      tests/git tests/providers tests/server tests/util` →
  440/440 green, 2 skipped.
- `vitest run tests/providers/github tests/providers/gitlab
      tests/server/http.test.ts` → 60/60 green
  (includes the rewritten base-default tests and the augmented
  remote-context status E2E).
- `node -e import('@contentrain/mcp/core/ops')` + overlay-reader
  + contracts + providers/local → all four subpaths resolve.

### Changesets

`.changeset/mcp-phase-11-studio-handoff.md` — `@contentrain/mcp`
minor bump (new subpath exports, two P2 bug fixes). Stacks with the
existing Phase 10 changesets into a single minor release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ABB65 ABB65 merged commit 4e8a1b6 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