feat(mcp,docs): studio handoff + embedding guide + two P2 fixes#37
Merged
Conversation
…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>
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
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.mdwas written at Phase 2 and had drifted through phases 3–10. Rewritten to the finished-refactor state:core/ops,core/overlay-reader,core/contracts,providers/local)@contentrain/types, not@contentrain/mcp/core/contracts(re-export kept for back-compat)buildContextChange(reader, operation)+OverlayReaderwrapping,provider.applyPlan({ base: CONTENTRAIN_BRANCH })invariantcontentrain,OverlayReaderbefore context/validation,isNotFoundErroris internal)startHttpMcpServerWithwith a Bearer/quota/metering Nitro middleware instead of a hand-rolled JSON-RPC layercapability_required— capability gate handles it, Studio doesn't filter client-sidePublic 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.basecontract alignmentContract docstring said "defaults to provider's content-tracking branch", but
GitHubProvider/GitLabProviderdefaulted to the repo's default branch (main/master/trunk). Tests locked in the bug. MCP's own write path bypassed the issue by passingbase: CONTENTRAIN_BRANCHexplicitly, but any Studio-style directprovider.applyPlancall withoutbasesilently forked from main.Both implementations now default to
CONTENTRAIN_BRANCH. Tests rewritten to assert:repos.getNOT called,getRefresolvesheads/contentrainProjects.showNOT called,startBranch: 'contentrain'Docstring tightened in
packages/types/src/provider.ts.P2 —
contentrain_statuscontext on remoteRemote
contentrain_statusreturnedcontext: nullunconditionally — Studio never sawlastOperationorstatsdespite.contentrain/context.jsonbeing committed by remote writes.readContextgains a reader overload;tools/context.tsuses it for remote sessions. HTTP E2E now seeds.contentrain/context.jsonand 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— theOverlayReaderprimitive. Required for any non-local write path that needsbuildContextChange/validateProjectto see post-commit state.Without these, a from-scratch Studio-style integration hits
ERR_PACKAGE_PATH_NOT_EXPORTEDon the first useful import.Test plan
pnpm -r typecheck(8 packages) → 0 errorsoxlint(397 files) → 0 warningspnpm --filter @contentrain/mcp build→ clean; dist includes new subpathsnode -e "import('@contentrain/mcp/core/ops')"+ overlay-reader + contracts + providers/local → all four resolvetests/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/mcpminor. Stacks with existing Phase 10 changesets into a single minor release.Scope notes
.internal/which is a symlink tostudio/.internal/— the Studio repo picks it up automatically..changeset/types-provider-alignment.mdfrom Phase 10 still carries the types minor; the docstring clarification in this PR doesn't warrant a separate changeset entry.🤖 Generated with Claude Code