{"_id":"@bdkha/gitlab-mr-mcp","name":"@bdkha/gitlab-mr-mcp","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@bdkha/gitlab-mr-mcp","version":"0.2.0","description":"MCP server to read and review GitLab merge requests on a self-hosted GitLab instance","type":"module","license":"MIT","author":{"name":"KHA Bui Dinh","email":"khabd@sanancorp.com"},"homepage":"https://github.com/bdkha/claude_code_gitlab_mcp#readme","repository":{"type":"git","url":"git+https://github.com/bdkha/claude_code_gitlab_mcp.git"},"bugs":{"url":"https://github.com/bdkha/claude_code_gitlab_mcp/issues"},"keywords":["mcp","model-context-protocol","gitlab","merge-request","code-review","claude","claude-code"],"bin":{"gitlab-mr-mcp":"dist/index.js"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc && chmod +x dist/index.js","dev":"tsc --watch","start":"node dist/index.js","inspect":"npx @modelcontextprotocol/inspector node dist/index.js","prepublishOnly":"npm run build"},"dependencies":{"@modelcontextprotocol/sdk":"^1.0.0","zod":"^3.23.0"},"devDependencies":{"typescript":"^5.5.0","@types/node":"^20.14.0"},"engines":{"node":">=18.17"},"_id":"@bdkha/gitlab-mr-mcp@0.2.0","gitHead":"0aedb8cd481c0fbf5a5709c47d1f4c5ba441e622","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-eBSShgmXVKd2oC94KVpwvGX93seKdNY3cFOSXiY2L0Agv8S4vi55/A+g4V1Mh+c344R7yrd5fTPW6AInNy9SZQ==","shasum":"14550dbc9e6d88cfffe66ed78020e86a853d2656","tarball":"https://registry.npmjs.org/@bdkha/gitlab-mr-mcp/-/gitlab-mr-mcp-0.2.0.tgz","fileCount":19,"unpackedSize":77099,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bdkha%2fgitlab-mr-mcp@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD5/ujycpC5gpBMk9bme4MK/B/BMY27DHWnJQbj6hmqLgIgRmdmbZ35KNxYls+uqTssXQRBGteCWYHmslvuLPHwa8w="}]},"_npmUser":{"name":"bdkha","email":"buidinhkha81@gmail.com"},"directories":{},"maintainers":[{"name":"bdkha","email":"buidinhkha81@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/gitlab-mr-mcp_0.2.0_1779442487105_0.744742770749546"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-22T09:34:46.973Z","0.2.0":"2026-05-22T09:34:47.309Z","modified":"2026-05-22T09:34:47.644Z"},"maintainers":[{"name":"bdkha","email":"buidinhkha81@gmail.com"}],"description":"MCP server to read and review GitLab merge requests on a self-hosted GitLab instance","homepage":"https://github.com/bdkha/claude_code_gitlab_mcp#readme","keywords":["mcp","model-context-protocol","gitlab","merge-request","code-review","claude","claude-code"],"repository":{"type":"git","url":"git+https://github.com/bdkha/claude_code_gitlab_mcp.git"},"author":{"name":"KHA Bui Dinh","email":"khabd@sanancorp.com"},"bugs":{"url":"https://github.com/bdkha/claude_code_gitlab_mcp/issues"},"license":"MIT","readme":"# gitlab-mr-mcp\n\nAn MCP (Model Context Protocol) server that lets Claude **read and review\nMerge Requests on a self-hosted GitLab instance**.\n\nIt exposes tools for listing/inspecting MRs, fetching diffs and discussions,\nposting general and inline review comments, replying to and resolving threads,\nand approving/unapproving merge requests — all through the GitLab REST API.\n\nAuthentication can use either a Personal Access Token **or** a browser-based\nOAuth 2.0 login (authorization-code + PKCE) with automatic refresh.\n\n## Requirements\n\n- Node.js **≥ 18.17**\n- A self-hosted GitLab instance reachable from where the MCP server runs\n- **One** of the following credentials (see [Configuration](#configuration)):\n  - A **Personal Access Token** with `api` scope, **or**\n  - A registered **OAuth Application** in your GitLab\n\n## Install\n\nMost users do **not** need to clone or build. The published npm package can be\nlaunched on demand via `npx`:\n\n```bash\nnpx -y @bdkha/gitlab-mr-mcp@latest help\n```\n\nPin to a specific version with `@bdkha/gitlab-mr-mcp@0.2.0`. See\n[Use with Claude Code](#use-with-claude-code) for the actual registration.\n\n### Build from source (contributors only)\n\n```bash\ngit clone https://github.com/bdkha/claude_code_gitlab_mcp.git\ncd claude_code_gitlab_mcp\nnpm install\nnpm run build\n```\n\nThis produces `dist/index.js`, the entry point for the MCP server.\n\n## Configuration\n\nThe server reads its config from environment variables. `GITLAB_URL` is always\nrequired; for credentials, set **either** `GITLAB_TOKEN` (PAT path) **or** the\n`GITLAB_OAUTH_*` variables and run `gitlab-mr-mcp login` once.\n\n| Variable                     | Required? | Purpose                                                                |\n| ---------------------------- | --------- | ---------------------------------------------------------------------- |\n| `GITLAB_URL`                 | yes       | Base URL of your GitLab (`https://gitlab.example.com`). No `/api/v4`.  |\n| `GITLAB_TOKEN`               | PAT path  | Personal Access Token with `api` scope.                                |\n| `GITLAB_OAUTH_CLIENT_ID`     | OAuth path | OAuth Application Client ID.                                          |\n| `GITLAB_OAUTH_CLIENT_SECRET` | optional  | Only for **confidential** OAuth apps. Public/PKCE apps omit this.     |\n| `GITLAB_OAUTH_SCOPE`         | optional  | Default `api`.                                                         |\n| `GITLAB_OAUTH_REDIRECT_PORT` | optional  | Loopback port for the OAuth callback. Default `7898`.                  |\n\nPriority when both PAT and a cached OAuth token are present (and matching\n`GITLAB_URL`): OAuth wins. PAT is the fallback. A sample `.env.example` is\nprovided.\n\n### Subcommands\n\nThe same binary runs the MCP server **or** the OAuth helper, depending on the\nfirst argument:\n\n```bash\ngitlab-mr-mcp           # default — run the MCP server on stdio\ngitlab-mr-mcp login     # browser-based OAuth login, caches tokens\ngitlab-mr-mcp logout    # delete cached tokens\ngitlab-mr-mcp status    # print current auth config + cached-token state\ngitlab-mr-mcp help      # usage\n```\n\nCached OAuth tokens live at `~/.config/gitlab-mr-mcp/tokens.json` (mode `0600`).\n\n## OAuth login\n\n1. In your GitLab profile (or admin area for a group-wide app), open\n   `<GITLAB_URL>/-/user_settings/applications` and create a new application:\n   - **Name:** anything (e.g. `gitlab-mr-mcp`)\n   - **Redirect URI:** `http://127.0.0.1:7898/callback` (must match\n     `GITLAB_OAUTH_REDIRECT_PORT`; pick another port if 7898 is in use)\n   - **Confidential:** unchecked is fine — the MCP uses PKCE. Leave it\n     checked if you want a `client_secret` and set\n     `GITLAB_OAUTH_CLIENT_SECRET` accordingly.\n   - **Scopes:** `api`\n2. Copy the issued **Application ID** into `GITLAB_OAUTH_CLIENT_ID`.\n3. Run the login flow once from your terminal:\n\n   ```bash\n   GITLAB_URL=https://gitlab.example.com \\\n   GITLAB_OAUTH_CLIENT_ID=xxxxxxxxxxxx \\\n   node /Users/kha/Documents/hu/mcp/dist/index.js login\n   ```\n\n   Your browser opens to the GitLab consent page; after approval the\n   callback page confirms success and the tokens are saved.\n4. From then on, the MCP server (run with the same `GITLAB_URL` +\n   `GITLAB_OAUTH_CLIENT_ID`) reads the cached token and **refreshes it\n   automatically** when it expires. No further interaction needed.\n\nRun `gitlab-mr-mcp status` any time to inspect the cache and expiry, or\n`gitlab-mr-mcp logout` to revoke the local copy (you can also revoke the\nissued token in GitLab's UI under *Authorized applications*).\n\n## Use with Claude Code\n\nImportant: `-e` env flags must appear **before** the `--` separator. Anything\nafter `--` is treated as the server command + its args, so loose `-e` tokens\nthere will be passed to your shell as separate commands.\n\n**PAT path (recommended for first install):**\n\n```bash\nclaude mcp add gitlab-mr \\\n  -e GITLAB_URL=https://gitlab.example.com \\\n  -e GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \\\n  -- npx -y @bdkha/gitlab-mr-mcp@latest\n```\n\n**OAuth path** (after running `npx -y @bdkha/gitlab-mr-mcp@latest login` once):\n\n```bash\nclaude mcp add gitlab-mr \\\n  -e GITLAB_URL=https://gitlab.example.com \\\n  -e GITLAB_OAUTH_CLIENT_ID=xxxxxxxxxxxx \\\n  -- npx -y @bdkha/gitlab-mr-mcp@latest\n```\n\n**Local dev build** (if you cloned the repo and ran `npm run build`):\n\n```bash\nclaude mcp add gitlab-mr \\\n  -e GITLAB_URL=https://gitlab.example.com \\\n  -e GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \\\n  -- node /absolute/path/to/dist/index.js\n```\n\nVerify it was registered correctly:\n\n```bash\nclaude mcp list\nclaude mcp get gitlab-mr\n```\n\nThen in any session you can ask things like:\n\n- \"List open MRs assigned to me on project `infra/api`.\"\n- \"Show me the diff for MR !142 in `infra/api`.\"\n- \"Leave an inline comment on line 27 of `src/auth.ts` in MR !142.\"\n- \"Approve MR !142 in `infra/api`.\"\n\n## Use with Claude Desktop\n\nEdit `~/Library/Application Support/Claude/claude_desktop_config.json`. The\nrecommended config uses `npx` so updates flow without rebuilding:\n\n```json\n{\n  \"mcpServers\": {\n    \"gitlab-mr\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@bdkha/gitlab-mr-mcp@latest\"],\n      \"env\": {\n        \"GITLAB_URL\": \"https://gitlab.example.com\",\n        \"GITLAB_TOKEN\": \"glpat-xxxxxxxxxxxxxxxxxxxx\"\n      }\n    }\n  }\n}\n```\n\nFor the OAuth path, replace `GITLAB_TOKEN` with `GITLAB_OAUTH_CLIENT_ID` (and\noptionally `GITLAB_OAUTH_CLIENT_SECRET`). Tokens are picked up from\n`~/.config/gitlab-mr-mcp/tokens.json` after a one-time\n`npx -y @bdkha/gitlab-mr-mcp@latest login`.\n\nRestart Claude Desktop. The `gitlab-mr` server should appear in the tools list.\n\n## Tools\n\nAll tools accept `project` as either a numeric project ID or a URL-encoded\nproject path (`group/subgroup/project`). `iid` is the merge request's internal\nID as shown in the URL.\n\n### Read\n\n- `list_merge_requests` — filter by `state`, `author_username`,\n  `assignee_username`, `reviewer_username`, `labels`, `target_branch`,\n  `source_branch`, `search`, plus pagination. If `project` is omitted, lists\n  across all accessible projects (combine with `scope`).\n- `get_merge_request` — full MR object including `diff_refs` needed for\n  inline comments.\n- `get_merge_request_diff` — per-file diff hunks.\n- `list_merge_request_discussions` — threaded discussions with diff position\n  and resolved status.\n- `list_merge_request_notes` — flat (non-threaded) notes list.\n\n### Write\n\n- `create_merge_request_note` — post a general comment.\n- `create_merge_request_discussion` — post an inline diff comment. Requires\n  a `position` built from `get_merge_request`'s `diff_refs` plus the file\n  path and line number from `get_merge_request_diff`.\n- `reply_to_discussion` — reply to an existing thread by `discussion_id`.\n- `resolve_discussion` — mark a thread resolved (or unresolved with\n  `resolved: false`).\n- `approve_merge_request` — optional `sha` to guard against approving an\n  outdated commit.\n- `unapprove_merge_request`.\n\n## Posting an inline comment — example\n\n1. `get_merge_request` → copy `diff_refs.base_sha`, `start_sha`, `head_sha`.\n2. `get_merge_request_diff` → pick a file (`new_path`) and a `new_line`\n   (line in the post-change file you want to comment on).\n3. `create_merge_request_discussion`:\n\n   ```json\n   {\n     \"project\": \"group/project\",\n     \"iid\": 142,\n     \"body\": \"Consider extracting this into a helper.\",\n     \"position\": {\n       \"base_sha\": \"abc123…\",\n       \"start_sha\": \"def456…\",\n       \"head_sha\": \"789abc…\",\n       \"new_path\": \"src/auth.ts\",\n       \"new_line\": 27\n     }\n   }\n   ```\n\nFor comments on **deleted** lines, set `old_path` + `old_line` instead.\nFor **unchanged context** lines, set both `new_line` and `old_line`.\n\n## Notes & limitations\n\n- v0.1 has no automatic pagination — `per_page` and `page` are exposed on\n  list/diff tools and the response footer reports `nextPage` from\n  GitLab's `x-next-page` header.\n- Issues, pipelines, releases, and MR creation/merging are intentionally\n  out of scope; this server is focused on MR review.\n- Errors from GitLab are surfaced verbatim (status + body), so 401/403 typically\n  indicate token scope or permissions.\n- On 401 with an OAuth token, the client refreshes once and retries\n  transparently. If refresh fails (refresh token expired/revoked), run\n  `gitlab-mr-mcp logout && gitlab-mr-mcp login` to re-authorize.\n","readmeFilename":"README.md","_rev":"1-cd2a07c5045f4aee45233172c40d2ede"}