gitlab-mcp-agent-server 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,10 @@ MCP server for GitLab integration (TypeScript + Node.js).
5
5
  Полный пользовательский сценарий подключения к ИИ-агенту:
6
6
  - `docs/USER_GUIDE.md`
7
7
 
8
- Основной сценарий: добавить сервер в `~/.codex/config.toml` (готовый блок есть в `docs/USER_GUIDE.md`).
8
+ Основной сценарий: модель `multi-instance`.
9
+ - Один MCP-блок в `~/.codex/config.toml` на один GitLab instance.
10
+ - Для `gitlab.com` и каждого self-hosted GitLab добавляется отдельный блок.
11
+ - Готовые блоки есть в `docs/USER_GUIDE.md`.
9
12
 
10
13
  Для конечного пользователя обычно достаточно:
11
14
  1. Зарегистрировать GitLab OAuth application.
@@ -13,9 +16,9 @@ MCP server for GitLab integration (TypeScript + Node.js).
13
16
 
14
17
  Остальное работает по дефолту:
15
18
  - OAuth auto-login при отсутствии токена.
16
- - token store в `~/.config/gitlab-mcp/token.json`.
19
+ - instance-aware token store в `~/.config/gitlab-mcp/<gitlab-host>/token.json`.
17
20
  - auto-refresh access token.
18
- - автоопределение проекта из git remote текущего `cwd`.
21
+ - поддержка явного `project` в tool input и fallback-резолва проекта.
19
22
 
20
23
  ## Local setup (development)
21
24
 
@@ -22,12 +22,12 @@ class GitLabApiClient {
22
22
  const data = await this.request(`/projects/${projectPath}/issues`, {
23
23
  method: 'POST',
24
24
  body: JSON.stringify(body)
25
- });
25
+ }, input.project);
26
26
  return mapIssue(data);
27
27
  }
28
28
  async getIssue(input) {
29
29
  const projectPath = encodeProjectRef(input.project);
30
- const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`);
30
+ const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`, undefined, input.project);
31
31
  return mapIssue(data);
32
32
  }
33
33
  async closeIssue(input) {
@@ -35,7 +35,7 @@ class GitLabApiClient {
35
35
  const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`, {
36
36
  method: 'PUT',
37
37
  body: JSON.stringify({ state_event: 'close' })
38
- });
38
+ }, input.project);
39
39
  return mapIssue(data);
40
40
  }
41
41
  async updateIssueLabels(input) {
@@ -43,7 +43,7 @@ class GitLabApiClient {
43
43
  const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`, {
44
44
  method: 'PUT',
45
45
  body: JSON.stringify({ labels: input.labels.join(',') })
46
- });
46
+ }, input.project);
47
47
  return mapIssue(data);
48
48
  }
49
49
  async listLabels(input) {
@@ -51,7 +51,7 @@ class GitLabApiClient {
51
51
  const query = input.search
52
52
  ? `/projects/${projectPath}/labels?search=${encodeURIComponent(input.search)}`
53
53
  : `/projects/${projectPath}/labels`;
54
- const data = await this.request(query);
54
+ const data = await this.request(query, undefined, input.project);
55
55
  return data.map(mapLabel);
56
56
  }
57
57
  async listIssues(input) {
@@ -73,7 +73,7 @@ class GitLabApiClient {
73
73
  query.set('page', String(input.page));
74
74
  }
75
75
  const querySuffix = query.size > 0 ? `?${query.toString()}` : '';
76
- const data = await this.request(`/projects/${projectPath}/issues${querySuffix}`);
76
+ const data = await this.request(`/projects/${projectPath}/issues${querySuffix}`, undefined, input.project);
77
77
  return data.map(mapIssue);
78
78
  }
79
79
  async createLabel(input) {
@@ -85,11 +85,11 @@ class GitLabApiClient {
85
85
  color: input.color ?? '#1f75cb',
86
86
  description: input.description
87
87
  })
88
- });
88
+ }, input.project);
89
89
  return mapLabel(data);
90
90
  }
91
- async request(path, init) {
92
- const token = await this.options.tokenProvider.getAccessToken();
91
+ async request(path, init, projectRef) {
92
+ const token = await this.options.tokenProvider.getAccessToken(projectRef);
93
93
  if (!token) {
94
94
  throw new errors_1.ConfigurationError('GitLab access token is not configured. Set GITLAB_OAUTH_ACCESS_TOKEN (oauth) or GITLAB_PAT (pat).');
95
95
  }
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadConfig = loadConfig;
4
+ exports.resolveDefaultTokenStorePath = resolveDefaultTokenStorePath;
4
5
  const node_child_process_1 = require("node:child_process");
5
6
  const node_os_1 = require("node:os");
6
7
  const node_path_1 = require("node:path");
7
8
  const zod_1 = require("zod");
8
- const DEFAULT_TOKEN_STORE_PATH = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'gitlab-mcp', 'token.json');
9
9
  const EnvSchema = zod_1.z.object({
10
10
  GITLAB_API_URL: zod_1.z.string().url().default('https://gitlab.com/api/v4'),
11
11
  GITLAB_AUTH_MODE: zod_1.z.enum(['oauth', 'pat']).default('oauth'),
@@ -14,7 +14,7 @@ const EnvSchema = zod_1.z.object({
14
14
  GITLAB_OAUTH_CLIENT_SECRET: zod_1.z.string().optional(),
15
15
  GITLAB_OAUTH_REDIRECT_URI: zod_1.z.string().optional(),
16
16
  GITLAB_OAUTH_SCOPES: zod_1.z.string().default('api'),
17
- GITLAB_OAUTH_TOKEN_STORE_PATH: zod_1.z.string().default(DEFAULT_TOKEN_STORE_PATH),
17
+ GITLAB_OAUTH_TOKEN_STORE_PATH: zod_1.z.string().optional(),
18
18
  GITLAB_OAUTH_AUTO_LOGIN: zod_1.z
19
19
  .enum(['true', 'false'])
20
20
  .optional()
@@ -56,6 +56,7 @@ function loadConfig() {
56
56
  const autoDetectedProject = env.GITLAB_AUTO_RESOLVE_PROJECT_FROM_GIT
57
57
  ? detectProjectFromGitRemote()
58
58
  : undefined;
59
+ const defaultTokenStorePath = env.GITLAB_OAUTH_TOKEN_STORE_PATH ?? resolveDefaultTokenStorePath(env.GITLAB_API_URL);
59
60
  return {
60
61
  gitlab: {
61
62
  apiUrl: env.GITLAB_API_URL,
@@ -66,7 +67,7 @@ function loadConfig() {
66
67
  clientSecret: env.GITLAB_OAUTH_CLIENT_SECRET,
67
68
  redirectUri: env.GITLAB_OAUTH_REDIRECT_URI,
68
69
  scopes: splitCsv(env.GITLAB_OAUTH_SCOPES),
69
- tokenStorePath: env.GITLAB_OAUTH_TOKEN_STORE_PATH,
70
+ tokenStorePath: defaultTokenStorePath,
70
71
  autoLogin: env.GITLAB_OAUTH_AUTO_LOGIN,
71
72
  openBrowser: env.GITLAB_OAUTH_OPEN_BROWSER
72
73
  },
@@ -84,6 +85,13 @@ function loadConfig() {
84
85
  }
85
86
  };
86
87
  }
88
+ function resolveDefaultTokenStorePath(apiUrl, filePrefix = 'token') {
89
+ const hostKey = sanitizeForFilename(new URL(apiUrl).host.toLowerCase());
90
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'gitlab-mcp', hostKey, `${filePrefix}.json`);
91
+ }
92
+ function sanitizeForFilename(value) {
93
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '_');
94
+ }
87
95
  function resolveAccessToken(env) {
88
96
  if (env.GITLAB_AUTH_MODE === 'oauth') {
89
97
  return env.GITLAB_OAUTH_ACCESS_TOKEN;
@@ -1,6 +1,10 @@
1
1
  # User Guide
2
2
 
3
- Этот гайд для конечного пользователя: как подключить `gitlab-mcp-agent-server` в **Codex** и работать через OAuth с авто-рефрешем токена.
3
+ Этот гайд для конечного пользователя: как подключить `gitlab-mcp-agent-server` в **Codex**.
4
+
5
+ Основная модель: **multi-instance**.
6
+ - Один блок MCP в `config.toml` = один GitLab instance.
7
+ - Для `gitlab.com` и каждого self-hosted GitLab создаётся отдельный MCP-блок.
4
8
 
5
9
  ## 1. Подготовка GitLab OAuth Application
6
10
 
@@ -25,15 +29,16 @@
25
29
 
26
30
  ## 3. Конфиг Codex (`~/.codex/config.toml`)
27
31
 
28
- Минимальный рабочий блок:
32
+ ### 3.1 Один instance (минимально)
29
33
 
30
34
  ```toml
31
- [mcp_servers.gitlab]
35
+ [mcp_servers.gitlab_com]
32
36
  command = "bash"
33
37
  args = ["-lc", """
34
38
  export NVM_DIR="$HOME/.nvm";
35
39
  [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh";
36
40
 
41
+ export GITLAB_API_URL="https://gitlab.com/api/v4";
37
42
  export GITLAB_OAUTH_CLIENT_ID="<APPLICATION_ID>";
38
43
  export GITLAB_OAUTH_CLIENT_SECRET="<SECRET>";
39
44
 
@@ -41,10 +46,10 @@ npx -y gitlab-mcp-agent-server
41
46
  """]
42
47
  ```
43
48
 
44
- Рекомендованный расширенный блок:
49
+ ### 3.2 Несколько instances (рекомендуемая модель)
45
50
 
46
51
  ```toml
47
- [mcp_servers.gitlab]
52
+ [mcp_servers.gitlab_com]
48
53
  command = "bash"
49
54
  args = ["-lc", """
50
55
  export NVM_DIR="$HOME/.nvm";
@@ -52,17 +57,28 @@ export NVM_DIR="$HOME/.nvm";
52
57
 
53
58
  export GITLAB_API_URL="https://gitlab.com/api/v4";
54
59
  export GITLAB_AUTH_MODE="oauth";
55
- export GITLAB_OAUTH_CLIENT_ID="<APPLICATION_ID>";
56
- export GITLAB_OAUTH_CLIENT_SECRET="<SECRET>";
60
+ export GITLAB_OAUTH_CLIENT_ID="<GITLAB_COM_APP_ID>";
61
+ export GITLAB_OAUTH_CLIENT_SECRET="<GITLAB_COM_APP_SECRET>";
57
62
  export GITLAB_OAUTH_REDIRECT_URI="http://127.0.0.1:8787/oauth/callback";
58
- export GITLAB_OAUTH_SCOPES="api";
59
- export GITLAB_OAUTH_TOKEN_STORE_PATH="$HOME/.config/gitlab-mcp/token.json";
60
63
  export GITLAB_OAUTH_AUTO_LOGIN="true";
61
- export GITLAB_OAUTH_OPEN_BROWSER="false";
64
+ export GITLAB_OAUTH_OPEN_BROWSER="true";
62
65
 
63
- # optional fallback if auto-detect from git remote is unavailable
64
- export GITLAB_DEFAULT_PROJECT="group/repo";
65
- export GITLAB_AUTO_RESOLVE_PROJECT_FROM_GIT="true";
66
+ npx -y gitlab-mcp-agent-server
67
+ """]
68
+
69
+ [mcp_servers.gitlab_work]
70
+ command = "bash"
71
+ args = ["-lc", """
72
+ export NVM_DIR="$HOME/.nvm";
73
+ [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh";
74
+
75
+ export GITLAB_API_URL="https://gitlab.work.local/api/v4";
76
+ export GITLAB_AUTH_MODE="oauth";
77
+ export GITLAB_OAUTH_CLIENT_ID="<WORK_APP_ID>";
78
+ export GITLAB_OAUTH_CLIENT_SECRET="<WORK_APP_SECRET>";
79
+ export GITLAB_OAUTH_REDIRECT_URI="http://127.0.0.1:8788/oauth/callback";
80
+ export GITLAB_OAUTH_AUTO_LOGIN="true";
81
+ export GITLAB_OAUTH_OPEN_BROWSER="true";
66
82
 
67
83
  npx -y gitlab-mcp-agent-server
68
84
  """]
@@ -70,6 +86,10 @@ npx -y gitlab-mcp-agent-server
70
86
 
71
87
  После изменения `config.toml` перезапусти Codex.
72
88
 
89
+ По умолчанию token store instance-aware:
90
+ - `~/.config/gitlab-mcp/gitlab.com/token.json`
91
+ - `~/.config/gitlab-mcp/gitlab.work.local/token.json`
92
+
73
93
  ## 4. Что происходит при первом запуске
74
94
 
75
95
  1. Агент вызывает любой GitLab tool (например `gitlab_list_labels`).
@@ -77,11 +97,7 @@ npx -y gitlab-mcp-agent-server
77
97
  3. Если `GITLAB_OAUTH_OPEN_BROWSER=true` и окружение GUI доступно, браузер откроется автоматически.
78
98
  4. Локальный URL `http://127.0.0.1:8787/` автоматически редиректит на GitLab OAuth.
79
99
  5. Если браузер не может быть открыт, сервер печатает URL авторизации в лог.
80
- 6. После подтверждения в GitLab и callback на `http://127.0.0.1:8787/oauth/callback` токены сохраняются в `GITLAB_OAUTH_TOKEN_STORE_PATH`.
81
-
82
- Если `GITLAB_DEFAULT_PROJECT` не указан:
83
- 1. сервер пытается автоматически определить проект из `git remote origin` в `cwd`;
84
- 2. если не удалось, tool запросит `project` явно.
100
+ 6. После подтверждения в GitLab и callback на `http://127.0.0.1:8787/oauth/callback` токены сохраняются в token store для конкретного instance.
85
101
 
86
102
  ## 5. Автообновление токена
87
103
 
@@ -93,13 +109,27 @@ npx -y gitlab-mcp-agent-server
93
109
  ## 6. Рекомендованные настройки для production
94
110
 
95
111
  1. Держи token store вне репозитория:
96
- - пример: `/home/<user>/.config/gitlab-mcp/token.json`
112
+ - пример: `/home/<user>/.config/gitlab-mcp/gitlab.com/token.json`
97
113
  2. Ограничь права файла:
98
- - `chmod 600 /home/<user>/.config/gitlab-mcp/token.json`
99
- 3. Оставь `GITLAB_OAUTH_OPEN_BROWSER=false` для headless окружений.
100
- 4. Для multi-repo режима лучше не задавать `GITLAB_DEFAULT_PROJECT`, чтобы проект брался из `git remote` текущего `cwd`.
114
+ - `chmod 600 /home/<user>/.config/gitlab-mcp/gitlab.com/token.json`
115
+ 3. Для headless окружений ставь `GITLAB_OAUTH_OPEN_BROWSER=false`.
101
116
 
102
- ## 7. Быстрая проверка работоспособности
117
+ ## 7. Сценарий пользовательского запроса
118
+
119
+ Пример запроса: `Покажи мне все задачи, которые сейчас есть в проекте?`
120
+
121
+ 1. Пользователь добавляет MCP-блок в `~/.codex/config.toml`.
122
+ 2. Codex поднимает сервер командой `npx -y gitlab-mcp-agent-server`.
123
+ 3. Codex вызывает tool `gitlab_list_issues`.
124
+ 4. Сервер резолвит проект в порядке:
125
+ - `project` из входа tool,
126
+ - `git remote origin` текущего `cwd`,
127
+ - `GITLAB_DEFAULT_PROJECT`.
128
+ 5. Если токена для этого instance нет, запускается OAuth flow.
129
+ 6. После callback токен сохраняется в token store этого instance.
130
+ 7. Сервер возвращает список issues.
131
+
132
+ ## 8. Быстрая проверка работоспособности
103
133
 
104
134
  1. Вызови `gitlab_list_labels`.
105
135
  2. Создай issue: `gitlab_create_issue`.
@@ -107,7 +137,7 @@ npx -y gitlab-mcp-agent-server
107
137
  4. Обнови labels: `gitlab_update_issue_labels`.
108
138
  5. Закрой issue: `gitlab_close_issue`.
109
139
 
110
- ## 8. Troubleshooting
140
+ ## 9. Troubleshooting
111
141
 
112
142
  `The redirect URI included is not valid`:
113
143
  1. Проверь, что URI совпадает 1-в-1:
@@ -119,3 +149,10 @@ npx -y gitlab-mcp-agent-server
119
149
  1. Проверь scope `api`.
120
150
  2. Убедись, что `client_id`/`client_secret` от того же приложения.
121
151
  3. Удали token store файл и пройди OAuth заново.
152
+
153
+ ## 10. Advanced (необязательно)
154
+
155
+ Если нужен тонкий контроль, можно использовать:
156
+ 1. `GITLAB_DEFAULT_PROJECT` для явного fallback проекта.
157
+ 2. `GITLAB_AUTO_RESOLVE_PROJECT_FROM_GIT` для автоопределения проекта из `git remote`.
158
+ 3. `GITLAB_OAUTH_TOKEN_STORE_PATH` для ручного override пути токена.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitlab-mcp-agent-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "MCP server for GitLab integration via OAuth",
5
5
  "main": "build/src/index.js",
6
6
  "bin": {