First implementation sub of #11404 (GitLab Workflow MCP Server — parity with gh-workflow). The scaffold sub #11768 is complete (MCP server + config + tool routing exist), but every gitlab-workflow service returns a {status: 'scaffolded'} placeholder — no real GitLab API behavior. @neo-gpt's epic-resolution review maps the BLOCKERs: no GitLabClient exists, services defer real behavior, and no gitlab specs exist. This sub delivers the foundational client + real IssueService issue operations + tests. Epic validated per gpt's resolution review (roadmap-fit: GitLab parity is an established Agent-OS tooling capability; approach: mirror gh-workflow; scaffold complete, implementation subs needed).
The Problem
ai/services/gitlab-workflow/ services (IssueService, MergeRequestService, LocalFileService) all return a private #scaffold() placeholder ({status:'scaffolded', message:'…real GitLab API behavior lands with the GitLabClient subtask'}). No GitLabClient exists anywhere, so the registered MCP tools are inert. No test/playwright/unit/ai/services/gitlab-workflow/ specs exist.
The Architectural Reality
The gh-workflow server's client layer is ai/services/github-workflow/GraphqlService.mjs — a GitHub-specific GraphQL client: query(query, variables) → PAT-auth (gh auth token) POST to https://api.github.com/graphql, with 429/502/503/504 retry + backoff. Services import it and call query() with constants from ./queries/*.mjs. GitLabClient must mirror this for GitLab, with the API differences:
IssueService.spec.mjs (mock GitLabClient: each op)
Decision Record impact
aligned-with ADR 0018 (which treats gitlab-workflow/ as a PoC excluded from the functional MCP-server count). This sub advances GitLab from scaffold-PoC toward functional but does NOT yet flip the classification (issues only, no MRs/sync) — a future full-parity sub updates ADR 0018. No ADR change here.
Acceptance Criteria
AC1: GitLabClient.mjs implements query(query, variables) — Bearer PAT auth from aiConfig.gitlab.token, POST to ${aiConfig.gitlab.hostUrl}/api/graphql, 429/5xx retry + backoff, returns json.data, throws a clear error on missing token/host or GraphQL errors.
AC2: IssueService real behavior for listIssues, createIssue, manageIssueComment, manageIssueLabels, manageIssueAssignees via GitLabClient (no remaining #scaffold() for those), handling GitLab iid + notes + project-root.
AC3: GitLabClient.spec.mjs (mock fetch: success / retry-on-5xx / auth-failure / GraphQL-error) + IssueService.spec.mjs (mock GitLabClient.query: each op) — both pass via npm run test-unit -- <specs>.
AC4: GL_IssueService wired in ai/services.mjs (makeSafe against the gitlab openapi); MCP tools dispatch to real behavior (toolService already wired).
Out of Scope
MergeRequestService real behavior — follow-up sub.
LocalFileService / issue-MR syncers / local cache — follow-up sub.
Live GitLab API integration test (no test GitLab project in CI) — unit-mock only here.
Avoided Traps
Shelling out to a glab CLI. Rejected — mirror GraphqlService's direct HTTP-GraphQL approach (no new CLI dependency).
Hardcoding the GitLab project path. Use config/options (project path/id), mirroring how gh-workflow uses owner/repo from config.
Reusing/generalizing GitHub's GraphqlService. GitLab's endpoint + schema (project/iid/notes) differ — a dedicated GitLabClient is correct, not an over-abstraction of two different APIs.
Context
First implementation sub of #11404 (GitLab Workflow MCP Server — parity with gh-workflow). The scaffold sub #11768 is complete (MCP server + config + tool routing exist), but every
gitlab-workflowservice returns a{status: 'scaffolded'}placeholder — no real GitLab API behavior. @neo-gpt's epic-resolution review maps the BLOCKERs: noGitLabClientexists, services defer real behavior, and no gitlab specs exist. This sub delivers the foundational client + realIssueServiceissue operations + tests. Epic validated per gpt's resolution review (roadmap-fit: GitLab parity is an established Agent-OS tooling capability; approach: mirror gh-workflow; scaffold complete, implementation subs needed).The Problem
ai/services/gitlab-workflow/services (IssueService, MergeRequestService, LocalFileService) all return a private#scaffold()placeholder ({status:'scaffolded', message:'…real GitLab API behavior lands with the GitLabClient subtask'}). NoGitLabClientexists anywhere, so the registered MCP tools are inert. Notest/playwright/unit/ai/services/gitlab-workflow/specs exist.The Architectural Reality
The gh-workflow server's client layer is
ai/services/github-workflow/GraphqlService.mjs— a GitHub-specific GraphQL client:query(query, variables)→ PAT-auth (gh auth token) POST tohttps://api.github.com/graphql, with 429/502/503/504 retry + backoff. Services import it and callquery()with constants from./queries/*.mjs.GitLabClientmust mirror this for GitLab, with the API differences:GraphqlService)GitLabClient)https://api.github.com/graphql${aiConfig.gitlab.hostUrl}/api/graphqlAuthorization: bearer <gh token>Authorization: Bearer <aiConfig.gitlab.token / NEO_GITLAB_PAT>repository(owner,name)project(fullPath)numberiid(+ GraphQLid)comments.nodesnotes.nodesConfig already exists:
aiConfig.gitlab.hostUrl(NEO_GITLAB_HOST, defaultgitlab.com) +aiConfig.gitlab.token(NEO_GITLAB_PAT).HealthServiceis already partially real (config-presence check).The Fix
ai/services/gitlab-workflow/GitLabClient.mjsGraphqlService(query + Bearer auth + retry)ai/services/gitlab-workflow/queries/issueQueries.mjsai/services/gitlab-workflow/queries/mutations.mjsai/services/gitlab-workflow/IssueService.mjs#scaffold()with real ops viaGitLabClienttest/playwright/unit/ai/services/gitlab-workflow/GitLabClient.spec.mjstest/playwright/unit/ai/services/gitlab-workflow/IssueService.spec.mjsGitLabClient.query)ai/services.mjsGL_IssueService(makeSafe + export), loadglSpecfrom the gitlab openapiContract Ledger Matrix
GitLabClient.query(query, variables)GraphqlService.mjspattern; GitLab GraphQL API${hostUrl}/api/graphql; returnsjson.data; retries 429/5xx with backoff; throws on auth/GraphQL errorsHealthServiceconfig check); GraphQLerrorssurfacedGitLabClient.spec.mjs(mock fetch: success, retry, auth-fail, GraphQL-error)IssueServiceissue ops (listIssues/createIssue/manageIssueComment/manageIssueLabels/manageIssueAssignees)IssueService; the gh-workflowIssueServicecontract;openapi.yamlGitLabClient, returning the MCP contract shapes the openapi definesIssueService.spec.mjs(mockGitLabClient: each op)Decision Record impact
aligned-withADR 0018 (which treatsgitlab-workflow/as a PoC excluded from the functional MCP-server count). This sub advances GitLab from scaffold-PoC toward functional but does NOT yet flip the classification (issues only, no MRs/sync) — a future full-parity sub updates ADR 0018. No ADR change here.Acceptance Criteria
GitLabClient.mjsimplementsquery(query, variables)— Bearer PAT auth fromaiConfig.gitlab.token, POST to${aiConfig.gitlab.hostUrl}/api/graphql, 429/5xx retry + backoff, returnsjson.data, throws a clear error on missing token/host or GraphQL errors.IssueServicereal behavior forlistIssues,createIssue,manageIssueComment,manageIssueLabels,manageIssueAssigneesviaGitLabClient(no remaining#scaffold()for those), handling GitLabiid+notes+project-root.GitLabClient.spec.mjs(mock fetch: success / retry-on-5xx / auth-failure / GraphQL-error) +IssueService.spec.mjs(mockGitLabClient.query: each op) — both pass vianpm run test-unit -- <specs>.GL_IssueServicewired inai/services.mjs(makeSafe against the gitlab openapi); MCP tools dispatch to real behavior (toolServicealready wired).Out of Scope
MergeRequestServicereal behavior — follow-up sub.LocalFileService/ issue-MR syncers / local cache — follow-up sub.package.jsonai:mcp-server-gitlab-workflowscript + broader docs + ADR 0018 PoC-reclassification — follow-up (epic residual per gpt's review).Avoided Traps
glabCLI. Rejected — mirrorGraphqlService's direct HTTP-GraphQL approach (no new CLI dependency).owner/repofrom config.GraphqlService. GitLab's endpoint + schema (project/iid/notes) differ — a dedicatedGitLabClientis correct, not an over-abstraction of two different APIs.Related
ai/services/github-workflow/GraphqlService.mjs+IssueService.mjs+test/playwright/unit/ai/services/github-workflow/IssueService.spec.mjs.Origin Session ID: a54e89a3-4259-4b41-9e26-561f665de744
Retrieval Hint: "GitLabClient GraphQL client gitlab-workflow IssueService real behavior"; mirror github-workflow GraphqlService pattern.