diff --git a/.changeset/refactor-provider-agnostic-engine.md b/.changeset/refactor-provider-agnostic-engine.md new file mode 100644 index 0000000..a70f2d0 --- /dev/null +++ b/.changeset/refactor-provider-agnostic-engine.md @@ -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. diff --git a/README.md b/README.md index ab330d0..d8635b6 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 | diff --git a/RELEASING.md b/RELEASING.md index 1ccdedb..e9762e2 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -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. diff --git a/packages/mcp/package.json b/packages/mcp/package.json index e2b0375..66fe5cb 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -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" diff --git a/packages/mcp/src/util/fs.ts b/packages/mcp/src/util/fs.ts index 1dafbde..db9b5df 100644 --- a/packages/mcp/src/util/fs.ts +++ b/packages/mcp/src/util/fs.ts @@ -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 { try { diff --git a/packages/mcp/src/util/serializer.ts b/packages/mcp/src/util/serializer.ts deleted file mode 100644 index cdff60e..0000000 --- a/packages/mcp/src/util/serializer.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Canonical JSON serialization — re-exported from @contentrain/types. - * Kept as a module for backward compatibility with MCP subpath exports. - */ -export { canonicalStringify, sortKeys } from '@contentrain/types' diff --git a/packages/mcp/tests/util/serializer.test.ts b/packages/mcp/tests/util/serializer.test.ts index 0241868..4e09492 100644 --- a/packages/mcp/tests/util/serializer.test.ts +++ b/packages/mcp/tests/util/serializer.test.ts @@ -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', () => {