Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .changeset/mcp-phase-10-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
"@contentrain/mcp": minor
---

chore(mcp): phase 10 alignment — docs parity, cohesion fixes, P1 bug fixes, new subpath exports

Follow-up to the phase-0-through-9 provider-agnostic refactor. Ships
in one PR because the pieces are interlocking: the bug fixes rely on
new primitives, the new primitives unlock the feature gaps the docs
claimed were already covered.

### New public subpath exports

- `@contentrain/mcp/core/contracts` — `RepoProvider` / `RepoReader` /
`RepoWriter` / capabilities / file-change / branch / commit types,
re-exported from `@contentrain/types` for backward compat.
- `@contentrain/mcp/providers/local` — `LocalProvider`, `LocalReader`,
related types. Previously internal-only.

These paths were referenced in package documentation since phase 5
but had no `exports` entry, so external imports failed with
`ERR_PACKAGE_PATH_NOT_EXPORTED`. Now callable.

### Bug fixes (P1)

- **Remote write base branch invariant** — `commit-plan.ts` and its
call sites now always fork remote feature branches from the
`contentrain` singleton branch, matching the local flow. Previous
behaviour forked from `config.repository.default_branch` (usually
`main`), breaking the single-source-of-truth invariant in any
project that set the repository's default branch explicitly.
- **Stale remote context / validation** — the new `OverlayReader`
primitive layers pending `FileChange`s on top of the underlying
reader. `buildContextChange` and post-save `validateProject` now
see the state the pending commit produces instead of the
pre-change base branch. Fixes commits whose context.json entry
counts or validation result reflected the old state.

### Read-only tools on HTTP + remote providers (P2)

- `contentrain_status`, `contentrain_describe`, and
`contentrain_content_list` no longer gate on `!projectRoot`. They
work against any `ToolProvider` — `LocalProvider`, `GitHubProvider`,
`GitLabProvider` — through the reader surface. Branch health + stack
detection (local-only) are skipped gracefully when no project root
is available.
- `contentrain_content_list` with `resolve: true` still requires local
disk (cross-model relation hydration walks other models' content
files); the reader path rejects it with a descriptive error.

### Cohesion

- `commitThroughProvider` — shared helper that encapsulates the
`LocalProvider` vs remote `RepoProvider` dispatch. The eight
repeated `if (provider instanceof LocalProvider) { … } else { … }`
blocks across `content.ts` / `model.ts` collapse to single calls.
Uniform `{ commitSha, workflowAction, sync? }` return shape.
- `providers/shared/{errors,paths}.ts` — consolidated
`isNotFoundError` + `normaliseContentRoot` / `resolveRepoPath` used
by both GitHub and GitLab providers. Removes four duplicate
`isNotFound` helpers with asymmetric semantics (GitLab's lenient
description-match fallback is gone — status-based check only).
- `workflow.ts` — `contentrain_submit` and `contentrain_merge` now
gate on explicit `provider.capabilities.X` instead of the
`!projectRoot` proxy. Matches the pattern `normalize.ts` adopted in
phase 6.
- `util/serializer.ts` was previously dead-code removed in phase 9.

### Test coverage

- `tests/providers/local/reader.test.ts` — new, 11 cases
- `tests/core/overlay-reader.test.ts` — new, 11 cases
- `tests/server/http.test.ts` — +5 cases (content_delete, model_save,
model_delete, validate, status remote) and an updated
capability-error test that now exercises `contentrain_submit`
(genuinely local-only) instead of `contentrain_status`.

### Tool surface

No changes. Same 16 tools, same parameters, same response shapes.
Stdio + LocalProvider flows behave identically to the previous
release.
18 changes: 18 additions & 0 deletions .changeset/types-provider-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@contentrain/types": minor
---

feat(types): RepoProvider contracts + widened `repository.provider`

- `ContentrainConfig.repository.provider` is now `'github' | 'gitlab'` (was a hardcoded `'github'`). Reflects the two remote providers `@contentrain/mcp` ships today.
- The provider-agnostic engine contracts used by `@contentrain/mcp` are now exposed directly from `@contentrain/types`:
- `RepoReader`, `RepoWriter`, `RepoProvider`
- `ProviderCapabilities`, `LOCAL_CAPABILITIES`
- `ApplyPlanInput`, `Commit`, `CommitAuthor`
- `FileChange`, `Branch`, `FileDiff`, `MergeResult`

Third-party tools can now implement a custom `RepoProvider` without
taking a runtime dependency on `@contentrain/mcp`.

`@contentrain/mcp/core/contracts` keeps re-exporting every symbol, so
existing MCP-based imports are unchanged.
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ MIT-licensed monorepo for Contentrain's open-source packages: MCP tools, CLI, Ty
```
contentrain-ai/
├── packages/
│ ├── mcp/ — 15 MCP tools (simple-git + zod + MCP SDK)
│ ├── mcp/ — 16 MCP tools, stdio + HTTP transports, Local / GitHub / GitLab providers (simple-git + zod + MCP SDK)
│ ├── cli/ — citty + tsdown (init/serve/validate/normalize/connect)
│ ├── types/ — Shared TypeScript types (@contentrain/types)
│ ├── rules/ — AI agent quality rules & conventions
Expand Down Expand Up @@ -90,7 +90,7 @@ When working with Contentrain content operations (models, content, normalize, va

| Package | Name | Description |
|---|---|---|
| packages/mcp | @contentrain/mcp | 15 MCP tools |
| packages/mcp | @contentrain/mcp | 16 MCP tools, stdio + HTTP transports, Local / GitHub / GitLab providers |
| packages/cli | contentrain | CLI (npx contentrain) |
| packages/types | @contentrain/types | Shared TypeScript types |
| packages/rules | @contentrain/rules | AI agent quality rules & conventions |
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ This is the strongest entry point into the product:

```
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ AI Agent │────▶│ MCP (15 tools) │────▶│ .contentrain/│
│ AI Agent │────▶│ MCP (16 tools) │────▶│ .contentrain/│
│ (decides) │ │ (enforces) │ │ (stores) │
└─────────────┘ └──────────────────┘ └──────┬───────┘
Expand Down Expand Up @@ -145,7 +145,7 @@ Works with Nuxt, Next.js, Astro, SvelteKit, Vue, React, Node, Go, Python, Swift,

- **Git-native** — every write goes through worktree isolation + review branches
- **Normalize flow** — scan codebase for hardcoded strings → extract → create i18n-ready content → patch source files
- **Local-first MCP** — 15 tools, stdio transport, works with Claude Code, Cursor, Windsurf, or any MCP client
- **MCP engine** — 16 tools over stdio or HTTP transport, works with Claude Code, Cursor, Windsurf, or any MCP client
- **Provider-agnostic engine** — the same tool surface runs over a local worktree, GitHub, or GitLab (self-hosted included) with zero tool-code changes. HTTP transport available for remote drivers such as Studio.
- **Canonical serialization** — sorted keys, deterministic output, clean git diffs, conflict-free parallel edits
- **Agent rules & skills** — behavioral policies and step-by-step workflows ship as npm packages
Expand Down Expand Up @@ -176,7 +176,7 @@ See [`AGENTS.md`](AGENTS.md) for the full skill catalog and agent guidance.

| Package | npm | Role |
|---|---|---|
| [`@contentrain/mcp`](packages/mcp) | [![npm](https://img.shields.io/npm/v/%40contentrain%2Fmcp)](https://www.npmjs.com/package/@contentrain/mcp) | 15 MCP tools + stdio / HTTP transport + Local / GitHub / GitLab providers |
| [`@contentrain/mcp`](packages/mcp) | [![npm](https://img.shields.io/npm/v/%40contentrain%2Fmcp)](https://www.npmjs.com/package/@contentrain/mcp) | 16 MCP tools + stdio / HTTP transport + Local / GitHub / GitLab providers |
| [`contentrain`](packages/cli) | [![npm](https://img.shields.io/npm/v/contentrain)](https://www.npmjs.com/package/contentrain) | CLI + Serve UI + MCP stdio entrypoint |
| [`@contentrain/query`](packages/sdk/js) | [![npm](https://img.shields.io/npm/v/%40contentrain%2Fquery)](https://www.npmjs.com/package/@contentrain/query) | Generated TypeScript query SDK |
| [`@contentrain/types`](packages/types) | [![npm](https://img.shields.io/npm/v/%40contentrain%2Ftypes)](https://www.npmjs.com/package/@contentrain/types) | Shared type definitions + constants |
Expand Down
6 changes: 6 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export default defineConfig({
{ text: 'Types', link: '/packages/types' },
]},
{ text: 'Guides', items: [
{ text: 'Providers & Transports', link: '/guides/providers' },
{ text: 'HTTP Transport', link: '/guides/http-transport' },
{ text: 'Normalize Flow', link: '/guides/normalize' },
{ text: 'Framework Integration', link: '/guides/frameworks' },
{ text: 'i18n Workflow', link: '/guides/i18n' },
Expand All @@ -56,6 +58,7 @@ export default defineConfig({
{ text: 'Model Kinds', link: '/reference/model-kinds' },
{ text: 'Field Types', link: '/reference/field-types' },
{ text: 'Configuration', link: '/reference/config' },
{ text: 'RepoProvider', link: '/reference/providers' },
]},
{ text: 'Starters', link: 'https://github.com/orgs/Contentrain/repositories?q=contentrain-starter&type=template' },
],
Expand Down Expand Up @@ -84,6 +87,8 @@ export default defineConfig({
{
text: 'Guides',
items: [
{ text: 'Providers & Transports', link: '/guides/providers' },
{ text: 'HTTP Transport', link: '/guides/http-transport' },
{ text: 'Normalize Flow', link: '/guides/normalize' },
{ text: 'Framework Integration', link: '/guides/frameworks' },
{ text: 'i18n Workflow', link: '/guides/i18n' },
Expand All @@ -96,6 +101,7 @@ export default defineConfig({
{ text: 'Model Kinds', link: '/reference/model-kinds' },
{ text: 'Field Types', link: '/reference/field-types' },
{ text: 'Configuration', link: '/reference/config' },
{ text: 'RepoProvider', link: '/reference/providers' },
],
},
],
Expand Down
6 changes: 4 additions & 2 deletions docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,18 @@ Contentrain AI inverts the traditional CMS workflow:

### 1. MCP (Infrastructure)

15 tools that AI agents call to manage content:
16 tools that AI agents call to manage content:

- **Read:** `contentrain_status`, `contentrain_describe`, `contentrain_describe_format`, `contentrain_content_list`
- **Project setup:** `contentrain_init`, `contentrain_scaffold`
- **Content and schema writes:** `contentrain_model_save`, `contentrain_model_delete`, `contentrain_content_save`, `contentrain_content_delete`
- **Normalize:** `contentrain_scan`, `contentrain_apply`
- **Workflow and operations:** `contentrain_validate`, `contentrain_submit`, `contentrain_bulk`
- **Workflow and operations:** `contentrain_validate`, `contentrain_submit`, `contentrain_merge`, `contentrain_bulk`

MCP is **deterministic infrastructure** — it doesn't make content decisions. The agent decides what to create; MCP executes it.

MCP runs over two transports (stdio for IDE agents, HTTP for Studio / CI / remote drivers) and three provider backends: **Local** (simple-git + worktree), **GitHub** (Octokit over the Git Data API), and **GitLab** (gitbeaker over the REST API). The tool surface is identical across all three; some tools require `LocalProvider` — see [Providers and transports](/guides/providers) for the capability matrix.

### 2. Agent (Intelligence)

The AI agent (Claude, GPT, etc.) is the intelligence layer:
Expand Down
10 changes: 10 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ If your IDE is detected during `contentrain init`, the MCP config is created aut

</details>

::: tip HTTP Transport
In addition to stdio, MCP also serves over HTTP at `POST /mcp`. Useful for Studio / CI runners / remote agents that drive Contentrain operations without a local IDE:

```bash
npx contentrain serve --mcpHttp --authToken $(openssl rand -hex 32)
```

See the [HTTP Transport guide](/guides/http-transport) for auth, deployment patterns, and programmatic embedding.
:::

### 3. Create a content model

Tell your agent:
Expand Down
123 changes: 123 additions & 0 deletions docs/guides/http-transport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
title: HTTP Transport
description: Run the Contentrain MCP server over HTTP for Studio, CI runners, and remote agent drivers — with Bearer authentication.
slug: http-transport
---

# HTTP Transport

The MCP server ships two transports. The default — stdio — is for IDE integration. The HTTP transport serves tool calls at `POST /mcp` so agents running on a different machine can drive Contentrain operations against a project.

Typical drivers:

- **Contentrain Studio** — hosts an agent that talks to MCP over HTTP, backed by a GitHubProvider or GitLabProvider pointed at a team's content repo.
- **CI runners** — deterministic content operations as part of a pipeline (scaffold, validate, submit).
- **Remote agents** — any MCP client that wants to operate a Contentrain project without a local checkout.

All three tunnel the same 16 tools through the same `RepoProvider` contract. Which backend answers depends on how the server is wired — see [Providers & Transports](/guides/providers) for the capability matrix.

## Starting the HTTP server

The simplest path is the CLI:

```bash
contentrain serve --mcpHttp --authToken $(openssl rand -hex 32)
```

This binds to `localhost:3333` by default, uses the current working directory as the project root, and wraps it in a `LocalProvider`. Flags:

- `--port <n>` (`CONTENTRAIN_PORT`) — listen port
- `--host <bind>` (`CONTENTRAIN_HOST`) — bind address. Default `localhost`; set to `0.0.0.0` to accept remote connections
- `--authToken <token>` (`CONTENTRAIN_AUTH_TOKEN`) — Bearer token required for every request
- `--root <path>` (`CONTENTRAIN_PROJECT_ROOT`) — project root when not the cwd

MCP tool calls land at `POST <host>:<port>/mcp`. Any other path returns 404.

## Authentication

When `--authToken` is set (or `CONTENTRAIN_AUTH_TOKEN` is exported), every request must carry `Authorization: Bearer <token>`. Missing or mismatched tokens get `401 Unauthorized` before the MCP session initialises.

```bash
curl -X POST http://localhost:3333/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"contentrain_describe_format","arguments":{}}}'
```

Auth is optional in dev — omit `--authToken` when binding to `localhost`. Production deployments should always set one.

## Programmatic embedding

CLI-wrapped HTTP always uses `LocalProvider`. To run HTTP against a remote provider (GitHub, GitLab) embed the server programmatically:

```ts
import { createGitHubProvider } from '@contentrain/mcp/providers/github'
import { startHttpMcpServerWith } from '@contentrain/mcp/server/http'

const provider = await createGitHubProvider({
auth: { type: 'pat', token: process.env.GITHUB_TOKEN! },
repo: { owner: 'acme', name: 'site' },
})

const handle = await startHttpMcpServerWith({
provider,
port: 3333,
host: '0.0.0.0',
authToken: process.env.MCP_BEARER_TOKEN,
})

// handle.url contains the fully-qualified URL, e.g. http://0.0.0.0:3333/mcp
// handle.close() stops the server
```

The same pattern works for `createGitLabProvider` with a `GitLabProvider`. Both require their respective optional peers (`@octokit/rest`, `@gitbeaker/rest`).

## Deployment patterns

### Studio (hosted agent)

Studio's agent builds a GitHubProvider or GitLabProvider per tenant, points it at the tenant's content repo, and talks to an embedded MCP server over HTTP+LocalProvider-style wiring but with a remote provider. Each session is ephemeral.

### CI

A GitHub Actions job can:

1. Check out the repository
2. `pnpm install`
3. Start `contentrain serve --mcpHttp --authToken $CI_TOKEN &`
4. Drive it with an MCP client (Claude, Cursor-headless, or a custom JSON-RPC client)
5. Let `contentrain_submit` push the review branch

Because `contentrain serve --mcpHttp` uses `LocalProvider`, every tool — including normalize and submit — is available.

### Remote agent

An agent running on a laptop can drive a Contentrain project that lives on a server by connecting to the server's HTTP MCP endpoint (over a VPN or behind a reverse proxy with TLS + Bearer auth). The agent sees the full tool surface; capability gates still apply based on the backing provider.

## Capability gates over HTTP

Not every tool works on every provider. A tool driven by an HTTP client against a remote provider returns a structured capability error when the required capability is missing:

```json
{
"error": "contentrain_scan requires local filesystem access.",
"capability_required": "astScan",
"hint": "This tool is unavailable when MCP is driven by a remote provider. Use a LocalProvider or the stdio transport."
}
```

Agent drivers should treat `capability_required` as a retry signal — prompt the user to switch transports, or fall back to a local-checkout session for that specific tool.

## Security notes

- Never expose HTTP MCP without `--authToken` on a non-`localhost` bind.
- Rotate tokens regularly; MCP does not support ACLs at the tool level, so a token is full project access.
- When Studio connects, the token is managed per workspace — see the Studio docs for the rotation workflow.
- All writes create feature branches from `contentrain`; the singleton source-of-truth branch is protected from direct pushes in team configurations.

## Next steps

- [Providers & Transports](/guides/providers) — deeper reference for each backend.
- [MCP package docs](/packages/mcp) — tool catalogue and response shapes.
- [Contentrain Studio](/studio) — the hosted surface that drives HTTP MCP.
6 changes: 6 additions & 0 deletions docs/guides/normalize.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ slug: normalize

The normalize flow is Contentrain's **primary value proposition** — the fastest path from "500 hardcoded strings scattered across my codebase" to "structured, translatable, manageable content." It runs in three phases: **Extract**, **Reuse**, and **Translate**.

::: warning LocalProvider required
Normalize (`contentrain_scan` and `contentrain_apply`) requires local disk access — AST scanners walk the source tree and patch files in place. It runs only on a `LocalProvider` (stdio or HTTP+LocalProvider).

Remote providers (`GitHubProvider`, `GitLabProvider`) reject these calls with `capability_required: astScan / sourceRead / sourceWrite`. Run normalize in a local checkout, then push the extracted content branch. See [Providers & Transports](/guides/providers) for the capability matrix.
:::

## The Problem

A typical SaaS landing page has 40-60 components with 300-800 hardcoded strings. Nobody notices until someone asks for a second language or a copy change across 12 pages.
Expand Down
Loading
Loading