gitlab-mcp-agent-server 0.1.0 → 0.2.1
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,11 +5,7 @@ MCP server for GitLab integration (TypeScript + Node.js).
|
|
|
5
5
|
Полный пользовательский сценарий подключения к ИИ-агенту:
|
|
6
6
|
- `docs/USER_GUIDE.md`
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npx -y gitlab-mcp-agent-server
|
|
12
|
-
```
|
|
8
|
+
Основной сценарий: добавить сервер в `~/.codex/config.toml` (готовый блок есть в `docs/USER_GUIDE.md`).
|
|
13
9
|
|
|
14
10
|
Для конечного пользователя обычно достаточно:
|
|
15
11
|
1. Зарегистрировать GitLab OAuth application.
|
|
@@ -11,12 +11,12 @@ class ProjectResolver {
|
|
|
11
11
|
if (project !== undefined && project !== null && String(project).trim() !== '') {
|
|
12
12
|
return project;
|
|
13
13
|
}
|
|
14
|
-
if (this.config.gitlab.defaultProject) {
|
|
15
|
-
return this.config.gitlab.defaultProject;
|
|
16
|
-
}
|
|
17
14
|
if (this.config.gitlab.autoResolveProjectFromGit && this.config.gitlab.autoDetectedProject) {
|
|
18
15
|
return this.config.gitlab.autoDetectedProject;
|
|
19
16
|
}
|
|
17
|
+
if (this.config.gitlab.defaultProject) {
|
|
18
|
+
return this.config.gitlab.defaultProject;
|
|
19
|
+
}
|
|
20
20
|
throw new errors_1.ConfigurationError('Project is not resolved. Provide `project` in tool input or set GITLAB_DEFAULT_PROJECT.');
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -64,15 +64,28 @@ class GitLabOAuthManager {
|
|
|
64
64
|
this.assertRedirectUri();
|
|
65
65
|
const redirect = new URL(this.options.redirectUri);
|
|
66
66
|
const state = (0, node_crypto_1.randomBytes)(16).toString('hex');
|
|
67
|
+
const authorizeUrl = new URL(`${this.oauthBaseUrl}/oauth/authorize`);
|
|
68
|
+
authorizeUrl.searchParams.set('client_id', this.options.clientId);
|
|
69
|
+
authorizeUrl.searchParams.set('redirect_uri', this.options.redirectUri);
|
|
70
|
+
authorizeUrl.searchParams.set('response_type', 'code');
|
|
71
|
+
authorizeUrl.searchParams.set('scope', this.options.scopes.join(' '));
|
|
72
|
+
authorizeUrl.searchParams.set('state', state);
|
|
73
|
+
const localEntryUrl = `${redirect.protocol}//${redirect.host}/`;
|
|
67
74
|
const code = await new Promise((resolve, reject) => {
|
|
68
75
|
const server = (0, node_http_1.createServer)((req, res) => {
|
|
69
76
|
if (!req.url) {
|
|
70
77
|
return;
|
|
71
78
|
}
|
|
72
79
|
const url = new URL(req.url, `${redirect.protocol}//${redirect.host}`);
|
|
80
|
+
if (url.pathname === '/') {
|
|
81
|
+
res.statusCode = 200;
|
|
82
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
83
|
+
res.end(renderOAuthEntryPage(authorizeUrl.toString()));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
73
86
|
if (url.pathname !== redirect.pathname) {
|
|
74
87
|
res.statusCode = 404;
|
|
75
|
-
res.end('Not found');
|
|
88
|
+
res.end('Not found. Open "/" to start OAuth authorization.');
|
|
76
89
|
return;
|
|
77
90
|
}
|
|
78
91
|
const responseState = url.searchParams.get('state');
|
|
@@ -98,18 +111,16 @@ class GitLabOAuthManager {
|
|
|
98
111
|
server.close();
|
|
99
112
|
resolve(authCode);
|
|
100
113
|
});
|
|
114
|
+
server.on('error', (error) => {
|
|
115
|
+
reject(new Error(`OAuth callback server failed on ${redirect.hostname}:${resolvePort(redirect)}: ${error.message}`));
|
|
116
|
+
});
|
|
101
117
|
server.listen(resolvePort(redirect), redirect.hostname, () => {
|
|
102
|
-
const
|
|
103
|
-
authorizeUrl.searchParams.set('client_id', this.options.clientId);
|
|
104
|
-
authorizeUrl.searchParams.set('redirect_uri', this.options.redirectUri);
|
|
105
|
-
authorizeUrl.searchParams.set('response_type', 'code');
|
|
106
|
-
authorizeUrl.searchParams.set('scope', this.options.scopes.join(' '));
|
|
107
|
-
authorizeUrl.searchParams.set('state', state);
|
|
108
|
-
const authorizeUrlText = authorizeUrl.toString();
|
|
109
|
-
const opened = this.options.openBrowser && openInBrowser(authorizeUrlText);
|
|
118
|
+
const opened = this.options.openBrowser && openInBrowser(localEntryUrl);
|
|
110
119
|
if (!opened) {
|
|
111
|
-
console.error('Open this URL to
|
|
112
|
-
console.error(
|
|
120
|
+
console.error('Open this local URL to start OAuth authorization:');
|
|
121
|
+
console.error(localEntryUrl);
|
|
122
|
+
console.error('If local redirect does not work, use direct GitLab OAuth URL:');
|
|
123
|
+
console.error(authorizeUrl.toString());
|
|
113
124
|
}
|
|
114
125
|
});
|
|
115
126
|
});
|
|
@@ -208,3 +219,35 @@ function hasOpenCommand(platform) {
|
|
|
208
219
|
return false;
|
|
209
220
|
}
|
|
210
221
|
}
|
|
222
|
+
function renderOAuthEntryPage(authorizeUrl) {
|
|
223
|
+
const escapedUrl = authorizeUrl.replace(/&/g, '&').replace(/"/g, '"');
|
|
224
|
+
return `<!doctype html>
|
|
225
|
+
<html lang="en">
|
|
226
|
+
<head>
|
|
227
|
+
<meta charset="utf-8" />
|
|
228
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
229
|
+
<title>GitLab OAuth</title>
|
|
230
|
+
<style>
|
|
231
|
+
body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; margin: 0; padding: 24px; background: #0b1220; color: #e5e7eb; }
|
|
232
|
+
.card { max-width: 640px; margin: 48px auto; background: #121a2b; border: 1px solid #1f2a44; border-radius: 12px; padding: 24px; }
|
|
233
|
+
h1 { margin: 0 0 12px; font-size: 20px; }
|
|
234
|
+
p { margin: 0 0 14px; color: #cbd5e1; line-height: 1.5; }
|
|
235
|
+
a.btn { display: inline-block; background: #2563eb; color: white; text-decoration: none; padding: 10px 14px; border-radius: 8px; font-weight: 600; }
|
|
236
|
+
.hint { margin-top: 12px; font-size: 13px; color: #94a3b8; }
|
|
237
|
+
</style>
|
|
238
|
+
</head>
|
|
239
|
+
<body>
|
|
240
|
+
<div class="card">
|
|
241
|
+
<h1>Authorize GitLab Access</h1>
|
|
242
|
+
<p>Click the button below to continue OAuth authorization.</p>
|
|
243
|
+
<a class="btn" href="${escapedUrl}">Authorize with GitLab</a>
|
|
244
|
+
<p class="hint">You will be redirected automatically in 3 seconds if no action is taken.</p>
|
|
245
|
+
</div>
|
|
246
|
+
<script>
|
|
247
|
+
setTimeout(function () {
|
|
248
|
+
window.location.href = ${JSON.stringify(authorizeUrl)};
|
|
249
|
+
}, 3000);
|
|
250
|
+
</script>
|
|
251
|
+
</body>
|
|
252
|
+
</html>`;
|
|
253
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OAuthTokenStore = void 0;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
5
6
|
class OAuthTokenStore {
|
|
6
7
|
filePath;
|
|
7
8
|
constructor(filePath) {
|
|
@@ -19,6 +20,7 @@ class OAuthTokenStore {
|
|
|
19
20
|
return parsed;
|
|
20
21
|
}
|
|
21
22
|
write(token) {
|
|
23
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.filePath), { recursive: true });
|
|
22
24
|
(0, node_fs_1.writeFileSync)(this.filePath, JSON.stringify(token, null, 2), 'utf8');
|
|
23
25
|
(0, node_fs_1.chmodSync)(this.filePath, 0o600);
|
|
24
26
|
}
|
package/docs/USER_GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# User Guide
|
|
2
2
|
|
|
3
|
-
Этот гайд для конечного пользователя: как подключить `gitlab-mcp-agent-server` в
|
|
3
|
+
Этот гайд для конечного пользователя: как подключить `gitlab-mcp-agent-server` в **Codex** и работать через OAuth с авто-рефрешем токена.
|
|
4
4
|
|
|
5
5
|
## 1. Подготовка GitLab OAuth Application
|
|
6
6
|
|
|
@@ -23,60 +23,61 @@
|
|
|
23
23
|
|
|
24
24
|
Остальные параметры имеют дефолты.
|
|
25
25
|
|
|
26
|
-
## 3.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
26
|
+
## 3. Конфиг Codex (`~/.codex/config.toml`)
|
|
27
|
+
|
|
28
|
+
Минимальный рабочий блок:
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
[mcp_servers.gitlab]
|
|
32
|
+
command = "bash"
|
|
33
|
+
args = ["-lc", """
|
|
34
|
+
export NVM_DIR="$HOME/.nvm";
|
|
35
|
+
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh";
|
|
36
|
+
|
|
37
|
+
export GITLAB_OAUTH_CLIENT_ID="<APPLICATION_ID>";
|
|
38
|
+
export GITLAB_OAUTH_CLIENT_SECRET="<SECRET>";
|
|
39
|
+
|
|
40
|
+
npx -y gitlab-mcp-agent-server
|
|
41
|
+
"""]
|
|
44
42
|
```
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
44
|
+
Рекомендованный расширенный блок:
|
|
45
|
+
|
|
46
|
+
```toml
|
|
47
|
+
[mcp_servers.gitlab]
|
|
48
|
+
command = "bash"
|
|
49
|
+
args = ["-lc", """
|
|
50
|
+
export NVM_DIR="$HOME/.nvm";
|
|
51
|
+
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh";
|
|
52
|
+
|
|
53
|
+
export GITLAB_API_URL="https://gitlab.com/api/v4";
|
|
54
|
+
export GITLAB_AUTH_MODE="oauth";
|
|
55
|
+
export GITLAB_OAUTH_CLIENT_ID="<APPLICATION_ID>";
|
|
56
|
+
export GITLAB_OAUTH_CLIENT_SECRET="<SECRET>";
|
|
57
|
+
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
|
+
export GITLAB_OAUTH_AUTO_LOGIN="true";
|
|
61
|
+
export GITLAB_OAUTH_OPEN_BROWSER="false";
|
|
62
|
+
|
|
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
|
+
|
|
67
|
+
npx -y gitlab-mcp-agent-server
|
|
68
|
+
"""]
|
|
71
69
|
```
|
|
72
70
|
|
|
71
|
+
После изменения `config.toml` перезапусти Codex.
|
|
72
|
+
|
|
73
73
|
## 4. Что происходит при первом запуске
|
|
74
74
|
|
|
75
75
|
1. Агент вызывает любой GitLab tool (например `gitlab_list_labels`).
|
|
76
76
|
2. Если токена нет, сервер запускает OAuth flow.
|
|
77
77
|
3. Если `GITLAB_OAUTH_OPEN_BROWSER=true` и окружение GUI доступно, браузер откроется автоматически.
|
|
78
|
-
4.
|
|
79
|
-
5.
|
|
78
|
+
4. Локальный URL `http://127.0.0.1:8787/` автоматически редиректит на GitLab OAuth.
|
|
79
|
+
5. Если браузер не может быть открыт, сервер печатает URL авторизации в лог.
|
|
80
|
+
6. После подтверждения в GitLab и callback на `http://127.0.0.1:8787/oauth/callback` токены сохраняются в `GITLAB_OAUTH_TOKEN_STORE_PATH`.
|
|
80
81
|
|
|
81
82
|
Если `GITLAB_DEFAULT_PROJECT` не указан:
|
|
82
83
|
1. сервер пытается автоматически определить проект из `git remote origin` в `cwd`;
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
2. Ограничь права файла:
|
|
97
98
|
- `chmod 600 /home/<user>/.config/gitlab-mcp/token.json`
|
|
98
99
|
3. Оставь `GITLAB_OAUTH_OPEN_BROWSER=false` для headless окружений.
|
|
99
|
-
4.
|
|
100
|
+
4. Для multi-repo режима лучше не задавать `GITLAB_DEFAULT_PROJECT`, чтобы проект брался из `git remote` текущего `cwd`.
|
|
100
101
|
|
|
101
102
|
## 7. Быстрая проверка работоспособности
|
|
102
103
|
|