contentrain_status
Project Status
Get full project status (read-only). Returns config, models, context. Do NOT manually edit .contentrain/ based on this output.
Takes no parameters.

io.github.ContentrainPublished by GitHub account Contentrainv3.12.3Updated Nov 4, 2026
Git content governance, 23 deterministic MCP tools. Hosted: docs.contentrain.io/developer/mcp-cloud
Git-native content governance server that lets an assistant define content models, save and publish localized content, validate it, and manage review branches.
Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.
Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.
Read https://sourceweft.com/mcp/io.github.Contentrain%2Fcontentrain/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.Add this to your client's mcpServers config. The client completes OAuth sign-in on first connect.
{
"mcpServers": {
"contentrain": {
"type": "http",
"url": "https://studio.contentrain.io/api/mcp/remote"
}
}
}@contentrain/mcp[npm version] [GitHub source] [Docs]
Provider-agnostic MCP engine for Contentrain — local-first by default, with optional GitHub and GitLab backends and an HTTP transport for remote drivers such as Studio.
Start here:
Contentrain is a content governance ecosystem for teams and AI agents. In it, this engine works like this:
This package is the runtime core behind Contentrain's MCP integration. It can be used as:
contentrain-mcp)createServer(projectRoot))Requirements:
22+Optional parser support for higher-quality source scanning:
@vue/compiler-sfc@astrojs/compilersvelteThey are listed as optional dependencies. The scanner still works without them, but Vue/Astro/Svelte detection is stronger when they are installed.
@contentrain/mcp manages a .contentrain/ directory in your project and exposes MCP tools for:
All write operations are designed around git-backed safety:
contentrain branch serves as the content state single source of truthcontentrain (branch name: cr/{scope}/{target}/{timestamp}-{suffix}, e.g. cr/content/blog-post/1730000000-a1b2; bulk locale copies, locale-scoped status changes and scaffold add a {locale} segment before the timestamp)contentrain, baseBranch advanced via update-ref, .contentrain/ files selectively synced to developer's working tree when the base branch is the one checked outCONTENTRAIN_BRANCH env → repository.default_branch → remote HEAD → main → master → checked-out branch → main (fallback). A write made on a feature branch never merges, moves or pushes that branchcontentrain tip: JSON changes are carried over key by key, so entries another writer pushed are kept; a same-value conflict writes nothing and returns CONTENT_WORKING_TREE_STALE.contentrain/** is read from the contentrain ref instead (source files still from the working tree); read tools report it as content_sourceremoteBranchCleanup: false in config.jsongit.push: false or CONTENTRAIN_NO_PUSH=1): nothing is ever pushed or deleted on the remote, and every result says so — see Git environment and local modegit block names the resolved base_branch; when it is not the checked-out branch, the result's warning says your working tree was left alonegit.verify: true or CONTENTRAIN_VERIFY=1 runs them, and a rejecting hook fails the write with its output — see Commit hookscontentrain branch after merge (locally by the transaction layer; in remote flows by the orchestrator that owns the merge)23 MCP tools, plus 5 media tools on a media-capable provider such as Studio, with annotations (readOnlyHint, destructiveHint, idempotentHint, and openWorldHint: false everywhere except contentrain_media_ingest, which fetches a caller-supplied URL server-side) for client safety hints.
Tool listing is capability-aware. tools/list only advertises tools the resolved provider + projectRoot pair can actually satisfy. A local stdio server lists the 23 core tools; a session driven by a remote provider (GitHub/GitLab, no local checkout) lists only the remote-safe subset — status, describe, describe_format, model_save, model_delete, content_save, content_delete, content_list, content_status, vocabulary_save, vocabulary_delete, validate. The requirement map lives in TOOL_REQUIREMENTS (@contentrain/mcp/tools/availability).
The five contentrain_media_* tools are a deterministic passthrough to the provider's optional media facet (RepoProvider.media) and are registered only when the provider exposes one (e.g. Studio MCP Cloud). Local stdio servers and plain GitHub/GitLab providers never list them. Ingest is URL-based (MCP has no binary channel); the provider implementation owns SSRF/MIME/size policy for the fetch.
This auto-creates the correct MCP config file for your IDE. See CLI docs for details.
If CONTENTRAIN_PROJECT_ROOT is omitted, the current working directory is used.
These apply to the local provider (stdio, LocalProvider):
Local mode keeps every write on local branches: no branch push, no review push, no push after an auto-merge, approve or reconcile, and no remote branch delete or prune. Fetching is not a push and still happens when a remote exists. Turn it on for a project with "git": { "push": false } in .contentrain/config.json, or for one run with CONTENTRAIN_NO_PUSH=1.
Precedence: the env wins over the config in both directions. CONTENTRAIN_NO_PUSH=1 turns pushing off even when config allows it; CONTENTRAIN_NO_PUSH=0 turns it back on over git.push: false. With neither, pushing is on.
It is never a silent success. Each write's git block reports remote_push: "disabled" with remote_note: "not pushed (local mode)", and next_steps carries a LOCAL MODE line. contentrain_submit returns an error instead of pushing; land review branches with contentrain_merge. contentrain_merge and contentrain_branch_delete delete the local branch as usual and leave any remote copy untouched; contentrain_branch_delete reports remote_skipped: "local-mode", contentrain_merge reports remote: { deleted: false, skipped: "local-mode" }.
By default Contentrain's machine commits (the cr/* content commit, the context.json commit, a reconcile merge commit, the seed commit of an empty repo) skip the repository's commit hooks (pre-commit, prepare-commit-msg, commit-msg). That keeps existing projects working: these commits are made in a temporary worktree without your node_modules, and their [contentrain] … subject fails a strict commitlint. Merges still run the merge hooks and pushes still run pre-push, as before.
To run commit hooks on every machine commit, set "git": { "verify": true } in .contentrain/config.json, or CONTENTRAIN_VERIFY=1 for one run (CONTENTRAIN_VERIFY=0 skips them over the config). With hooks on:
context.json commit (the content is already on contentrain) is reported in the result's warning, not swallowed;npx lint-staged needs the tool available there.createServer also accepts an options object: { provider, projectRoot?, instructions? }. instructions sets the MCP instructions string clients receive at initialize (defaults to defaultInstructions for the tools the server registers — with every tool available, DEFAULT_INSTRUCTIONS — kept under 512 characters; pass '' to omit). The HTTP entries (startHttpMcpServer, startHttpMcpServerWith) take the same instructions option.
Typical agent workflow:
contentrain_statuscontentrain_initcontentrain_model_save or contentrain_scaffoldcontentrain_content_savecontentrain_validatecontentrain_scan then contentrain_applycontentrain_submitNormalize is intentionally split into two phases:
contentrain_scan finds candidate strings.
contentrain_apply with mode: "extract":
cr/normalize/extract/{timestamp}-{suffix})contentrain_apply with mode: "reuse":
cr/normalize/reuse/{model or domain}/{timestamp}-{suffix})This split keeps content extraction separate from source rewriting.
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 transport, or HTTP transport configured with a LocalProvider).
Remote providers such as GitHubProvider expose astScan: false, sourceRead: false, and sourceWrite: false, so a server built on one does not register contentrain_scan or contentrain_apply at all (see TOOL_REQUIREMENTS). A provider that can read the source tree but not write it lists contentrain_apply; its reuse mode then returns a uniform capability error:
Agents driving a remote transport should fall back to a local transport (or a local checkout) before invoking normalize.
MCP supports three backends behind the same RepoProvider contract:
@octokit/rest ships as an optional peer dependency.@gitbeaker/rest ships as an optional peer dependency. Supports gitlab.com and self-hosted CE / EE.Each remote provider implements the same surface: reader (readFile / listDirectory / fileExists), writer (applyPlan — one atomic commit), branch ops (list / create / delete / diff / merge / isMerged / getDefaultBranch). mergeBranch goes straight through on GitHub; on GitLab it opens an MR and immediately accepts it so the final MergeResult shape matches either way. The GitHub provider also takes a full commit SHA as applyPlan's base (and createBranch's fromRef) for compare-and-set writes: a missing branch forks from that commit, and a branch that has moved away from it refuses the write with PROVIDER_CONFLICT.
Capabilities: sourceRead, sourceWrite, astScan, localWorktree are all false; pushRemote, branchProtection, pullRequestFallback are true. Normalize (scan / apply) is not registered on a GitLabProvider server — fall back to a local transport for those flows.
There is no Bitbucket provider. A Bitbucket checkout works through the LocalProvider path like any other local Git repository.
The package also exposes low-level modules for embedding and advanced use:
@contentrain/mcp/server@contentrain/mcp/server/http@contentrain/mcp/core/config@contentrain/mcp/core/context@contentrain/mcp/core/model-manager@contentrain/mcp/core/content-manager@contentrain/mcp/core/validator@contentrain/mcp/core/scanner@contentrain/mcp/core/graph-builder@contentrain/mcp/core/apply-manager@contentrain/mcp/core/scan-config@contentrain/mcp/core/doctor@contentrain/mcp/core/contracts@contentrain/mcp/core/ops — plan APIs (including planReconcile and its bindRef reader adapter) plus content-root-relative path helpers: contentDirPath, contentFilePath, documentFilePath, metaFilePath@contentrain/mcp/core/overlay-reader@contentrain/mcp/core/migration — the allowlisted migration write path: createMigrationWriter, scopeHash, decide. See Migration writes below@contentrain/mcp/util/detect@contentrain/mcp/util/fs@contentrain/mcp/git/transaction@contentrain/mcp/git/branch-lifecycle — branch health/cleanup plus the remote cr/* lifecycle: deleteRemoteBranch, listRemoteCrBranches, pruneMergedRemoteBranches, isRefMerged, classifyMergedBranches@contentrain/mcp/git/base-branch — resolveBaseBranch, the one base-branch resolver every MCP write and contentrain status / serve use (env → repository.default_branch → <remote>/HEAD → main → master → checked-out branch → main); resolveBaseBranchSource (the same, plus which rung answered); resolveInitDefaultBranch (what init records as repository.default_branch, or null)@contentrain/mcp/git/errors@contentrain/mcp/git/reconcile — reconcileBranches, the local reconcile executor@contentrain/mcp/tools/annotations@contentrain/mcp/templates@contentrain/mcp/providers/local@contentrain/mcp/providers/github@contentrain/mcp/providers/gitlabThese are intended for Contentrain tooling and advanced integrations, not for direct manual editing of .contentrain/ files.
A migration does not deliver content, it delivers a codebase: src/, public/,
package.json, a lockfile, deploy configuration. The content write path was
never built for any of that, and widening it would quietly move a security
boundary the whole product rests on — "Contentrain only writes .contentrain/"
is a promise, not an implementation detail.
@contentrain/mcp/core/migration is the gate instead. It wraps a provider whose
applyPlan can already write any path, and puts consent in front of it.
What it guarantees:
Every refusal happens before the provider is called, and a plan is refused whole: a plan with one path outside the scope writes nothing at all, not even its allowed part. A half-applied migration is harder to recover from than one that never started.
The path matcher is a security control and is written to fail closed. Traversal,
absolute paths, backslash separators, percent-encoding, empty segments and
control characters are refused before any pattern is consulted; patterns are
anchored at both ends, so src/* does not cover src/a/b and nothing matches
by being a prefix; and regex metacharacters in a pattern are literal, so an
allowlist entry cannot silently cover more than it reads.
Key design decisions in this package:
RepoProvider contract (remote providers get the remote-safe subset); media tools ride the provider's optional media facet@octokit/rest, @gitbeaker/rest) are optional peer dependencies — pulled in only when their provider is usedTOOL_REQUIREMENTS is the mapFrom the monorepo root:
contentrain — CLI and local review tooling@contentrain/query — generated runtime query SDK@contentrain/rules — IDE/agent rules and prompts@contentrain/types — shared schema and model typesFull documentation at ai.contentrain.io/packages/mcp.
Contentrain is a content governance ecosystem for teams and AI agents. This package is one of the open-source tools (docs). Studio is the team surface for review and roles; it drives this engine over the HTTP transport and hosts it for external agents as MCP Cloud, and the contentrain_media_* tools need a provider with a media stack such as Studio's. Migrate moves WordPress sites to Astro, and Bridge is a free WordPress plugin that exports a site as a Contentrain store.
MIT
model.locales)contentrain_validate checks an i18n: true model's parity against every locale
in config.locales.supported. A model that declares locales — a subset of that
list — is checked against the subset instead, which is how a partially-translated
site states the truth rather than failing on translations it never had. Absent, as
on every model that predates the field, means the whole project list.
A translation that was never written is a warning: an entry of a collection
that one locale has and another lacks (Entry parity), like a document's missing
translation. A dictionary key missing from a locale (Key parity) is a warning
too. A locale with no content file (Locale file missing) is an error. Every message
names the list it was evaluated against, so the two cases read apart:
contentrain_model_save accepts locales and rejects a locale outside
config.locales.supported. contentrain_validate fix:true reports a broken
declaration but never invents one: narrowing a model's coverage is a content
decision, and the only value the tool could derive — the locales that happen to
have files today — would write the current gaps into the schema and silence the
errors that reveal them.
Structural contentrain_model_save edits preserve existing top-level form and
comments blocks. These blocks belong to the runtime provider; the structural
tool does not enable or change them. Its response lists preserved_blocks when
configuration was carried forward.
Source: packages/mcp/README.md at commit b3e0f74
Project Status
Get full project status (read-only). Returns config, models, context. Do NOT manually edit .contentrain/ based on this output.
Takes no parameters.
Describe Model
Get full schema of a single model (read-only). Do NOT manually create content files — use contentrain_content_save instead.
modelrequiredlocaleinclude_sampleDescribe Format
Describes the Contentrain content file format for any language/platform. Returns a comprehensive specification of the file structure, JSON formats, markdown conventions, meta files, and locale strategies.
Takes no parameters.
Initialize Project
Initialize .contentrain/ structure. Changes are auto-committed to git — do NOT manually create .contentrain/ files.
stackdomainslocalesScaffold Template
Template-based project setup. Available templates: blog, landing, docs, ecommerce, saas, i18n, mobile. Changes are auto-committed to git.
localestemplaterequiredwith_sample_contentSave Model
Create or update a model definition. Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.
idrequiredi18nrequiredkindrequirednamerequireddomainrequiredfieldslocalesdescriptiontitle_fieldrequiredcontent_pathlocale_strategyDelete Model
Delete a model and its content/meta. Changes are auto-committed to git — do NOT manually edit .contentrain/ files.
modelrequiredconfirmrequiredSave Content
Save content entries. Entry format varies by model kind: DICTIONARY — provide "locale" and "data" (flat key-value, all string values); "id" and "slug" are ignored; data keys are the identities. COLLECTION — provide "locale" and "data"; "id" is optional (auto-generated if omitted); "slug" is ignored. DOCUMENT — provide "slug" (required), "locale", and "data"; use the "body" key inside data for markdown content. SINGLETON — provide only "locale" and "data". MEDIA FIELDS (image/video/file): for a media-library asset, pass its storage path ("media/...") or URL; in cloud mode these are automatically normalized to absolute public delivery URLs on save (in markdown bodies too), so saved content renders in a browser anywhere with no SDK — in local mode the relative path is kept as-is. For external images (e.g. a CDN or Unsplash URL), pass the URL directly; it is saved untouched. Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.
modelrequiredentriesrequiredDelete Content
Delete content entries. For dictionaries, use "keys" to remove specific keys (omit to delete entire locale file). Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.
idkeysslugmodelrequiredlocaleconfirmrequiredList Content
List content entries (read-only). Each entry carries its publish state under "meta" — status, plus publish_at/expire_at when set — read from the listed locale's meta; singletons and dictionaries carry it at the top level. Local and remote sessions return the same shape. Change status with contentrain_content_status. Returns data from .contentrain/ — do NOT manually create or modify content files.
limitmodelrequiredfilterlocaleoffsetresolveChange Entry Status
Change the status of content entries — this is how an entry is published, unpublished or archived. Address records the way their meta is laid out: COLLECTION — "entry_ids"; DOCUMENT — "slugs"; SINGLETON and DICTIONARY — neither (one record per locale). i18n models change every supported locale unless "locale" is given. Setting status "published" validates the targeted entries first; if they have errors, nothing is committed. Records already at the requested status are reported as unchanged and not rewritten. contentrain_content_list shows each entry's current status under "meta". Changes are committed to git and follow the project's review or auto-merge workflow — do NOT manually edit .contentrain/meta files.
modelrequiredslugslocalestatusrequiredentry_idsSave Vocabulary Terms
Add or update canonical vocabulary terms. Terms nest as { "term-slug": { "en": "…", "tr": "…" } } — the OUTER key is the term, the INNER key is a locale. Merges with the existing vocabulary: a term you omit is untouched. Changes are auto-committed to git — do NOT manually edit .contentrain/vocabulary.json.
termsrequiredDelete Vocabulary Terms
Remove canonical vocabulary terms by slug. Content already using a term is not touched — the vocabulary only advises. Changes are auto-committed to git.
termsrequiredconfirmrequiredValidate Project
Validate project content against model schemas. Detects required field violations, type mismatches, broken relations, secret leaks, i18n parity issues, and more. If fix:true, auto-fixes structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout, scheduling keys an older content_save leaked into document frontmatter) and backfills a missing model title_field — do NOT manually edit .contentrain/ files. Content without meta gets meta with status published, matching what delivery already serves. A fix that would change whether an entry is delivered is listed in visibility_changes and left on its branch for review instead of auto-merged.
fixmodelSubmit Branches
Push cr/* branches to remote. MCP is push-only — PR creation is handled by the platform. Do NOT manually push or create PRs.
messagebranchesMerge Branch
Merge a review-mode branch into contentrain. Local git operation — no external platform needed. Merges the feature branch into the contentrain branch, advances the base branch via update-ref, selectively syncs .contentrain/ files to the working tree, and prunes the merged branch. Target by exact "branch" name, or resolve by "model" (+ optional "locale"/"latest").
modelbranchlatestlocaleconfirmrequiredList Branches
List pending contentrain (cr/*) branches with their merge status against the contentrain branch. Use this to discover branch names for contentrain_merge / contentrain_branch_delete, and to monitor branch-health limits (warning at 50, blocked at 80 unmerged).
remoteunmerged_onlyDelete Branch
Delete a pending contentrain (cr/*) branch that will not be merged — e.g. a branch left behind by a failed operation, or a superseded draft. Only cr/* branches can be deleted; the contentrain branch is protected. This is destructive: the branch and its unmerged commits are removed.
branchrequiredconfirmrequiredReconcile Diverged Branches
Reconcile a diverged contentrain ↔ base-branch pair with a content-aware three-way merge. DRY RUN (default, dry_run:true): reports what would merge and which conflicts need a decision — touches nothing. EXECUTE (dry_run:false): performs the merge as a two-parent commit on contentrain and fast-forwards the base branch. Everything one side changed merges mechanically (entry-, key-, term+locale-level); only the same item changed differently on both sides becomes a conflict. Answer conflicts by passing resolutions (from the dry-run ids) and running again — a resolution whose values changed since the dry-run is dropped and the conflict re-reported. Recommended workflow: always run dry_run first, review the summary and conflicts, then execute.
dry_runresolutionsScan Source Code
Scan project source code for content strings. Three modes: "graph" builds import/component graph for project intelligence, "candidates" extracts string literals with pre-filtering and pagination, "summary" provides quick overview stats. Read-only — no changes to disk or git. MCP finds strings deterministically; the agent decides what is content. Recommended workflow: start with "summary" or "graph" for orientation, then paginate through "candidates" to evaluate strings.
modelimitpathsoffsetexcludeincludemax_lengthmin_lengthApply Normalize
Apply normalize operations. Two modes: "extract" writes agent-approved strings to Contentrain content files (source untouched), "reuse" patches source files with agent-provided replacement expressions. DRY RUN (default, dry_run:true): validates inputs, resolves conflicts, and returns a full preview — NO changes to disk or git. EXECUTE (dry_run:false): writes files to disk, commits to a branch, and requires branch health check to pass. Recommended workflow: always run dry_run first, review the preview, then call again with dry_run:false to execute. Normalize operations always use review workflow (never auto-merge).
moderequiredscopedry_runpatchesextractionsBulk Operations
Batch operations on content entries: copy a locale, or delete many collection entries. All operations are auto-committed to git. To publish or change status, use contentrain_content_status. DRY RUN (dry_run:true): runs the whole operation in a throwaway worktree and reports exactly what would change — nothing is committed and no branch survives. Recommended workflow: preview with dry_run:true, review the counts, then call again to execute.
modelrequiredconfirmdry_runentry_idsoperationrequiredsource_localetarget_localeProject Health Report
Project health report (read-only). Returns structured checks: git, node, .contentrain/ structure, model parse, orphan content, branch pressure, SDK freshness. Pass `usage: true` for a deeper analysis of content-key references in source files (unused keys, duplicate dictionary values, locale coverage). Local-filesystem only — unavailable over remote providers.
usage100% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
pnpm add @contentrain/mcpnpx contentrain setup claude-code # or: cursor, vscode, windsurf, copilotCONTENTRAIN_PROJECT_ROOT=/path/to/project npx -y @contentrain/mcpimport { createServer } from '@contentrain/mcp/server'import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
const server = createServer(process.cwd())const transport = new StdioServerTransport()
await server.connect(transport){ "error": "contentrain_scan requires local filesystem access.", "capability_required": "astScan", "hint": "This tool is unavailable when MCP is driven by a remote provider (e.g. GitHubProvider). Use a LocalProvider or the stdio transport."}pnpm add @gitbeaker/restimport { createGitLabProvider } from '@contentrain/mcp/providers/gitlab'import { createServer } from '@contentrain/mcp/server'
const provider = await createGitLabProvider({ auth: { type: 'pat', token: process.env.GITLAB_TOKEN! }, project: { projectId: 'acme/site', // or numeric project ID host: 'https://gitlab.company.com', // omit for gitlab.com },})
const server = createServer({ provider })// serve over stdio or the HTTP transport from @contentrain/mcp/server/httpimport { createMigrationWriter, scopeHash } from '@contentrain/mcp/core/migration'
const scope = { allow: ['src/**', 'public/**', 'package.json', 'astro.config.mjs'], branch: 'migration/acme', base: 'main',}
// Show the user `scope`, not the hash. Then bind their consent to it:const approval = { scope_hash: await scopeHash(scope), approver: { kind: 'human', id: userId, role: 'owner' }, approved_at: new Date().toISOString(),}
const writer = createMigrationWriter(provider, scope, approval)await writer.applyPlan({ ...plan, step: 'emit', actor })writer.audit // which file, in which step, by which actor, in which commitpnpm --filter @contentrain/mcp buildpnpm --filter @contentrain/mcp testpnpm --filter @contentrain/mcp typecheckpnpm exec oxlint packages/mcp/src packages/mcp/testsLocale file missing: tr.json (checked against the model's own locales [en, tr])Entry parity: entry "a1b2c3" exists in en but missing in tr (checked against the project's supported locales [en, tr, da])