Skip to content

feat(mcp): phase 8 — GitLabProvider (full integration)#34

Merged
ABB65 merged 1 commit into
next-mcpfrom
refactor/phase-8-gitlab-provider
Apr 17, 2026
Merged

feat(mcp): phase 8 — GitLabProvider (full integration)#34
ABB65 merged 1 commit into
next-mcpfrom
refactor/phase-8-gitlab-provider

Conversation

@ABB65

@ABB65 ABB65 commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

Full GitLab provider — same RepoProvider contract 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/rest as 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)

  • Atomic commit — one Commits.create call with an actions array (create / update / delete) vs GitHub's createBlob × N → createTree → createCommit → updateRef/createRef. applyPlan probes 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. MergeResult keeps the same { merged, sha, pullRequestUrl } shape; the MR URL is available for audit. Semantic parity with GitHub's repos.merge.
  • ReadsRepositoryFiles.showRaw returns UTF-8 directly; fileExists falls back to a tree listing when the file endpoint 404s so directories resolve to true matching 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 E2E
  • packages/mcp/package.json@gitbeaker/rest >=43.0.0 as optional peer, ./providers/gitlab export, build targets extended
  • packages/mcp/README.md — new "Remote Providers" section with GitLab install + usage, "Bitbucket — coming soon" note

Test plan

  • pnpm --filter @contentrain/mcp typecheck → 0 errors
  • oxlint packages/mcp/src packages/mcp/tests → 0 warnings (135 files)
  • fast suite (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)
  • GitLab provider only (tests/providers/gitlab) → 31/31 green in <1s
  • HTTP E2E (tests/server/http.test.ts) → 8/8 green including both GitHub and GitLab remote content_save E2Es
  • Full tool suite (tests/tools) → 90/91 on parallel run; 1 pre-existing flaky timeout on content.test.tsblocks 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

  • Conformance fixtures (shared harness across providers) — deferred. Per-provider unit tests + the HTTP E2E exercise every method in RepoProvider today; a single shared conformance harness is a separate hardening pass.
  • Next: Phase 9 (changeset, top-level README calling out all three providers, release).

🤖 Generated with Claude Code

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>
@ABB65 ABB65 merged commit 4e28f00 into next-mcp Apr 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant