feat(mcp): phase 8 — GitLabProvider (full integration)#34
Merged
Conversation
Adds a production-grade GitLab provider that implements the same
RepoProvider contract as the existing GitHubProvider — full reader +
writer + branch ops + merge. No feature gaps versus GitHub. Sits
behind the same HTTP transport so Studio (and any agent driving MCP
remotely) can point at gitlab.com or a self-hosted GitLab CE / EE
instance with no tool-facing behaviour changes.
Why gitbeaker / @gitbeaker/rest:
The library is the only actively-maintained, TypeScript-native,
feature-complete GitLab SDK on npm (v43.8.0 released Nov 2025,
MIT, 1.7k stars). It mirrors @octokit/rest's role for GitHub and
lets the codebase keep a single peer-dep + dynamic-import pattern
across git hosts. The alternative — hand-rolled fetch against the
v4 REST API — trades ~500 lines of typed client code for ~1,300
lines of manual pagination, response typing, and rate-limit
handling without any concrete benefit. The GitLab docs' other
listed clients (`gitlab-yaac`, `backbone-gitlab`) are both dead
(last published 10–11 years ago).
API shape differences versus GitHub:
- Atomic commit: GitHub needs createBlob × N → createTree →
createCommit → updateRef/createRef (4+N calls). GitLab's Commits
API accepts a single POST with an actions array that covers
create / update / delete — one call does the lot. applyPlan
resolves each action's verb by probing file existence at the
target ref so GitLab's strict server-side validation never
rejects mixed-state plans.
- Merge: GitLab has no direct branch-to-branch merge endpoint.
`mergeBranch` opens an MR and immediately accepts it — the
resulting MergeResult keeps the { merged, sha, pullRequestUrl }
shape, with the MR URL available for audit. Callers see the
same contract as GitHub's repos.merge result.
- Reads: RepositoryFiles.showRaw returns UTF-8 text directly
(no base64 decode), and fileExists falls back to a tree listing
when the file endpoint 404s so directories resolve to `true`
the same way LocalReader / GitHubReader do.
Changes:
- packages/mcp/src/providers/gitlab/ (new, 9 files, ~480 LOC)
- types.ts, paths.ts, client.ts, capabilities.ts — small plumbing
mirroring the GitHub provider shape.
- reader.ts — RepoReader over RepositoryFiles.showRaw +
Repositories.allRepositoryTrees. Blob fallback for edge /
browser runtimes.
- apply-plan.ts — single Commits.create call with create /
update / delete action resolution. Filters no-op deletes
(delete-nonexistent would be a 400). Uses startBranch for
first commit on a new feature branch.
- branch-ops.ts — listBranches (server `search` + client-side
prefix guard), createBranch, deleteBranch, getBranchDiff
(Repositories.compare), mergeBranch (MR create + accept),
isMerged (compare with empty commits list),
getDefaultBranch.
- provider.ts — GitLabProvider class implementing RepoProvider,
delegates to the helpers above.
- factory.ts — createGitLabClient + createGitLabProvider with
dynamic import of @gitbeaker/rest. Supports pat / oauth / job
token auth. Accepts optional `host` for self-hosted GitLab.
- index.ts — barrel.
- packages/mcp/package.json
- @gitbeaker/rest >=43.0.0 added as optional peer (same pattern
as @octokit/rest).
- ./providers/gitlab export added; build/dev tsdown targets
extended.
- packages/mcp/tests/providers/gitlab/ (new, 3 files, 31 tests)
- reader.test.ts — readFile (string + Blob paths), contentRoot
prefixing, default-branch resolution, listDirectory 404 →
[], fileExists (file hit, directory fallback, empty tree,
double-404, non-404 error propagation). 12 tests.
- apply-plan.test.ts — new-branch commit with startBranch,
existing-branch update/create action resolution, delete
filtering, contentRoot prefixing, default-branch fallback,
empty-plan throw, Commit envelope normalisation. 7 tests.
- branch-ops.test.ts — getDefaultBranch, listBranches (no
prefix + prefix with substring guard), createBranch,
deleteBranch, getBranchDiff (added / modified / removed +
empty diffs), mergeBranch (accepted + unaccepted), isMerged
(merged + ahead). 12 tests.
- packages/mcp/tests/server/http.test.ts
- New E2E: `commits content_save through a GitLabProvider-like
remote provider`. Mirrors the existing GitHub E2E — drives
contentrain_content_save over HTTP against a mocked gitbeaker
client, asserts pending-review workflow, captured Commits.create
payload contains the expected action paths (content + meta +
context.json), and startBranch is set for the new branch.
- packages/mcp/README.md
- New "Remote Providers" section listing the three backends and
their capability differences.
- GitLab installation + usage snippet (PAT auth + self-hosted
host config).
- "Bitbucket — coming soon" note.
- providers/gitlab added to Core Exports.
Bitbucket is intentionally not in this PR. Bitbucket Cloud's API
shape is materially different (tree reads require a separate blob
hash lookup; the commits endpoint is per-file rather than per-plan)
and market share is lower. Shipping it later as its own PR keeps
this change focused and releasable.
Verification:
- pnpm --filter @contentrain/mcp typecheck → 0 errors.
- oxlint packages/mcp/src + tests → 0 warnings (135 files).
- vitest run tests/core tests/conformance tests/serialization-parity
tests/git tests/providers tests/server tests/util
→ 413/413 green, 2 skipped (31 new GitLab tests + 1 new HTTP E2E).
- vitest run tests/tools (full suite)
→ 90/91 on the parallel run with one pre-existing flaky timeout
(content.test.ts `blocks new writes when 80 active contentrain
branches exist` — 80 sequential git checkouts inside a 120s
budget gets squeezed under heavy parallel load). Re-run in
isolation → 17/17 green in 450s. Independent of Phase 8; the
new GitLab code is not exercised by that test.
Phase 8 scope after this PR:
- 8.1 GitLabProvider (full) ✓ (this PR)
- 8.2 BitbucketProvider — deferred, see README.
- 8.3 Conformance fixtures — the reader + apply-plan + branch-ops
unit tests exercise every method in RepoProvider; a shared
conformance harness across providers is a separate hardening
pass (Phase 9 or later).
What Phase 9 picks up next:
- Changeset + release notes.
- Top-level README updates calling out the three providers.
- Final cleanup pass.
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
Full GitLab provider — same
RepoProvidercontract as the existing GitHub provider, no feature gaps. Sits behind the same HTTP transport so Studio (and any agent driving MCP remotely) can point at gitlab.com or a self-hosted GitLab CE / EE instance.Driven by
@gitbeaker/restas an optional peer dependency. After reviewing the landscape (see PR review discussion), it's the only actively-maintained, TypeScript-native, feature-complete GitLab SDK on npm — mirroring@octokit/rest's role for GitHub keeps the codebase's peer-dep + dynamic-import pattern consistent across git hosts.API shape differences GitLab handles (vs GitHub)
Commits.createcall with an actions array (create/update/delete) vs GitHub'screateBlob × N → createTree → createCommit → updateRef/createRef.applyPlanprobes file existence at the target ref so GitLab's strict server-side validation never rejects mixed-state plans.mergeBranchopens an MR and immediately accepts it.MergeResultkeeps the same{ merged, sha, pullRequestUrl }shape; the MR URL is available for audit. Semantic parity with GitHub'srepos.merge.RepositoryFiles.showRawreturns UTF-8 directly;fileExistsfalls back to a tree listing when the file endpoint 404s so directories resolve totruematching LocalReader / GitHubReader.Bitbucket
Intentionally not in this PR — Bitbucket Cloud's API shape is materially different (tree reads require separate blob-hash lookups; commits endpoint is per-file rather than per-plan) and the market share is lower. It ships later as its own focused PR. README notes "coming soon".
Changes
packages/mcp/src/providers/gitlab/— 9 files, ~480 LOC (types, paths, client, capabilities, reader, apply-plan, branch-ops, provider, factory, index)packages/mcp/tests/providers/gitlab/— 3 files, 31 tests (reader 12, apply-plan 7, branch-ops 12)packages/mcp/tests/server/http.test.ts— new GitLab E2E, mirrors existing GitHub E2Epackages/mcp/package.json—@gitbeaker/rest >=43.0.0as optional peer,./providers/gitlabexport, build targets extendedpackages/mcp/README.md— new "Remote Providers" section with GitLab install + usage, "Bitbucket — coming soon" noteTest plan
pnpm --filter @contentrain/mcp typecheck→ 0 errorsoxlint packages/mcp/src packages/mcp/tests→ 0 warnings (135 files)tests/core tests/conformance tests/serialization-parity tests/git tests/providers tests/server tests/util) → 413/413 green, 2 skipped (31 new GitLab tests + 1 new HTTP E2E included)tests/providers/gitlab) → 31/31 green in <1stests/server/http.test.ts) → 8/8 green including both GitHub and GitLab remote content_save E2Estests/tools) → 90/91 on parallel run; 1 pre-existing flaky timeout oncontent.test.ts→blocks new writes when 80 active contentrain branches exist(80 sequential git checkouts under a 120s budget gets squeezed under heavy parallel load). Re-run in isolation → 17/17 green in 450s. Independent of Phase 8; the flaky test does not exercise any new GitLab code.Scope notes
RepoProvidertoday; a single shared conformance harness is a separate hardening pass.🤖 Generated with Claude Code