@zereight/mcp-gitlab 1.0.62 → 1.0.64
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 +1 -0
- package/build/index.js +106 -6
- package/build/schemas.js +4 -2
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -111,6 +111,7 @@ $ sh scripts/image_push.sh docker_user_name
|
|
|
111
111
|
- `USE_GITLAB_WIKI`: When set to 'true', enables the wiki-related tools (list_wiki_pages, get_wiki_page, create_wiki_page, update_wiki_page, delete_wiki_page). By default, wiki features are disabled.
|
|
112
112
|
- `USE_MILESTONE`: When set to 'true', enables the milestone-related tools (list_milestones, get_milestone, create_milestone, edit_milestone, delete_milestone, get_milestone_issue, get_milestone_merge_requests, promote_milestone, get_milestone_burndown_events). By default, milestone features are disabled.
|
|
113
113
|
- `USE_PIPELINE`: When set to 'true', enables the pipeline-related tools (list_pipelines, get_pipeline, list_pipeline_jobs, get_pipeline_job, get_pipeline_job_output, create_pipeline, retry_pipeline, cancel_pipeline). By default, pipeline features are disabled.
|
|
114
|
+
- `GITLAB_AUTH_COOKIE_PATH`: Path to an authentication cookie file for GitLab instances that require cookie-based authentication. When provided, the cookie will be included in all GitLab API requests.
|
|
114
115
|
|
|
115
116
|
## Tools 🛠️
|
|
116
117
|
|
package/build/index.js
CHANGED
|
@@ -3,7 +3,9 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
-
import
|
|
6
|
+
import nodeFetch from "node-fetch";
|
|
7
|
+
import fetchCookie from "fetch-cookie";
|
|
8
|
+
import { CookieJar, parse as parseCookie } from "tough-cookie";
|
|
7
9
|
import { SocksProxyAgent } from "socks-proxy-agent";
|
|
8
10
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
9
11
|
import { HttpProxyAgent } from "http-proxy-agent";
|
|
@@ -51,6 +53,7 @@ const server = new Server({
|
|
|
51
53
|
},
|
|
52
54
|
});
|
|
53
55
|
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
|
|
56
|
+
const GITLAB_AUTH_COOKIE_PATH = process.env.GITLAB_AUTH_COOKIE_PATH;
|
|
54
57
|
const IS_OLD = process.env.GITLAB_IS_OLD === "true";
|
|
55
58
|
const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true";
|
|
56
59
|
const USE_GITLAB_WIKI = process.env.USE_GITLAB_WIKI === "true";
|
|
@@ -91,6 +94,76 @@ if (HTTPS_PROXY) {
|
|
|
91
94
|
}
|
|
92
95
|
httpsAgent = httpsAgent || new HttpsAgent(sslOptions);
|
|
93
96
|
httpAgent = httpAgent || new Agent();
|
|
97
|
+
// Create cookie jar with clean Netscape file parsing
|
|
98
|
+
const createCookieJar = () => {
|
|
99
|
+
if (!GITLAB_AUTH_COOKIE_PATH)
|
|
100
|
+
return null;
|
|
101
|
+
try {
|
|
102
|
+
const cookiePath = GITLAB_AUTH_COOKIE_PATH.startsWith("~/")
|
|
103
|
+
? path.join(process.env.HOME || "", GITLAB_AUTH_COOKIE_PATH.slice(2))
|
|
104
|
+
: GITLAB_AUTH_COOKIE_PATH;
|
|
105
|
+
const jar = new CookieJar();
|
|
106
|
+
const cookieContent = fs.readFileSync(cookiePath, "utf8");
|
|
107
|
+
cookieContent.split("\n").forEach(line => {
|
|
108
|
+
// Handle #HttpOnly_ prefix
|
|
109
|
+
if (line.startsWith("#HttpOnly_")) {
|
|
110
|
+
line = line.slice(10);
|
|
111
|
+
}
|
|
112
|
+
// Skip comments and empty lines
|
|
113
|
+
if (line.startsWith("#") || !line.trim()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Parse Netscape format: domain, flag, path, secure, expires, name, value
|
|
117
|
+
const parts = line.split("\t");
|
|
118
|
+
if (parts.length >= 7) {
|
|
119
|
+
const [domain, , path, secure, expires, name, value] = parts;
|
|
120
|
+
// Build cookie string in standard format
|
|
121
|
+
const cookieStr = `${name}=${value}; Domain=${domain}; Path=${path}${secure === "TRUE" ? "; Secure" : ""}${expires !== "0" ? `; Expires=${new Date(parseInt(expires) * 1000).toUTCString()}` : ""}`;
|
|
122
|
+
// Use tough-cookie's parse function for robust parsing
|
|
123
|
+
const cookie = parseCookie(cookieStr);
|
|
124
|
+
if (cookie) {
|
|
125
|
+
const url = `${secure === "TRUE" ? "https" : "http"}://${domain.startsWith(".") ? domain.slice(1) : domain}`;
|
|
126
|
+
jar.setCookieSync(cookie, url);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return jar;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error("Error loading cookie file:", error);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// Initialize cookie jar and fetch
|
|
138
|
+
const cookieJar = createCookieJar();
|
|
139
|
+
const fetch = cookieJar ? fetchCookie(nodeFetch, cookieJar) : nodeFetch;
|
|
140
|
+
// Ensure session is established for the current request
|
|
141
|
+
async function ensureSessionForRequest() {
|
|
142
|
+
if (!cookieJar || !GITLAB_AUTH_COOKIE_PATH)
|
|
143
|
+
return;
|
|
144
|
+
// Extract the base URL from GITLAB_API_URL
|
|
145
|
+
const apiUrl = new URL(GITLAB_API_URL);
|
|
146
|
+
const baseUrl = `${apiUrl.protocol}//${apiUrl.hostname}`;
|
|
147
|
+
// Check if we already have GitLab session cookies
|
|
148
|
+
const gitlabCookies = cookieJar.getCookiesSync(baseUrl);
|
|
149
|
+
const hasSessionCookie = gitlabCookies.some(cookie => cookie.key === '_gitlab_session' || cookie.key === 'remember_user_token');
|
|
150
|
+
if (!hasSessionCookie) {
|
|
151
|
+
try {
|
|
152
|
+
// Establish session with a lightweight request
|
|
153
|
+
await fetch(`${GITLAB_API_URL}/user`, {
|
|
154
|
+
...DEFAULT_FETCH_CONFIG,
|
|
155
|
+
redirect: 'follow'
|
|
156
|
+
}).catch(() => {
|
|
157
|
+
// Ignore errors - the important thing is that cookies get set during redirects
|
|
158
|
+
});
|
|
159
|
+
// Small delay to ensure cookies are fully processed
|
|
160
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
// Ignore session establishment errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
94
167
|
// Modify DEFAULT_HEADERS to include agent configuration
|
|
95
168
|
const DEFAULT_HEADERS = {
|
|
96
169
|
Accept: "application/json",
|
|
@@ -366,7 +439,7 @@ const allTools = [
|
|
|
366
439
|
},
|
|
367
440
|
{
|
|
368
441
|
name: "get_pipeline_job_output",
|
|
369
|
-
description: "Get the output/trace of a GitLab pipeline job
|
|
442
|
+
description: "Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage",
|
|
370
443
|
inputSchema: zodToJsonSchema(GetPipelineJobOutputSchema),
|
|
371
444
|
},
|
|
372
445
|
{
|
|
@@ -1963,9 +2036,11 @@ async function getPipelineJob(projectId, jobId) {
|
|
|
1963
2036
|
*
|
|
1964
2037
|
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1965
2038
|
* @param {number} jobId - The ID of the job
|
|
2039
|
+
* @param {number} limit - Maximum number of lines to return from the end (default: 1000)
|
|
2040
|
+
* @param {number} offset - Number of lines to skip from the end (default: 0)
|
|
1966
2041
|
* @returns {Promise<string>} The job output/trace
|
|
1967
2042
|
*/
|
|
1968
|
-
async function getPipelineJobOutput(projectId, jobId) {
|
|
2043
|
+
async function getPipelineJobOutput(projectId, jobId, limit, offset) {
|
|
1969
2044
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1970
2045
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`);
|
|
1971
2046
|
const response = await fetch(url.toString(), {
|
|
@@ -1979,7 +2054,28 @@ async function getPipelineJobOutput(projectId, jobId) {
|
|
|
1979
2054
|
throw new Error(`Job trace not found or job is not finished yet`);
|
|
1980
2055
|
}
|
|
1981
2056
|
await handleGitLabError(response);
|
|
1982
|
-
|
|
2057
|
+
const fullTrace = await response.text();
|
|
2058
|
+
// Apply client-side pagination to limit context window usage
|
|
2059
|
+
if (limit !== undefined || offset !== undefined) {
|
|
2060
|
+
const lines = fullTrace.split('\n');
|
|
2061
|
+
const startOffset = offset || 0;
|
|
2062
|
+
const maxLines = limit || 1000;
|
|
2063
|
+
// Return lines from the end, skipping offset lines and limiting to maxLines
|
|
2064
|
+
const startIndex = Math.max(0, lines.length - startOffset - maxLines);
|
|
2065
|
+
const endIndex = lines.length - startOffset;
|
|
2066
|
+
const selectedLines = lines.slice(startIndex, endIndex);
|
|
2067
|
+
const result = selectedLines.join('\n');
|
|
2068
|
+
// Add metadata about truncation
|
|
2069
|
+
if (startIndex > 0 || endIndex < lines.length) {
|
|
2070
|
+
const totalLines = lines.length;
|
|
2071
|
+
const shownLines = selectedLines.length;
|
|
2072
|
+
const skippedFromStart = startIndex;
|
|
2073
|
+
const skippedFromEnd = startOffset;
|
|
2074
|
+
return `[Log truncated: showing ${shownLines} of ${totalLines} lines, skipped ${skippedFromStart} from start, ${skippedFromEnd} from end]\n\n${result}`;
|
|
2075
|
+
}
|
|
2076
|
+
return result;
|
|
2077
|
+
}
|
|
2078
|
+
return fullTrace;
|
|
1983
2079
|
}
|
|
1984
2080
|
/**
|
|
1985
2081
|
* Create a new pipeline
|
|
@@ -2339,6 +2435,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2339
2435
|
if (!request.params.arguments) {
|
|
2340
2436
|
throw new Error("Arguments are required");
|
|
2341
2437
|
}
|
|
2438
|
+
// Ensure session is established for every request if cookie authentication is enabled
|
|
2439
|
+
if (GITLAB_AUTH_COOKIE_PATH) {
|
|
2440
|
+
await ensureSessionForRequest();
|
|
2441
|
+
}
|
|
2342
2442
|
switch (request.params.name) {
|
|
2343
2443
|
case "fork_repository": {
|
|
2344
2444
|
const forkArgs = ForkRepositorySchema.parse(request.params.arguments);
|
|
@@ -2823,8 +2923,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2823
2923
|
};
|
|
2824
2924
|
}
|
|
2825
2925
|
case "get_pipeline_job_output": {
|
|
2826
|
-
const { project_id, job_id } = GetPipelineJobOutputSchema.parse(request.params.arguments);
|
|
2827
|
-
const jobOutput = await getPipelineJobOutput(project_id, job_id);
|
|
2926
|
+
const { project_id, job_id, limit, offset } = GetPipelineJobOutputSchema.parse(request.params.arguments);
|
|
2927
|
+
const jobOutput = await getPipelineJobOutput(project_id, job_id, limit, offset);
|
|
2828
2928
|
return {
|
|
2829
2929
|
content: [
|
|
2830
2930
|
{
|
package/build/schemas.js
CHANGED
|
@@ -178,6 +178,8 @@ export const CancelPipelineSchema = z.object({
|
|
|
178
178
|
export const GetPipelineJobOutputSchema = z.object({
|
|
179
179
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
180
180
|
job_id: z.number().describe("The ID of the job"),
|
|
181
|
+
limit: z.number().optional().describe("Maximum number of lines to return from the end of the log (default: 1000)"),
|
|
182
|
+
offset: z.number().optional().describe("Number of lines to skip from the end of the log (default: 0)"),
|
|
181
183
|
});
|
|
182
184
|
// User schemas
|
|
183
185
|
export const GitLabUserSchema = z.object({
|
|
@@ -272,14 +274,14 @@ export const GitLabRepositorySchema = z.object({
|
|
|
272
274
|
project_access: z
|
|
273
275
|
.object({
|
|
274
276
|
access_level: z.number(),
|
|
275
|
-
notification_level: z.number().optional(),
|
|
277
|
+
notification_level: z.number().nullable().optional(),
|
|
276
278
|
})
|
|
277
279
|
.optional()
|
|
278
280
|
.nullable(),
|
|
279
281
|
group_access: z
|
|
280
282
|
.object({
|
|
281
283
|
access_level: z.number(),
|
|
282
|
-
notification_level: z.number().optional(),
|
|
284
|
+
notification_level: z.number().nullable().optional(),
|
|
283
285
|
})
|
|
284
286
|
.optional()
|
|
285
287
|
.nullable(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.64",
|
|
4
4
|
"description": "MCP server for using the GitLab API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "zereight",
|
|
@@ -33,11 +33,13 @@
|
|
|
33
33
|
"@modelcontextprotocol/sdk": "1.8.0",
|
|
34
34
|
"@types/node-fetch": "^2.6.12",
|
|
35
35
|
"express": "^5.1.0",
|
|
36
|
+
"fetch-cookie": "^3.1.0",
|
|
36
37
|
"form-data": "^4.0.0",
|
|
37
38
|
"http-proxy-agent": "^7.0.2",
|
|
38
39
|
"https-proxy-agent": "^7.0.6",
|
|
39
40
|
"node-fetch": "^3.3.2",
|
|
40
41
|
"socks-proxy-agent": "^8.0.5",
|
|
42
|
+
"tough-cookie": "^5.1.2",
|
|
41
43
|
"zod-to-json-schema": "^3.23.5"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|