---
title: "GitLab"
description: "The verified connector ai.oz0/gitlab - GitLab's issues, merge requests, pipelines and repositories through GitLab's own MCP server, on GitLab.com or your own instance, on your own OAuth application, with GitLab's webhooks as events."
canonical_url: "https://docs.oz0.ai/build/plugins/verified/gitlab"
---
# GitLab

> The verified connector ai.oz0/gitlab - GitLab's issues, merge requests, pipelines and repositories through GitLab's own MCP server, on GitLab.com or your own instance, on your own OAuth application, with GitLab's webhooks as events.

For: tenant administrators and plugin users.

Connect GitLab issues, merge requests, pipelines and repositories through GitLab's MCP server. The connector supports GitLab.com, self-managed GitLab and GitLab Dedicated, uses your tenant's OAuth application, and receives GitLab webhooks as events.

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Manifest name
    </td>
    
    <td>
      <code>
        gitlab
      </code>
      
       (hub name <code>
        ai.oz0/gitlab
      </code>
      
      ), <code>
        kind: connector
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Tools
    </td>
    
    <td>
      <code>
        gitlab
      </code>
      
      : GitLab's MCP server, <code>
        https://&lt;host>/api/v4/mcp
      </code>
      
      ; <code>
        gitlab_events
      </code>
      
      : the webhooks' normaliser, which reaches nothing
    </td>
  </tr>
  
  <tr>
    <td>
      Connection
    </td>
    
    <td>
      <code>
        gitlab
      </code>
      
      : authorization code with PKCE on your own non-confidential OAuth application, the <code>
        mcp
      </code>
      
       scope, refreshed by the edge
    </td>
  </tr>
  
  <tr>
    <td>
      Event source
    </td>
    
    <td>
      <code>
        gitlab
      </code>
      
      : issue, merge request and pipeline events, checked against the webhook's secret token
    </td>
  </tr>
  
  <tr>
    <td>
      Agent
    </td>
    
    <td>
      <code>
        gitlab
      </code>
      
      , a generic delegate on the <code>
        default
      </code>
      
       alias
    </td>
  </tr>
  
  <tr>
    <td>
      Pin
    </td>
    
    <td>
      <code>
        snapshots/gitlab.json
      </code>
      
       (21 functions) and the toolsets header
    </td>
  </tr>
  
  <tr>
    <td>
      Egress
    </td>
    
    <td>
      The connection's host only: <code>
        gitlab.com
      </code>
      
      , or your instance's
    </td>
  </tr>
</tbody>
</table>

Sources, read 2026-10-17: [GitLab MCP server](https://docs.gitlab.com/user/model_context_protocol/mcp_server/),
[its tools](https://docs.gitlab.com/user/model_context_protocol/mcp_server_tools/),
[OAuth 2.0 identity provider API](https://docs.gitlab.com/api/oauth2/),
[GitLab as an OAuth 2.0 provider](https://docs.gitlab.com/integration/oauth_provider/),
[webhooks](https://docs.gitlab.com/user/project/integrations/webhooks/) and
[webhook events](https://docs.gitlab.com/user/project/integrations/webhook_events/).

## Functions

The tool sends `X-Gitlab-Enabled-Mcp-Server-Toolsets: core,work_items,merge_requests,repository,ci` with every
request, so GitLab lists those toolsets and `meta`, and never `duo_agent_platform` (which needs a GitLab Duo licence),
`wikis` or `code_security`. The plugin pins 21 of the functions GitLab lists there, and the connection gives a token
to those only: GitLab's other functions (merging, committing, running pipelines) show in the catalog but cannot be
called through this plugin.

<table>
<thead>
  <tr>
    <th>
      Function
    </th>
    
    <th>
      Does
    </th>
    
    <th>
      Asks a person by default
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        gitlab.list_work_items
      </code>
    </td>
    
    <td>
      Lists or searches issues and other work items in a project or group
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_work_item
      </code>
    </td>
    
    <td>
      Reads one work item, with its notes when asked
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_work_item_types
      </code>
    </td>
    
    <td>
      Lists the work item types a project or group has
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.list_merge_requests
      </code>
    </td>
    
    <td>
      Lists or searches merge requests
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_merge_request
      </code>
    </td>
    
    <td>
      Reads a merge request, with its diffs, commits, notes or pipelines when asked
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.list_pipelines
      </code>
    </td>
    
    <td>
      Lists a project's pipelines
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_pipeline
      </code>
    </td>
    
    <td>
      Reads a pipeline, with its jobs when asked
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_job
      </code>
    </td>
    
    <td>
      Reads a CI/CD job, with its log when asked
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_repository_file
      </code>
    </td>
    
    <td>
      Reads a file at a branch, tag or commit
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.list_repository_tree
      </code>
    </td>
    
    <td>
      Lists a directory
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.list_commits
      </code>
    </td>
    
    <td>
      Lists commits
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_commit
      </code>
    </td>
    
    <td>
      Reads a commit, with its diff when asked
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.list_branches
      </code>
    </td>
    
    <td>
      Lists branches
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_project
      </code>
      
      , <code>
        gitlab.list_projects
      </code>
    </td>
    
    <td>
      Reads a project, lists projects
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_user
      </code>
    </td>
    
    <td>
      Reads a user, or the signed-in one
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.search
      </code>
    </td>
    
    <td>
      Searches the instance
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.search_labels
      </code>
    </td>
    
    <td>
      Searches a project's or group's labels
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.get_mcp_server_version
      </code>
    </td>
    
    <td>
      Reads the MCP server's version
    </td>
    
    <td>
      no
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.save_note
      </code>
    </td>
    
    <td>
      Comments on a work item or merge request
    </td>
    
    <td>
      yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gitlab.save_work_item
      </code>
    </td>
    
    <td>
      Creates an issue, or changes one (title, description, labels, state)
    </td>
    
    <td>
      yes
    </td>
  </tr>
</tbody>
</table>

The two writes wait for an operator who did not start the job, unless your
[tool policy](https://docs.oz0.ai/govern/tool-policy) names other deciders.

## Connect it

1. Allow MCP access at GitLab. On GitLab.com an Owner allows access to the MCP server for the top-level group; on
a self-managed instance or Dedicated an administrator allows it for the instance.
2. Create an OAuth application at GitLab, for a user, a group or the instance: clear Confidential (it has no
client secret), select the `mcp` scope, and give it the redirect URL that `oz0 connection show gitlab` prints
under "calls back".
3. Connect. The host defaults to `gitlab.com`; give your instance's for a self-managed one or Dedicated:

```bash [Terminal]
oz0 connection set gitlab --param host=gitlab.example.com --param client_id=<the application ID>
oz0 connection start gitlab     # opens GitLab's sign-in; the edge keeps and refreshes the token
```

The person who connects signs in as themselves, and every call acts as them, within what GitLab lets them see and
do. The edge refreshes the token before it expires (GitLab's live two hours) and asks for the connection to be
connected again when GitLab refuses a refresh. A changed host gets no token until the connection is connected again
for it, so a token GitLab issued for one instance never goes to another.

A host on a private network needs it allowed: `oz0 server start --connect-allow-net <CIDR>`.

## Webhooks as events

Point a project's or group's webhook at `https://<edge>/events/<tenant>/gitlab`, with Secret token set to the
value of the tenant secret `GITLAB_WEBHOOK_TOKEN` (`oz0 secret set GITLAB_WEBHOOK_TOKEN --signing --host <edge>:7443` reads it from standard input; the edge takes it only as a signing key bound to its own address),
and select issue, merge request and pipeline events. The edge compares `X-Gitlab-Token` with the secret before
anything reads the delivery, and takes a redelivery once by its `Idempotency-Key`. A [routine](https://docs.oz0.ai/build/parts/routines) fires
on them:

```yaml [triage.yaml]
triggers:
  - name: opened
    event:
      source: gitlab
      types: [issue]
      filter: event.action == 'open'
```

Each event has a `type` (`issue`, `merge_request` or `pipeline`), an `action` (GitLab's, such as `open`, `update`,
`merge`, or a pipeline's status, such as `failed`), a subject (`group/project#7`, `group/project!3`,
`group/project/pipelines/31`), attributes for filters (project, iid, state, branches, labels, failed jobs) and the
text an agent reads, which is outside input: what an issue's author wrote is in it.

GitLab sends every kind of work item under its issue events. Only issues (and Service Desk issues) become `issue`
events, with `attributes.work_item_type` set to `Issue`; tasks, incidents, epics, objectives and key results are
accepted and ignored, so GitLab never counts them as failed deliveries and never turns the webhook off for them.

## Limits

- Writes ask a person by default (`save_note` and `save_work_item`, through the plugin's suggested policy, which
an admin may switch with a reason; up to 1.0.0 under `requires_approval`, and an update from it keeps asking), and
are not safe to repeat: GitLab's MCP server
takes no idempotency key, so a `save_note` sent twice comments twice. Orchestrator Zero makes an approved call once.
- No push of changes. The plugin cannot apply a [reviewed changeset](https://docs.oz0.ai/govern/reviewed-changes/changesets#changesets): its
writes are `save_note` and `save_work_item`. When it can, it refuses a change to a plugin's tests or rules, and to
CODEOWNERS or CI workflows, without the second approval those need, as `oz0_github.push_changes` does
([protected paths](https://docs.oz0.ai/govern/reviewed-changes/protected-paths#protected-paths)).
- The AI label goes at the start of `save_note`'s `body` and `save_work_item`'s `description`: GitLab reads
nothing Orchestrator Zero sends beside the call, so the node puts the tenant's label in the text people read, after
the approval. A work item's title carries no label.
- No annotations or output schemas from GitLab. GitLab documents neither for its MCP tools. The plugin marks the
reads read-only and the writes as writes, and declares the output schemas of the single-object reads (`get_*`) from
what GitLab's documentation says they return, with no required fields; the lists and searches answer in text.

### Rate limits

GitLab.com allows 60 MCP requests a minute per user on Free and 600 on Premium and Ultimate, and counts them toward its general limits too; a self-managed instance's administrator sets one instance-wide limit. Each call is one session of its own. Above the limit GitLab answers 429 and the call is not made. A read is called again after the wait GitLab asks for, once your tenant approved the plugin's listing; a write is not, and the agent reads an error that says it is retryable and how long GitLab asks to wait.

- Sign-in. OAuth only, on your own application. Personal, project and group access tokens of the legacy kind
cannot carry the `mcp` scope (GitLab answers 403), fine-grained personal access tokens are not offered, and dynamic
client registration is not used.
- A refresh replaces the token in force. GitLab revokes the access token a refresh replaces, so a call that read
the old token a moment before can fail once with 401; the next call gets the new one.
- Webhooks with a secret token, not a signing token. GitLab's signing token (HMAC per the Standard Webhooks
specification, GitLab 19.0 and later) is not checked yet; set the secret token, which GitLab sends over TLS.
- Issues only, of the work items. Tasks, incidents, epics, objectives and key results arrive under GitLab's issue
events and are ignored, not refused, so they never count against the webhook.
- No EU regional host on GitLab.com. GitLab Dedicated and self-managed instances run where their owner chooses.
- Licence. The toolsets the plugin uses are on GitLab's Free tier; nothing it calls needs GitLab Duo.
- Installing or updating runs the delegate's eval as the [quality gate](https://docs.oz0.ai/improve/evals), on the tenant's own
models.
- Tested against a fake of GitLab's server and OAuth endpoints, shaped from GitLab's documentation, not against
GitLab itself. "Verified" means the project tested, pinned and secured the plugin, never that GitLab endorses it.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
