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
35 changes: 35 additions & 0 deletions .changeset/refactor-provider-agnostic-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@contentrain/mcp": minor
---

feat(mcp): provider-agnostic engine + HTTP transport + GitHub & GitLab providers

The MCP package is now driven by a `RepoProvider` abstraction. All
tools route through the same reader + writer + branch-ops contract,
and the server accepts any provider (not just local disk).

Shipped in this release:

- **HTTP transport** (`@contentrain/mcp/server/http`) — Streamable
HTTP MCP transport with optional Bearer auth. Works against any
provider.
- **GitHubProvider** (`@contentrain/mcp/providers/github`) — Octokit
over the Git Data + Repos APIs. `@octokit/rest` is an optional
peer dependency.
- **GitLabProvider** (`@contentrain/mcp/providers/gitlab`) —
gitbeaker over the GitLab REST API. Supports gitlab.com and
self-hosted CE / EE. `@gitbeaker/rest` is an optional peer
dependency.
- **Reader-backed reads everywhere** — `listModels`,
`readModel`, `countEntries`, `checkReferences`, and
`validateProject` now have reader overloads, so remote providers
get the same read-side behaviour (validation, reference
integrity, entry counts) as LocalProvider.
- **Capability-gated tools** — normalize / scan / apply reject with
a uniform `capability_required` error on providers that do not
expose local disk access.

No tool-surface changes. Stdio transport + LocalProvider remain the
default and behave identically to the previous release.

Bitbucket provider is on the roadmap; see the README.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,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
- **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
- **Serve UI** — local web dashboard for browsing models, content, validation, and normalize status
Expand Down Expand Up @@ -175,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 — content operations engine |
| [`@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`](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
14 changes: 14 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,17 @@ The GitHub `Release` workflow expects:
- Do not maintain a custom release manifest.
- Do not create manual monorepo-wide release tags.
- Internal workspaces such as `docs` and `packages/cli/src/serve-ui` must remain `private: true`.

## 🔌 `@contentrain/mcp` Optional Peer Dependencies

`@contentrain/mcp` ships the remote providers as optional peers so
consumers only install what they use:

- `@octokit/rest` — required when using `GitHubProvider`.
- `@gitbeaker/rest` — required when using `GitLabProvider`.

Stdio + LocalProvider flows (the default) do not need either. When
the MCP server is invoked with a provider whose peer is missing,
the factory throws a helpful error pointing at the install command.
Release audits do not need to bundle these peers — npm's
`peerDependenciesMeta.optional: true` handles it.
4 changes: 2 additions & 2 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@
"dist"
],
"scripts": {
"build": "tsdown src/index.ts src/server.ts src/core/config.ts src/core/context.ts src/core/model-manager.ts src/core/content-manager.ts src/core/meta-manager.ts src/core/validator/index.ts src/core/scanner.ts src/core/scan-config.ts src/core/graph-builder.ts src/core/apply-manager.ts src/util/detect.ts src/util/fs.ts src/util/id.ts src/util/serializer.ts src/git/transaction.ts src/git/branch-lifecycle.ts src/templates/index.ts src/providers/github/index.ts src/providers/gitlab/index.ts src/server/http/index.ts --format esm --dts --external typescript",
"dev": "tsdown src/index.ts src/server.ts src/core/config.ts src/core/context.ts src/core/model-manager.ts src/core/content-manager.ts src/core/meta-manager.ts src/core/validator/index.ts src/core/scanner.ts src/core/scan-config.ts src/core/graph-builder.ts src/core/apply-manager.ts src/util/detect.ts src/util/fs.ts src/util/id.ts src/util/serializer.ts src/git/transaction.ts src/git/branch-lifecycle.ts src/templates/index.ts src/providers/github/index.ts src/providers/gitlab/index.ts src/server/http/index.ts --format esm --dts --external typescript --watch",
"build": "tsdown src/index.ts src/server.ts src/core/config.ts src/core/context.ts src/core/model-manager.ts src/core/content-manager.ts src/core/meta-manager.ts src/core/validator/index.ts src/core/scanner.ts src/core/scan-config.ts src/core/graph-builder.ts src/core/apply-manager.ts src/util/detect.ts src/util/fs.ts src/util/id.ts src/git/transaction.ts src/git/branch-lifecycle.ts src/templates/index.ts src/providers/github/index.ts src/providers/gitlab/index.ts src/server/http/index.ts --format esm --dts --external typescript",
"dev": "tsdown src/index.ts src/server.ts src/core/config.ts src/core/context.ts src/core/model-manager.ts src/core/content-manager.ts src/core/meta-manager.ts src/core/validator/index.ts src/core/scanner.ts src/core/scan-config.ts src/core/graph-builder.ts src/core/apply-manager.ts src/util/detect.ts src/util/fs.ts src/util/id.ts src/git/transaction.ts src/git/branch-lifecycle.ts src/templates/index.ts src/providers/github/index.ts src/providers/gitlab/index.ts src/server/http/index.ts --format esm --dts --external typescript --watch",
"test": "vitest run",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist"
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/src/util/fs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readFile, readdir, access, writeFile, mkdir } from 'node:fs/promises'
import { join } from 'node:path'
import { canonicalStringify } from './serializer.js'
import { canonicalStringify } from '@contentrain/types'

export async function pathExists(filePath: string): Promise<boolean> {
try {
Expand Down
5 changes: 0 additions & 5 deletions packages/mcp/src/util/serializer.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/mcp/tests/util/serializer.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { canonicalStringify } from '../../src/util/serializer.js'
import { canonicalStringify } from '@contentrain/types'

describe('canonicalStringify', () => {
it('sorts keys lexicographically', () => {
Expand Down
Loading