Context
Neo currently has a comprehensive GitHub Workflow MCP Server (ai/services/github-workflow/ + ai/mcp/server/github-workflow/) that handles tickets, pull requests, discussions, archives, and Memory-Core-integrated A2A coordination. This epic creates a parallel GitLab Workflow MCP Server matching the same architectural pattern, scoped to issues + merge requests only (no GitLab Discussions equivalent).
Operator-stated rationale (2026-05-15): substrate lives inside Neo; usage is for client projects, not Neo-internal coordination. "create the mcp server and services (and testing) inside neo, but only use it for the client project."
Empirical anchor: tonight's substrate-evolution work (#11381 contentPath.mjs + ADR 0004 §1.3 strategic principle) made this MORE viable than a week ago — the universal-ordinal-100 path math + GitHub-as-source-of-truth → regeneratable-cache strategic principle generalize to any forge (GitHub, GitLab, etc.).
The Problem
External client projects use GitLab as their forge. Neo's Agent OS substrate (Memory Core integration, A2A coordination, ticket/PR lifecycle tracking, ingestion-to-Knowledge-Base) is currently locked to GitHub via github-workflow. Without a parallel gitlab-workflow server:
- Client projects can't use Neo's agent-coordination patterns (A2A on tickets/MRs, ingestion of tickets-as-Memory-Core-graph-nodes, etc.)
- Agents working on client projects fall back to manual
gh-CLI-style coordination without the substrate-evolution-guard layer ADR 0004 + 0005 + 0006 provides
- Each client deployment re-derives integration patterns instead of consuming canonical Neo substrate
The Architectural Reality
Reusable from github-workflow (no rebuild):
ai/services/github-workflow/shared/contentPath.mjs — provider-agnostic universal ordinal-100 path math (just-merged via PR #11381); applies identically for GitLab
_index.json schema (ContentIndexEntry typedef on contentPath.mjs) — provider-agnostic
- ADR 0004 §1.3 strategic principle (GitHub-as-source-of-truth →
resources/content/ regeneratable cache) generalizes verbatim to GitLab-as-source-of-truth
- MCP server scaffolding pattern (
ai/mcp/server/<name>/server.mjs, openapi.yaml, config.template.mjs shape)
- Memory Core integration patterns (
add_memory, A2A add_message, KB ingestion)
- Testing patterns (
test/playwright/unit/ai/services/<name>/*.spec.mjs)
Need new implementation:
- GitLab API client (REST + GraphQL via
@gitbeaker/node or native-fetch)
IssueSyncer for GitLab Issues
MergeRequestSyncer for GitLab MRs (GitLab's PR-equivalent; no Discussions analog)
LocalFileService for GitLab-specific paths (folder shape under resources/content/)
- MCP server
gitlab-workflow with OpenAPI tool surface mirroring github-workflow's tool set (e.g., get_issue, create_issue, get_merge_request, update_issue_assignees, manage_pr_reviewers, etc.)
- Config + auth (GitLab Personal Access Token handling, env-var integration; optional OIDC parallel to KB/MC v12.1 stack)
Architectural-decision commitment (operator-aligned, NOT an open question):
Parallel-substrate-with-shared-helpers, NOT enforced base-class extraction. Same pattern as Knowledge Base + Memory Core servers (parallel, sibling). Rationale:
- Copy
IssueSyncer.mjs skeleton → swap API client → reuse shared/contentPath.mjs for path math
- Diverging signatures (GitLab Issue API vs GitHub Issue API) make enforced base-class inheritance more cost than benefit
- If shared-base extraction surfaces empirically during implementation (e.g., 3 sites of duplication > 50 lines each), file follow-up ticket; do NOT pre-design
The Fix (sequenced sub-tasks)
Filing the epic now with the task enumeration; sub-issues will be filed as agents claim them per ticket-create-workflow.md §10 chained-call pattern.
Sub-task 1: MCP server scaffolding + OpenAPI tool surface
Create ai/mcp/server/gitlab-workflow/:
server.mjs (entry point; mirror github-workflow/server.mjs shape)
openapi.yaml (tool surface declaring get_issue, list_issues, create_issue, update_issue_assignees, update_issue_labels, get_merge_request, list_merge_requests, manage_pr_reviewers, add_message/A2A-bridge if applicable, etc.)
config.template.mjs (gitignored template; GitLab host URL, PAT env-var, sync intervals)
config.mjs (gitignored runtime)
Sub-task 2: GitLab API client
Create ai/services/gitlab-workflow/api/GitLabClient.mjs:
- REST + GraphQL methods for Issues + MRs
- Auth via PAT (
Authorization: Bearer ${token} header)
- Pagination handling (GitLab uses link headers; different from GitHub's
?per_page)
- Rate-limit handling
- Reuse Neo's existing HTTP-client patterns (probably the same
httpModule.request + Promise wrapper pattern from TextEmbeddingService.mjs)
Sub-task 3: IssueSyncer + MergeRequestSyncer + LocalFileService
Create ai/services/gitlab-workflow/:
sync/IssueSyncer.mjs — mirrors github-workflow/sync/IssueSyncer.mjs shape; consumes shared/contentPath.mjs from gh-workflow OR duplicates the path-math (decision: import from github-workflow/shared/contentPath.mjs since it's provider-agnostic)
sync/MergeRequestSyncer.mjs — mirrors github-workflow/sync/PullRequestSyncer.mjs shape
LocalFileService.mjs — file-IO for resources/content/gitlab/... substrate
Folder shape decision (operator-aligned, NOT open):
resources/content/gitlab/issues/chunk-N/issue-<NNN>.md
resources/content/gitlab/merge-requests/chunk-N/mr-<NNN>.md
resources/content/gitlab/archive/issues/v<X.Y.Z>/chunk-N/...
resources/content/gitlab/archive/merge-requests/v<X.Y.Z>/chunk-N/...
Universal ordinal-100 substrate; matches ADR 0004 §2.1 target shape under a gitlab/ namespace prefix.
Sub-task 4: Tests (Playwright unit + integration)
Create test/playwright/unit/ai/services/gitlab-workflow/:
GitLabClient.spec.mjs — mock-HTTP-server pattern (same as TextEmbeddingService.retry.spec.mjs); covers auth, pagination, rate-limit
IssueSyncer.spec.mjs — end-to-end sync logic with mock API responses
MergeRequestSyncer.spec.mjs — same
LocalFileService.spec.mjs — file-path round-trips
- MCP-server smoke spec at
test/playwright/unit/ai/mcp/server/gitlab-workflow/ (mirror gh-workflow's MCP tests)
Sub-task 5: Config integration + auth + docs
- Add
gitlab-workflow config to ai/mcp/server/gitlab-workflow/config.template.mjs per config.mjs pattern; env-var bindings for NEO_GITLAB_HOST, NEO_GITLAB_PAT, etc.
- Add gitignore entry for
ai/mcp/server/gitlab-workflow/config.mjs
- Add
worktree-bootstrap.mjs integration (the script that copies gitignored config.mjs files from main checkout to worktrees on session boot)
- Document in
learn/agentos/ how to point the server at a GitLab instance (PAT generation, env-var setup, host URL configuration)
- Add to
package.json script registry if any new npm run invocations needed (e.g., ai:sync-gitlab)
Optional Sub-task 6: Memory Core integration validation
- Verify GitLab tickets/MRs ingest cleanly into Memory Core graph
- Verify A2A flows work end-to-end with GitLab-side artifact references (commentIds, MR review IDs)
- Verify Knowledge Base ingestion picks up GitLab-sourced markdown without special-case logic
If Memory Core integration "just works" via the substrate-agnostic patterns (which it should — add_message + add_memory are forge-agnostic), this sub-task closes via empirical-test rather than implementation.
Acceptance Criteria
Out of Scope
- GitLab Discussions / Wiki / Snippets: no analog to GitHub Discussions tab; not in this epic's scope. If client project needs them, file separate follow-up tickets.
- GitLab Pipelines / CI integration: separate domain (CI orchestration is its own substrate; not part of "tickets + MRs" parity scope).
- Shared-base-class extraction between
github-workflow and gitlab-workflow: explicitly rejected per the architectural-decision commitment above. If empirical duplication surfaces post-implementation (e.g., 3+ sites of >50 lines each), file follow-up substrate-evolution ticket.
- Replacing Neo's internal
github-workflow usage: Neo continues using GitHub as forge; gitlab-workflow exists for external client deployments only.
- OIDC / OAuth2.1 / SSO integration beyond PAT-based auth: optional Sub-task; gate on client-project requirement. Initial scope is PAT-only.
Avoided Traps
- Pre-designing the shared-base-class abstraction: rejected. Per Neo's KB + MC sibling-server precedent + ADR 0001 single-writer pattern, parallel-substrate-with-shared-helpers is the canonical shape. Pre-design adds cognitive load without empirical demand.
- Mapping GitHub Discussions to GitLab MR comments: rejected as scope-creep. No semantic equivalence; if client project needs threaded discussion, file separately.
- Adding
gitlab-workflow to Neo's daemon orchestrator: rejected. Daemon orchestration is Neo-internal substrate; gitlab-workflow is opt-in client-project substrate. Different lifecycle classes.
- Pre-filing all 5 sub-issues at epic-creation time: rejected as backlog-pollution. Sub-issues file as agents claim them per
ticket-create-workflow.md §10 chained-call pattern. The epic body's task enumeration is the durable substrate; sub-issues materialize on demand.
- Treating this as "external substrate not in Neo": rejected per operator-stated framing 2026-05-15. "create the mcp server and services (and testing) inside neo, but only use it for the client project." The substrate is Neo's; the deployment is client-side via env-var/config.
Related
- Substrate precedent:
ai/mcp/server/github-workflow/ + ai/services/github-workflow/ — full-feature parallel substrate.
- Sibling parallel-substrate pattern:
ai/mcp/server/knowledge-base/ + ai/mcp/server/memory-core/ — also parallel sibling servers; same architectural pattern.
- Foundational substrate (provider-agnostic):
ai/services/github-workflow/shared/contentPath.mjs (PR #11381, merged 2026-05-15T08:19:41Z).
- Strategic anchor: ADR 0004 §1.3 Regeneratable-Cache Strategic Principle (PR #11401, merged 2026-05-15T08:25:15Z) — generalizes to GitLab-as-source-of-truth verbatim.
- Substrate-evolution-guard anchors: ADR 0004 §2.6 Clean-Cut Pattern + §5.6 Deprecation-theater anti-pattern (PR #11398, merged 2026-05-15T08:23:55Z) — apply to any future
gitlab-workflow substrate evolution.
- Empirical velocity anchor: this epic is scoped to closeable inside ~1 calendar day with 3-agent focus per operator velocity-calibration 2026-05-15 (~20-30 PRs/day swarm throughput observed during this nightshift session arc).
Origin Session
- Origin Session ID:
b8a152e1-a41c-49aa-8196-8e5d2eba84ca
- Operator-direction A2A: 2026-05-15 nightshift conversation — "my take would be: create the mcp server and services (and testing) inside neo, but only use it for the client project."
Retrieval Hint
Search for GitLab workflow MCP server epic parallel-substrate IssueSyncer MergeRequestSyncer contentPath universal ordinal-100 client project.
Context
Neo currently has a comprehensive GitHub Workflow MCP Server (
ai/services/github-workflow/+ai/mcp/server/github-workflow/) that handles tickets, pull requests, discussions, archives, and Memory-Core-integrated A2A coordination. This epic creates a parallel GitLab Workflow MCP Server matching the same architectural pattern, scoped to issues + merge requests only (no GitLab Discussions equivalent).Operator-stated rationale (2026-05-15): substrate lives inside Neo; usage is for client projects, not Neo-internal coordination. "create the mcp server and services (and testing) inside neo, but only use it for the client project."
Empirical anchor: tonight's substrate-evolution work (#11381
contentPath.mjs+ ADR 0004 §1.3 strategic principle) made this MORE viable than a week ago — the universal-ordinal-100 path math + GitHub-as-source-of-truth → regeneratable-cache strategic principle generalize to any forge (GitHub, GitLab, etc.).The Problem
External client projects use GitLab as their forge. Neo's Agent OS substrate (Memory Core integration, A2A coordination, ticket/PR lifecycle tracking, ingestion-to-Knowledge-Base) is currently locked to GitHub via
github-workflow. Without a parallelgitlab-workflowserver:gh-CLI-style coordination without the substrate-evolution-guard layer ADR 0004 + 0005 + 0006 providesThe Architectural Reality
Reusable from
github-workflow(no rebuild):ai/services/github-workflow/shared/contentPath.mjs— provider-agnostic universal ordinal-100 path math (just-merged via PR #11381); applies identically for GitLab_index.jsonschema (ContentIndexEntrytypedef oncontentPath.mjs) — provider-agnosticresources/content/regeneratable cache) generalizes verbatim to GitLab-as-source-of-truthai/mcp/server/<name>/server.mjs,openapi.yaml,config.template.mjsshape)add_memory, A2Aadd_message, KB ingestion)test/playwright/unit/ai/services/<name>/*.spec.mjs)Need new implementation:
@gitbeaker/nodeor native-fetch)IssueSyncerfor GitLab IssuesMergeRequestSyncerfor GitLab MRs (GitLab's PR-equivalent; no Discussions analog)LocalFileServicefor GitLab-specific paths (folder shape underresources/content/)gitlab-workflowwith OpenAPI tool surface mirroringgithub-workflow's tool set (e.g.,get_issue,create_issue,get_merge_request,update_issue_assignees,manage_pr_reviewers, etc.)Architectural-decision commitment (operator-aligned, NOT an open question):
Parallel-substrate-with-shared-helpers, NOT enforced base-class extraction. Same pattern as Knowledge Base + Memory Core servers (parallel, sibling). Rationale:
IssueSyncer.mjsskeleton → swap API client → reuseshared/contentPath.mjsfor path mathThe Fix (sequenced sub-tasks)
Filing the epic now with the task enumeration; sub-issues will be filed as agents claim them per
ticket-create-workflow.md §10chained-call pattern.Sub-task 1: MCP server scaffolding + OpenAPI tool surface
Create
ai/mcp/server/gitlab-workflow/:server.mjs(entry point; mirrorgithub-workflow/server.mjsshape)openapi.yaml(tool surface declaringget_issue,list_issues,create_issue,update_issue_assignees,update_issue_labels,get_merge_request,list_merge_requests,manage_pr_reviewers,add_message/A2A-bridge if applicable, etc.)config.template.mjs(gitignored template; GitLab host URL, PAT env-var, sync intervals)config.mjs(gitignored runtime)Sub-task 2: GitLab API client
Create
ai/services/gitlab-workflow/api/GitLabClient.mjs:Authorization: Bearer ${token}header)?per_page)httpModule.request+Promisewrapper pattern fromTextEmbeddingService.mjs)Sub-task 3:
IssueSyncer+MergeRequestSyncer+LocalFileServiceCreate
ai/services/gitlab-workflow/:sync/IssueSyncer.mjs— mirrorsgithub-workflow/sync/IssueSyncer.mjsshape; consumesshared/contentPath.mjsfrom gh-workflow OR duplicates the path-math (decision: import fromgithub-workflow/shared/contentPath.mjssince it's provider-agnostic)sync/MergeRequestSyncer.mjs— mirrorsgithub-workflow/sync/PullRequestSyncer.mjsshapeLocalFileService.mjs— file-IO forresources/content/gitlab/...substrateFolder shape decision (operator-aligned, NOT open):
resources/content/gitlab/issues/chunk-N/issue-<NNN>.mdresources/content/gitlab/merge-requests/chunk-N/mr-<NNN>.mdresources/content/gitlab/archive/issues/v<X.Y.Z>/chunk-N/...resources/content/gitlab/archive/merge-requests/v<X.Y.Z>/chunk-N/...Universal ordinal-100 substrate; matches ADR 0004 §2.1 target shape under a
gitlab/namespace prefix.Sub-task 4: Tests (Playwright unit + integration)
Create
test/playwright/unit/ai/services/gitlab-workflow/:GitLabClient.spec.mjs— mock-HTTP-server pattern (same asTextEmbeddingService.retry.spec.mjs); covers auth, pagination, rate-limitIssueSyncer.spec.mjs— end-to-end sync logic with mock API responsesMergeRequestSyncer.spec.mjs— sameLocalFileService.spec.mjs— file-path round-tripstest/playwright/unit/ai/mcp/server/gitlab-workflow/(mirror gh-workflow's MCP tests)Sub-task 5: Config integration + auth + docs
gitlab-workflowconfig toai/mcp/server/gitlab-workflow/config.template.mjsperconfig.mjspattern; env-var bindings forNEO_GITLAB_HOST,NEO_GITLAB_PAT, etc.ai/mcp/server/gitlab-workflow/config.mjsworktree-bootstrap.mjsintegration (the script that copies gitignoredconfig.mjsfiles from main checkout to worktrees on session boot)learn/agentos/how to point the server at a GitLab instance (PAT generation, env-var setup, host URL configuration)package.jsonscript registry if any newnpm runinvocations needed (e.g.,ai:sync-gitlab)Optional Sub-task 6: Memory Core integration validation
If Memory Core integration "just works" via the substrate-agnostic patterns (which it should —
add_message+add_memoryare forge-agnostic), this sub-task closes via empirical-test rather than implementation.Acceptance Criteria
ai/mcp/server/gitlab-workflow/server.mjs+openapi.yaml+config.template.mjsexist and mirror thegithub-workflowshapeai/services/gitlab-workflow/api/GitLabClient.mjsimplements REST + GraphQL methods for Issues + MRs with PAT authIssueSyncer+MergeRequestSyncer+LocalFileServiceimplemented inai/services/gitlab-workflow/ai/services/github-workflow/shared/contentPath.mjsdirectly (NO duplication of path math; the universal ordinal-100 helper IS provider-agnostic per ADR 0004 §1.3)resources/content/gitlab/...matches ADR 0004 §2.1 universal ordinal-100 pattern withgitlab/namespace prefixgithub-workflowfor the equivalent surface area (IssueSyncer + MergeRequestSyncer + LocalFileService + MCP-server-smoke)worktree-bootstrap.mjsupdatedlearn/agentos/cover PAT setup + host configuration + how a client-project deployment points at a specific GitLab instancegithub-workflowexclusively —gitlab-workflowis opt-in via client-project-specific config + env-vars; no Neo-internal coordination uses it.Out of Scope
github-workflowandgitlab-workflow: explicitly rejected per the architectural-decision commitment above. If empirical duplication surfaces post-implementation (e.g., 3+ sites of >50 lines each), file follow-up substrate-evolution ticket.github-workflowusage: Neo continues using GitHub as forge;gitlab-workflowexists for external client deployments only.Avoided Traps
gitlab-workflowto Neo's daemon orchestrator: rejected. Daemon orchestration is Neo-internal substrate;gitlab-workflowis opt-in client-project substrate. Different lifecycle classes.ticket-create-workflow.md §10chained-call pattern. The epic body's task enumeration is the durable substrate; sub-issues materialize on demand.Related
ai/mcp/server/github-workflow/+ai/services/github-workflow/— full-feature parallel substrate.ai/mcp/server/knowledge-base/+ai/mcp/server/memory-core/— also parallel sibling servers; same architectural pattern.ai/services/github-workflow/shared/contentPath.mjs(PR #11381, merged 2026-05-15T08:19:41Z).gitlab-workflowsubstrate evolution.Origin Session
b8a152e1-a41c-49aa-8196-8e5d2eba84caRetrieval Hint
Search for
GitLab workflow MCP server epic parallel-substrate IssueSyncer MergeRequestSyncer contentPath universal ordinal-100 client project.